]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1790] add support for top level maps in global CB parameters
authorRazvan Becheriu <razvan@isc.org>
Wed, 17 Jan 2024 16:52:05 +0000 (18:52 +0200)
committerRazvan Becheriu <razvan@isc.org>
Fri, 26 Jan 2024 12:19:54 +0000 (14:19 +0200)
26 files changed:
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp6/json_config_parser.cc
src/lib/cc/stamped_value.cc
src/lib/cc/stamped_value.h
src/lib/cc/tests/stamped_value_unittest.cc
src/lib/config_backend/tests/config_backend_mgr_unittest.cc
src/lib/dhcpsrv/cb_ctl_dhcp.h
src/lib/dhcpsrv/cb_ctl_dhcp4.cc
src/lib/dhcpsrv/cb_ctl_dhcp6.cc
src/lib/dhcpsrv/cfg_globals.cc
src/lib/dhcpsrv/cfg_globals.h
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.h
src/lib/dhcpsrv/parsers/expiration_config_parser.cc
src/lib/dhcpsrv/parsers/expiration_config_parser.h
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h
src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
src/share/api/remote-global-parameter4-del.json
src/share/api/remote-global-parameter4-get-all.json
src/share/api/remote-global-parameter4-get.json
src/share/api/remote-global-parameter4-set.json
src/share/api/remote-global-parameter6-del.json
src/share/api/remote-global-parameter6-get-all.json
src/share/api/remote-global-parameter6-get.json
src/share/api/remote-global-parameter6-set.json

