Parcourir la source

[master] Merge branch 'trac3589'

Marcin Siodelski il y a 10 ans
Parent
commit
ad44a39d06

+ 1 - 0
doc/devel/mainpage.dox

@@ -88,6 +88,7 @@
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
  *   - @subpage cfgmgr
+ *   - @subpage optionsConfig
  *   - @subpage allocengine
  * - @subpage libdhcp_ddns
  * - @subpage dhcpDatabaseBackends

+ 15 - 16
src/bin/dhcp4/dhcp4_srv.cc

@@ -601,10 +601,9 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
     for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
          opt != requested_opts.end(); ++opt) {
         if (!msg->getOption(*opt)) {
-            Subnet::OptionDescriptor desc =
-                subnet->getOptionDescriptor("dhcp4", *opt);
-            if (desc.option && !msg->getOption(*opt)) {
-                msg->addOption(desc.option);
+            OptionDescriptor desc = subnet->getCfgOption()->get("dhcp4", *opt);
+            if (desc.option_ && !msg->getOption(*opt)) {
+                msg->addOption(desc.option_);
             }
         }
     }
@@ -652,10 +651,10 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
     for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
          code != requested_opts.end(); ++code) {
         if  (!vendor_rsp->getOption(*code)) {
-            Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id,
-                                                                              *code);
-            if (desc.option) {
-                vendor_rsp->addOption(desc.option);
+            OptionDescriptor desc = subnet->getCfgOption()->get(vendor_id,
+                                                                *code);
+            if (desc.option_) {
+                vendor_rsp->addOption(desc.option_);
                 added = true;
             }
         }
@@ -691,10 +690,10 @@ Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
         OptionPtr opt = msg->getOption(required_options[i]);
         if (!opt) {
             // Check whether option has been configured.
-            Subnet::OptionDescriptor desc =
-                subnet->getOptionDescriptor("dhcp4", required_options[i]);
-            if (desc.option) {
-                msg->addOption(desc.option);
+            OptionDescriptor desc = subnet->getCfgOption()->
+                get("dhcp4", required_options[i]);
+            if (desc.option_) {
+                msg->addOption(desc.option_);
             }
         }
     }
@@ -1981,12 +1980,12 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
 
         // Now try to set up file field in DHCPv4 packet. We will just copy
         // content of the boot-file option, which contains the same information.
-        Subnet::OptionDescriptor desc =
-            subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME);
+        OptionDescriptor desc = subnet->getCfgOption()->
+            get("dhcp4", DHO_BOOT_FILE_NAME);
 
-        if (desc.option) {
+        if (desc.option_) {
             boost::shared_ptr<OptionString> boot =
-                boost::dynamic_pointer_cast<OptionString>(desc.option);
+                boost::dynamic_pointer_cast<OptionString>(desc.option_);
             if (boot) {
                 std::string filename = boot->getValue();
                 rsp->setFile((const uint8_t*)filename.c_str(), filename.size());

+ 3 - 73
src/bin/dhcp4/json_config_parser.cc

@@ -16,6 +16,7 @@
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcpsrv/dbaccess_parser.h>
@@ -41,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 = SubnetConfigParser::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
@@ -244,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);
         }
@@ -447,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) ||

+ 80 - 80
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -231,7 +231,7 @@ public:
     /// @return Descriptor of the option. If the descriptor holds a
     /// NULL option pointer, it means that there was no such option
     /// in the subnet.
-    Subnet::OptionDescriptor
+    OptionDescriptor
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
@@ -243,8 +243,8 @@ public:
                           << subnet_address.toText()
                           << "does not exist in Config Manager";
         }
-        Subnet::OptionContainerPtr options =
-            subnet->getOptionDescriptors("dhcp4");
+        OptionContainerPtr options =
+            subnet->getCfgOption()->getAll("dhcp4");
         if (expected_options_count != options->size()) {
             ADD_FAILURE() << "The number of options in the subnet '"
                           << subnet_address.toText() << "' is different "
@@ -253,13 +253,13 @@ public:
         }
 
         // Get the search index. Index #1 is to search using option code.
-        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+        const OptionContainerTypeIndex& idx = options->get<1>();
 
         // Get the options for specified index. Expecting one option to be
         // returned but in theory we may have multiple options with the same
         // code so we get the range.
-        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-                  Subnet::OptionContainerTypeIndex::const_iterator> range =
+        std::pair<OptionContainerTypeIndex::const_iterator,
+                  OptionContainerTypeIndex::const_iterator> range =
             idx.equal_range(option_code);
         if (std::distance(range.first, range.second) > 1) {
             ADD_FAILURE() << "There is more than one option having the"
@@ -267,7 +267,7 @@ public:
                           << subnet_address.toText() << "'. Expected "
                 " at most one option";
         } else if (std::distance(range.first, range.second) == 0) {
-            return (Subnet::OptionDescriptor(OptionPtr(), false));
+            return (OptionDescriptor(OptionPtr(), false));
         }
 
         return (*range.first);
@@ -319,35 +319,35 @@ public:
     /// @param expected_data_len length of the reference data.
     /// @param extra_data if true extra data is allowed in an option
     /// after tested data.
-    void testOption(const Subnet::OptionDescriptor& option_desc,
+    void testOption(const OptionDescriptor& option_desc,
                     uint16_t expected_code, const uint8_t* expected_data,
                     size_t expected_data_len,
                     bool extra_data = false) {
         // Check if option descriptor contains valid option pointer.
-        ASSERT_TRUE(option_desc.option);
+        ASSERT_TRUE(option_desc.option_);
         // Verify option type.
-        EXPECT_EQ(expected_code, option_desc.option->getType());
+        EXPECT_EQ(expected_code, option_desc.option_->getType());
         // We may have many different option types being created. Some of them
         // have dedicated classes derived from Option class. In such case if
         // we want to verify the option contents against expected_data we have
         // to prepare raw buffer with the contents of the option. The easiest
         // way is to call pack() which will prepare on-wire data.
-        util::OutputBuffer buf(option_desc.option->getData().size());
-        option_desc.option->pack(buf);
+        util::OutputBuffer buf(option_desc.option_->getData().size());
+        option_desc.option_->pack(buf);
         if (extra_data) {
             // The length of the buffer must be at least equal to size of the
             // reference data but it can sometimes be greater than that. This is
             // because some options carry suboptions that increase the overall
             // length.
-            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
                       expected_data_len);
         } else {
-            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
                       expected_data_len);
         }
         // Verify that the data is correct. Do not verify suboptions and a header.
         const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
-        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
                             expected_data_len));
     }
 
@@ -378,9 +378,9 @@ public:
         std::string config = createConfigWithOption(params);
         ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
         // The subnet should now hold one option with the specified option code.
-        Subnet::OptionDescriptor desc =
+        OptionDescriptor desc =
             getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
-        ASSERT_TRUE(desc.option);
+        ASSERT_TRUE(desc.option_);
         testOption(desc, option_code, expected_data, expected_data_len);
     }
 
@@ -1797,17 +1797,17 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(56);
     // Expect single option with the code equal to 56.
     ASSERT_EQ(1, std::distance(range.first, range.second));
@@ -1884,17 +1884,17 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
                                                       classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp4.
-    Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(56, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get("dhcp4", 56);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(56, desc1.option_->getType());
     // Try to get the option from the space isc.
-    Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
-    ASSERT_TRUE(desc2.option);
-    EXPECT_EQ(56, desc1.option->getType());
+    OptionDescriptor desc2 = subnet->getCfgOption()->get("isc", 56);
+    ASSERT_TRUE(desc2.option_);
+    EXPECT_EQ(56, desc1.option_->getType());
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
-    ASSERT_FALSE(desc3.option);
+    OptionDescriptor desc3 = subnet->getCfgOption()->get("non-existing", 56);
+    ASSERT_FALSE(desc3.option_);
 }
 
 // The goal of this test is to verify that it is possible to
@@ -2039,23 +2039,23 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
-    Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222);
-    EXPECT_TRUE(desc.option);
-    EXPECT_EQ(222, desc.option->getType());
+    OptionDescriptor desc = subnet->getCfgOption()->get("dhcp4", 222);
+    EXPECT_TRUE(desc.option_);
+    EXPECT_EQ(222, desc.option_->getType());
 
     // This opton should comprise two sub-options.
     // One of them is 'foo' with code 1.
-    OptionPtr option_foo = desc.option->getOption(1);
+    OptionPtr option_foo = desc.option_->getOption(1);
     ASSERT_TRUE(option_foo);
     EXPECT_EQ(1, option_foo->getType());
 
     // ...another one 'foo2' with code 2.
-    OptionPtr option_foo2 = desc.option->getOption(2);
+    OptionPtr option_foo2 = desc.option_->getOption(2);
     ASSERT_TRUE(option_foo2);
     EXPECT_EQ(2, option_foo2->getType());
 }
@@ -2104,17 +2104,17 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(56);
     // Expect single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range.first, range.second));
@@ -2150,9 +2150,9 @@ TEST_F(Dhcp4ParserTest, optionDataBoolean) {
                                      " boolean value"));
 
     // The subnet should now hold one option with the code 19.
-    Subnet::OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"),
+    OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"),
                                                         19);
-    ASSERT_TRUE(desc.option);
+    ASSERT_TRUE(desc.option_);
 
     // This option should be set to "true", represented as 0x1 in the option
     // buffer.
@@ -2255,17 +2255,17 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"),
                                                        classify_);
     ASSERT_TRUE(subnet1);
-    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp4");
     ASSERT_EQ(1, options1->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
+    const OptionContainerTypeIndex& idx1 = options1->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range1 =
         idx1.equal_range(56);
     // Expect single option with the code equal to 56.
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
@@ -2280,12 +2280,12 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"),
                                                        classify_);
     ASSERT_TRUE(subnet2);
-    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp4");
     ASSERT_EQ(1, options2->size());
 
-    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+    const OptionContainerTypeIndex& idx2 = options2->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range2 =
         idx2.equal_range(23);
     ASSERT_EQ(1, std::distance(range2.first, range2.second));
 
@@ -2358,17 +2358,17 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(56);
     // Expect single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range.first, range.second));
@@ -2401,25 +2401,25 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options =
-        subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options =
+        subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(DHO_NIS_SERVERS);
     // Expect single option with the code equal to NIS_SERVERS option code.
     ASSERT_EQ(1, std::distance(range.first, range.second));
     // The actual pointer to the option is held in the option field
     // in the structure returned.
-    OptionPtr option = range.first->option;
+    OptionPtr option = range.first->option_;
     ASSERT_TRUE(option);
     // Option object returned for here is expected to be Option6IA
     // which is derived from Option. This class is dedicated to
@@ -2611,18 +2611,18 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
-    Subnet::OptionDescriptor desc =
-        subnet->getOptionDescriptor("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
-    EXPECT_TRUE(desc.option);
-    EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option->getType());
+    OptionDescriptor desc =
+        subnet->getCfgOption()->get("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+    EXPECT_TRUE(desc.option_);
+    EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option_->getType());
 
     // Option with the code 1 should be added as a sub-option.
-    OptionPtr option_foo = desc.option->getOption(1);
+    OptionPtr option_foo = desc.option_->getOption(1);
     ASSERT_TRUE(option_foo);
     EXPECT_EQ(1, option_foo->getType());
     // This option comprises a single uint32_t value thus it is
@@ -2635,7 +2635,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     EXPECT_EQ(1234, option_foo_uint32->getValue());
 
     // Option with the code 2 should be added as a sub-option.
-    OptionPtr option_foo2 = desc.option->getOption(2);
+    OptionPtr option_foo2 = desc.option_->getOption(2);
     ASSERT_TRUE(option_foo2);
     EXPECT_EQ(2, option_foo2->getType());
     // This option comprises the IPV4 address. Such option is
@@ -2647,7 +2647,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
 
     // Option with the code 3 should not be added.
-    EXPECT_FALSE(desc.option->getOption(3));
+    EXPECT_FALSE(desc.option_->getOption(3));
 }
 
 // This test checks if vendor options can be specified in the config file
@@ -2695,18 +2695,18 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
-    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 100);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
     // Try to get the option from the vendor space 1234
-    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100);
-    ASSERT_TRUE(desc2.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc2 = subnet->getCfgOption()->get(1234, 100);
+    ASSERT_TRUE(desc2.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
 
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 100);
-    ASSERT_FALSE(desc3.option);
+    OptionDescriptor desc3 = subnet->getCfgOption()->get(5678, 100);
+    ASSERT_FALSE(desc3.option_);
 }
 
 // This test checks if vendor options can be specified in the config file,
@@ -2756,14 +2756,14 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
-    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 100);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
 
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100);
-    ASSERT_FALSE(desc2.option);
+    OptionDescriptor desc2 = subnet->getCfgOption()->get(5678, 100);
+    ASSERT_FALSE(desc2.option_);
 }
 
 

+ 3 - 3
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1163,7 +1163,7 @@ TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
     OptionPtr rai_response = offer->getOption(DHO_DHCP_AGENT_OPTIONS);
     ASSERT_TRUE(rai_response);
 
-    EXPECT_TRUE(rai_response->equal(rai_query));
+    EXPECT_TRUE(rai_response->equals(rai_query));
 }
 
 /// @todo move vendor options tests to a separate file.
@@ -2166,7 +2166,7 @@ TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
 
     // ... and check if it is the modified value
     OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER);
-    EXPECT_TRUE(clientid->equal(expected));
+    EXPECT_TRUE(clientid->equals(expected));
 }
 
 // Checks if callouts installed on pkt4_received is able to delete
@@ -2295,7 +2295,7 @@ TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) {
 
     // ... and check if it is the modified value
     OptionPtr expected = createOption(DHO_DHCP_SERVER_IDENTIFIER);
-    EXPECT_TRUE(clientid->equal(expected));
+    EXPECT_TRUE(clientid->equals(expected));
 }
 
 // Checks if callouts installed on pkt4_send is able to delete

