Browse Source

[2463] Preserve global configuration values.

Rather tham clear global configuration values we replace them (override)
in subsequent configuration commits. This guarantees that global
values are always available when subnet configuration is performed.
Marcin Siodelski 12 years ago
parent
commit
03e57865c0
1 changed files with 82 additions and 33 deletions
  1. 82 33
      src/bin/dhcp6/config_parser.cc

+ 82 - 33
src/bin/dhcp6/config_parser.cc

@@ -62,8 +62,10 @@ typedef std::map<string, string> StringStorage;
 /// no subnet object created yet to store them.
 typedef std::vector<Pool6Ptr> PoolStorage;
 
-/// @brief Collection of options.
-typedef std::vector<OptionPtr> OptionStorage;
+/// @brief Collection of option descriptors. This container allows to search for
+/// options using option code or persistency flag. This is useful when merging
+/// existing options with newly configured options.
+typedef Subnet::OptionContainer OptionStorage;
 
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
@@ -193,7 +195,9 @@ public:
                       << " as unsigned 32-bit integer.");
         }
 
-        storage_->insert(pair<string, uint32_t>(param_name_, value_));
+        // If a given parameter already exists in the storage we override
+        // its value. If it doesn't we insert new element.
+        (*storage_)[param_name_] = value_;
     }
 
     /// @brief does nothing
@@ -204,8 +208,7 @@ public:
     /// subnet is stored in subnet-specific storage. It is not commited
     /// here, but is rather used by \ref Subnet6Parser when constructing
     /// the subnet.
-    virtual void commit() {
-    }
+    virtual void commit() { }
 
     /// @brief factory that constructs Uint32Parser objects
     ///
@@ -263,7 +266,9 @@ public:
     virtual void build(ConstElementPtr value) {
         value_ = value->str();
         boost::erase_all(value_, "\"");
-        storage_->insert(pair<string, string>(param_name_, value_));
+        // If a given parameter already exists in the storage we override
+        // its value. If it doesn't we insert new element.
+        (*storage_)[param_name_] = value_;
     }
 
     /// @brief does nothing
@@ -274,8 +279,7 @@ public:
     /// subnet is stored in subnet-specific storage. It is not commited
     /// here, but is rather used by its parent parser when constructing
     /// an object, e.g. the subnet.
-    virtual void commit() {
-    }
+    virtual void commit() { }
 
     /// @brief factory that constructs StringParser objects
     ///
@@ -559,11 +563,41 @@ public:
         createOption();
     }
 
-    /// @brief Does nothing.
+    /// @brief Commits option values.
     ///
-    /// This function does noting because option data is committed
-    /// by a higher level parser.
-    virtual void commit() { }
+    /// This function iterates over all options configured for a particular
+    /// subnet or globally and moves them to the storage. If there are any options
+    /// already present in the storage they will be replaced if options with
+    /// the same code are present in the intermediate storage.
+    ///
+    /// @throw isc::InvalidOperation if failed to set pointer to storage
+    /// prior to calling commit. If that happens the data in the storage remain
+    /// not modified.
+    virtual void commit() {
+        if (options_ == NULL) {
+            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+                      "commiting option data.");
+        }
+        // Get all new options from the intermediate storage. Note that it holds
+        // all options configured since last commit.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, intermediate_storage_) {
+            uint16_t opt_type = desc.option->getType();
+            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 ones.
+            Subnet::OptionContainerTypeRange range =
+                idx.equal_range(opt_type);
+            if (std::distance(range.first, range.second) > 0) {
+                idx.erase(range.first, range.second);
+            }
+        }
+        // Append all options from the intermediate storage to the
+        // main storage.
+        options_->insert(options_->end(),
+                         intermediate_storage_.begin(),
+                         intermediate_storage_.end());
+    }
 
     /// @brief Set storage for the parser.
     ///
@@ -654,8 +688,14 @@ private:
             // if definition does not exist.
             OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
                                         binary));
