Browse Source

[master] Merge branch 'trac2315'

Conflicts:
	src/bin/dhcp4/config_parser.cc
	src/bin/dhcp6/config_parser.cc
Marcin Siodelski 12 years ago
parent
commit
741fe7bc96

+ 6 - 4
src/bin/dhcp4/config_parser.cc

@@ -1117,8 +1117,8 @@ private:
             subnet_->addPool4(*it);
         }
 
-        const Subnet::OptionContainer& options = subnet_->getOptions();
-        const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
         // Add subnet specific options.
         BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
@@ -1127,7 +1127,7 @@ private:
                 LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
                     .arg(desc.option->getType()).arg(addr.toText());
             }
-            subnet_->addOption(desc.option);
+            subnet_->addOption(desc.option, false, "dhcp4");
         }
 
         // Check all global options and add them to the subnet object if
@@ -1137,6 +1137,8 @@ private:
         BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
             // Get all options specified locally in the subnet and having
             // code equal to global option's code.
+            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
+            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
             Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
             // @todo: In the future we will be searching for options using either
             // an option code or namespace. Currently we have only the option
@@ -1147,7 +1149,7 @@ private:
             // want to issue a warning about dropping the configuration of
             // a global option if one already exsists.
             if (std::distance(range.first, range.second) == 0) {
-                subnet_->addOption(desc.option);
+                subnet_->addOption(desc.option, false, "dhcp4");
             }
         }
     }

+ 20 - 18
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -471,11 +471,11 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -542,11 +542,11 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -609,11 +609,11 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
 
     Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
     ASSERT_TRUE(subnet1);
-    const Subnet::OptionContainer& options1 = subnet1->getOptions();
-    ASSERT_EQ(1, options1.size());
+    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("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 Subnet::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
@@ -633,10 +633,10 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     // Test another subnet in the same way.
     Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
     ASSERT_TRUE(subnet2);
-    const Subnet::OptionContainer& options2 = subnet2->getOptions();
-    ASSERT_EQ(1, options2.size());
+    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(1, options2->size());
 
-    const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range2 =
         idx2.equal_range(23);
@@ -718,11 +718,11 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -761,11 +761,13 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options =
+        subnet->getOptionDescriptors("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 Subnet::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

+ 6 - 5
src/bin/dhcp6/config_parser.cc

@@ -1151,9 +1151,8 @@ private:
             subnet_->addPool6(*it);
         }
 
-        // Get the options search index.
-        const Subnet::OptionContainer& options = subnet_->getOptions();
-        const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
         // Add subnet specific options.
         BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
@@ -1162,7 +1161,7 @@ private:
                 LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
                     .arg(desc.option->getType()).arg(addr.toText());
             }
-            subnet_->addOption(desc.option);
+            subnet_->addOption(desc.option, false, "dhcp6");
         }
 
         // Check all global options and add them to the subnet object if
@@ -1172,6 +1171,8 @@ private:
         BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
             // Get all options specified locally in the subnet and having
             // code equal to global option's code.
+            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
+            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
             Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
             // @todo: In the future we will be searching for options using either
             // an option code or namespace. Currently we have only the option
@@ -1182,7 +1183,7 @@ private:
             // want to issue a warning about dropping the configuration of
             // a global option if one already exsists.
             if (std::distance(range.first, range.second) == 0) {
-                subnet_->addOption(desc.option);
+                subnet_->addOption(desc.option, false, "dhcp6");
             }
         }
     }

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 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
@@ -340,8 +340,8 @@ void 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();
     // Get the list of options configured for a subnet.
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
     // Try to match requested options with those configured for a subnet.
     // If match is found, append configured option to the answer message.
     BOOST_FOREACH(uint16_t opt, requested_opts) {

+ 18 - 18
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -463,11 +463,11 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -541,11 +541,11 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -609,11 +609,11 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
 
     Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet1);