+ 5 - 5
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -52,7 +52,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     // Add Router option.
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
-    subnet_->addOption(opt_routers, false, "dhcp4");
+    subnet_->getCfgOption()->add(opt_routers, false, "dhcp4");
 }
 
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -86,24 +86,24 @@ void Dhcpv4SrvTest::configureRequestedOptions() {
         option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
     option_dns_servers->addAddress(IOAddress("192.0.2.1"));
     option_dns_servers->addAddress(IOAddress("192.0.2.100"));
-    ASSERT_NO_THROW(subnet_->addOption(option_dns_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, "dhcp4"));
 
     // domain-name
     OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
     OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
     option_domain_name->writeFqdn("example.com");
-    subnet_->addOption(option_domain_name, false, "dhcp4");
+    subnet_->getCfgOption()->add(option_domain_name, false, "dhcp4");
 
     // log-servers
     Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
     option_log_servers->addAddress(IOAddress("192.0.2.2"));
     option_log_servers->addAddress(IOAddress("192.0.2.10"));
-    ASSERT_NO_THROW(subnet_->addOption(option_log_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, "dhcp4"));
 
     // cookie-servers
     Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
     option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
-    ASSERT_NO_THROW(subnet_->addOption(option_cookie_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, "dhcp4"));
 }
 
 void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {

+ 6 - 6
src/bin/dhcp6/dhcp6_srv.cc

@@ -733,9 +733,9 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // Get the list of options that client requested.
     const std::vector<uint16_t>& requested_opts = option_oro->getValues();
     BOOST_FOREACH(uint16_t opt, requested_opts) {
-        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
-        if (desc.option) {
-            answer->addOption(desc.option);
+        OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", opt);
+        if (desc.option_) {
+            answer->addOption(desc.option_);
         }
     }
 }
@@ -779,9 +779,9 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer
     bool added = false;
     const std::vector<uint16_t>& requested_opts = oro->getValues();
     BOOST_FOREACH(uint16_t opt, requested_opts) {
-        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt);
-        if (desc.option) {
-            vendor_rsp->addOption(desc.option);
+        OptionDescriptor desc = subnet->getCfgOption()->get(vendor_id, opt);
+        if (desc.option_) {
+            vendor_rsp->addOption(desc.option_);
             added = true;
         }
     }

+ 3 - 74
src/bin/dhcp6/json_config_parser.cc

@@ -19,6 +19,7 @@
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
@@ -55,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 = SubnetConfigParser::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
@@ -455,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);
         }
@@ -666,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) {

+ 80 - 77
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -243,7 +243,7 @@ public:
     /// @return Descriptor of the option. If the descriptor holds a
     /// NULL option pointer, it means that there was no such option
     /// in the subnet.
-    Subnet::OptionDescriptor
+    OptionDescriptor
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
@@ -255,8 +255,8 @@ public:
                           << subnet_address.toText()
                           << "does not exist in Config Manager";
         }
-        Subnet::OptionContainerPtr options =
-            subnet->getOptionDescriptors("dhcp6");
+        OptionContainerPtr options =
+            subnet->getCfgOption()->getAll("dhcp6");
         if (expected_options_count != options->size()) {
             ADD_FAILURE() << "The number of options in the subnet '"
                           << subnet_address.toText() << "' is different "
@@ -265,13 +265,13 @@ public:
         }
 
         // Get the search index. Index #1 is to search using option code.
-        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+        const OptionContainerTypeIndex& idx = options->get<1>();
 
         // Get the options for specified index. Expecting one option to be
         // returned but in theory we may have multiple options with the same
         // code so we get the range.
-        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-                  Subnet::OptionContainerTypeIndex::const_iterator> range =
+        std::pair<OptionContainerTypeIndex::const_iterator,
+                  OptionContainerTypeIndex::const_iterator> range =
             idx.equal_range(option_code);
         if (std::distance(range.first, range.second) > 1) {
             ADD_FAILURE() << "There is more than one option having the"
@@ -279,7 +279,7 @@ public:
                           << subnet_address.toText() << "'. Expected "
                 " at most one option";
         } else if (std::distance(range.first, range.second) == 0) {
-            return (Subnet::OptionDescriptor(OptionPtr(), false));
+            return (OptionDescriptor(OptionPtr(), false));
         }
 
         return (*range.first);
@@ -413,35 +413,35 @@ public:
     /// @param expected_data_len length of the reference data.
     /// @param extra_data if true extra data is allowed in an option
     /// after tested data.
-    void testOption(const Subnet::OptionDescriptor& option_desc,
+    void testOption(const OptionDescriptor& option_desc,
                     uint16_t expected_code, const uint8_t* expected_data,
                     size_t expected_data_len,
                     bool extra_data = false) {
         // Check if option descriptor contains valid option pointer.
-        ASSERT_TRUE(option_desc.option);
+        ASSERT_TRUE(option_desc.option_);
         // Verify option type.
-        EXPECT_EQ(expected_code, option_desc.option->getType());
+        EXPECT_EQ(expected_code, option_desc.option_->getType());
         // We may have many different option types being created. Some of them
         // have dedicated classes derived from Option class. In such case if
         // we want to verify the option contents against expected_data we have
         // to prepare raw buffer with the contents of the option. The easiest
         // way is to call pack() which will prepare on-wire data.
-        util::OutputBuffer buf(option_desc.option->getData().size());
-        option_desc.option->pack(buf);
+        util::OutputBuffer buf(option_desc.option_->getData().size());
+        option_desc.option_->pack(buf);
         if (extra_data) {
             // The length of the buffer must be at least equal to size of the
             // reference data but it can sometimes be greater than that. This is
             // because some options carry suboptions that increase the overall
             // length.
-            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
                       expected_data_len);
         } else {
-            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
                       expected_data_len);
         }
         // Verify that the data is correct. Do not verify suboptions and a header.
         const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
-        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
                             expected_data_len));
     }
 
@@ -473,9 +473,9 @@ public:
         ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
 
         // The subnet should now hold one option with the specified code.
-        Subnet::OptionDescriptor desc =
+        OptionDescriptor desc =
             getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
-        ASSERT_TRUE(desc.option);
+        ASSERT_TRUE(desc.option_);
         testOption(desc, option_code, expected_data, expected_data_len);
         CfgMgr::instance().clear();
     }
@@ -1002,7 +1002,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
     ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
     subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     ASSERT_TRUE(subnet);
-    EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
+    EXPECT_TRUE(ifaceid->equals(subnet->getInterfaceId()));
 }
 
 
@@ -2030,17 +2030,17 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(D6O_SUBSCRIBER_ID);
     // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range.first, range.second));
@@ -2126,17 +2126,17 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
                                                       classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp6.
-    Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(38, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get("dhcp6", 38);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(38, desc1.option_->getType());
     // Try to get the option from the space isc.
-    Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
-    ASSERT_TRUE(desc2.option);
-    EXPECT_EQ(38, desc1.option->getType());
+    OptionDescriptor desc2 = subnet->getCfgOption()->get("isc", 38);
+    ASSERT_TRUE(desc2.option_);
+    EXPECT_EQ(38, desc1.option_->getType());
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
-    ASSERT_FALSE(desc3.option);
+    OptionDescriptor desc3 = subnet->getCfgOption()->get("non-existing", 38);
+    ASSERT_FALSE(desc3.option_);
 }
 
 // The goal of this test is to verify that it is possible to
@@ -2283,23 +2283,23 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
-    Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", 100);
-    EXPECT_TRUE(desc.option);
-    EXPECT_EQ(100, desc.option->getType());
+    OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", 100);
+    EXPECT_TRUE(desc.option_);
+    EXPECT_EQ(100, desc.option_->getType());
 
     // This opton should comprise two sub-options.
     // Onf of them is 'foo' with code 110.
-    OptionPtr option_foo = desc.option->getOption(110);
+    OptionPtr option_foo = desc.option_->getOption(110);
     ASSERT_TRUE(option_foo);
     EXPECT_EQ(110, option_foo->getType());
 
     // ...another one 'foo2' with code 111.
-    OptionPtr option_foo2 = desc.option->getOption(111);
+    OptionPtr option_foo2 = desc.option_->getOption(111);
     ASSERT_TRUE(option_foo2);
     EXPECT_EQ(111, option_foo2->getType());
 }
@@ -2344,17 +2344,17 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                        classify_);
     ASSERT_TRUE(subnet1);
-    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options1->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
+    const OptionContainerTypeIndex& idx1 = options1->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range1 =
         idx1.equal_range(D6O_SUBSCRIBER_ID);
     // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
@@ -2370,12 +2370,12 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"),
                                                        classify_);
     ASSERT_TRUE(subnet2);
-    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options2->size());
 
-    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+    const OptionContainerTypeIndex& idx2 = options2->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range2 =
         idx2.equal_range(D6O_USER_CLASS);
     ASSERT_EQ(1, std::distance(range2.first, range2.second));
 
@@ -2405,9 +2405,9 @@ TEST_F(Dhcp6ParserTest, optionDataBoolean) {
     CfgMgr::instance().commit();
 
     // The subnet should now hold one option with the code 1000.
-    Subnet::OptionDescriptor desc =
+    OptionDescriptor desc =
         getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000);
-    ASSERT_TRUE(desc.option);
+    ASSERT_TRUE(desc.option_);
 
     // This option should be set to "true", represented as 0x1 in the option
     // buffer.
@@ -2541,17 +2541,17 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(D6O_SUBSCRIBER_ID);
     // Expect single option with the code equal to 38.
     ASSERT_EQ(1, std::distance(range.first, range.second));
@@ -2584,23 +2584,23 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
     // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(D6O_IA_NA);
     // Expect single option with the code equal to IA_NA option code.
     ASSERT_EQ(1, std::distance(range.first, range.second));
     // The actual pointer to the option is held in the option field
     // in the structure returned.
-    OptionPtr option = range.first->option;
+    OptionPtr option = range.first->option_;
     ASSERT_TRUE(option);
     // Option object returned for here is expected to be Option6IA
     // which is derived from Option. This class is dedicated to
@@ -2664,18 +2664,18 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
-    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get(4491, 100);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
     // Try to get the option from the vendor space 1234
-    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100);
-    ASSERT_TRUE(desc2.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc2 = subnet->getCfgOption()->get(1234, 100);
+    ASSERT_TRUE(desc2.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
 
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 38);
-    ASSERT_FALSE(desc3.option);
+    OptionDescriptor desc3 = subnet->getCfgOption()->get(5678, 38);
+    ASSERT_FALSE(desc3.option_);
 }
 
 // This test checks if vendor options can be specified in the config file,
@@ -2726,14 +2726,14 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
-    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
-    ASSERT_TRUE(desc1.option);
-    EXPECT_EQ(100, desc1.option->getType());
+    OptionDescriptor desc1 = subnet->getCfgOption()->get(4491, 100);
+    ASSERT_TRUE(desc1.option_);
+    EXPECT_EQ(100, desc1.option_->getType());
 
     // Try to get the non-existing option from the non-existing
     // option space and  expect that option is not returned.
-    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100);
-    ASSERT_FALSE(desc2.option);
+    OptionDescriptor desc2 = subnet->getCfgOption()->get(5678, 100);
+    ASSERT_FALSE(desc2.option_);
 }
 
 /// @todo add tests similar to vendorOptionsCsv and vendorOptionsHex, but for
@@ -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
@@ -2865,18 +2869,17 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
-    Subnet::OptionDescriptor desc =
-        subnet->getOptionDescriptor("dhcp6", D6O_VENDOR_OPTS);
-    EXPECT_TRUE(desc.option);
-    EXPECT_EQ(D6O_VENDOR_OPTS, desc.option->getType());
+    OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", D6O_VENDOR_OPTS);
+    EXPECT_TRUE(desc.option_);
+    EXPECT_EQ(D6O_VENDOR_OPTS, desc.option_->getType());
 
     // Option with the code 110 should be added as a sub-option.
-    OptionPtr option_foo = desc.option->getOption(110);
+    OptionPtr option_foo = desc.option_->getOption(110);
     ASSERT_TRUE(option_foo);
     EXPECT_EQ(110, option_foo->getType());
     // This option comprises a single uint32_t value thus it is
@@ -2889,7 +2892,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     EXPECT_EQ(1234, option_foo_uint32->getValue());
 
     // Option with the code 111 should be added as a sub-option.
-    OptionPtr option_foo2 = desc.option->getOption(111);
+    OptionPtr option_foo2 = desc.option_->getOption(111);
     ASSERT_TRUE(option_foo2);
     EXPECT_EQ(111, option_foo2->getType());
     // This option comprises the IPV4 address. Such option is
@@ -2901,7 +2904,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
 
     // Option with the code 112 should not be added.
-    EXPECT_FALSE(desc.option->getOption(112));
+    EXPECT_FALSE(desc.option_->getOption(112));
 }
 
 // Tests of the hooks libraries configuration.  All tests have the pre-

+ 3 - 3
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -701,7 +701,7 @@ TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_receive) {
 
     // ... and check if it is the modified value
     OptionPtr expected = createOption(D6O_CLIENTID);
-    EXPECT_TRUE(clientid->equal(expected));
+    EXPECT_TRUE(clientid->equals(expected));
 }
 
 // Checks if callouts installed on pkt6_received is able to delete
@@ -822,7 +822,7 @@ TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_send) {
 
     // ... and check if it is the modified value
     OptionPtr expected = createOption(D6O_SERVERID);
-    EXPECT_TRUE(clientid->equal(expected));
+    EXPECT_TRUE(clientid->equals(expected));
 }
 
 // Checks if callouts installed on pkt6_send is able to delete

+ 7 - 3
src/lib/dhcp/option.cc

@@ -274,9 +274,13 @@ void Option::setUint32(uint32_t value) {
     writeUint32(value, &data_[0], data_.size());
 }
 
-bool Option::equal(const OptionPtr& other) const {
-    return ( (getType() == other->getType()) &&
-             (getData() == other->getData()) );
+bool Option::equals(const OptionPtr& other) const {
+    return (equals(*other));
+}
+
+bool Option::equals(const Option& other) const {
+    return ( (getType() == other.getType()) &&
+             (getData() == other.getData()) );
 }
 
 Option::~Option() {

+ 16 - 5
src/lib/dhcp/option.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -352,7 +352,17 @@ public:
     /// just to force that every option has virtual dtor
     virtual ~Option();
 
-    /// @brief Checks if two options are equal
+    /// @brief Checks if options are equal.
+    ///
+    /// This method calls a virtual @c equals function to compare objects.
+    /// This method is not meant to be overriden in the derived classes.
+    /// Instead, the other @c equals function must be overriden.
+    ///
+    /// @param other Pointer to the option to compare this option to.
+    /// @return true if both options are equal, false otherwise.
+    bool equals(const OptionPtr& other) const;
+
+    /// @brief Checks if two options are equal.
     ///
     /// Equality verifies option type and option content. Care should
     /// be taken when using this method. Implementation for derived
@@ -361,9 +371,10 @@ public:
     /// will detect differences between base Option and derived
     /// objects.
     ///
-    /// @param other the other option
-    /// @return true if both options are equal
-    virtual bool equal(const OptionPtr& other) const;
+    /// @param other Instance of the option to compare to.
+    ///
+    /// @return true if options are equal, false otherwise.
+    virtual bool equals(const Option& other) const;
 
 protected:
 

+ 32 - 8
src/lib/dhcp/tests/option_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -547,8 +547,9 @@ TEST_F(OptionTest, setData) {
                             buf_.size()));
 }
 