-            // If option is created succesfully, add it to the storage.
-            options_->push_back(option);
+            // The intermerdiate storage is used here because it is
+            // reset for each new parser created. Thus it collects only new
+            // options and can be later used to merge new options with
+            // options configured earlier (replace those that have the same
+            // option code and add new to storage). Merge is performed in
+            // commit stage.
+            Subnet::OptionDescriptor desc(option, false);
+            intermediate_storage_.push_back(desc);
         } else {
             // We have exactly one option definition for the particular option code.
             // use it to create option instance.
@@ -665,7 +705,8 @@ private:
             assert(factory != NULL);
             try {
                 OptionPtr option = factory(Option::V6, option_code, binary);
-                options_->push_back(option);
+                Subnet::OptionDescriptor desc(option, false);
+                intermediate_storage_.push_back(desc);
             } catch (const isc::Exception& ex) {
                 isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
                           << " option definition (code " << option_code << "): "
@@ -707,6 +748,9 @@ private:
     /// Pointer to options storage. This storage is provided by
     /// the calling class and is shared by all OptionDataParser objects.
     OptionStorage* options_;
+    /// Intermediate option storage. It holds all newly configured
+    /// option values.
+    OptionStorage intermediate_storage_;
 };
 
 /// @brief Parser for option data values with ina subnet.
@@ -741,6 +785,8 @@ public:
             parser->setStorage(options_);
             // Build the instance of a singkle option.
             parser->build(option_value);
+            // Store a parser as it will be used to commit.
+            parsers_.push_back(parser);
         }
     }
 
@@ -752,11 +798,14 @@ public:
     }
 
 
-    /// @brief Does nothing.
+    /// @brief Commit all option values.
     ///
-    /// @todo Currently this function does nothing but in the future
-    /// we may need to extend it to commit at this level.
-    void commit() { }
+    /// This function invokes commit for all option values.
+    void commit() {
+        BOOST_FOREACH(ParserPtr parser, parsers_) {
+            parser->commit();
+        }
+    }
 
     /// @brief Create DhcpDataListParser object
     ///
@@ -769,6 +818,8 @@ public:
 
     /// Pointer to options instances storage.
     OptionStorage* options_;
+    /// Collection of parsers;
+    ParserCollection parsers_;
 };
 
 /// @brief this class parses a single subnet
@@ -830,6 +881,10 @@ public:
     /// created in other parsers are used here and added to newly created Subnet6
     /// objects. Subnet6 are then added to DHCP CfgMgr.
     void commit() {
+        // Invoke commit on all sub-data parsers.
+        BOOST_FOREACH(ParserPtr parser, parsers_) {
+            parser->commit();
+        }
 
         StringStorage::const_iterator it = string_values_.find("subnet");
         if (it == string_values_.end()) {
@@ -871,23 +926,23 @@ public:
         const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
 
         // Add subnet specific options.
-        BOOST_FOREACH(OptionPtr option, options_) {
-            Subnet::OptionContainerTypeRange range = idx.equal_range(option->getType());
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
+            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
             if (std::distance(range.first, range.second) > 0) {
                 LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
-                    .arg(option->getType()).arg(addr.toText());
+                    .arg(desc.option->getType()).arg(addr.toText());
             }
-            subnet->addOption(option);
+            subnet->addOption(desc.option);
         }
 
         // 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 precedense.
-        BOOST_FOREACH(OptionPtr option, option_defaults) {
+        // the one configured in the subnet scope always takes precedence.
+        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::OptionContainerTypeRange range = idx.equal_range(option->getType());
+            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
             // @todo: In the future we will be searching for options using either
             // option code or namespace. Currently we have only the option
             // code available so if there is at least one option found with the
@@ -897,7 +952,7 @@ public:
             // want to issue warning about dropping configuration of
             // global option if one already exsist.
             if (std::distance(range.first, range.second) == 0) {
-                subnet->addOption(option);
+                subnet->addOption(desc.option);
             }
         }
 
@@ -1153,12 +1208,6 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
                   "Null pointer is passed to configuration parser");
     }
 
-    /// Reset global storage. Containers being reset below may contain
-    /// data from the previous configuration attempts.
-    option_defaults.clear();
-    uint32_defaults.clear();
-    string_defaults.clear();
-
     /// @todo: append most essential info here (like "2 new subnets configured")
     string config_details;