-    const Subnet::OptionContainer& options1 = subnet1->getOptions();
-    ASSERT_EQ(1, options1.size());
+    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("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 Subnet::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
@@ -633,10 +633,10 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     // Test another subnet in the same way.
     Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
     ASSERT_TRUE(subnet2);
-    const Subnet::OptionContainer& options2 = subnet2->getOptions();
-    ASSERT_EQ(1, options2.size());
+    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(1, options2->size());
 
-    const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range2 =
         idx2.equal_range(101);
@@ -727,11 +727,11 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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
@@ -769,11 +769,11 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("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 Subnet::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

+ 28 - 0
src/lib/dhcp/libdhcp++.cc

@@ -74,6 +74,34 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
     return (OptionDefinitionPtr());
 }
 
+bool
+LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
+    if (u == Option::V6) {
+        if (code < 79 &&
+            code != 10 &&
+            code != 35) {
+            return (true);
+        }
+
+    } else if (u == Option::V4) {
+        if (!(code == 84 ||
+              code == 96 ||
+              (code > 101 && code < 112) ||
+              code == 115 ||
+              code == 126 ||
+              code == 127 ||
+              (code > 146 && code < 150) ||
+              (code > 177  && code < 208) ||
+              (code > 213 && code <  220) ||
+              (code > 221 && code < 224))) {
+                return (true);
+            }
+
+    }
+
+    return (false);
+}
+
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
                        uint16_t type,

+ 15 - 0
src/lib/dhcp/libdhcp++.h

@@ -55,6 +55,21 @@ public:
     static OptionDefinitionPtr getOptionDef(const Option::Universe u,
                                             const uint16_t code);
 
+    /// @brief Check if the specified option is a standard option.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param code option code.
+    ///
+    /// @return true if the specified option is a standard option.
+    /// @todo We arleady create option definitions for the subset if
+    /// standard options. We are aiming that this function checks
+    /// the presence of the standard option definition and if it finds
+    /// it, then the true value is returned. However, at this point
+    /// this is not doable because some of the definitions (for less
+    /// important options) are not created yet.
+    static bool isStandardOption(const Option::Universe u,
+                                 const uint16_t code);
+
     /// @brief Factory function to create instance of option.
     ///
     /// Factory method creates instance of specified option. The option

+ 12 - 1
src/lib/dhcp/option_definition.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -42,6 +42,14 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+    DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief Forward declaration to OptionDefinition.
 class OptionDefinition;
 
@@ -492,6 +500,9 @@ typedef boost::multi_index_container<
     >
 > OptionDefContainer;
 
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
 /// Type of the index #1 - option type.
 typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
 /// Pair of iterators to represent the range of options definitions

+ 60 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -453,6 +453,66 @@ TEST_F(LibDhcpTest, unpackOptions4) {
     EXPECT_TRUE(x == options.end()); // option 2 not found
 }
 