-// This test verifies that options can be compared using equal() method.
-TEST_F(OptionTest, equal) {
+// This test verifies that options can be compared using equals(OptionPtr)
+// method.
+TEST_F(OptionTest, equalsWithPointers) {
 
     // Five options with varying lengths
     OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
@@ -561,13 +562,36 @@ TEST_F(OptionTest, equal) {
     // Another instance with the same type and content as opt2
     OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
 
-    EXPECT_TRUE(opt1->equal(opt1));
+    EXPECT_TRUE(opt1->equals(opt1));
 
-    EXPECT_FALSE(opt1->equal(opt2));
-    EXPECT_FALSE(opt1->equal(opt3));
-    EXPECT_FALSE(opt1->equal(opt4));
+    EXPECT_FALSE(opt1->equals(opt2));
+    EXPECT_FALSE(opt1->equals(opt3));
+    EXPECT_FALSE(opt1->equals(opt4));
 
-    EXPECT_TRUE(opt2->equal(opt5));
+    EXPECT_TRUE(opt2->equals(opt5));
+}
+
+// This test verifies that options can be compared using equals(Option) method.
+TEST_F(OptionTest, equals) {
+
+    // Five options with varying lengths
+    Option opt1(Option::V6, 258, buf_.begin(), buf_.begin() + 1);
+    Option opt2(Option::V6, 258, buf_.begin(), buf_.begin() + 2);
+    Option opt3(Option::V6, 258, buf_.begin(), buf_.begin() + 3);
+
+    // The same content as opt2, but different type
+    Option opt4(Option::V6, 1, buf_.begin(), buf_.begin() + 2);
+
+    // Another instance with the same type and content as opt2
+    Option opt5(Option::V6, 258, buf_.begin(), buf_.begin() + 2);
+
+    EXPECT_TRUE(opt1.equals(opt1));
+
+    EXPECT_FALSE(opt1.equals(opt2));
+    EXPECT_FALSE(opt1.equals(opt3));
+    EXPECT_FALSE(opt1.equals(opt4));
+
+    EXPECT_TRUE(opt2.equals(opt5));
 }
 
 // This test verifies that the name of the option space being encapsulated by

+ 6 - 6
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -790,33 +790,33 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     // closest to the client.
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay3_opt1));
+    EXPECT_TRUE(opt->equals(relay3_opt1));
 
     // We want to ge that one inserted by relay1 (first match, starting from
     // closest to the server.
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay1_opt1));
+    EXPECT_TRUE(opt->equals(relay1_opt1));
 
     // We just want option from the first relay (closest to the client)
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay3_opt1));
+    EXPECT_TRUE(opt->equals(relay3_opt1));
 
     // We just want option from the last relay (closest to the server)
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay1_opt1));
+    EXPECT_TRUE(opt->equals(relay1_opt1));
 
     // Let's try to ask for something that is inserted by the middle relay
     // only.
     opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay2_opt1));
+    EXPECT_TRUE(opt->equals(relay2_opt1));
 
     opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
     ASSERT_TRUE(opt);
-    EXPECT_TRUE(opt->equal(relay2_opt1));
+    EXPECT_TRUE(opt->equals(relay2_opt1));
 
     opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
     EXPECT_FALSE(opt);

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -46,6 +46,7 @@ libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
+libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h

+ 195 - 0
src/lib/dhcpsrv/cfg_option.cc

@@ -0,0 +1,195 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option.h>
+#include <boost/lexical_cast.hpp>
+#include <limits>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+bool
+OptionDescriptor::equals(const OptionDescriptor& other) const {
+    return (persistent_ == other.persistent_ &&
+            option_->equals(other.option_));
+}
+
+bool
+CfgOption::equals(const CfgOption& other) const {
+    return (options_.equals(other.options_) &&
+            vendor_options_.equals(other.vendor_options_));
+}
+
+void
+CfgOption::add(const OptionPtr& option, const bool persistent,
+               const std::string& option_space) {
+    if (!option) {
+        isc_throw(isc::BadValue, "option being configured must not be NULL");
+
+    } else  if (!OptionSpace::validateName(option_space)) {
+        isc_throw(isc::BadValue, "invalid option space name: '"
+                  << option_space << "'");
+    }
+
+    const uint32_t vendor_id = optionSpaceToVendorId(option_space);
+    if (vendor_id) {
+        vendor_options_.addItem(OptionDescriptor(option, persistent),
+                                vendor_id);
+    } else {
+        options_.addItem(OptionDescriptor(option, persistent), option_space);
+    }
+}
+
+void
+CfgOption::mergeTo(CfgOption& other) const {
+    // Merge non-vendor options.
+    mergeInternal(options_, other.options_);
+    // Merge vendor options.
+    mergeInternal(vendor_options_, other.vendor_options_);
+}
+
+void
+CfgOption::copyTo(CfgOption& other) const {
+    // Remove any existing data in the destination.
+    other.options_.clearItems();
+    other.vendor_options_.clearItems();
+    mergeTo(other);
+}
+
+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) {
+    // Get all options for the particular option space.
+    OptionContainerPtr options = getAll(option_space);
+    // For each option in the option space we will append sub-options
+    // from the option spaces they encapsulate.
+    for (OptionContainer::const_iterator opt = options->begin();
+         opt != options->end(); ++opt) {
+        // Get encapsulated option space for the option.
+        const std::string& encap_space = opt->option_->getEncapsulatedSpace();
+        // Empty value means that no option space is encapsulated.
+        if (!encap_space.empty()) {
+            // Retrieve all options from the encapsulated option space.
+            OptionContainerPtr encap_options = getAll(encap_space);
+            for (OptionContainer::const_iterator encap_opt =
+                     encap_options->begin(); encap_opt != encap_options->end();
+                 ++encap_opt) {
+                // Add sub-option if there isn't one added already.
+                if (!opt->option_->getOption(encap_opt->option_->getType())) {
+                    opt->option_->addOption(encap_opt->option_);
+                }
+            }
+        }
+    }
+}
+
+template <typename Selector>
+void
+CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
+                         OptionDescriptor, Selector>& src_container,
+                         OptionSpaceContainer<OptionContainer,
+                         OptionDescriptor, Selector>& dest_container) const {
+    // Get all option spaces used in source container.
+    std::list<Selector> selectors = src_container.getOptionSpaceNames();
+
+    // For each space in the source container retrieve the actual options and
+    // match them with the options held in the destination container under
+    // the same space.
+    for (typename std::list<Selector>::const_iterator it = selectors.begin();
+         it != selectors.end(); ++it) {
+        // Get all options in the destination container for the particular
+        // option space.
+        OptionContainerPtr dest_all = dest_container.getItems(*it);
+        OptionContainerPtr src_all = src_container.getItems(*it);
+        // For each option under this option space check if there is a
+        // corresponding option in the destination container. If not,
+        // add one.
+        for (OptionContainer::const_iterator src_opt = src_all->begin();
+             src_opt != src_all->end(); ++src_opt) {
+            const OptionContainerTypeIndex& idx = dest_all->get<1>();
+            const OptionContainerTypeRange& range =
+                idx.equal_range(src_opt->option_->getType());
+            // If there is no such option in the destination container,
+            // add one.
+            if (std::distance(range.first, range.second) == 0) {
+                dest_container.addItem(OptionDescriptor(src_opt->option_,
+                                                        src_opt->persistent_),
+                                       *it);
+            }
+        }
+    }
+}
+
+
+OptionContainerPtr
+CfgOption::getAll(const std::string& option_space) const {
+    return (options_.getItems(option_space));
+}
+
+OptionContainerPtr
+CfgOption::getAll(const uint32_t vendor_id) const {
+    return (vendor_options_.getItems(vendor_id));
+}
+
+uint32_t
+CfgOption::optionSpaceToVendorId(const std::string& option_space) {
+    if (option_space.size() < 8) {
+        // 8 is a minimal length of "vendor-X" format
+        return (0);
+    }
+    if (option_space.substr(0,7) != "vendor-") {
+        return (0);
+    }
+
+    // text after "vendor-", supposedly numbers only
+    std::string x = option_space.substr(7);
+
+    int64_t check;
+    try {
+        check = boost::lexical_cast<int64_t>(x);
+    } catch (const boost::bad_lexical_cast &) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
+        //           << ") as unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check > std::numeric_limits<uint32_t>::max()) {
+        /// @todo: Should we throw here?
+        //isc_throw(BadValue, "Value " << x << "is too large"
+        //          << " for unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check < 0) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Value " << x << "is negative."
+        //       << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+        return (0);
+    }
+
+    // value is small enough to fit
+    return (static_cast<uint32_t>(check));
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 404 - 0
src/lib/dhcpsrv/cfg_option.h