index a7bf06954ed18754cf02d6ba1310ed5f651ef637..5be7f2cdf70abaca2001dc91da63dc383a42b6bf 100644 (file)
@@ -459,7 +459,7 @@ processDhcp4Config(isc::data::ConstElementPtr config_set) {
         if (expiration_cfg) {
             parameter_name = "expired-leases-processing";
             ExpirationConfigParser parser;
-            parser.parse(expiration_cfg);
+            parser.parse(expiration_cfg, CfgMgr::instance().getStagingCfg()->getCfgExpiration());
         }
 
         // The hooks-libraries configuration must be parsed after parsing
@@ -577,32 +577,8 @@ processDhcp4Config(isc::data::ConstElementPtr config_set) {
 
         ConstElementPtr compatibility = mutable_cfg->get("compatibility");
         if (compatibility) {
-            for (auto const& kv : compatibility->mapValue()) {
-                if (!kv.second || (kv.second->getType() != Element::boolean)) {
-                    isc_throw(DhcpConfigError,
-                              "compatibility parameter values must be "
-                              << "boolean (" << kv.first << " at "
-                              << kv.second->getPosition() << ")");
-                }
-                if (kv.first == "lenient-option-parsing") {
-                    CfgMgr::instance().getStagingCfg()->setLenientOptionParsing(
-                        kv.second->boolValue());
-                } else if (kv.first == "ignore-dhcp-server-identifier") {
-                    CfgMgr::instance().getStagingCfg()->setIgnoreServerIdentifier(
-                        kv.second->boolValue());
-                } else if (kv.first == "ignore-rai-link-selection") {
-                    CfgMgr::instance().getStagingCfg()->setIgnoreRAILinkSelection(
-                        kv.second->boolValue());
-                } else if (kv.first == "exclude-first-last-24") {
-                    CfgMgr::instance().getStagingCfg()->setExcludeFirstLast24(
-                        kv.second->boolValue());
-                } else {
-                    isc_throw(DhcpConfigError,
-                              "unsupported compatibility parameter: "
-                              << kv.first << " (" << kv.second->getPosition()
-                              << ")");
-                }
-            }
+            CompatibilityParser parser;
+            parser.parse(compatibility, *CfgMgr::instance().getStagingCfg());
         }
 
         // Make parsers grouping.
index e39163fe6fce66309008b226df7587d2ce76ebad..963974f1b9bd887617037cadaf8815cfb05f2ed4 100644 (file)
@@ -584,7 +584,7 @@ processDhcp6Config(isc::data::ConstElementPtr config_set) {
         if (expiration_cfg) {
             parameter_name = "expired-leases-processing";
             ExpirationConfigParser parser;
-            parser.parse(expiration_cfg);
+            parser.parse(expiration_cfg, CfgMgr::instance().getStagingCfg()->getCfgExpiration());
         }
 
         // The hooks-libraries configuration must be parsed after parsing
@@ -709,23 +709,8 @@ processDhcp6Config(isc::data::ConstElementPtr config_set) {
 
         ConstElementPtr compatibility = mutable_cfg->get("compatibility");
         if (compatibility) {
-            for (auto const& kv : compatibility->mapValue()) {
-                if (!kv.second || (kv.second->getType() != Element::boolean)) {
-                    isc_throw(DhcpConfigError,
-                              "compatibility parameter values must be "
-                              << "boolean (" << kv.first << " at "
-                              << kv.second->getPosition() << ")");
-                }
-                if (kv.first == "lenient-option-parsing") {
-                    CfgMgr::instance().getStagingCfg()->setLenientOptionParsing(
-                        kv.second->boolValue());
-                } else {
-                    isc_throw(DhcpConfigError,
-                              "unsupported compatibility parameter: "
-                              << kv.first << " (" << kv.second->getPosition()
-                              << ")");
-                }
-            }
+            CompatibilityParser parser;
+            parser.parse(compatibility, *CfgMgr::instance().getStagingCfg());
         }
 
         // Make parsers grouping.
index 077c688584246fc77b70bc4ec1d986113d71a459..3fbad88626972dccdcf06d69614b58a84082b8c6 100644 (file)
@@ -163,13 +163,35 @@ StampedValue::validateConstruct() const {
                   << name_ << "' parameter is NULL");
     }
 
-    if ((value_->getType() != Element::string) &&
-        (value_->getType() != Element::integer) &&
-        (value_->getType() != Element::boolean) &&
-        (value_->getType() != Element::real)) {
+    auto type = value_->getType();
+    if ((type != Element::string) &&
+        (type != Element::integer) &&
+        (type != Element::boolean) &&
+        (type != Element::real) &&
+        (type != Element::map)) {
         isc_throw(TypeError, "StampedValue: provided value of the '"
                   << name_ << "' parameter has invalid type: "
-                  << Element::typeToName(value_->getType()));
+                  << Element::typeToName(type));
+    }
+
+    if (type == Element::map) {
+        size_t count = value_->mapValue().size();
+        if (count > 1) {
+            isc_throw(BadValue, "StampedValue: provided value of the '"
+                      << name_ << "' parameter has more than one element in the map");
+        }
+        if (count == 1) {
+            type = value_->mapValue().begin()->second->getType();
+            if ((type != Element::string) &&
+                (type != Element::integer) &&
+                (type != Element::boolean) &&
+                (type != Element::real)) {
+                isc_throw(BadValue, "StampedValue: provided value of the '"
+                          << name_ << "/" << value_->mapValue().begin()->first
+                          << "' parameter has invalid type: "
+                          << Element::typeToName(type));
+            }
+        }
     }
 }
 
index 212059348645e43b3368ebd812c2c736c46e3f5e..6d17bdbc7906c7ccb11ff337c5cb8e46a3a3be89 100644 (file)
@@ -29,7 +29,7 @@ typedef boost::shared_ptr<StampedValue> StampedValuePtr;
 /// e.g. global parameter of the DHCP server.
 ///
 /// Global configuration elements having simple types, e.g. DHCP
-/// timers, need to be associatied with modification timestamps.
+/// timers, need to be associated with modification timestamps.
 /// This association is made by deriving from @c StampedElement.
 /// The values can be strings, integers, booleans or real numbers.
 ///
@@ -53,7 +53,7 @@ public:
     ///
     /// @throw BadValue if the value is null.
     /// @throw TypeError if the value is neither a string, integer,
-    /// bool nor real.
+    /// bool, real or a map with only one element of these types.
     StampedValue(const std::string& name, const ElementPtr& value);
 
     /// @brief Constructor creating a string value.
@@ -76,7 +76,7 @@ public:
     ///
     /// @throw BadValue if the value is null.
     /// @throw TypeError if the value is neither a string, integer,
-    /// bool nor real.
+    /// bool, real or a map with only one element of these types.
     static StampedValuePtr create(const std::string& name,
                                   const ElementPtr& value);
 
@@ -170,8 +170,8 @@ private:
     /// This is called from the constructors.
     ///
     /// @throw BadValue if the value is null.
-    /// @throw TypeError if the value type is neither a string,
-    /// integer, boolean nor real.
+    /// @throw TypeError if the value type is neither a string, integer,
+    /// boolean, real or a map with only one element of these types.
     void validateConstruct() const;
 
     /// @brief Checks if the value is accessed correctly.
index d305a34520aa0d3f18b437f1b3f26141e94a9bb9..f23bc72b60f1b4d54218838099c757124c39c4e9 100644 (file)
@@ -161,11 +161,82 @@ TEST(StampedValueTest, convertStringToDouble) {
     EXPECT_THROW(StampedValue::create("bar", "hoho", Element::real), BadValue);
 }
 
+// Tests that stamped value from map can be created, but only with at most one element.
+TEST(StampedValueTest, createFromMap) {
+    StampedValuePtr value;
+    ElementPtr map = Element::createMap();
+    ASSERT_NO_THROW(value = StampedValue::create("bar", map));
+    EXPECT_FALSE(value->amNull());
+    EXPECT_EQ(Element::map, value->getType());
+    EXPECT_EQ("bar", value->getName());
+    ASSERT_THROW(value->getValue(), TypeError);
+    EXPECT_EQ(value->getElementValue()->getType(), Element::map);
+    ASSERT_EQ(value->getElementValue()->mapValue().size(), 0);
+
+    EXPECT_THROW(value->getIntegerValue(), TypeError);
+    EXPECT_THROW(value->getBoolValue(), TypeError);
+    EXPECT_THROW(value->getDoubleValue(), TypeError);
+
+    map->set("foo", Element::create("0"));
+    ASSERT_NO_THROW(value = StampedValue::create("bar", map));
+    EXPECT_FALSE(value->amNull());
+    EXPECT_EQ(Element::map, value->getType());
+    EXPECT_EQ("bar", value->getName());
+    ASSERT_THROW(value->getValue(), TypeError);
+    EXPECT_EQ(value->getElementValue()->getType(), Element::map);
+    ASSERT_EQ(value->getElementValue()->mapValue().size(), 1);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->first, "foo");
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->getType(), Element::string);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->stringValue(), "0");
+
+    map->set("foo", Element::create(true));
+    ASSERT_NO_THROW(value = StampedValue::create("bar", map));
+    EXPECT_FALSE(value->amNull());
+    EXPECT_EQ(Element::map, value->getType());
+    EXPECT_EQ("bar", value->getName());
+    ASSERT_THROW(value->getValue(), TypeError);
+    EXPECT_EQ(value->getElementValue()->getType(), Element::map);
+    ASSERT_EQ(value->getElementValue()->mapValue().size(), 1);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->first, "foo");
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->getType(), Element::boolean);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->boolValue(), true);
+
+    map->set("foo", Element::create(0));
+    ASSERT_NO_THROW(value = StampedValue::create("bar", map));
+    EXPECT_FALSE(value->amNull());
+    EXPECT_EQ(Element::map, value->getType());
+    EXPECT_EQ("bar", value->getName());
+    ASSERT_THROW(value->getValue(), TypeError);
+    EXPECT_EQ(value->getElementValue()->getType(), Element::map);
+    ASSERT_EQ(value->getElementValue()->mapValue().size(), 1);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->first, "foo");
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->getType(), Element::integer);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->intValue(), 0);
+
+    map->set("foo", Element::create(0.0));
+    ASSERT_NO_THROW(value = StampedValue::create("bar", map));
+    EXPECT_FALSE(value->amNull());
+    EXPECT_EQ(Element::map, value->getType());
+    EXPECT_EQ("bar", value->getName());
+    ASSERT_THROW(value->getValue(), TypeError);
+    EXPECT_EQ(value->getElementValue()->getType(), Element::map);
+    ASSERT_EQ(value->getElementValue()->mapValue().size(), 1);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->first, "foo");
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->getType(), Element::real);
+    EXPECT_EQ(value->getElementValue()->mapValue().begin()->second->doubleValue(), 0.0);
+}
+
 // Tests that the value must have an allowed type.
 TEST(StampedValueTest, createFailures) {
     EXPECT_THROW(StampedValue::create("bar", ElementPtr()), BadValue);
-    EXPECT_THROW(StampedValue::create("bar", Element::createMap()), TypeError);
     EXPECT_THROW(StampedValue::create("bar", Element::createList()), TypeError);
+    ElementPtr map = Element::createMap();
+    map->set("foo", Element::create("0"));
+    map->set("test", Element::create("true"));
+    EXPECT_THROW(StampedValue::create("bar", map), BadValue);
+    map = Element::createMap();
+    map->set("foo", Element::createMap());
+    EXPECT_THROW(StampedValue::create("bar", map), BadValue);
 
     EXPECT_THROW(StampedValue::create("bar", "1", Element::map), TypeError);
     EXPECT_THROW(StampedValue::create("bar", "1", Element::list), TypeError);
index 99866e292fa03f615eb621d50e206aa1cd2afafc..45e38f2faf0b1d081896d98953b51b27e8d89460 100644 (file)
@@ -376,16 +376,16 @@ public:
     void addTestData() {
         // Add two properties with different names into the first backend.
         config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
-                                             BackendSelector(BackendSelector::Type::MYSQL));
+                                              BackendSelector(BackendSelector::Type::MYSQL));
         config_mgr_.getPool()->createProperty(std::make_pair("wolves", 3),
-                                             BackendSelector(BackendSelector::Type::MYSQL));
+                                              BackendSelector(BackendSelector::Type::MYSQL));
 
         // Add two properties into the second backend. Both properties share the
         // name so as we can test retrieving multiple records from the same backend.
         config_mgr_.getPool()->createProperty(std::make_pair("cats", 2),
-                                             BackendSelector(BackendSelector::Type::POSTGRESQL));
+                                              BackendSelector(BackendSelector::Type::POSTGRESQL));
         config_mgr_.getPool()->createProperty(std::make_pair("cats", 4),
-                                             BackendSelector(BackendSelector::Type::POSTGRESQL));
+                                              BackendSelector(BackendSelector::Type::POSTGRESQL));
     }
 
     /// Instance of the test configuration manager.