+TEST_F(LibDhcpTest, isStandardOption4) {
+    // Get all option codes that are not occupied by standard options.
+    const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
+                                          109, 110, 111, 115, 126, 127, 147, 148, 149,
+                                          178, 179, 180, 181, 182, 183, 184, 185, 186,
+                                          187, 188, 189, 190, 191, 192, 193, 194, 195,
+                                          196, 197, 198, 199, 200, 201, 202, 203, 204,
+                                          205, 206, 207, 214, 215, 216, 217, 218, 219,
+                                          222, 223 };
+    const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
+
+    // Try all possible option codes.
+    for (size_t i = 0; i < 256; ++i) {
+        // Some ranges of option codes are unassigned and thus the isStandardOption
+        // should return false for them.
+        bool check_unassigned = false;
+        // Check the array of unassigned options to find out whether option code
+        // is assigned to standard option or unassigned.
+        for (size_t j = 0; j < unassigned_num; ++j) {
+            // If option code is found within the array of unassigned options
+            // we the isStandardOption function should return false.
+            if (unassigned_codes[j] == i) {
+                check_unassigned = true;
+                EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
+                                                       unassigned_codes[j]))
+                    << "Test failed for option code " << unassigned_codes[j];
+                break;
+            }
+        }
+        // If the option code belongs to the standard option then the
+        // isStandardOption should return true.
+        if (!check_unassigned) {
+            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
+                << "Test failed for the option code " << i;
+        }
+    }
+}
+
+TEST_F(LibDhcpTest, isStandardOption6) {
+    // All option codes in the range from 0 to 78 (except 10 and 35)
+    // identify the standard options.
+    for (uint16_t code = 0; code < 79; ++code) {
+        if (code != 10 && code != 35) {
+            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
+                << "Test failed for option code " << code;
+        }
+    }
+
+    // Check the option codes 10 and 35. They are unassigned.
+    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
+    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
+
+    // Check a range of option codes above 78. Those are option codes
+    // identifying non-standard options.
+    for (uint16_t code = 79; code < 512; ++code) {
+        EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
+            << "Test failed for option code " << code;
+    }
+}
+
 TEST_F(LibDhcpTest, stdOptionDefs4) {
 
     // Create a buffer that holds dummy option data.

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 
@@ -28,6 +29,84 @@ CfgMgr::instance() {
     return (cfg_mgr);
 }
 
+void
+CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
+                     const std::string& option_space) {
+    // @todo we need better validation of the provided option space name here.
+    // This will be implemented when #2313 is merged.
+    if (option_space.empty()) {
+        isc_throw(BadValue, "option space name must not be empty");
+    } else if (!def) {
+        // Option definition must point to a valid object.
+        isc_throw(MalformedOptionDefinition, "option definition must not be NULL");
+
+    } else if (getOptionDef(option_space, def->getCode())) {
+        // Option definition must not be overriden.
+        isc_throw(DuplicateOptionDefinition, "option definition already added"
+                  << " to option space " << option_space);
+
+    } else if ((option_space == "dhcp4" &&
+                LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+               (option_space == "dhcp6" &&
+                LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
+        // We must not override standard (assigned) option. The standard options
+        // belong to dhcp4 or dhcp6 option space.
+        isc_throw(BadValue, "unable to override definition of option '"
+                  << def->getCode() << "' in standard option space '"
+                  << option_space << "'.");
+
+    }
+    // Get existing option definitions for the option space.
+    OptionDefContainerPtr defs = getOptionDefs(option_space);
+    // getOptionDefs always returns a valid pointer to
+    // the container. Let's make an assert to make sure.
+    assert(defs);
+    // Actually add the new definition.
+    defs->push_back(def);
+    option_def_spaces_[option_space] = defs;
+}
+
+OptionDefContainerPtr
+CfgMgr::getOptionDefs(const std::string& option_space) const {
+    // @todo Validate the option space once the #2313 is implemented.
+
+    // Get all option definitions for the particular option space.
+    const OptionDefsMap::const_iterator& defs =
+        option_def_spaces_.find(option_space);
+    // If there are no option definitions for the particular option space
+    // then return empty container.
+    if (defs == option_def_spaces_.end()) {
+        return (OptionDefContainerPtr(new OptionDefContainer()));
+    }
+    // If option definitions found, return them.
+    return (defs->second);
+}
+
+OptionDefinitionPtr
+CfgMgr::getOptionDef(const std::string& option_space,
+                     const uint16_t option_code) const {
+    // @todo Validate the option space once the #2313 is implemented.
+
+    // Get a reference to option definitions for a particular option space.
+    OptionDefContainerPtr defs = getOptionDefs(option_space);
+    // If there are no matching option definitions then return the empty pointer.
+    if (!defs || defs->empty()) {
+        return (OptionDefinitionPtr());
+    }
+    // If there are some option definitions for a particular option space
+    // use an option code to get the one we want.
+    const OptionDefContainerTypeIndex& idx = defs->get<1>();
+    const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+    // If there is no definition that matches option code, return empty pointer.
+    if (std::distance(range.first, range.second) == 0) {
+        return (OptionDefinitionPtr());
+    }
+    // If there is more than one definition matching an option code, return
+    // the first one. This should not happen because we check for duplicates
+    // when addOptionDef is called.
+    return (*range.first);
+}
+
 Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
@@ -119,6 +198,10 @@ void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
     subnets4_.push_back(subnet);
 }
 
+void CfgMgr::deleteOptionDefs() {
+    option_def_spaces_.clear();
+}
+
 void CfgMgr::deleteSubnets4() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
     subnets4_.clear();

+ 62 - 1
src/lib/dhcpsrv/cfgmgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -17,6 +17,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcp/option_definition.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <util/buffer.h>
@@ -77,6 +78,41 @@ public:
     /// accessing it.
     static CfgMgr& instance();
 
+    /// @brief Add new option definition.
+    ///
+    /// @param def option definition to be added.
+    /// @param option_space name of the option space to add definition to.
+    ///
+    /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+    /// option definition already exists.
+    /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+    /// an option definition is NULL.
+    /// @throw isc::BadValue when the option space name is empty or
+    /// when trying to override the standard option (in dhcp4 or dhcp6
+    /// option space).
+    void addOptionDef(const OptionDefinitionPtr& def,
+                      const std::string& option_space);
+
+    /// @brief Return option definitions for particular option space.
+    ///
+    /// @param option_space option space.
+    ///
+    /// @return pointer to the collection of option definitions for
+    /// the particular option space. The option collection is empty
+    /// if no option exists for the option space specified.
+    OptionDefContainerPtr
+    getOptionDefs(const std::string& option_space) const;
+
+    /// @brief Return option definition for a particular option space and code.
+    ///
+    /// @param option_space option space.
+    /// @param option_code option code.
+    ///
+    /// @return an option definition or NULL pointer if option definition
+    /// has not been found.
+    OptionDefinitionPtr getOptionDef(const std::string& option_space,
+                                     const uint16_t option_code) const;
+
     /// @brief get IPv6 subnet by address
     ///
     /// Finds a matching subnet, based on an address. This can be used
@@ -86,6 +122,8 @@ public:
     ///    (for directly connected clients)
     ///
     /// @param hint an address that belongs to a searched subnet
+    ///
+    /// @return a subnet object
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
     /// @brief get IPv6 subnet by interface-id
@@ -93,12 +131,19 @@ public:
     /// Another possibility to find a subnet is based on interface-id.
     ///
     /// @param interface_id content of interface-id option returned by a relay
+    ///
+    /// @return a subnet object
     /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
     /// @brief adds an IPv6 subnet
+    ///
+    /// @param subnet new subnet to be added.
     void addSubnet6(const Subnet6Ptr& subnet);
 
+    /// @brief Delete all option definitions.
+    void deleteOptionDefs();
+
     /// @todo: Add subnet6 removal routines. Currently it is not possible
     /// to remove subnets. The only case where subnet6 removal would be
     /// needed is a dynamic server reconfiguration - a use case that is not
@@ -125,6 +170,8 @@ public:
     ///    (for directly connected clients)
     ///
     /// @param hint an address that belongs to a searched subnet
+    ///
+    /// @return a subnet object
     Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
 
     /// @brief adds a subnet4
@@ -141,6 +188,7 @@ public:
     /// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
     /// completely new?
     void deleteSubnets4();
+
 protected:
 
     /// @brief Protected constructor.
@@ -169,6 +217,19 @@ protected:
     /// pattern will use calling inRange() method on each subnet until
     /// a match is found.
     Subnet4Collection subnets4_;
+
+private:
+
+    /// A map containing option definitions for various option spaces.
+    /// They key of this map is the name of the option space. The
+    /// value is the the option container holding option definitions
+    /// for the particular option space.
+    typedef std::map<std::string, OptionDefContainerPtr> OptionDefsMap;
+
+    /// A map containing option definitions for different option spaces.
+    /// The map key holds an option space name.
+    OptionDefsMap option_def_spaces_;
+
 };
 
 } // namespace isc::dhcp