@@ -0,0 +1,404 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CFG_OPTION_H
+#define CFG_OPTION_H
+
+#include <dhcp/option.h>
+#include <dhcpsrv/key_from_key.h>
+#include <dhcpsrv/option_space_container.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Option descriptor.
+///
+/// Option descriptor holds instance of an option and additional information
+/// for this option. This information comprises whether this option is sent
+/// to DHCP client only on request (persistent = false) or always
+/// (persistent = true).
+struct OptionDescriptor {
+    /// Option instance.
+    OptionPtr option_;
+    /// Persistent flag, if true option is always sent to the client,
+    /// if false option is sent to the client on request.
+    bool persistent_;
+
+    /// @brief Constructor.
+    ///
+    /// @param opt option
+    /// @param persist if true option is always sent.
+    OptionDescriptor(const OptionPtr& opt, bool persist)
+        : option_(opt), persistent_(persist) {};
+
+    /// @brief Constructor
+    ///
+    /// @param persist if true option is always sent.
+    OptionDescriptor(bool persist)
+        : option_(OptionPtr()), persistent_(persist) {};
+
+    /// @brief Checks if the one descriptor is equal to another.
+    ///
+    /// @param other Other option descriptor to compare to.
+    ///
+    /// @return true if descriptors equal, false otherwise.
+    bool equals(const OptionDescriptor& other) const;
+
+    /// @brief Equality operator.
+    ///
+    /// @param other Other option descriptor to compare to.
+    ///
+    /// @return true if descriptors equal, false otherwise.
+    bool operator==(const OptionDescriptor& other) const {
+        return (equals(other));
+    }
+
+    /// @brief Inequality operator.
+    ///
+    /// @param other Other option descriptor to compare to.
+    ///
+    /// @return true if descriptors unequal, false otherwise.
+    bool operator!=(const OptionDescriptor& other) const {
+        return (!equals(other));
+    }
+};
+
+/// A pointer to option descriptor.
+typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
+/// @brief Multi index container for DHCP option descriptors.
+///
+/// This container comprises three indexes to access option
+/// descriptors:
+/// - sequenced index: used to access elements in the order they
+/// have been added to the container,
+/// - option type index: used to search option descriptors containing
+/// options with specific option code (aka option type).
+/// - persistency flag index: used to search option descriptors with
+/// 'persistent' flag set to true.
+///
+/// This container is the equivalent of three separate STL containers:
+/// - std::list of all options,
+/// - std::multimap of options with option code used as a multimap key,
+/// - std::multimap of option descriptors with option persistency flag
+/// used as a multimap key.
+/// The major advantage of this container over 3 separate STL containers
+/// is automatic synchronization of all indexes when elements are added,
+/// removed or modified in the container. With separate containers,
+/// the synchronization would have to be guaranteed by the Subnet class
+/// code. This would increase code complexity and presumably it would
+/// be much harder to add new search criteria (indexes).
+///
+/// @todo we may want to search for options using option spaces when
+/// they are implemented.
+///
+/// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
+typedef boost::multi_index_container<
+    // Container comprises elements of OptionDescriptor type.
+    OptionDescriptor,
+    // Here we start enumerating various indexes.
+    boost::multi_index::indexed_by<
+        // Sequenced index allows accessing elements in the same way
+        // as elements in std::list.
+        // Sequenced is an index #0.
+        boost::multi_index::sequenced<>,
+        // Start definition of index #1.
+        boost::multi_index::hashed_non_unique<
+            // KeyFromKeyExtractor is the index key extractor that allows
+            // accessing option type being held by the OptionPtr through
+            // OptionDescriptor structure.
+            KeyFromKeyExtractor<
+                // Use option type as the index key. The type is held
+                // in OptionPtr object so we have to call Option::getType
+                // to retrieve this key for each element.
+                boost::multi_index::const_mem_fun<
+                    Option,
+                    uint16_t,
+                    &Option::getType
+                >,
+                // Indicate that OptionPtr is a member of
+                // OptionDescriptor structure.
+                boost::multi_index::member<
+                    OptionDescriptor,
+                    OptionPtr,
+                    &OptionDescriptor::option_
+                 >
+            >
+        >,
+        // Start definition of index #2.
+        // Use 'persistent' struct member as a key.
+        boost::multi_index::hashed_non_unique<
+            boost::multi_index::member<
+                OptionDescriptor,
+                bool,
+                &OptionDescriptor::persistent_
+            >
+        >
+    >
+> OptionContainer;
+
+/// Pointer to the OptionContainer object.
+typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
+/// Type of the index #1 - option type.
+typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
+/// Pair of iterators to represent the range of options having the
+/// same option type value. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerTypeIndex::const_iterator,
+                  OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
+/// Type of the index #2 - option persistency flag.
+typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
+
+/// @brief Represents option data configuration for the DHCP server.
+///
+/// This class holds a collection of options to be sent to a DHCP client.
+/// Options are grouped by the option space or vendor identifier (for
+/// vendor options).
+///
+/// The server configuration allows for specifying two distinct collections
+/// of options: global options and per-subnet options in which some options
+/// may overlap.
+///
+/// The collection of global options specify options being sent to the client
+/// belonging to any subnets, i.e. global options are "inherited" by all
+/// subnets.
+///
+/// The per-subnet options are configured for a particular subnet and are sent
+/// to clients which belong to this subnet. The values of the options specified
+/// for a particular subnet override the values of the global options.
+///
+/// This class represents a single collection of options (either global or
+/// per-subnet). Each subnet holds its own object of the @c CfgOption type. The
+/// @c CfgMgr holds a @c CfgOption object representing global options.
+///
+/// Note that having a separate copy of the @c CfgOption to represent global
+/// options is useful when the client requests stateless configuration from
+/// the DHCP server and no subnet is selected for this client. This client
+/// will only receive global options.
+class CfgOption {
+public:
+
+    /// @name Methods and operators used for comparing objects.
+    ///
+    //@{
+    /// @brief Check if configuration is equal to other configuration.
+    ///
+    /// @param other An object holding configuration to compare to.
+    ///
+    /// @return true if configurations are equal, false otherwise.
+    bool equals(const CfgOption& other) const;
+
+    /// @brief Equality operator.
+    ///
+    /// @param other An object holding configuration to compare to.
+    ///
+    /// @return true if configurations are equal, false otherwise.
+    bool operator==(const CfgOption& other) const {
+        return (equals(other));
+    }
+
+    /// @brief Inequality operator.
+    ///
+    /// @param other An object holding configuration to compare to.
+    ///
+    /// @return true if configurations are unequal, false otherwise.
+    bool operator!=(const CfgOption& other) const {
+        return (!equals(other));
+    }
+
+    //@}
+
+    /// @brief Adds instance of the option to the configuration.
+    ///
+    /// There are two types of options which may be passed to this method:
+    /// - vendor options
+    /// - non-vendor options
+    ///
+    /// The non-vendor options are grouped by the name of the option space
+    /// (specified in textual format). The vendor options are grouped by the
+    /// vendor identifier, which is a 32-bit unsigned integer value.
+    ///
+    /// In order to add new vendor option to the list the option space name
+    /// (last argument of this method) should be specified as "vendor-X" where
+    /// "X" is a 32-bit unsigned integer, e.g. "vendor-1234". Options for which
+    /// the @c option_space argument doesn't follow this format are added as
+    /// non-vendor options.
+    ///
+    /// @param option Pointer to the option being added.
+    /// @param persistent Boolean value which specifies if the option should
+    /// be sent to the client regardless if requested (true), or nor (false)
+    /// @param option_space Option space name.
+    ///
+    /// @throw isc::BadValue if the option space is invalid.
+    void add(const OptionPtr& option, const bool persistent,
+             const std::string& option_space);
+
+    /// @brief Merges this configuration to another configuration.
+    ///
+    /// This method iterates over the configuration items held in this
+    /// configuration and copies them to the configuration specified
+    /// as a parameter. If an item exists in the destination it is not
+    /// copied.
+    ///
+    /// @param [out] other Configuration object to merge to.
+    void mergeTo(CfgOption& other) const;
+
+    /// @brief Copies this configuration to another configuration.
+    ///
+    /// This method copies options configuration to another object.
+    ///
+    /// @param [out] other An object to copy the configuration to.
+    void copyTo(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
+    /// name in the format of "vendor-X" where X is 32-bit unsiged integer.
+    /// See @c getAll(uint32_t) for vendor options.
+    ///
+    /// @param option_space Name of the option space.
+    ///
+    /// @return Pointer to the container holding returned options. This
+    /// container is empty if no options have been found.
+    OptionContainerPtr getAll(const std::string& option_space) const;
+
+    /// @brief Returns vendor options for the specified vendor id.
+    ///
+    /// @param vendor_id Vendor id for which options are to be returned.
+    ///
+    /// @return Pointer to the container holding returned options. This
+    /// container is empty if no options have been found.
+    OptionContainerPtr getAll(const uint32_t vendor_id) const;
+
+    /// @brief Returns option for the specified key and option code.
+    ///
+    /// The key should be a string, in which case it specifies an option space
+    /// name, or an uint32_t value, in which case it specifies a vendor
+    /// identifier.
+    ///
+    /// @param key Option space name or vendor identifier.
+    /// @param option_code Code of the option to be returned.
+    /// @tparam Selector one of: @c std::string or @c uint32_t
+    ///
+    /// @return Descriptor of the option. If option hasn't been found, the
+    /// descriptor holds NULL option.
+    template<typename Selector>
+    OptionDescriptor get(const Selector& key,
+                         const uint16_t option_code) const {
+        OptionContainerPtr options = getAll(key);
+        if (!options || options->empty()) {
+            return (OptionDescriptor(false));
+        }
+
+        const OptionContainerTypeIndex& idx = options->get<1>();
+        const OptionContainerTypeRange& range = idx.equal_range(option_code);
+        if (std::distance(range.first, range.second) == 0) {
+            return (OptionDescriptor(false));
+        }
+
+        return (*range.first);
+    }
+
+    /// @brief Converts option space name to vendor id.
+    ///
+    /// If the option space name is specified in the following format:
+    /// "vendor-X" where X is an uint32_t number, it is assumed to be
+    /// a vendor space and the uint32_t number is returned by this function.
+    /// If the option space name is invalid this method will return 0, which
+    /// is not a valid vendor-id, to signal an error.
+    ///
+    /// @todo remove this function once when the conversion is dealt by the
+    /// appropriate functions returning options by option space names.
+    ///
+    /// @param option_space Option space name.
+    /// @return vendor id.
+    static uint32_t optionSpaceToVendorId(const std::string& option_space);
+
+private:
+
+    /// @brief Appends encapsulated options to the options in an option space.
+    ///
+    /// This method appends sub-options to the options belonging to the
+    /// particular option space. For example: if the option space "foo"
+    /// is specified, this function will go over all options belonging to
+    /// "foo" and will check which option spaces they encapsulate. For each
+    /// such option it will retrieve options for these option spaces and append
+    /// as sub-options to options belonging to "foo".
+    ///
+    /// @param option_space Name of the option space containing option 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
+    /// option container. This function is templated because containers
+    /// may use different type of selectors. For non-vendor options
+    /// the selector is of the @c std::string type, for vendor options
+    /// the selector is of the @c uint32_t type.
+    ///
+    /// @param src_container Reference to a container from which the data
+    /// will be merged.
+    /// @param [out] dest_container Reference to a container to which the
+    /// data will be merged.
+    /// @tparam Type of the selector: @c std::string or @c uint32_t.
+    template <typename Selector>
+    void mergeInternal(const OptionSpaceContainer<OptionContainer,
+                       OptionDescriptor, Selector>& src_container,
+                       OptionSpaceContainer<OptionContainer,
+                       OptionDescriptor, Selector>& dest_container) const;
+
+    /// @brief Type of the container holding options grouped by option space.
+    typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+                                 std::string> OptionSpaceCollection;
+    /// @brief Container holding options grouped by option space.
+    OptionSpaceCollection options_;
+
+    /// @brief Type of the container holding options grouped by vendor id.
+    typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+                                 uint32_t> VendorOptionSpaceCollection;
+    /// @brief Container holding options grouped by vendor id.
+    VendorOptionSpaceCollection vendor_options_;
+};
+
+/// @name Pointers to the @c CfgOption objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgOption> CfgOptionPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgOption> ConstCfgOptionPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_OPTION_H

+ 2 - 0
src/lib/dhcpsrv/cfg_option_def.cc

@@ -22,6 +22,8 @@ namespace dhcp {
 
 void
 CfgOptionDef::copyTo(CfgOptionDef& new_config) const {
+    // Remove any existing option definitions from the destination.
+    new_config.option_definitions_.clearItems();
     const std::list<std::string>& names =
         option_definitions_.getOptionSpaceNames();
     for (std::list<std::string>::const_iterator name = names.begin();

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

@@ -43,11 +43,6 @@ public:
     /// to an object passed as parameter. There are no shared objects or
     /// pointers between the original object and a copy.
     ///
-    /// @warning This function doesn't perform a cleanup of the @c new_config
-    /// before copying the new configuration into it. It is caller's
-    /// responsibility to make sure that this object doesn't contain any
-    /// garbage data.
-    ///
     /// @param [out] new_config An object to which the configuration will be
     /// copied.
     void copyTo(CfgOptionDef& new_config) const;

+ 1 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -141,7 +141,7 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option,
         }
 
         if ( (*subnet)->getInterfaceId() &&
-             ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
+             ((*subnet)->getInterfaceId()->equals(iface_id_option))) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
                 .arg((*subnet)->toText());

+ 78 - 265
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -15,6 +15,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <hooks/hooks_manager.h>
 #include <util/encode/hex.h>
@@ -42,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)
 {
@@ -52,7 +52,6 @@ ParserContext::ParserContext(const ParserContext& rhs):
     boolean_values_(),
     uint32_values_(),
     string_values_(),
-    options_(),
     hooks_libraries_(),
     universe_(rhs.universe_)
 {
@@ -76,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_;
@@ -280,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();
     }
 }
 
@@ -332,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().
+    if (!option_descriptor_.option_) {
         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();
-    Subnet::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);
-    Subnet::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.
-    Subnet::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;
@@ -378,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
@@ -390,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 '"
@@ -442,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
@@ -509,14 +510,14 @@ 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
         // old option is replaced.
-        option_descriptor_.option = option;
-        option_descriptor_.persistent = false;
+        option_descriptor_.option_ = option;
+        option_descriptor_.persistent_ = false;
     } else {
 
         // Option name should match the definition. The option name
@@ -536,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);
-            Subnet::OptionDescriptor desc(option, false);
-            option_descriptor_.option = option;
-            option_descriptor_.persistent = false;
+                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
@@ -558,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);
     }
 }
@@ -600,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 ****************************
@@ -976,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 +1002,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 Subnet::OptionContainerPtr sub_opts =
-                global_context_->options_->getItems(encapsulated_space);
-        // Append sub-options to the option.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
-            if (desc.option) {
-                option->addOption(desc.option);
-            }
-        }
-    }
-}
-
-void
 SubnetConfigParser::createSubnet() {
     std::string subnet_txt;
     try {
@@ -1145,119 +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(Subnet::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.
-            Subnet::OptionDescriptor existing_desc =
-                            subnet_->getOptionDescriptor("option_space",
-                                                 desc.option->getType());
-            if (existing_desc.option) {
-                duplicate_option_warning(desc.option->getType(), addr);
-            }
-            // Add sub-options (if any).
-            appendSubOptions(option_space, desc.option);
-
-            // Check if the option space defines a vendor-option
-            uint32_t vendor_id = optionSpaceToVendorId(option_space);
-            if (vendor_id) {
-                // This is a vendor option
-                subnet_->addVendorOption(desc.option, false, vendor_id);
-            } else {
-                // This is a normal option
-                subnet_->addOption(desc.option, false, option_space);
-            }
-        }
-    }
-
-    // Check all global options and add them to the subnet object if
-    // they have been configured in the global scope. If they have been
-    // configured in the subnet scope we don't add global option because
-    // the one configured in the subnet scope always takes precedence.
-    space_names = global_context_->options_->getOptionSpaceNames();
-    BOOST_FOREACH(std::string option_space, space_names) {
-        // Get all global options for the particular option space.
-        BOOST_FOREACH(Subnet::OptionDescriptor desc,
-                *(global_context_->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);
-            // Check if the particular option has been already added.
-            // This would mean that it has been configured in the
-            // subnet scope. Since option values configured in the
-            // subnet scope take precedence over globally configured
-            // values we don't add option from the global storage
-            // if there is one already.
-            Subnet::OptionDescriptor existing_desc =
-                    subnet_->getOptionDescriptor(option_space,
-                                                desc.option->getType());
-            if (!existing_desc.option) {
-                // Add sub-options (if any).
-                appendSubOptions(option_space, desc.option);
-
-                uint32_t vendor_id = optionSpaceToVendorId(option_space);
-                if (vendor_id) {
-                    // This is a vendor option
-                    subnet_->addVendorOption(desc.option, false, vendor_id);
-                } else {
-                    // This is a normal option
-                    subnet_->addOption(desc.option, false, option_space);
-                }
-            }
-        }
-    }
-}
-
-uint32_t
-SubnetConfigParser::optionSpaceToVendorId(const std::string& option_space) {
-    if (option_space.size() < 8) {
-        // 8 is a minimal length of "vendor-X" format
-        return (0);
-    }
-    if (option_space.substr(0,7) != "vendor-") {
-        return (0);
-    }
-
-    // text after "vendor-", supposedly numbers only
-    string x = option_space.substr(7);
-
-    int64_t check;
-    try {
-        check = boost::lexical_cast<int64_t>(x);
-    } catch (const boost::bad_lexical_cast &) {
-        /// @todo: Should we throw here?
-        // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
-        //           << ") as unsigned 32-bit integer.");
-        return (0);
-    }
-    if (check > std::numeric_limits<uint32_t>::max()) {
-        /// @todo: Should we throw here?
-        //isc_throw(BadValue, "Value " << x << "is too large"
-        //          << " for unsigned 32-bit integer.");
-        return (0);
-    }
-    if (check < 0) {
-        /// @todo: Should we throw here?
-        // isc_throw(BadValue, "Value " << x << "is negative."
-        //       << " Only 0 or larger are allowed for unsigned 32-bit integer.");
-        return (0);
-    }
-
-    // value is small enough to fit
-    return (static_cast<uint32_t>(check));
+    // Merge globally defined options to the subnet specific options.
+    CfgMgr::instance().getStagingCfg()->getCfgOption()->mergeTo(*options_);
+    // Copy all options to the subnet configuration.
+    options_->copyTo(*subnet_->getCfgOption());
+    // Append suboptions to the top-level options.
+    subnet_->getCfgOption()->encapsulate();
 }
 
 isc::dhcp::Triplet<uint32_t>

+ 34 - 74
src/lib/dhcpsrv/dhcp_parsers.h

@@ -21,6 +21,7 @@
 #include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
@@ -42,8 +43,8 @@ typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
 
 /// Collection of containers holding option spaces. Each container within
 /// a particular option space holds so-called option descriptors.
-typedef OptionSpaceContainer<Subnet::OptionContainer,
-    Subnet::OptionDescriptor, std::string> OptionStorage;
+typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+                             std::string> OptionStorage;
 /// @brief Shared pointer to option storage.
 typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 
@@ -213,9 +214,6 @@ public:
     /// @brief Storage for string parameters.
     StringStoragePtr string_values_;
 
-    /// @brief Storage for options.
-    OptionStoragePtr options_;
-
     /// @brief Hooks libraries pointer.
     ///
     /// The hooks libraries information is a vector of strings, each containing
@@ -513,20 +511,19 @@ public:
     ///
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
-    /// @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.
+    /// @param [out] cfg Pointer to the configuration object where parsed option
+    /// should be stored or NULL if this is a global option.
+    /// @param address_family Address family: @c AF_INET or @c AF_INET6.
     /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