@@ -448,25 +448,25 @@ TEST_F(ConfigBackendMgrTest, getSingleProperty) {
 
     // No dogs in the postgresql backend and no cats in mysql backend.
     EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs",
-                                                   BackendSelector(BackendSelector::Type::POSTGRESQL)));
+                                                    BackendSelector(BackendSelector::Type::POSTGRESQL)));
     EXPECT_EQ(0, config_mgr_.getPool()->getProperty("cats",
-                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+                                                    BackendSelector(BackendSelector::Type::MYSQL)));
 
     // If the selectors are pointing to the right databases, the dogs and cats
     // should be returned properly.
     EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs",
-                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+                                                    BackendSelector(BackendSelector::Type::MYSQL)));
     EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats",
-                                                   BackendSelector(BackendSelector::Type::POSTGRESQL)));
+                                                    BackendSelector(BackendSelector::Type::POSTGRESQL)));
 
     // Also make sure that the variant of getProperty function taking two arguments
     // would return the value.
     EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs", 1,
-                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+                                                    BackendSelector(BackendSelector::Type::MYSQL)));
 
     // If the value is not matching it should return 0.
     EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs", 2,
-                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+                                                    BackendSelector(BackendSelector::Type::MYSQL)));
 
     // Try to use the backend that is not present.
     EXPECT_THROW(config_mgr_.getPool()->getProperty("cats",
@@ -483,18 +483,18 @@ TEST_F(ConfigBackendMgrTest, getMultipleProperties) {
     // There is one dogs entry in mysql.
     PropertiesList mysql_list =
         config_mgr_.getPool()->getProperties("dogs",
-                                            BackendSelector(BackendSelector::Type::MYSQL));
+                                             BackendSelector(BackendSelector::Type::MYSQL));
     ASSERT_EQ(1, mysql_list.size());
 
     // There is also one wolves entry in mysql.
     mysql_list = config_mgr_.getPool()->getProperties("wolves",
-                                                     BackendSelector(BackendSelector::Type::MYSQL));
+                                                      BackendSelector(BackendSelector::Type::MYSQL));
     ASSERT_EQ(1, mysql_list.size());
 
     // There are two cats entries in postgresql.
     PropertiesList postgresql_list =
         config_mgr_.getPool()->getProperties("cats",
-                                            BackendSelector(BackendSelector::Type::POSTGRESQL));
+                                             BackendSelector(BackendSelector::Type::POSTGRESQL));
     ASSERT_EQ(2, postgresql_list.size());
 
     // Try to use the backend that is not present.