+ 53 - 4
src/lib/dhcpsrv/subnet.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -44,14 +44,63 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
 }
 
 void
-Subnet::addOption(OptionPtr& option, bool persistent /* = false */) {
+Subnet::addOption(OptionPtr& option, bool persistent,
+                  const std::string& option_space) {
+    // @todo Once the #2313 is merged we need to use the OptionSpace object to
+    // validate the option space name here. For now, let's check that the name
+    // is not empty as the empty namespace has a special meaning here - it is
+    // returned when desired namespace is not found when getOptions is called.
+    if (option_space.empty()) {
+        isc_throw(isc::BadValue, "option space name must not be empty");
+    }
     validateOption(option);
-    options_.push_back(OptionDescriptor(option, persistent));
+
+    OptionContainerPtr container = getOptionDescriptors(option_space);
+    // getOptionDescriptors is expected to return the pointer to the
+    // valid container. Let's make sure it does by performing an assert.
+    assert(container);
+    // Actually add the new descriptor.
+    container->push_back(OptionDescriptor(option, persistent));
+    option_spaces_[option_space] = container;
 }
 
 void
 Subnet::delOptions() {
-    options_.clear();
+    option_spaces_.clear();
+}
+
+Subnet::OptionContainerPtr
+Subnet::getOptionDescriptors(const std::string& option_space) const {
+    // Search the map to get the options container for the particular
+    // option space.
+    const OptionSpacesPtr::const_iterator& options =
+        option_spaces_.find(option_space);
+    // If the option space has not been found it means that no option
+    // has been configured for this option space yet. Thus we have to
+    // return an empty container to the caller.
+    if (options == option_spaces_.end()) {
+        // The default constructor creates an empty container.
+        return (OptionContainerPtr(new OptionContainer()));
+    }
+    // We found some option container for the option space specified.
+    // Let's return a const reference to it.
+    return (options->second);
+}
+
+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);
 }
 
 std::string Subnet::toText() const {

+ 35 - 12
src/lib/dhcpsrv/subnet.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -78,6 +78,9 @@ public:
             : option(OptionPtr()), persistent(persist) {};
     };
 