-    OptionDataParser(const std::string& dummy, OptionStoragePtr options,
-                    ParserContextPtr global_context);
+    OptionDataParser(const std::string& dummy, const CfgOptionPtr& cfg,
+                     const uint16_t address_family);
 
     /// @brief Parses the single option data.
     ///
     /// This method parses the data of a single option from the configuration.
     /// The option data includes option name, option code and data being
     /// carried by this option. Eventually it creates the instance of the
-    /// option.
+    /// option and adds it to the Configuration Manager.
     ///
     /// @param option_data_entries collection of entries that define value
     /// for a particular option.
@@ -536,15 +533,7 @@ public:
     /// calling build.
     virtual void build(isc::data::ConstElementPtr option_data_entries);
 
-    /// @brief Commits option value.
-    ///
-    /// This function adds a new option to the storage or replaces an existing
-    /// option with the same code.
-    ///
-    /// @throw isc::InvalidOperation if failed to set pointer to storage or
-    /// failed
-    /// to call build() prior to commit. If that happens data in the storage
-    /// remain un-modified.
+    /// @brief Does nothing.
     virtual void commit();
 
     /// @brief virtual destructor to ensure orderly destruction of derivations.
@@ -554,9 +543,7 @@ 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. This
-    /// method is pure virtual requiring derivations to manage which option
-    /// space(s) is valid for search.
+    /// 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
@@ -564,8 +551,9 @@ protected:
     /// 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) = 0;
+    virtual OptionDefinitionPtr
+    findServerSpaceOptionDefinition(const std::string& option_space,
+                                    const uint32_t option_code) const;
 
 private:
 
@@ -595,19 +583,18 @@ private:
     /// Storage for uint32 values (e.g. option code).
     Uint32StoragePtr uint32_values_;
 
-    /// Pointer to options storage. This storage is provided by
-    /// the calling class and is shared by all OptionDataParser objects.
-    OptionStoragePtr options_;
-
     /// Option descriptor holds newly configured option.
-    Subnet::OptionDescriptor option_descriptor_;
+    OptionDescriptor option_descriptor_;
 
     /// Option space name where the option belongs to.
     std::string option_space_;
 
-    /// Parsing context which contains global values, options and option
-    /// definitions.
-    ParserContextPtr global_context_;
+    /// @brief Configuration holding option being parsed or NULL if the option
+    /// is global.
+    CfgOptionPtr cfg_;
+
+    /// @brief Address family: @c AF_INET or @c AF_INET6.
+    uint16_t address_family_;
 };
 
 ///@brief Function pointer for OptionDataParser factory methods
@@ -625,15 +612,11 @@ public:
     /// @brief Constructor.
     ///
     /// @param dummy nominally would be param name, this is always ignored.
-    /// @param options parsed option storage for options in this list
-    /// @param global_context is a pointer to the global context which
-    /// stores global scope parameters, options, option defintions.
-    /// @param optionDataParserFactory factory method for creating individual
-    /// option parsers
-    /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
-    OptionDataListParser(const std::string& dummy, OptionStoragePtr options,
-                        ParserContextPtr global_context,
-                        OptionDataParserFactory *optionDataParserFactory);
+    /// @param [out] cfg Pointer to the configuration object where options
+    /// should be stored or NULL if this is global option scope.
+    /// @param address_family Address family: @c AF_INET or AF_INET6
+    OptionDataListParser(const std::string& dummy, const CfgOptionPtr& cfg,
+                         const uint16_t address_family);
 
     /// @brief Parses entries that define options' data for a subnet.
     ///
@@ -650,24 +633,16 @@ public:
     void commit();
 
 private:
-    /// Pointer to options instances storage.
-    OptionStoragePtr options_;
-
-    /// Intermediate option storage. This storage is used by
-    /// lower level parsers to add new options.  Values held
-    /// in this storage are assigned to main storage (options_)
-    /// if overall parsing was successful.
-    OptionStoragePtr local_options_;
 
     /// Collection of parsers;
     ParserCollection parsers_;
 
-    /// Parsing context which contains global values, options and option
-    /// definitions.
-    ParserContextPtr global_context_;
+    /// @brief Pointer to a configuration where options are stored.
+    CfgOptionPtr cfg_;
+
+    /// @brief Address family: @c AF_INET or @c AF_INET6
+    uint16_t address_family_;
 
-    /// Factory to create server-specific option data parsers
-    OptionDataParserFactory *optionDataParserFactory_;
 };
 
 
@@ -961,15 +936,6 @@ public:
     /// @brief Adds the created subnet to a server's configuration.
     virtual void commit() = 0;
 
-    /// @brief tries to convert option_space string to numeric vendor_id
-    ///
-    /// This will work if the option_space has format "vendor-X", where
-    /// X can be any value between 1 and MAX_UINT32.
-    /// This is used to detect whether a given option-space is a vendor
-    /// space or not. Returns 0 if the format is different.
-    /// @return numeric vendor-id (or 0 if the format does not match)
-    static uint32_t optionSpaceToVendorId(const std::string& option_space);
-
 protected:
     /// @brief creates parsers for entries in subnet definition
     ///
@@ -1039,12 +1005,6 @@ protected:
 
 private:
 
-    /// @brief Append sub-options to an option.
-    ///
-    /// @param option_space a name of the encapsulated option space.
-    /// @param option option instance to append sub-options to.
-    void appendSubOptions(const std::string& option_space, OptionPtr& option);
-
     /// @brief Create a new subnet using a data from child parsers.
     ///
     /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
@@ -1062,9 +1022,6 @@ protected:
     /// Storage for pools belonging to this subnet.
     PoolStoragePtr pools_;
 
-    /// Storage for options belonging to this subnet.
-    OptionStoragePtr options_;
-
     /// Parsers are stored here.
     ParserCollection parsers_;
 
@@ -1077,6 +1034,9 @@ protected:
 
     /// Pointer to relay information
     isc::dhcp::Subnet::RelayInfoPtr relay_info_;
+
+    /// Pointer to the options configuration.
+    CfgOptionPtr options_;
 };
 
 /// @brief Parser for  D2ClientConfig

+ 29 - 0
src/lib/dhcpsrv/libdhcpsrv.dox

@@ -81,6 +81,35 @@ one that occurred before it etc.
 the \ref isc::dhcp::SrvConfig object. Kea developers are actively working
 on migrating the other configuration parameters to it.
 
+@section optionsConfig Options Configuration Information
+
+The \ref isc::dhcp::CfgOption object holds a collection of options being
+sent to the client. Since each subnet comes with a distnict set of
+options, every \ref isc::dhcp::Subnet object holds its own copy of the
+\ref isc::dhcp::CfgOption object with specific options.
+
+The DHCP server also allows for configuration of "global" options
+which are shared by all subnets. The rule here is that if a particular
+option appears in the global options set and the subnet specific options
+set, the subnet specific option takes precedence. The global options
+configuration is held in the dedicated instance of the
+\ref isc::dhcp::CfgOption class. This instance is owned by the
+\ref isc::dhcp::SrvConfig class.
+
+When the new configuration is parsed, the global options are merged into
+the \ref isc::dhcp::CfgOption instances for all subnets. This is
+causing some overhead during the reconfiguration of the server but on
+the other hand it avoids the lookup of options in two places (among
+subnet specific options and global options) during each packet
+processing.
+
+One of the benefits of keeping a separate set of global options is
+that there may be cases when the server administrator doesn't specify
+any subnet configuration and only wants global options to be used.
+This is the case, when the DHCP server is used for stateless
+configuration, i.e. client's are not allocated an address or prefix,
+and only stateless configruation is handed out.
+
 @section allocengine Allocation Engine
 
 Allocation Engine (\ref isc::dhcp::AllocEngine) is what its name say - an engine

+ 51 - 1
src/lib/dhcpsrv/option_space_container.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -89,6 +89,56 @@ public:
         option_space_map_.clear();
     }
 
+    /// @brief Check if two containers are equal.
+    ///
+    /// This method checks if option space container contains exactly
+    /// the same selectors and that there are exactly the same items
+    /// added for each selector. The order of items doesn't matter.
+    ///
+    /// @param other Other container to compare to.
+    ///
+    /// @return true if containers are equal, false otherwise.
+    bool equals(const OptionSpaceContainer& other) const {
+        for (typename OptionSpaceMap::const_iterator it =
+                 option_space_map_.begin(); it != option_space_map_.end();
+             ++it) {
+
+            typename OptionSpaceMap::const_iterator other_it =
+                other.option_space_map_.find(it->first);
+            if (other_it == other.option_space_map_.end()) {
+                return (false);
+            }
+
+            // If containers have different sizes it is an indication that
+            // they are unequal.
+            if (it->second->size() != other_it->second->size()) {
+                return (false);
+            }
+
+            // If they have the same sizes, we have to compare each element.
+            for (typename ContainerType::const_iterator items_it =
+                     it->second->begin();
+                 items_it != it->second->end(); ++items_it) {
+
+                bool match_found = false;
+                for (typename ContainerType::const_iterator other_items_it =
+                         other_it->second->begin();
+                     other_items_it != other_it->second->end();
+                     ++other_items_it) {
+                    if (items_it->equals(*other_items_it)) {
+                        match_found = true;
+                        break;
+                    }
+                }
+
+                if (!match_found) {
+                    return (false);
+                }
+            }
+        }
+        return (true);
+    }
+
 private:
 
     /// A map holding container (option space name or vendor-id is the key).

+ 8 - 4
src/lib/dhcpsrv/srv_config.cc

@@ -25,11 +25,13 @@ namespace isc {
 namespace dhcp {
 
 SrvConfig::SrvConfig()
-    : sequence_(0), cfg_option_def_(new CfgOptionDef()) {
+    : sequence_(0), cfg_option_def_(new CfgOptionDef()),
+      cfg_option_(new CfgOption()) {
 }
 
-SrvConfig::SrvConfig(uint32_t sequence)
-    : sequence_(sequence), cfg_option_def_(new CfgOptionDef()) {
+SrvConfig::SrvConfig(const uint32_t sequence)
+    : sequence_(sequence), cfg_option_def_(new CfgOptionDef()),
+      cfg_option_(new CfgOption()) {
 }
 
 std::string
@@ -90,6 +92,7 @@ SrvConfig::copy(SrvConfig& new_config) const {
     new_config.setCfgIface(cfg_iface_);
     // Replace option definitions.
     cfg_option_def_->copyTo(*new_config.cfg_option_def_);
+    cfg_option_->copyTo(*new_config.cfg_option_);
 }
 
 void
@@ -132,7 +135,8 @@ SrvConfig::equals(const SrvConfig& other) const {
     }
     // Logging information is equal between objects, so check other values.
     return ((cfg_iface_ == other.cfg_iface_) &&
-            (*cfg_option_def_ == *other.cfg_option_def_));
+            (*cfg_option_def_ == *other.cfg_option_def_) &&
+            (*cfg_option_ == *other.cfg_option_));
 }
 
 }

+ 28 - 1
src/lib/dhcpsrv/srv_config.h

@@ -16,6 +16,7 @@
 #define DHCPSRV_CONFIG_H
 
 #include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option_def.h>
 #include <dhcpsrv/logging_info.h>
 #include <boost/shared_ptr.hpp>
@@ -66,7 +67,7 @@ public:
     /// @brief Constructor.
     ///
     /// Sets arbitrary configuration sequence number.
-    SrvConfig(uint32_t sequence);
+    SrvConfig(const uint32_t sequence);
 
     /// @brief Returns summary of the configuration in the textual format.
     ///
@@ -163,6 +164,26 @@ public:
         return (cfg_option_def_);
     }
 
+    /// @brief Returns pointer to the non-const object holding options.
+    ///
+    /// This method returns a pointer to the object which holds instances
+    /// of the options to be returned to the clients belonging to any subnet.
+    ///
+    /// @return Pointer to the object holding options.
+    CfgOptionPtr getCfgOption() {
+        return (cfg_option_);
+    }
+
+    /// @brief Returns pointer to the const object holding options.
+    ///
+    /// This method returns a pointer to the object which holds instances
+    /// of the options to be returned to the clients belonging to any subnet.
+    ///
+    /// @return Pointer to the object holding options.
+    const ConstCfgOptionPtr getCfgOption() const {
+        return (cfg_option_);
+    }
+
     //@}
 
     /// @brief Copies the currnet configuration to a new configuration.
@@ -250,6 +271,12 @@ private:
     /// by option space name.
     CfgOptionDefPtr cfg_option_def_;
 
+    /// @brief Pointer to options (data) configuration.
+    ///
+    /// This object holds the instances of the options to be sent to clients
+    /// connected to any subnet.
+    CfgOptionPtr cfg_option_;
+
 };
 
 /// @name Pointers to the @c SrvConfig object.

+ 3 - 95
src/lib/dhcpsrv/subnet.cc

@@ -37,7 +37,9 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
      t1_(t1), t2_(t2), valid_(valid_lifetime),
      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
-     last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay)
+     last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay),
+     cfg_option_(new CfgOption())
+
       {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
@@ -59,20 +61,6 @@ Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
 }
 
 void
-Subnet::addOption(const OptionPtr& option, bool persistent,
-                  const std::string& option_space) {
-    // Check that the option space name is valid.
-    if (!OptionSpace::validateName(option_space)) {
-        isc_throw(isc::BadValue, "invalid option space name: '"
-                  << option_space << "'");
-    }
-    validateOption(option);
-
-    // Actually add new option descriptor.
-    option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
-}
-
-void
 Subnet::setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay) {
     relay_ = relay;
 }
@@ -99,64 +87,6 @@ Subnet::allowClientClass(const isc::dhcp::ClientClass& class_name) {
     white_list_.insert(class_name);
 }
 
