Browse Source

[3589] Two option configurations can be now merged together.

Marcin Siodelski 10 years ago
parent
commit
ed84b5a554

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

@@ -96,6 +96,52 @@ CfgOption::add(const OptionPtr& option, const bool persistent,
     }
 }
 
+void
+CfgOption::merge(CfgOption& other) const {
+    // Merge non-vendor options.
+    mergeInternal(options_, other.options_);
+    mergeInternal(vendor_options_, other.vendor_options_);
+    // Merge verndor options.
+}
+
+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));

+ 33 - 3
src/lib/dhcpsrv/cfg_option.h

@@ -251,6 +251,16 @@ public:
     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 merge(CfgOption& other) const;
+
     /// @brief Returns all options for the specified option space.
     ///
     /// This method will not return vendor options, i.e. having option space
@@ -278,12 +288,13 @@ public:
     ///
     /// @param key Option space name or vendor identifier.
     /// @param option_code Code of the option to be returned.
-    /// @tparam T one of: @c std::string or @c uint32_t
+    /// @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 T>
-    OptionDescriptor get(const T& key, const uint16_t option_code) const {
+    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));
@@ -300,6 +311,25 @@ public:
 
 private:
 
+    /// @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;

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

@@ -125,6 +125,80 @@ TEST(CfgOptionTest, add) {
     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.merge(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 single option can be retrieved from the configuration
 // using option code and option space.
 TEST(CfgOption, get) {