+    /// A pointer to option descriptor.
+    typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
     /// @brief Extractor class to extract key with another key.
     ///
     /// This class solves the problem of accessing index key values
@@ -198,6 +201,8 @@ public:
         >
     > 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
@@ -216,9 +221,11 @@ public:
     /// @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(OptionPtr& option, bool persistent = false);
+    void addOption(OptionPtr& option, bool persistent,
+                   const std::string& option_space);
 
     /// @brief Delete all options configured for the subnet.
     void delOptions();
@@ -252,14 +259,24 @@ public:
         return (t2_);
     }
 
-    /// @brief Return a collection of options.
+    /// @brief Return a collection of option descriptors.
     ///
-    /// @return reference to collection of options configured for a subnet.
-    /// The returned reference is valid as long as the Subnet object which
-    /// returned it still exists.
-    const OptionContainer& getOptions() const {
-        return (options_);
-    }
+    /// @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 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 the last address that was tried from this pool
     ///
@@ -353,9 +370,6 @@ protected:
     /// @brief a tripet (min/default/max) holding allowed valid lifetime values
     Triplet<uint32_t> valid_;
 
-    /// @brief a collection of DHCP options configured for a subnet.
-    OptionContainer options_;
-
     /// @brief last allocated address
     ///
     /// This is the last allocated address that was previously allocated from
@@ -366,6 +380,15 @@ protected:
     /// that purpose it should be only considered a help that should not be
     /// fully trusted.
     isc::asiolink::IOAddress last_allocated_;
+
+private:
+
+    /// Container holding options grouped by option space names.
+    typedef std::map<std::string, OptionContainerPtr> OptionSpacesPtr;
+
+    /// @brief a collection of DHCP option spaces holding options
+    /// configured for a subnet.
+    OptionSpacesPtr option_spaces_;
 };
 
 /// @brief A configuration holder for IPv4 subnet.

+ 162 - 1
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -44,9 +44,171 @@ public:
     ~CfgMgrTest() {
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().deleteOptionDefs();
     }
 };
 