-void
-Subnet::delOptions() {
-    option_spaces_.clearItems();
-}
-
-Subnet::OptionContainerPtr
-Subnet::getOptionDescriptors(const std::string& option_space) const {
-    return (option_spaces_.getItems(option_space));
-}
-
-Subnet::OptionDescriptor
-Subnet::getOptionDescriptor(const std::string& option_space,
-                            const uint16_t option_code) {
-    OptionContainerPtr options = getOptionDescriptors(option_space);
-    if (!options || options->empty()) {
-        return (OptionDescriptor(false));
-    }
-    const OptionContainerTypeIndex& idx = options->get<1>();
-    const OptionContainerTypeRange& range = idx.equal_range(option_code);
-    if (std::distance(range.first, range.second) == 0) {
-        return (OptionDescriptor(false));
-    }
-
-    return (*range.first);
-}
-
-void Subnet::addVendorOption(const OptionPtr& option, bool persistent,
-                             uint32_t vendor_id){
-
-    validateOption(option);
-
-    vendor_option_spaces_.addItem(OptionDescriptor(option, persistent), vendor_id);
-}
-
-Subnet::OptionContainerPtr
-Subnet::getVendorOptionDescriptors(uint32_t vendor_id) const {
-    return (vendor_option_spaces_.getItems(vendor_id));
-}
-
-Subnet::OptionDescriptor
-Subnet::getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code) {
-    OptionContainerPtr options = getVendorOptionDescriptors(vendor_id);
-    if (!options || options->empty()) {
-        return (OptionDescriptor(false));
-    }
-    const OptionContainerTypeIndex& idx = options->get<1>();
-    const OptionContainerTypeRange& range = idx.equal_range(option_code);
-    if (std::distance(range.first, range.second) == 0) {
-        return (OptionDescriptor(false));
-    }
-
-    return (*range.first);
-}
-
-void Subnet::delVendorOptions() {
-    vendor_option_spaces_.clearItems();
-}
-
 isc::asiolink::IOAddress Subnet::getLastAllocated(Lease::Type type) const {
     // check if the type is valid (and throw if it isn't)
     checkType(type);
@@ -330,17 +260,6 @@ Subnet::getIface() const {
     return (iface_);
 }
 
-void
-Subnet4::validateOption(const OptionPtr& option) const {
-    if (!option) {
-        isc_throw(isc::BadValue,
-                  "option configured for subnet must not be NULL");
-    } else if (option->getUniverse() != Option::V4) {
-        isc_throw(isc::BadValue,
-                  "expected V4 option to be added to the subnet");
-    }
-}
-
 bool
 Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const {
 
@@ -384,16 +303,5 @@ void Subnet6::checkType(Lease::Type type) const {
     }
 }
 
-void
-Subnet6::validateOption(const OptionPtr& option) const {
-    if (!option) {
-        isc_throw(isc::BadValue,
-                  "option configured for subnet must not be NULL");
-    } else if (option->getUniverse() != Option::V6) {
-        isc_throw(isc::BadValue,
-                  "expected V6 option to be added to the subnet");
-    }
-}
-
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 14 - 215
src/lib/dhcpsrv/subnet.h

@@ -15,22 +15,17 @@
 #ifndef SUBNET_H
 #define SUBNET_H
 
-#include <boost/shared_ptr.hpp>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/hashed_index.hpp>
-#include <boost/multi_index/sequenced_index.hpp>
-#include <boost/multi_index/mem_fun.hpp>
-#include <boost/multi_index/member.hpp>
-
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/classify.h>
-#include <dhcpsrv/key_from_key.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/triplet.h>
 #include <dhcpsrv/lease.h>
 
+#include <boost/shared_ptr.hpp>
+
 namespace isc {
 namespace dhcp {
 
@@ -54,119 +49,6 @@ typedef uint32_t SubnetID;
 class Subnet {
 public:
 
-    /// @brief Option descriptor.
-    ///
-    /// Option descriptor holds information about option configured for
-    /// a particular subnet. This information comprises the actual option
-    /// instance and information whether this option is sent to DHCP client
-    /// only on request (persistent = false) or always (persistent = true).
-    struct OptionDescriptor {
-        /// Option instance.
-        OptionPtr option;
-        /// Persistent flag, if true option is always sent to the client,
-        /// if false option is sent to the client on request.
-        bool persistent;
-
-        /// @brief Constructor.
-        ///
-        /// @param opt option
-        /// @param persist if true option is always sent.
-        OptionDescriptor(const OptionPtr& opt, bool persist)
-            : option(opt), persistent(persist) {};
-
-        /// @brief Constructor
-        ///
-        /// @param persist if true option is always sent.
-        OptionDescriptor(bool persist)
-            : option(OptionPtr()), persistent(persist) {};
-    };
-
-    /// A pointer to option descriptor.
-    typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
-
-    /// @brief Multi index container for DHCP option descriptors.
-    ///
-    /// This container comprises three indexes to access option
-    /// descriptors:
-    /// - sequenced index: used to access elements in the order they
-    /// have been added to the container,
-    /// - option type index: used to search option descriptors containing
-    /// options with specific option code (aka option type).
-    /// - persistency flag index: used to search option descriptors with
-    /// 'persistent' flag set to true.
-    ///
-    /// This container is the equivalent of three separate STL containers:
-    /// - std::list of all options,
-    /// - std::multimap of options with option code used as a multimap key,
-    /// - std::multimap of option descriptors with option persistency flag
-    /// used as a multimap key.
-    /// The major advantage of this container over 3 separate STL containers
-    /// is automatic synchronization of all indexes when elements are added,
-    /// removed or modified in the container. With separate containers,
-    /// the synchronization would have to be guaranteed by the Subnet class
-    /// code. This would increase code complexity and presumably it would
-    /// be much harder to add new search criteria (indexes).
-    ///
-    /// @todo we may want to search for options using option spaces when
-    /// they are implemented.
-    ///
-    /// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
-    typedef boost::multi_index_container<
-        // Container comprises elements of OptionDescriptor type.
-        OptionDescriptor,
-        // Here we start enumerating various indexes.
-        boost::multi_index::indexed_by<
-            // Sequenced index allows accessing elements in the same way
-            // as elements in std::list.
-            // Sequenced is an index #0.
-            boost::multi_index::sequenced<>,
-            // Start definition of index #1.
-            boost::multi_index::hashed_non_unique<
-                // KeyFromKeyExtractor is the index key extractor that allows
-                // accessing option type being held by the OptionPtr through
-                // OptionDescriptor structure.
-                KeyFromKeyExtractor<
-                    // Use option type as the index key. The type is held
-                    // in OptionPtr object so we have to call Option::getType
-                    // to retrieve this key for each element.
-                    boost::multi_index::const_mem_fun<
-                        Option,
-                        uint16_t,
-                        &Option::getType
-                    >,
-                    // Indicate that OptionPtr is a member of
-                    // OptionDescriptor structure.
-                    boost::multi_index::member<
-                        OptionDescriptor,
-                        OptionPtr,
-                        &OptionDescriptor::option
-                    >
-                 >
-            >,
-            // Start definition of index #2.
-            // Use 'persistent' struct member as a key.
-            boost::multi_index::hashed_non_unique<
-                boost::multi_index::member<
-                    OptionDescriptor,
-                    bool,
-                    &OptionDescriptor::persistent
-                >
-            >
-        >
-    > OptionContainer;
-
-    /// Pointer to the OptionContainer object.
-    typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
-    /// Type of the index #1 - option type.
-    typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
-    /// Pair of iterators to represent the range of options having the
-    /// same option type value. The first element in this pair represents
-    /// the beginning of the range, the second element represents the end.
-    typedef std::pair<OptionContainerTypeIndex::const_iterator,
-                      OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
-    /// Type of the index #2 - option persistency flag.
-    typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
-
     /// @brief Holds optional information about relay.
     ///
     /// In some cases it is beneficial to have additional information about
@@ -190,33 +72,6 @@ public:
     /// @brief checks if specified address is in range
     bool inRange(const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief Add new option instance to the collection.
-    ///
-    /// @param option option instance.
-    /// @param persistent if true, send an option regardless if client
-    /// requested it or not.
-    /// @param option_space name of the option space to add an option to.
-    ///
-    /// @throw isc::BadValue if invalid option provided.
-    void addOption(const OptionPtr& option, bool persistent,
-                   const std::string& option_space);
-
-
-    /// @brief Adds new vendor option instance to the collection.
-    ///
-    /// @param option option instance.
-    /// @param persistent if true, send an option regardless if client
-    /// requested it or not.
-    /// @param vendor_id enterprise id of the vendor space to add an option to.
-    void addVendorOption(const OptionPtr& option, bool persistent,
-                         uint32_t vendor_id);
-
-    /// @brief Delete all options configured for the subnet.
-    void delOptions();
-
-    /// @brief Deletes all vendor options configured for the subnet.
-    void delVendorOptions();
-
     /// @brief checks if the specified address is in pools
     ///
     /// Note the difference between inSubnet() and inPool(). For a given
@@ -247,42 +102,16 @@ public:
         return (t2_);
     }
 
-    /// @brief Return a collection of option descriptors.
-    ///
-    /// @param option_space name of the option space.
-    ///
-    /// @return pointer to collection of options configured for a subnet.
-    OptionContainerPtr
-    getOptionDescriptors(const std::string& option_space) const;
-
-    /// @brief Return a collection of vendor option descriptors.
-    ///
-    /// @param vendor_id enterprise id of the option space.
-    ///
-    /// @return pointer to collection of options configured for a subnet.
-    OptionContainerPtr
-    getVendorOptionDescriptors(uint32_t vendor_id) const;
-
-    /// @brief Return single option descriptor.
-    ///
-    /// @param option_space name of the option space.
-    /// @param option_code code of the option to be returned.
-    ///
-    /// @return option descriptor found for the specified option space
-    /// and option code.
-    OptionDescriptor
-    getOptionDescriptor(const std::string& option_space,
-                        const uint16_t option_code);
+    /// @brief Returns pointer to the option data configuration for this subnet.
+    CfgOptionPtr getCfgOption() {
+        return (cfg_option_);
+    }
 
-    /// @brief Return single vendor option descriptor.
-    ///
-    /// @param vendor_id enterprise id of the option space.
-    /// @param option_code code of the option to be returned.
-    ///
-    /// @return option descriptor found for the specified option space
-    /// and option code.
-    OptionDescriptor
-    getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code);
+    /// @brief Returns const pointer to the option data configuration for this
+    /// subnet.
+    ConstCfgOptionPtr getCfgOption() const {
+        return (cfg_option_);
+    }
 
     /// @brief returns the last address that was tried from this pool
     ///
@@ -526,11 +355,6 @@ protected:
     /// @throw BadValue if invalid value is used
     virtual void checkType(Lease::Type type) const = 0;
 
-    /// @brief Check if option is valid and can be added to a subnet.
-    ///
-    /// @param option option to be validated.
-    virtual void validateOption(const OptionPtr& option) const = 0;
-
     /// @brief subnet-id
     ///
     /// Subnet-id is a unique value that can be used to find or identify
@@ -607,19 +431,8 @@ protected:
 
 private:
 
-    /// A collection of option spaces grouping option descriptors.
-    typedef OptionSpaceContainer<OptionContainer,
-        OptionDescriptor, std::string> OptionSpaceCollection;
-
-    /// A collection of vendor space option descriptors.
-    typedef OptionSpaceContainer<OptionContainer,
-        OptionDescriptor, uint32_t> VendorOptionSpaceCollection;
-
-    /// Regular options are kept here
-    OptionSpaceCollection option_spaces_;
-
-    /// Vendor options are kept here
-    VendorOptionSpaceCollection vendor_option_spaces_;
+    /// @brief Pointer to the option data configuration for this subnet.
+    CfgOptionPtr cfg_option_;
 };
 
 /// @brief A generic pointer to either Subnet4 or Subnet6 object
@@ -662,13 +475,6 @@ public:
 
 protected:
 
-    /// @brief Check if option is valid and can be added to a subnet.
-    ///
-    /// @param option option to be validated.
-    ///
-    /// @throw isc::BadValue if provided option is invalid.
-    virtual void validateOption(const OptionPtr& option) const;
-
     /// @brief Returns default address for pool selection
     /// @return ANY IPv4 address
     virtual isc::asiolink::IOAddress default_pool() const {
@@ -741,13 +547,6 @@ public:
 
 protected:
 
-    /// @brief Check if option is valid and can be added to a subnet.
-    ///
-    /// @param option option to be validated.
-    ///
-    /// @throw isc::BadValue if provided option is invalid.
-    virtual void validateOption(const OptionPtr& option) const;
-
     /// @brief Returns default address for pool selection
     /// @return ANY IPv6 address
     virtual isc::asiolink::IOAddress default_pool() const {

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -56,6 +56,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc

+ 480 - 0
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -0,0 +1,480 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option.h>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+#include <limits>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the option configurations can be compared.
+TEST(CfgOptionTest, equals) {
+    CfgOption cfg1;
+    CfgOption cfg2;
+
+    // Initially the configurations should be equal.
+    ASSERT_TRUE(cfg1 == cfg2);
+    ASSERT_FALSE(cfg1 != cfg2);
+
+    // Add 9 options to two different option spaces. Each option have different
+    // option code and content.
+    for (uint16_t code = 1; code < 10; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, code)));
+        ASSERT_NO_THROW(cfg1.add(option, false, "isc"));
+        ASSERT_NO_THROW(cfg1.add(option, true, "vendor-123"));
+    }
+
+    // Configurations should now be different.
+    ASSERT_FALSE(cfg1 == cfg2);
+    ASSERT_TRUE(cfg1 != cfg2);
+
+    // Add 8 options (excluding the option with code 1) to the same option
+    // spaces.
+    for (uint16_t code = 2; code < 10; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, code)));
+        ASSERT_NO_THROW(cfg2.add(option, false, "isc"));
+        ASSERT_NO_THROW(cfg2.add(option, true, "vendor-123"));
+    }
+
+    // Configurations should still be unequal.
+    ASSERT_FALSE(cfg1 == cfg2);
+    ASSERT_TRUE(cfg1 != cfg2);
+
+    // Add missing option to the option space isc.
+    ASSERT_NO_THROW(cfg2.add(OptionPtr(new Option(Option::V6, 1,
+                                                  OptionBuffer(10, 0x01))),
+                             false, "isc"));
+    // Configurations should still be unequal because option with code 1
+    // is missing in the option space vendor-123.
+    ASSERT_FALSE(cfg1 == cfg2);
+    ASSERT_TRUE(cfg1 != cfg2);
+
+    // Add missing option.
+    ASSERT_NO_THROW(cfg2.add(OptionPtr(new Option(Option::V6, 1,
+                                                  OptionBuffer(10, 0x01))),
+                             true, "vendor-123"));
+    // Configurations should now be equal.
+    ASSERT_TRUE(cfg1 == cfg2);
+    ASSERT_FALSE(cfg1 != cfg2);
+
+}
+
+// This test verifies that multiple options can be added to the configuration
+// and that they can be retrieved using the option space name.
+TEST(CfgOptionTest, add) {
+    CfgOption cfg;
+
+    // Differentiate options by their codes (100-109)
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+    }
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp6 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(cfg.add(option, false, "isc"));
+    }
+
+    // Get options from the Subnet and check if all 10 are there.
+    OptionContainerPtr options = cfg.getAll("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
+
+    // Validate codes of options added to dhcp6 option space.
+    uint16_t expected_code = 100;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    options = cfg.getAll("isc");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(7, options->size());
+
+    // Validate codes of options added to isc option space.
+    expected_code = 105;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    // Try to get options from a non-existing option space.
+    options = cfg.getAll("abcd");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+}
+
+// This test verifies that two option configurations can be merged.
+TEST(CfgOption, merge) {
+    CfgOption cfg_src;
+    CfgOption cfg_dst;
+
+    // Create collection of options in option space dhcp6, with option codes
+    // from the range of 100 to 109 and holding one byte of data equal to 0xFF.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+        ASSERT_NO_THROW(cfg_src.add(option, false, "dhcp6"));
+    }
+
+    // Create collection of options in vendor space 123, with option codes
+    // from the range of 100 to 109 and holding one byte of data equal to 0xFF.
+    for (uint16_t code = 100; code < 110; code += 2) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+        ASSERT_NO_THROW(cfg_src.add(option, false, "vendor-123"));
+    }
+
+    // Create destination configuration (configuration that we merge the
+    // other configuration to).
+
+    // Create collection of options having even option codes in the range of
+    // 100 to 108.
+    for (uint16_t code = 100; code < 110; code += 2) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+        ASSERT_NO_THROW(cfg_dst.add(option, false, "dhcp6"));
+    }
+
+    // Create collection of options having odd option codes in the range of
+    // 101 to 109.
+    for (uint16_t code = 101; code < 110; code += 2) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+        ASSERT_NO_THROW(cfg_dst.add(option, false, "vendor-123"));
+    }
+
+    // Merge source configuration to the destination configuration. The options
+    // in the destination should be preserved. The options from the source
+    // configuration should be added.
+    ASSERT_NO_THROW(cfg_src.mergeTo(cfg_dst));
+
+    // Validate the options in the dhcp6 option space in the destination.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionDescriptor desc = cfg_dst.get("dhcp6", code);
+        ASSERT_TRUE(desc.option_);
+        ASSERT_EQ(1, desc.option_->getData().size());
+        // The options with even option codes should hold one byte of data
+        // equal to 0x1. These are the ones that we have initially added to
+        // the destination configuration. The other options should hold the
+        // values of 0xFF which indicates that they have been merged from the
+        // source configuration.
+        if ((code % 2) == 0) {
+            EXPECT_EQ(0x01, desc.option_->getData()[0]);
+        } else {
+            EXPECT_EQ(0xFF, desc.option_->getData()[0]);
+        }
+    }
+
+    // Validate the options in the vendor space.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionDescriptor desc = cfg_dst.get(123, code);
+        ASSERT_TRUE(desc.option_);
+        ASSERT_EQ(1, desc.option_->getData().size());
+        // This time, the options with even option codes should hold a byte
+        // of data equal to 0xFF. The other options should hold the byte of
+        // data equal to 0x01.
+        if ((code % 2) == 0) {
+            EXPECT_EQ(0xFF, desc.option_->getData()[0]);
+        } else {
+            EXPECT_EQ(0x01, desc.option_->getData()[0]);
+        }
+    }
+}
+
+// This test verifies that the options configuration can be copied between
+// objects.
+TEST(CfgOptionTest, copy) {
+    CfgOption cfg_src;
+    // Add 10 options to the custom option space in the source configuration.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+        ASSERT_NO_THROW(cfg_src.add(option, false, "foo"));
+    }
+
+    CfgOption cfg_dst;
+    // Add 20 options to the custom option space in destination configuration.
+    for (uint16_t code = 100; code < 120; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+        ASSERT_NO_THROW(cfg_dst.add(option, false, "isc"));
+    }
+
+    // Copy entire configuration to the destination. This should override any
+    // existing data.
+    ASSERT_NO_THROW(cfg_src.copyTo(cfg_dst));
+
+    // Validate options in the destination configuration.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionDescriptor desc = cfg_dst.get("foo", code);
+        ASSERT_TRUE(desc.option_);
+        ASSERT_EQ(1, desc.option_->getData().size());
+        EXPECT_EQ(0x01, desc.option_->getData()[0]);
+    }
+
+    // Any existing options should be removed.
+    OptionContainerPtr container = cfg_dst.getAll("isc");
+    ASSERT_TRUE(container);
+    EXPECT_TRUE(container->empty());
+
+    // The option space "foo" should contain exactly 10 options.
+    container = cfg_dst.getAll("foo");
+    ASSERT_TRUE(container);
+    EXPECT_EQ(10, container->size());
+}
+
+// This test verifies that encapsulated options are added as sub-options
+// to the top level options on request.
+TEST(CfgOptionTest, encapsulate) {
+    CfgOption cfg;
+    // Create top-level options encapsulating "foo" option space.
+    for (uint16_t code = 1000; code < 1020; ++code) {
+        OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                                  code, 1234));
+        option->setEncapsulatedSpace("foo");
+        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+    }
+
+    // Create top level options encapsulating "bar" option space.
+    for (uint16_t code = 1020; code < 1040; ++code) {
+        OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                                  code, 2345));
+        option->setEncapsulatedSpace("bar");
+        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+    }
+
+    // Create sub-options belonging to "foo" option space.
+    for (uint16_t code = 1; code < 20; ++code) {
+        OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+                                                               0x01));
+        ASSERT_NO_THROW(cfg.add(option, false, "foo"));
+    }
+
+    // Create sub-options belonging to "bar" option space.
+    for (uint16_t code = 100;  code < 130; ++code) {
+        OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+                                                               code, 0x02));
+        ASSERT_NO_THROW(cfg.add(option, false, "bar"));
+    }
+
+    // Append options from "foo" and "bar" space as sub-options.
+    ASSERT_NO_THROW(cfg.encapsulate());
+
+    // Verify that we have 40 top-level options.
+    OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+    ASSERT_EQ(40, options->size());
+
+    for (uint16_t code = 1000; code < 1040; ++code) {
+        OptionUint16Ptr option = boost::dynamic_pointer_cast<
+            OptionUint16>(cfg.get(DHCP6_OPTION_SPACE, code).option_);
+        ASSERT_TRUE(option) << "option with code " << code << " not found";
+        const OptionCollection& suboptions = option->getOptions();
+        for (OptionCollection::const_iterator suboption =
+                 suboptions.begin(); suboption != suboptions.end();
+             ++suboption) {
+            OptionUint8Ptr opt = boost::dynamic_pointer_cast<
+                OptionUint8>(suboption->second);
+            ASSERT_TRUE(opt);
+            if (code < 1020) {
+                EXPECT_EQ(0x01, opt->getValue());
+            } else {
+                EXPECT_EQ(0x02, opt->getValue());
+            }
+        }
+    }
+}
+
+// This test verifies that single option can be retrieved from the configuration
+// using option code and option space.
+TEST(CfgOption, get) {
+    CfgOption cfg;
+
+    // Add 10 options to a "dhcp6" option space in the subnet.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+    }
+
+    // Check that we can get each added option descriptor using
+    // individually.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream stream;
+        // First, try the invalid option space name.
+        OptionDescriptor desc = cfg.get("isc", code);
+        // Returned descriptor should contain NULL option ptr.
+        EXPECT_FALSE(desc.option_);
+        // Now, try the valid option space.
+        desc = cfg.get("dhcp6", code);
+        // Test that the option code matches the expected code.
+        ASSERT_TRUE(desc.option_);
+        EXPECT_EQ(code, desc.option_->getType());
+    }
+}
+
+// This test verifies that the same options can be added to the configuration
+// under different option space.
+TEST(CfgOptionTest, addNonUniqueOptions) {
+    CfgOption cfg;
+
+    // Create a set of options with non-unique codes.
+    for (int i = 0;  i < 2; ++i) {
+        // In the inner loop we create options with unique codes (100-109).
+        for (uint16_t code = 100; code < 110; ++code) {
+            OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+            ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+        }
+    }
+
+    // Sanity check that all options are there.
+    OptionContainerPtr options = cfg.getAll("dhcp6");
+    ASSERT_EQ(20, options->size());
+
+    // Use container index #1 to get the options by their codes.
+    OptionContainerTypeIndex& idx = options->get<1>();
+    // Look for the codes 100-109.
+    for (uint16_t code = 100; code < 110; ++ code) {
+        // For each code we should get two instances of options->
+        std::pair<OptionContainerTypeIndex::const_iterator,
+                  OptionContainerTypeIndex::const_iterator> range =
+            idx.equal_range(code);
+        // Distance between iterators indicates how many options
+        // have been retured for the particular code.
+        ASSERT_EQ(2, distance(range.first, range.second));
+        // Check that returned options actually have the expected option code.
+        for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
+             option_desc != range.second; ++option_desc) {
+            ASSERT_TRUE(option_desc->option_);
+            EXPECT_EQ(code, option_desc->option_->getType());
+        }
+    }
+
+    // Let's try to find some non-exiting option.
+    const uint16_t non_existing_code = 150;
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(non_existing_code);
+    // Empty set is expected.
+    EXPECT_EQ(0, distance(range.first, range.second));
+}
+
+// This test verifies that the option with the persistency flag can be
+// added to the configuration and that options with the persistency flags
+// can be retrieved.
+TEST(Subnet6Test, addPersistentOption) {
+    CfgOption cfg;
+
+    // Add 10 options to the subnet with option codes 100 - 109.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        // We create 10 options and want some of them to be flagged
+        // persistent and some non-persistent. Persistent options are
+        // those that server sends to clients regardless if they ask
+        // for them or not. We pick 3 out of 10 options and mark them
+        // non-persistent and 7 other options persistent.
+        // Code values: 102, 105 and 108 are divisible by 3
+        // and options with these codes will be flagged non-persistent.
+        // Options with other codes will be flagged persistent.
+        bool persistent = (code % 3) ? true : false;
+        ASSERT_NO_THROW(cfg.add(option, persistent, "dhcp6"));
+    }
+
+    // Get added options from the subnet.
+    OptionContainerPtr options = cfg.getAll("dhcp6");
+
+    // options->get<2> returns reference to container index #2. This
+    // index is used to access options by the 'persistent' flag.
+    OptionContainerPersistIndex& idx = options->get<2>();
+
+    // Get all persistent options->
+    std::pair<OptionContainerPersistIndex::const_iterator,
+              OptionContainerPersistIndex::const_iterator> range_persistent =
+        idx.equal_range(true);
+    // 3 out of 10 options have been flagged persistent.
+    ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
+
+    // Get all non-persistent options->
+    std::pair<OptionContainerPersistIndex::const_iterator,
+              OptionContainerPersistIndex::const_iterator> range_non_persistent =
+        idx.equal_range(false);
+    // 7 out of 10 options have been flagged persistent.
+    ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
+}
+
+// This test verifies that the vendor option can be added to the configuration.
+TEST(CfgOptionTest, addVendorOptions) {
+    CfgOption cfg;
+
+    // Differentiate options by their codes (100-109)
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(cfg.add(option, false, "vendor-12345678"));
+    }
+
+    // Second option space uses corner case value for vendor id = max uint8.
+    uint32_t vendor_id = std::numeric_limits<uint32_t>::max();
+    std::ostringstream option_space;
+    option_space << "vendor-" << vendor_id;
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp6 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(cfg.add(option, false, option_space.str()));
+    }
+
+    // Get options from the Subnet and check if all 10 are there.
+    OptionContainerPtr options = cfg.getAll(12345678);
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
+
+    // Validate codes of options added to dhcp6 option space.
+    uint16_t expected_code = 100;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    options = cfg.getAll(vendor_id);
+    ASSERT_TRUE(options);
+    ASSERT_EQ(7, options->size());
+
+    // Validate codes of options added to isc option space.
+    expected_code = 105;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    // Try to get options from a non-existing option space.
+    options = cfg.getAll(1111111);
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+}
+
+
+} // end of anonymous namespace