@@ -550,7 +550,7 @@ TEST_F(ConfigBackendMgrTest, unregister) {
 
     // Try to use the backend that is not present.
     EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
-                                                       BackendSelector(BackendSelector::Type::MYSQL)),
+                                                      BackendSelector(BackendSelector::Type::MYSQL)),
                  NoSuchDatabase);
 }
 
index 104fa871c2ec05a25c04051c1892c59f355d948e..14935bb7f47029c99fd8fee259fee036ebf7b76c 100644 (file)
@@ -34,16 +34,19 @@ public:
 
 protected:
 
-    /// @brief Adds globals fetched from config backend(s) to a SrvConfig instance
+    /// @brief It translates the top level map parameters from flat naming
+    /// format (e.g. map-name/element-name) to proper ElementMap objects and
+    /// adds all globals fetched from config backend(s) to a SrvConfig instance
+    ///
+    /// Iterates over the given collection of global parameters and adds them to
+    /// the given configuration's list of configured globals.
     ///
-    /// Iterates over the given collection of global parameters and adds them to the
-    /// given configuration's list of configured globals.
     ///
     /// @param external_cfg SrvConfig instance to update
     /// @param cb_globals collection of global parameters supplied by configuration
     /// backend
-    void addGlobalsToConfig(SrvConfigPtr external_cfg,
-                            data::StampedValueCollection& cb_globals) const {
+    void translateAndAddGlobalsToConfig(SrvConfigPtr external_cfg,
+                                        data::StampedValueCollection& cb_globals) const {
         auto const& index = cb_globals.get<data::StampedValueNameIndexTag>();
         for (auto const& cb_global : index) {
 
@@ -51,8 +54,21 @@ protected:
                 continue;
             }
 
-            external_cfg->addConfiguredGlobal(cb_global->getName(),
-                                              cb_global->getElementValue());
+            std::string name = cb_global->getName();
+            auto pos = name.find('/');
+            if (pos != std::string::npos) {
+                const std::string sub_elem(name.substr(pos + 1));
+                name = name.substr(0, pos);
+                data::ElementPtr sub_param = boost::const_pointer_cast<data::Element>(external_cfg->getConfiguredGlobal(name));
+                if (!sub_param) {
+                    sub_param = data::Element::createMap();
+                }
+                sub_param->set(sub_elem, cb_global->getElementValue());
+                external_cfg->addConfiguredGlobal(name, sub_param);
+            } else {
+                // Reuse name and value.
+                external_cfg->addConfiguredGlobal(name, cb_global->getElementValue());
+            }
         }
     }
 };
index 1bc678fdcaa1a4b073a2bee379a980e45ed953a5..39bac90b50fb9f77106131f35fba559c85fe662a 100644 (file)
@@ -79,7 +79,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector,
             // database query and the number of global parameters is small.
             data::StampedValueCollection globals;
             globals = getMgr().getPool()->getAllGlobalParameters4(backend_selector, server_selector);
-            addGlobalsToConfig(external_cfg, globals);
+            translateAndAddGlobalsToConfig(external_cfg, globals);
 
             // Add defaults.
             external_cfg->applyDefaultsConfiguredGlobals(SimpleParser4::GLOBAL4_DEFAULTS);
@@ -165,7 +165,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector,
             data::StampedValueCollection globals;
             globals = getMgr().getPool()->getModifiedGlobalParameters4(backend_selector, server_selector,
                                                                        lb_modification_time);
-            addGlobalsToConfig(external_cfg, globals);
+            translateAndAddGlobalsToConfig(external_cfg, globals);
             globals_fetched = true;
         }
     }
index fba428a38bae242152483e625d98eea6fff5f141..d056602bbd2f2333ef974f9ccc8beb420e9d530e 100644 (file)
@@ -77,7 +77,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector
             // database query and the number of global parameters is small.
             data::StampedValueCollection globals;
             globals = getMgr().getPool()->getAllGlobalParameters6(backend_selector, server_selector);
-            addGlobalsToConfig(external_cfg, globals);
+            translateAndAddGlobalsToConfig(external_cfg, globals);
 
             // Add defaults.
             external_cfg->applyDefaultsConfiguredGlobals(SimpleParser6::GLOBAL6_DEFAULTS);
@@ -164,7 +164,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector
             data::StampedValueCollection globals;
             globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector,
                                                                        lb_modification_time);
-            addGlobalsToConfig(external_cfg, globals);
+            translateAndAddGlobalsToConfig(external_cfg, globals);
             globals_fetched = true;
         }
     }