+// This test verifies that multiple option definitions can be added
+// under different option spaces.
+TEST_F(CfgMgrTest, getOptionDefs) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Create a set of option definitions with codes between 100 and 109.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream option_name;
+        // Option name is unique, e.g. option-100, option-101 etc.
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        // Add option definition to "isc" option space.
+        // Option codes are not duplicated so expect no error
+        // when adding them.
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    }
+
+    // Create a set of option definitions with codes between 105 and 114 and
+    // add them to the different option space.
+    for (uint16_t code = 105; code < 115; ++code) {
+        std::ostringstream option_name;
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+    }
+
+    // Sanity check that all 10 option definitions are there.
+    OptionDefContainerPtr option_defs1 = cfg_mgr.getOptionDefs("isc");
+    ASSERT_TRUE(option_defs1);
+    ASSERT_EQ(10, option_defs1->size());
+
+    // Iterate over all option definitions and check that they have
+    // valid codes. Also, their order should be the same as they
+    // were added (codes 100-109).
+    uint16_t code = 100;
+    for (OptionDefContainer::const_iterator it = option_defs1->begin();
+         it != option_defs1->end(); ++it, ++code) {
+        OptionDefinitionPtr def(*it);
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Sanity check that all 10 option definitions are there.
+    OptionDefContainerPtr option_defs2 = cfg_mgr.getOptionDefs("abcde");
+    ASSERT_TRUE(option_defs2);
+    ASSERT_EQ(10, option_defs2->size());
+
+    // Check that the option codes are valid.
+    code = 105;
+    for (OptionDefContainer::const_iterator it = option_defs2->begin();
+         it != option_defs2->end(); ++it, ++code) {
+        OptionDefinitionPtr def(*it);
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Let's make one more check that the empty set is returned when
+    // invalid option space is used.
+    OptionDefContainerPtr option_defs3 = cfg_mgr.getOptionDefs("non-existing");
+    ASSERT_TRUE(option_defs3);
+    EXPECT_TRUE(option_defs3->empty());
+}
+
+// This test verifies that single option definition is correctly
+// returned with getOptionDef function.
+TEST_F(CfgMgrTest, getOptionDef) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Create a set of option definitions with codes between 100 and 109.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream option_name;
+        // Option name is unique, e.g. option-100, option-101 etc.
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        // Add option definition to "isc" option space.
+        // Option codes are not duplicated so expect no error
+        // when adding them.
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    }
+
+    // Create a set of option definitions with codes between 105 and 114 and
+    // add them to the different option space.
+    for (uint16_t code = 105; code < 115; ++code) {
+        std::ostringstream option_name;
+        option_name << "option-other-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+    }
+
+    // Try to get option definitions one by one using all codes
+    // that we expect to be there.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionDefinitionPtr def = cfg_mgr.getOptionDef("isc", code);
+        ASSERT_TRUE(def);
+        // Check that the option name is in the format of 'option-[code]'.
+        // That way we make sure that the options that have the same codes
+        // within different option spaces are different.
+        std::ostringstream option_name;
+        option_name << "option-" << code;
+        EXPECT_EQ(option_name.str(), def->getName());
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Check that the option codes are valid.
+    for (uint16_t code = 105; code < 115; ++code) {
+        OptionDefinitionPtr def = cfg_mgr.getOptionDef("abcde", code);
+        ASSERT_TRUE(def);
+        // Check that the option name is in the format of 'option-other-[code]'.
+        // That way we make sure that the options that have the same codes
+        // within different option spaces are different.
+        std::ostringstream option_name;
+        option_name << "option-other-" << code;
+        EXPECT_EQ(option_name.str(), def->getName());
+
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Check that an option definition can be added to the standard
+    // (dhcp4 and dhcp6) option spaces when the option code is not
+    // reserved by the standard option.
+    OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def6, "dhcp6"));
+
+    OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def4, "dhcp4"));
+
+    // Try to query the option definition from an non-existing
+    // option space and expect NULL pointer.
+    OptionDefinitionPtr def = cfg_mgr.getOptionDef("non-existing", 56);
+    EXPECT_FALSE(def);
+
+    // Try to get the non-existing option definition from an
+    // existing option space.
+    EXPECT_FALSE(cfg_mgr.getOptionDef("isc", 56));
+
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST_F(CfgMgrTest, addOptionDefNegative) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // The option code 65 is reserved for standard options either in
+    // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
+    // option space fails.
+    OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
+
+    // Try reserved option space names.
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+    // Try empty option space name.
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
+    // Try NULL option definition.
+    ASSERT_THROW(cfg_mgr.addOptionDef(OptionDefinitionPtr(), "isc"),
+                 isc::dhcp::MalformedOptionDefinition);
+    // Try adding option definition twice and make sure that it
+    // fails on the second attempt.
+    ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    EXPECT_THROW(cfg_mgr.addOptionDef(def, "isc"), DuplicateOptionDefinition);
+}
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
@@ -86,7 +248,6 @@ TEST_F(CfgMgrTest, subnet4) {
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
-
 TEST_F(CfgMgrTest, subnet6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 

+ 88 - 28
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -115,13 +115,15 @@ TEST(Subnet4Test, addInvalidOption) {
     // 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), isc::BadValue);
+    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), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+                 isc::BadValue);
 }
 
 // This test verifies that inRange() and inPool() methods work properly.