+ 8 - 93
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -269,41 +269,6 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
     EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
-// Checks whether option space can be detected as vendor-id
-TEST_F(DhcpParserTest, vendorOptionSpace) {
-    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId(""));
-    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId("dhcp4"));
-    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId("vendor-"));
-    EXPECT_EQ(1, SubnetConfigParser::optionSpaceToVendorId("vendor-1"));
-    EXPECT_EQ(4491, SubnetConfigParser::optionSpaceToVendorId("vendor-4491"));
-    EXPECT_EQ(12345678, SubnetConfigParser::optionSpaceToVendorId("vendor-12345678"));
-}
-
-/// @brief Test Implementation of abstract OptionDataParser class. Allows
-/// testing basic option parsing.
-class UtestOptionDataParser : public OptionDataParser {
-public:
-
-    UtestOptionDataParser(const std::string&,
-        OptionStoragePtr options, ParserContextPtr global_context)
-        :OptionDataParser("", options, global_context) {
-    }
-
-    static OptionDataParser* factory(const std::string& param_name,
-        OptionStoragePtr options, ParserContextPtr global_context) {
-        return (new UtestOptionDataParser(param_name, options, global_context));
-    }
-
-protected:
-    // Dummy out last two params since test derivation doesn't use them.
-    virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
-                std::string&, uint32_t) {
-        OptionDefinitionPtr def;
-        // always return empty
-        return (def);
-    }
-};
-
 /// @brief Test Fixture class which provides basic structure for testing
 /// configuration parsing.  This is essentially the same structure provided
 /// by dhcp servers.