index 7aab23038dbc681833ae26ef33fc9c9c94b33ce3..7e49fc72133985d1a8115e185da7d0ee921ecb9a 100644 (file)
@@ -52,6 +52,13 @@ CfgGlobals::nameToIndex = {
     { "allocator", ALLOCATOR },
     { "ddns-ttl-percent", DDNS_TTL_PERCENT },
     { "ddns-conflict-resolution-mode", DDNS_CONFLICT_RESOLUTION_MODE },
+    { "compatibility", COMPATIBILITY },
+    { "control-socket", CONTROL_SOCKET },
+    { "dhcp-ddns", DHCP_DDNS },
+    { "expired-leases-processing", EXPIRED_LEASES_PROCESSING },
+    { "multi-threading", MULTI_THREADING },
+    { "sanity-checks", SANITY_CHECKS },
+    { "dhcp-queue-control", DHCP_QUEUE_CONTROL },
 
     // DHCPv4 specific parameters.
     { "echo-client-id", ECHO_CLIENT_ID },
@@ -67,7 +74,8 @@ CfgGlobals::nameToIndex = {
     { "preferred-lifetime", PREFERRED_LIFETIME },
     { "min-preferred-lifetime", MIN_PREFERRED_LIFETIME },
     { "max-preferred-lifetime", MAX_PREFERRED_LIFETIME },
-    { "pd-allocator", PD_ALLOCATOR }
+    { "pd-allocator", PD_ALLOCATOR },
+    { "server-id", SERVER_ID }
 };
 
 // Load time sanity check.
index 88904c9f576d2a8ee12b1e899cc5b5eefa4a7477..51209362d18baf794bc41c46882dd282d144960f 100644 (file)
@@ -75,6 +75,13 @@ public:
         ALLOCATOR,
         DDNS_TTL_PERCENT,
         DDNS_CONFLICT_RESOLUTION_MODE,
+        COMPATIBILITY,
+        CONTROL_SOCKET,
+        DHCP_DDNS,
+        EXPIRED_LEASES_PROCESSING,
+        MULTI_THREADING,
+        SANITY_CHECKS,
+        DHCP_QUEUE_CONTROL,
 
         // DHCPv4 specific parameters.
         ECHO_CLIENT_ID,
@@ -91,6 +98,7 @@ public:
         MIN_PREFERRED_LIFETIME,
         MAX_PREFERRED_LIFETIME,
         PD_ALLOCATOR,
+        SERVER_ID,
 
         // Size sentinel.
         SIZE
index e7fc071692b8fea339fea84e6e6c81a012c62e8f..0a77a72bf47fc8ce1a6e43a2749e9de8bbcca949 100644 (file)
@@ -1657,5 +1657,33 @@ D2ClientConfigParser::setAllDefaults(isc::data::ConstElementPtr d2_config) {
     return (SimpleParser::setDefaults(mutable_d2, D2_CLIENT_CONFIG_DEFAULTS));
 }
 
+void
+CompatibilityParser::parse(ConstElementPtr compatibility, SrvConfig& srv_cfg) {
+    if (compatibility) {
+        for (auto const& kv : compatibility->mapValue()) {
+            if (!kv.second || (kv.second->getType() != Element::boolean)) {
+                isc_throw(DhcpConfigError,
+                          "compatibility parameter values must be "
+                          << "boolean (" << kv.first << " at "
+                          << kv.second->getPosition() << ")");
+            }
+            if (kv.first == "lenient-option-parsing") {
+                srv_cfg.setLenientOptionParsing(kv.second->boolValue());
+            } else if (kv.first == "ignore-dhcp-server-identifier") {
+                srv_cfg.setIgnoreServerIdentifier(kv.second->boolValue());
+            } else if (kv.first == "ignore-rai-link-selection") {
+                srv_cfg.setIgnoreRAILinkSelection(kv.second->boolValue());
+            } else if (kv.first == "exclude-first-last-24") {
+                srv_cfg.setExcludeFirstLast24(kv.second->boolValue());
+            } else {
+                isc_throw(DhcpConfigError,
+                          "unsupported compatibility parameter: "
+                          << kv.first << " (" << kv.second->getPosition()
+                          << ")");
+            }
+        }
+    }
+}
+
 } // namespace dhcp
 } // namespace isc
index b41653907dab311bb1b7759722ea487a32b248a2..cfc5e527effcf66a6ec131626c7fe5b688f70662 100644 (file)
@@ -1073,6 +1073,15 @@ private:
     getMode(isc::data::ConstElementPtr scope, const std::string& name);
 };
 
+class CompatibilityParser : public isc::data::SimpleParser {
+public:
+    /// @brief Parse compatibility flags
+    ///
+    /// @param cfg The configuration element to be parsed
+    /// @param srv_cfg The configuration where the parameters are stored
+    void parse(isc::data::ConstElementPtr cfg, isc::dhcp::SrvConfig& srv_cfg);
+};
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
 