@@ -261,26 +263,59 @@ 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));
+        ASSERT_NO_THROW(subnet->addOption(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"));
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    Subnet::OptionContainer options = subnet->getOptions();
-    ASSERT_EQ(10, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
 
-    // Validate codes of added options.
+    // Validate codes of options added to dhcp6 option space.
     uint16_t expected_code = 100;
-    for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
-         option_desc != options.end(); ++option_desc) {
+    for (Subnet::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 = subnet->getOptionDescriptors("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();
+         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 = subnet->getOptionDescriptors("abcd");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    // Delete options from all spaces.
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    // Make sure that all options have been removed.
+    options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    options = subnet->getOptionDescriptors("isc");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
 }
 
 TEST(Subnet6Test, addNonUniqueOptions) {
@@ -292,19 +327,19 @@ 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));
+            ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
         }
     }
 
     // Sanity check that all options are there.
-    Subnet::OptionContainer options = subnet->getOptions();
-    ASSERT_EQ(20, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(20, options->size());
 
     // Use container index #1 to get the options by their codes.
-    Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    Subnet::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.
+        // For each code we should get two instances of options->
         std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
                   Subnet::OptionContainerTypeIndex::const_iterator> range =
             idx.equal_range(code);
@@ -329,8 +364,8 @@ TEST(Subnet6Test, addNonUniqueOptions) {
 
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    options = subnet->getOptionDescriptors("dhcp6");
+    EXPECT_EQ(0, options->size());
 }
 
 TEST(Subnet6Test, addInvalidOption) {
@@ -342,13 +377,13 @@ TEST(Subnet6Test, addInvalidOption) {
     // 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), isc::BadValue);
+    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), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
 }
 
 TEST(Subnet6Test, addPersistentOption) {
@@ -367,24 +402,24 @@ 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));
+        ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
     }
 
     // Get added options from the subnet.
-    Subnet::OptionContainer options = subnet->getOptions();
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
 
-    // options.get<2> returns reference to container index #2. This
+    // 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>();
+    Subnet::OptionContainerPersistIndex& idx = options->get<2>();
 
-    // Get all persistent options.
+    // Get all persistent options->
     std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
               Subnet::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.
+    // Get all non-persistent options->
     std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
               Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
         idx.equal_range(false);
@@ -393,8 +428,33 @@ TEST(Subnet6Test, addPersistentOption) {
 
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    options = subnet->getOptionDescriptors("dhcp6");
+    EXPECT_EQ(0, options->size());
+}
+
+TEST(Subnet6Test, getOptionDescriptor) {
+    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"));
+    }
+
+    // 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.
+        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+        // Returned descriptor should contain NULL option ptr.
+        EXPECT_FALSE(desc.option);
+        // Now, try the valid option space.
+        desc = subnet->getOptionDescriptor("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 inRange() and inPool() methods work properly.