@@ -397,10 +362,8 @@ public:
     ParserPtr createConfigParser(const std::string& config_id) {
         ParserPtr parser;
         if (config_id.compare("option-data") == 0) {
-            parser.reset(new OptionDataListParser(config_id,
-                                              parser_context_->options_,
-                                              parser_context_,
-                                              UtestOptionDataParser::factory));
+            parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(),
+                                                  AF_INET));
 
         } else if (config_id.compare("option-def") == 0) {
             parser.reset(new OptionDefListParser(config_id,
@@ -459,20 +422,19 @@ public:
     OptionPtr getOptionPtr(std::string space, uint32_t code)
     {
         OptionPtr option_ptr;
-        Subnet::OptionContainerPtr options =
-                            parser_context_->options_->getItems(space);
+        OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
+            getCfgOption()->getAll(space);
         // Should always be able to get options list even if it is empty.
         EXPECT_TRUE(options);
         if (options) {
             // Attempt to find desired option.
-            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-            const Subnet::OptionContainerTypeRange& range =
-                                                        idx.equal_range(code);
+            const OptionContainerTypeIndex& idx = options->get<1>();
+            const OptionContainerTypeRange& range = idx.equal_range(code);
             int cnt = std::distance(range.first, range.second);
             EXPECT_EQ(1, cnt);
             if (cnt == 1) {
-                Subnet::OptionDescriptor desc = *(idx.begin());
-                option_ptr = desc.option;
+                OptionDescriptor desc = *(idx.begin());
+                option_ptr = desc.option_;
                 EXPECT_TRUE(option_ptr);
             }
         }
@@ -1116,20 +1078,6 @@ public:
                      values->getPosition(name).file_));
     }
 
-    /// @brief Check that option storage in the context holds one option
-    /// of the specified type.
-    ///
-    /// @param ctx A pointer to a context.
-    /// @param opt_type Expected option type.
-    void checkOptionType(const ParserContext& ctx, const uint16_t opt_type) {
-        Subnet::OptionContainerPtr options =
-            ctx.options_->getItems("option-space");
-        ASSERT_TRUE(options);
-        Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-        Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
-        ASSERT_EQ(1, std::distance(range.first, range.second));
-    }
-
     /// @brief Test copy constructor or assignment operator when values
     /// being copied are NULL.
     ///
@@ -1141,7 +1089,6 @@ public:
         ctx.boolean_values_.reset();
         ctx.uint32_values_.reset();
         ctx.string_values_.reset();
-        ctx.options_.reset();
         ctx.hooks_libraries_.reset();
 
         // Even if the fields of the context are NULL, it should get
@@ -1157,7 +1104,6 @@ public:
         EXPECT_FALSE(ctx_new->boolean_values_);
         EXPECT_FALSE(ctx_new->uint32_values_);
         EXPECT_FALSE(ctx_new->string_values_);
-        EXPECT_FALSE(ctx_new->options_);
         EXPECT_FALSE(ctx_new->hooks_libraries_);
 
     }
@@ -1214,14 +1160,6 @@ public:
                                       Element::Position("kea.conf", 100, 200));
 
 
-        // Add new option, with option code 10, to the context.
-        ASSERT_TRUE(ctx.options_);
-        OptionPtr opt1(new Option(Option::V6, 10));
-        Subnet::OptionDescriptor desc1(opt1, false);
-        std::string option_space = "option-space";
-        ASSERT_TRUE(desc1.option);
-        ctx.options_->addItem(desc1, option_space);
-
         // Allocate container for hooks libraries and add one library name.
         ctx.hooks_libraries_.reset(new std::vector<std::string>());
         ctx.hooks_libraries_->push_back("library1");
@@ -1301,14 +1239,6 @@ public:
                             ctx_new->string_values_);
         }
 
-        // New context has the same option.
-        ASSERT_TRUE(ctx_new->options_);
-        {
-            SCOPED_TRACE("Check that the option values are equal in both"
-                         " contexts");
-            checkOptionType(*ctx_new, 10);
-        }
-
         // New context has the same hooks library.
         ASSERT_TRUE(ctx_new->hooks_libraries_);
         {
@@ -1424,21 +1354,6 @@ public:
 
         }
 
-        // Change the option. This should not affect the option instance in the
-        // new context.
-        {
-            SCOPED_TRACE("Check that option remains the same in the new context"
-                         " when the option in the original context is changed");
-            ctx.options_->clearItems();
-            Subnet::OptionDescriptor desc(OptionPtr(new Option(Option::V6,
-                                                               100)),
-                                          false);
-
-            ASSERT_TRUE(desc.option);
-            ctx.options_->addItem(desc, "option-space");
-            checkOptionType(*ctx_new, 10);
-        }
-
         // Change the list of libraries. this should not affect the list in the
         // new context.
         ctx.hooks_libraries_->clear();

+ 20 - 2
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -281,8 +281,14 @@ TEST_F(SrvConfigTest, copy) {
 
     conf1.addLoggingInfo(info);
     conf1.setCfgIface(cfg_iface);
-    conf1.getCfgOptionDef()->add(OptionDefinitionPtr(new OptionDefinition("option-foo", 5,
-                                                                          "string")), "isc");
+
+    // Add option definition.
+    OptionDefinitionPtr def(new OptionDefinition("option-foo", 5, "string"));
+    conf1.getCfgOptionDef()->add(def, "isc");
+
+    // Add an option.
+    OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(10, 0xFF)));
+    conf1.getCfgOption()->add(option, true, "dhcp6");
 
     // Make sure both configurations are different.
     ASSERT_TRUE(conf1 != conf2);
@@ -354,6 +360,18 @@ TEST_F(SrvConfigTest, equality) {
                                                      "uint16_t")), "isc");
     EXPECT_TRUE(conf1 == conf2);
     EXPECT_FALSE(conf1 != conf2);
+
+    // Differ by option data.
+    OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(1, 0xFF)));
+    conf1.getCfgOption()->add(option, false, "isc");
+
+    EXPECT_FALSE(conf1 == conf2);
+    EXPECT_TRUE(conf1 != conf2);
+
+    conf2.getCfgOption()->add(option, false, "isc");
+
+    EXPECT_TRUE(conf1 == conf2);
+    EXPECT_FALSE(conf1 != conf2);
 }
 
 } // end of anonymous namespace

+ 48 - 112
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -243,19 +243,11 @@ TEST(Subnet4Test, addInvalidOption) {
     // Create the V4 subnet.
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
 
-    // Some dummy option code.
-    uint16_t code = 100;
-    // Create option with invalid universe (V6 instead of V4).
-    // Attempt to add this option should result in exception.
-    OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-    EXPECT_THROW(subnet->addOption(option1, false, "dhcp4"),
-                 isc::BadValue);
-
     // Create NULL pointer option. Attempt to add NULL option
     // should result in exception.
     OptionPtr option2;
     ASSERT_FALSE(option2);
-    EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+    EXPECT_THROW(subnet->getCfgOption()->add(option2, false, "dhcp4"),
                  isc::BadValue);
 }
 
@@ -646,57 +638,45 @@ TEST(Subnet6Test, addOptions) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
     // with option codes that we have added to dhcp6 option space.
     for (uint16_t code = 105; code < 112; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addOption(option, false, "isc"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "isc"));
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
     // Validate codes of options added to dhcp6 option space.
     uint16_t expected_code = 100;
-    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+    for (OptionContainer::const_iterator option_desc = options->begin();
          option_desc != options->end(); ++option_desc) {
-        ASSERT_TRUE(option_desc->option);
-        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
         ++expected_code;
     }
 
-    options = subnet->getOptionDescriptors("isc");
+    options = subnet->getCfgOption()->getAll("isc");
     ASSERT_TRUE(options);
     ASSERT_EQ(7, options->size());
 
     // Validate codes of options added to isc option space.
     expected_code = 105;
-    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+    for (OptionContainer::const_iterator option_desc = options->begin();
          option_desc != options->end(); ++option_desc) {
-        ASSERT_TRUE(option_desc->option);
-        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
         ++expected_code;
     }
 
     // Try to get options from a non-existing option space.
-    options = subnet->getOptionDescriptors("abcd");
-    ASSERT_TRUE(options);
-    EXPECT_TRUE(options->empty());
-
-    // Delete options from all spaces.
-    subnet->delOptions();
-
-    // Make sure that all options have been removed.
-    options = subnet->getOptionDescriptors("dhcp6");
-    ASSERT_TRUE(options);
-    EXPECT_TRUE(options->empty());
-
-    options = subnet->getOptionDescriptors("isc");
+    options = subnet->getCfgOption()->getAll("abcd");
     ASSERT_TRUE(options);
     EXPECT_TRUE(options->empty());
 }
@@ -710,63 +690,40 @@ TEST(Subnet6Test, addNonUniqueOptions) {
         // In the inner loop we create options with unique codes (100-109).
         for (uint16_t code = 100; code < 110; ++code) {
             OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-            ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+            ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
         }
     }
 
     // Sanity check that all options are there.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(20, options->size());
 
     // Use container index #1 to get the options by their codes.
-    Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    OptionContainerTypeIndex& idx = options->get<1>();
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
         // For each code we should get two instances of options->
-        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-                  Subnet::OptionContainerTypeIndex::const_iterator> range =
+        std::pair<OptionContainerTypeIndex::const_iterator,
+                  OptionContainerTypeIndex::const_iterator> range =
             idx.equal_range(code);
         // Distance between iterators indicates how many options
         // have been retured for the particular code.
         ASSERT_EQ(2, distance(range.first, range.second));
         // Check that returned options actually have the expected option code.
-        for (Subnet::OptionContainerTypeIndex::const_iterator option_desc = range.first;
+        for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
              option_desc != range.second; ++option_desc) {
-            ASSERT_TRUE(option_desc->option);
-            EXPECT_EQ(code, option_desc->option->getType());
+            ASSERT_TRUE(option_desc->option_);
+            EXPECT_EQ(code, option_desc->option_->getType());
         }
     }
 
     // Let's try to find some non-exiting option.
     const uint16_t non_existing_code = 150;
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
         idx.equal_range(non_existing_code);
     // Empty set is expected.
     EXPECT_EQ(0, distance(range.first, range.second));
-
-    subnet->delOptions();
-
-    options = subnet->getOptionDescriptors("dhcp6");
-    EXPECT_EQ(0, options->size());
-}
-
-TEST(Subnet6Test, addInvalidOption) {
-    // Create as subnet to add options to it.
-    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
-
-    // Some dummy option code.
-    uint16_t code = 100;
-    // Create option with invalid universe (V4 instead of V6).
-    // Attempt to add this option should result in exception.
-    OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
-    EXPECT_THROW(subnet->addOption(option1, false, "dhcp6"), isc::BadValue);
-
-    // Create NULL pointer option. Attempt to add NULL option
-    // should result in exception.
-    OptionPtr option2;
-    ASSERT_FALSE(option2);
-    EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
 }
 
 TEST(Subnet6Test, addPersistentOption) {
@@ -785,43 +742,38 @@ TEST(Subnet6Test, addPersistentOption) {
         // and options with these codes will be flagged non-persistent.
         // Options with other codes will be flagged persistent.
         bool persistent = (code % 3) ? true : false;
-        ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent, "dhcp6"));
     }
 
     // Get added options from the subnet.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
 
     // options->get<2> returns reference to container index #2. This
     // index is used to access options by the 'persistent' flag.
-    Subnet::OptionContainerPersistIndex& idx = options->get<2>();
+    OptionContainerPersistIndex& idx = options->get<2>();
 
     // Get all persistent options->
-    std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
-              Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
+    std::pair<OptionContainerPersistIndex::const_iterator,
+              OptionContainerPersistIndex::const_iterator> range_persistent =
         idx.equal_range(true);
     // 3 out of 10 options have been flagged persistent.
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
     // Get all non-persistent options->
-    std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
-              Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
+    std::pair<OptionContainerPersistIndex::const_iterator,
+              OptionContainerPersistIndex::const_iterator> range_non_persistent =
         idx.equal_range(false);
     // 7 out of 10 options have been flagged persistent.
     ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
-
-    subnet->delOptions();
-
-    options = subnet->getOptionDescriptors("dhcp6");
-    EXPECT_EQ(0, options->size());
 }
 
-TEST(Subnet6Test, getOptionDescriptor) {
+TEST(Subnet6Test, getOptions) {
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 56, 1, 2, 3, 4));
 
     // Add 10 options to a "dhcp6" option space in the subnet.
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
     }
 
     // Check that we can get each added option descriptor using
@@ -829,23 +781,19 @@ TEST(Subnet6Test, getOptionDescriptor) {
     for (uint16_t code = 100; code < 110; ++code) {
         std::ostringstream stream;
         // First, try the invalid option space name.
-        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+        OptionDescriptor desc = subnet->getCfgOption()->get("isc", code);
         // Returned descriptor should contain NULL option ptr.
-        EXPECT_FALSE(desc.option);
+        EXPECT_FALSE(desc.option_);
         // Now, try the valid option space.
-        desc = subnet->getOptionDescriptor("dhcp6", code);
+        desc = subnet->getCfgOption()->get("dhcp6", code);
         // Test that the option code matches the expected code.
-        ASSERT_TRUE(desc.option);
-        EXPECT_EQ(code, desc.option->getType());
+        ASSERT_TRUE(desc.option_);
+        EXPECT_EQ(code, desc.option_->getType());
     }
 }
 
 
-TEST(Subnet6Test, addVendorOptions) {
-
-    uint32_t vendor_id1 = 12345678;
-    uint32_t vendor_id2 = 87654321;
-    uint32_t vendor_id_bogus = 1111111;
+TEST(Subnet6Test, addVendorOption) {
 
     // Create as subnet to add options to it.
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
@@ -853,57 +801,45 @@ TEST(Subnet6Test, addVendorOptions) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id1));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "vendor-12345678"));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
     // with option codes that we have added to dhcp6 option space.
     for (uint16_t code = 105; code < 112; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id2));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "vendor-87654321"));
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    Subnet::OptionContainerPtr options = subnet->getVendorOptionDescriptors(vendor_id1);
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(12345678);
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
     // Validate codes of options added to dhcp6 option space.
     uint16_t expected_code = 100;
-    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+    for (OptionContainer::const_iterator option_desc = options->begin();
          option_desc != options->end(); ++option_desc) {
-        ASSERT_TRUE(option_desc->option);
-        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
         ++expected_code;
     }
 
-    options = subnet->getVendorOptionDescriptors(vendor_id2);
+    options = subnet->getCfgOption()->getAll(87654321);
     ASSERT_TRUE(options);
     ASSERT_EQ(7, options->size());
 
     // Validate codes of options added to isc option space.
     expected_code = 105;
-    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+    for (OptionContainer::const_iterator option_desc = options->begin();
          option_desc != options->end(); ++option_desc) {
-        ASSERT_TRUE(option_desc->option);
-        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
         ++expected_code;
     }
 
     // Try to get options from a non-existing option space.
-    options = subnet->getVendorOptionDescriptors(vendor_id_bogus);
-    ASSERT_TRUE(options);
-    EXPECT_TRUE(options->empty());
-
-    // Delete options from all spaces.
-    subnet->delVendorOptions();
-
-    // Make sure that all options have been removed.
-    options = subnet->getVendorOptionDescriptors(vendor_id1);
-    ASSERT_TRUE(options);
-    EXPECT_TRUE(options->empty());
-
-    options = subnet->getVendorOptionDescriptors(vendor_id2);
+    options = subnet->getCfgOption()->getAll(1111111);
     ASSERT_TRUE(options);
     EXPECT_TRUE(options->empty());
 }