index 52d9740e86c3419bf88fddb5cb37bb98c58d8563..ab6930e643109cca5354a53d8c46b3453462be33 100644 (file)
@@ -18,11 +18,8 @@ namespace isc {
 namespace dhcp {
 
 void
-ExpirationConfigParser::parse(ConstElementPtr expiration_config) {
-    CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
-
+ExpirationConfigParser::parse(ConstElementPtr expiration_config, CfgExpirationPtr cfg) {
     std::string param;
-
     try {
         param = "reclaim-timer-wait-time";
         if (expiration_config->contains(param)) {
index 44ba77d6e038d8088af2546c3264494d9a965237..39d7ee066c8e3bdba49c0a76859dae0c60a6e0da 100644 (file)
@@ -46,11 +46,12 @@ public:
     /// of the expired leases.
     ///
     /// @param expiration_config pointer to the content of parsed values
+    /// @param expiration pointer to config parameters to be updated
     ///
     /// @throw DhcpConfigError if unknown parameter specified or the
-    /// parameter contains invalid value..
-    void parse(isc::data::ConstElementPtr expiration_config);
-
+    /// parameter contains invalid value.
+    void parse(isc::data::ConstElementPtr expiration_config,
+               isc::dhcp::CfgExpirationPtr expiration);
 };
 
 } // end of namespace isc::dhcp
index ab6ee721689f699ed37e82dbd03a430e3699e29d..babcc425d10970f5c54484e33de7ae6aed4c59d4 100644 (file)
@@ -5,10 +5,20 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
+#include <cc/simple_parser.h>
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/parsers/base_network_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/multi_threading_config_parser.h>
+#include <dhcpsrv/parsers/sanity_checks_parser.h>
 #include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/cfg_multi_threading.h>
 #include <dhcpsrv/srv_config.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/dhcpsrv_log.h>
@@ -16,7 +26,7 @@
 #include <process/logging_info.h>
 #include <log/logger_manager.h>
 #include <log/logger_specification.h>
-#include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_*
+#include <dhcp/pkt.h>
 #include <stats/stats_mgr.h>
 #include <util/strutil.h>
 
@@ -176,14 +186,17 @@ SrvConfig::merge(ConfigBase& other) {
         // Merge globals.
         mergeGlobals(other_srv_config);
 
+        // Merge global maps.
+        mergeGlobalMaps(other_srv_config);
+
         // Merge option defs. We need to do this next so we
         // pass these into subsequent merges so option instances
         // at each level can be created based on the merged
         // definitions.
-        cfg_option_def_->merge((*other_srv_config.getCfgOptionDef()));
+        cfg_option_def_->merge(*other_srv_config.getCfgOptionDef());
 
         // Merge options.
-        cfg_option_->merge(cfg_option_def_, (*other_srv_config.getCfgOption()));
+        cfg_option_->merge(cfg_option_def_, *other_srv_config.getCfgOption());
 
         if (!other_srv_config.getClientClassDictionary()->empty()) {
             // Client classes are complicated because they are ordered and may
@@ -277,11 +290,111 @@ SrvConfig::mergeGlobals(SrvConfig& other) {
     }
 }
 
+void
+SrvConfig::mergeGlobalMaps(SrvConfig& other) {
+    ElementPtr config = Element::createMap();
+    for (auto const& other_global : other.getConfiguredGlobals()->valuesMap()) {
+        config->set(other_global.first, other_global.second);
+    }
+    std::string parameter_name;
+    try {
+        ConstElementPtr compatibility = config->get("compatibility");
+        parameter_name = "compatibility";
+        if (compatibility) {
+            CompatibilityParser parser;
+            parser.parse(compatibility, *this);
+            addConfiguredGlobal("compatibility", compatibility);
+        }
+        ConstElementPtr control_socket = config->get("control-socket");
+        parameter_name = "control-socket";
+        if (control_socket) {
+            ControlSocketParser parser;
+            parser.parse(*this, control_socket);
+            addConfiguredGlobal("control-socket", control_socket);
+        }
+        ElementPtr dhcp_ddns = boost::const_pointer_cast<Element>(config->get("dhcp-ddns"));
+        parameter_name = "dhcp-ddns";
+        if (dhcp_ddns) {
+            // Apply defaults
+            D2ClientConfigParser::setAllDefaults(dhcp_ddns);
+            D2ClientConfigParser parser;
+            // D2 client configuration.
+            D2ClientConfigPtr d2_client_cfg;
+            d2_client_cfg = parser.parse(dhcp_ddns);
+            if (!d2_client_cfg) {
+                d2_client_cfg.reset(new D2ClientConfig());
+            }
+            d2_client_cfg->validateContents();
+            setD2ClientConfig(d2_client_cfg);
+            addConfiguredGlobal("dhcp-ddns", dhcp_ddns);
+        }
+        ConstElementPtr expiration_cfg = config->get("expired-leases-processing");
+        parameter_name = "expired-leases-processing";
+        if (expiration_cfg) {
+            ExpirationConfigParser parser;
+            parser.parse(expiration_cfg, getCfgExpiration());
+            addConfiguredGlobal("expired-leases-processing", expiration_cfg);
+        }
+        ElementPtr multi_threading = boost::const_pointer_cast<Element>(config->get("multi-threading"));
+        parameter_name = "multi-threading";
+        if (multi_threading) {
+            if (CfgMgr::instance().getFamily() == AF_INET) {
+                SimpleParser::setDefaults(multi_threading, SimpleParser4::DHCP_MULTI_THREADING4_DEFAULTS);
+            } else {
+                SimpleParser::setDefaults(multi_threading, SimpleParser6::DHCP_MULTI_THREADING6_DEFAULTS);
+            }
+            MultiThreadingConfigParser parser;
+            parser.parse(*this, multi_threading);
+            addConfiguredGlobal("multi-threading", multi_threading);
+        }
+        bool multi_threading_enabled = true;
+        uint32_t thread_count = 0;
+        uint32_t queue_size = 0;
+        CfgMultiThreading::extract(getDHCPMultiThreading(),
+                                   multi_threading_enabled, thread_count, queue_size);
+        ElementPtr sanity_checks = boost::const_pointer_cast<Element>(config->get("sanity-checks"));
+        parameter_name = "sanity-checks";
+        if (sanity_checks) {
+            if (CfgMgr::instance().getFamily() == AF_INET) {
+                SimpleParser::setDefaults(sanity_checks, SimpleParser4::SANITY_CHECKS4_DEFAULTS);
+            } else {
+                SimpleParser::setDefaults(sanity_checks, SimpleParser6::SANITY_CHECKS6_DEFAULTS);
+            }
+            SanityChecksParser parser;
+            parser.parse(*this, sanity_checks);
+            addConfiguredGlobal("multi-threading", sanity_checks);
+        }
+        ConstElementPtr server_id = config->get("server-id");
+        parameter_name = "server-id";
+        if (server_id) {
+            DUIDConfigParser parser;
+            const CfgDUIDPtr& cfg = getCfgDUID();
+            parser.parse(cfg, server_id);
+            addConfiguredGlobal("server-id", server_id);
+        }
+        ElementPtr queue_control = boost::const_pointer_cast<Element>(config->get("dhcp-queue-control"));
+        parameter_name = "dhcp-queue-control";
+        if (queue_control) {
+            if (CfgMgr::instance().getFamily() == AF_INET) {
+                SimpleParser::setDefaults(queue_control, SimpleParser4::DHCP_QUEUE_CONTROL4_DEFAULTS);
+            } else {
+                SimpleParser::setDefaults(queue_control, SimpleParser6::DHCP_QUEUE_CONTROL6_DEFAULTS);
+            }
+            DHCPQueueControlParser parser;
+            setDHCPQueueControl(parser.parse(queue_control, multi_threading_enabled));
+            addConfiguredGlobal("dhcp-queue-control", queue_control);
+        }
+    } catch (const isc::Exception& ex) {
+        isc_throw(BadValue, "Invalid parameter " << parameter_name << " error: " << ex.what());
+    } catch (...) {
+        isc_throw(BadValue, "Invalid parameter " << parameter_name);
+    }
+}
+
 void
 SrvConfig::removeStatistics() {
     // Removes statistics for v4 and v6 subnets
     getCfgSubnets4()->removeStatistics();
-
     getCfgSubnets6()->removeStatistics();
 }
 
@@ -318,7 +431,6 @@ SrvConfig::updateStatistics() {
     if (LeaseMgrFactory::haveInstance()) {
         // Updates  statistics for v4 and v6 subnets
         getCfgSubnets4()->updateStatistics();
-
         getCfgSubnets6()->updateStatistics();
     }
 }
index eb27c20c2c85e3441f1e3510bbf9c6ed61f1d2c2..3e0a1cec54ffe13e9ccc88246670ab3f51f29693 100644 (file)
@@ -893,6 +893,17 @@ public:
     /// @c extractConfiguredGlobals should be called after.
     void clearConfiguredGlobals() {
         configured_globals_->clear();
+        lenient_option_parsing_ = false;
+        ignore_dhcp_server_identifier_ = false;
+        ignore_rai_link_selection_ = false;
+        exclude_first_last_24_ = false;
+        control_socket_.reset();
+        d2_client_config_.reset(new D2ClientConfig());
+        cfg_expiration_.reset(new CfgExpiration());
+        dhcp_multi_threading_.reset();
+        cfg_consist_.reset(new CfgConsistency());
+        cfg_duid_.reset(new CfgDUID());
+        dhcp_queue_control_.reset();
     }
 
     /// @brief Applies defaults to global parameters.
@@ -1098,6 +1109,30 @@ private:
     /// into this configuration.
     void mergeGlobals(SrvConfig& other);
 
+    /// @brief Merges the global maps specified in the given configuration
+    /// into this configuration.
+    ///
+    /// Configurable global values may be specified either via JSON
+    /// configuration (e.g. "echo-client-id":true) or as global parameters
+    /// within a configuration back end.  Regardless of the source, these
+    /// values once provided, are stored in @c SrvConfig::configured_globals_.
+    /// Any such value that does not have an explicit specification should be
+    /// considered "unspecified" at the global scope.
+    ///
+    /// This function adds the configured globals from the "other" config
+    /// into this config's configured globals.  If a value already exists
+    /// in this config, it will be overwritten with the value from the
+    /// "other" config.
+    ///
+    /// It then iterates over this merged list of globals, setting
+    /// any of the corresponding SrvConfig members that map to a
+    /// a configurable parameter (e.g. c@ SrvConfig::echo_client_id_,
+    /// @c SrvConfig::server_tag_).
+    ///
+    /// @param other An object holding the configuration to be merged
+    /// into this configuration.
+    void mergeGlobalMaps(SrvConfig& other);
+
     /// @brief Sequence number identifying the configuration.
     uint32_t sequence_;
 
index fd765072328c83c9b434c8d8a4fbbea621f94113..7f7c171ec942c7e352c37b8d12863b328045acd6 100644 (file)
@@ -106,7 +106,7 @@ ExpirationConfigParserTest::renderConfig() const {
 
     // Parse the configuration. This may emit exceptions.
     ExpirationConfigParser parser;
-    parser.parse(config_element);
+    parser.parse(config_element, CfgMgr::instance().getStagingCfg()->getCfgExpiration());
 
     // No exception so return configuration.
     return (CfgMgr::instance().getStagingCfg()->getCfgExpiration());
@@ -246,7 +246,9 @@ TEST_F(ExpirationConfigParserTest, notNumberValue) {
 
     // Parse the configuration. It should throw exception.
     ExpirationConfigParser parser;
-    EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+    EXPECT_THROW(parser.parse(config_element,
+                              CfgMgr::instance().getStagingCfg()->getCfgExpiration()),
+                 DhcpConfigError);
 }
 
 } // end of anonymous namespace
index 3c1564a08dbf69df5759fba8045776d34ce8c6b0..0ed6f52b2dfb48432107a0a560794a9f6e7bcb76 100644 (file)
@@ -5,7 +5,7 @@
         "This command deletes a global DHCPv4 parameter from the configuration database. The server uses the value specified in the configuration file, or a default value if the parameter is not specified, after deleting the parameter from the database."
     ],
     "cmd-comment": [
-        "This command carries the list including exactly one name of the parameter to be deleted. The ``server-tags`` list is mandatory and it must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+        "This command carries the list including exactly one name of the parameter to be deleted. If deleting a map parameter, the ``map-name/parameter-name`` format must be used. The ``server-tags`` list is mandatory and it must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
     ],
     "cmd-syntax": [
         "{",
index 1eb42970016aabeeb034f068769666934a28b1f4..efb6a1984d26d1be3431bcd6e6910fea5a5b5951 100644 (file)
@@ -21,7 +21,7 @@
     "hook": "cb_cmds",
     "name": "remote-global-parameter4-get-all",
     "resp-comment": [
-        "The returned response contains a list of maps. Each map contains a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
+        "The returned response contains a list of maps. Each map contains a global parameter name:value pair. The value may be a JSON string, integer, real, boolean or a map containing only one of these types. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
     ],
     "resp-syntax": [
         "{",
index 1d7f09bbd65d57a790ccddb5f0e217435cb4869b..dcf63cd5485c1ce63cf68717bfc96cf547e3c21c 100644 (file)
@@ -5,7 +5,7 @@
         "This command fetches the selected global parameter for the server from the specified database."
     ],
     "cmd-comment": [
-        "This command carries a list including exactly one name of the parameter to be fetched. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
+        "This command carries a list including exactly one name of the parameter to be fetched. If retrieving a map parameter, the ``map-name/parameter-name`` format must be used. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
     ],
     "cmd-syntax": [
         "{",
@@ -22,7 +22,7 @@
     "hook": "cb_cmds",
     "name": "remote-global-parameter4-get",
     "resp-comment": [
-        "The returned response contains a map with a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter4-get`` command fetches the value associated with all servers."
+        "The returned response contains a map with a global parameter name:value pair. The value may be a JSON string, integer, real, boolean or a map containing only one of these types. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter4-get`` command fetches the value associated with all servers."
     ],
     "resp-syntax": [
         "{",
index d09af68713d7e31dbc73be854db25adfd653e0c1..d5c44139f359fd14c48827abfad23e0a1a3561d3 100644 (file)
@@ -5,7 +5,7 @@
         "This command creates or updates one or more global parameters in the configuration database."
     ],
     "cmd-comment": [
-        "This command carries multiple global parameters with their values. Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter4-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
+        "This command carries multiple global parameters with their values (including maps with scalar parameters). Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter4-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
     ],
     "cmd-syntax": [
         "{",
index 545921ef9926264d7783eab43a34920adc6155ff..7b4041782f61ac2bff68679c5f7561ea227822b7 100644 (file)
@@ -5,7 +5,7 @@
         "This command deletes a global DHCPv6 parameter from the configuration database. The server uses the value specified in the configuration file, or a default value if the parameter is not specified in the configuration file, after deleting the parameter from the database."
     ],
     "cmd-comment": [
-        "This command carries the list including exactly one name of the parameter to be deleted. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+        "This command carries the list including exactly one name of the parameter to be deleted. If deleting a map parameter, the ``map-name/parameter-name`` format must be used. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
     ],
     "cmd-syntax": [
         "{",
index 12d8541330ea10d50f8e2a76902d812aacbc7f7f..5ddfa55203e9a615bc919afa8b798bea0ab42fd3 100644 (file)
@@ -21,7 +21,7 @@
     "hook": "cb_cmds",
     "name": "remote-global-parameter6-get-all",
     "resp-comment": [
-        "The returned response contains a list of maps. Each map contains a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
+        "The returned response contains a list of maps. Each map contains a global parameter name:value pair. The value may be a JSON string, integer, real, boolean or a map containing only one of these types. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
     ],
     "resp-syntax": [
         "{",
index 4c3975eabfde70a067560c42c7a652ea77acfab5..0950dc5174746a30f098822692339d285e872bc5 100644 (file)
@@ -5,7 +5,7 @@
         "This command fetches the selected global parameter for the server from the specified database."
     ],
     "cmd-comment": [
-        "This command carries a list including exactly one name of the parameter to be fetched. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
+        "This command carries a list including exactly one name of the parameter to be fetched. If retrieving a map parameter, the ``map-name/parameter-name`` format must be used. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
     ],
     "cmd-syntax": [
         "{",
@@ -22,7 +22,7 @@
     "hook": "cb_cmds",
     "name": "remote-global-parameter6-get",
     "resp-comment": [
-        "The returned response contains a map with a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter6-get`` fetches the value associated with all servers."
+        "The returned response contains a map with a global parameter name:value pair. The value may be a JSON string, integer, real, boolean or a map containing only one of these types. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter6-get`` fetches the value associated with all servers."
     ],
     "resp-syntax": [
         "{",
index 2f1fc6770a0937bebd8985ff1abcf57d6dd14c65..fb547c4f2c9dfa1ca0be334ba289b2cf5e02322d 100644 (file)
@@ -5,7 +5,7 @@
         "This command creates or updates one or more global parameters in the configuration database."
     ],
     "cmd-comment": [
-        "This command carries multiple global parameters with their values. Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter6-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
+        "This command carries multiple global parameters with their values (including maps with scalar parameters). Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter6-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
     ],
     "cmd-syntax": [
         "{",