diff options
author | Razvan Becheriu <razvan@isc.org> | 2024-01-17 17:52:05 +0100 |
---|---|---|
committer | Razvan Becheriu <razvan@isc.org> | 2024-01-26 13:19:54 +0100 |
commit | a48e9ee568482f4d42b73975779818697fe19576 (patch) | |
tree | 09fc4455a774bcce630dae6f639ab24a5af75bc1 | |
parent | [#3198] add ChangeLog entry (diff) | |
download | kea-a48e9ee568482f4d42b73975779818697fe19576.tar.xz kea-a48e9ee568482f4d42b73975779818697fe19576.zip |
[#1790] add support for top level maps in global CB parameters
26 files changed, 376 insertions, 106 deletions
diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index a7bf06954e..5be7f2cdf7 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -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. diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index e39163fe6f..963974f1b9 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -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. diff --git a/src/lib/cc/stamped_value.cc b/src/lib/cc/stamped_value.cc index 077c688584..3fbad88626 100644 --- a/src/lib/cc/stamped_value.cc +++ b/src/lib/cc/stamped_value.cc @@ -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)); + } + } } } diff --git a/src/lib/cc/stamped_value.h b/src/lib/cc/stamped_value.h index 2120593486..6d17bdbc79 100644 --- a/src/lib/cc/stamped_value.h +++ b/src/lib/cc/stamped_value.h @@ -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. diff --git a/src/lib/cc/tests/stamped_value_unittest.cc b/src/lib/cc/tests/stamped_value_unittest.cc index d305a34520..f23bc72b60 100644 --- a/src/lib/cc/tests/stamped_value_unittest.cc +++ b/src/lib/cc/tests/stamped_value_unittest.cc @@ -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); diff --git a/src/lib/config_backend/tests/config_backend_mgr_unittest.cc b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc index 99866e292f..45e38f2faf 100644 --- a/src/lib/config_backend/tests/config_backend_mgr_unittest.cc +++ b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc @@ -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); } diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp.h b/src/lib/dhcpsrv/cb_ctl_dhcp.h index 104fa871c2..14935bb7f4 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp.h +++ b/src/lib/dhcpsrv/cb_ctl_dhcp.h @@ -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()); + } } } }; diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc index 1bc678fdca..39bac90b50 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc @@ -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; } } diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc index fba428a38b..d056602bbd 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc @@ -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; } } diff --git a/src/lib/dhcpsrv/cfg_globals.cc b/src/lib/dhcpsrv/cfg_globals.cc index 7aab23038d..7e49fc7213 100644 --- a/src/lib/dhcpsrv/cfg_globals.cc +++ b/src/lib/dhcpsrv/cfg_globals.cc @@ -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. diff --git a/src/lib/dhcpsrv/cfg_globals.h b/src/lib/dhcpsrv/cfg_globals.h index 88904c9f57..51209362d1 100644 --- a/src/lib/dhcpsrv/cfg_globals.h +++ b/src/lib/dhcpsrv/cfg_globals.h @@ -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 diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index e7fc071692..0a77a72bf4 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -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 diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index b41653907d..cfc5e527ef 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -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 diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc index 52d9740e86..ab6930e643 100644 --- a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc +++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc @@ -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)) { diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.h b/src/lib/dhcpsrv/parsers/expiration_config_parser.h index 44ba77d6e0..39d7ee066c 100644 --- a/src/lib/dhcpsrv/parsers/expiration_config_parser.h +++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.h @@ -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 diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index ab6ee72168..babcc425d1 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -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 @@ -278,10 +291,110 @@ 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(); } } diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index eb27c20c2c..3e0a1cec54 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -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_; diff --git a/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc index fd76507232..7f7c171ec9 100644 --- a/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc @@ -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 diff --git a/src/share/api/remote-global-parameter4-del.json b/src/share/api/remote-global-parameter4-del.json index 3c1564a08d..0ed6f52b2d 100644 --- a/src/share/api/remote-global-parameter4-del.json +++ b/src/share/api/remote-global-parameter4-del.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter4-get-all.json b/src/share/api/remote-global-parameter4-get-all.json index 1eb4297001..efb6a1984d 100644 --- a/src/share/api/remote-global-parameter4-get-all.json +++ b/src/share/api/remote-global-parameter4-get-all.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter4-get.json b/src/share/api/remote-global-parameter4-get.json index 1d7f09bbd6..dcf63cd548 100644 --- a/src/share/api/remote-global-parameter4-get.json +++ b/src/share/api/remote-global-parameter4-get.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter4-set.json b/src/share/api/remote-global-parameter4-set.json index d09af68713..d5c44139f3 100644 --- a/src/share/api/remote-global-parameter4-set.json +++ b/src/share/api/remote-global-parameter4-set.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter6-del.json b/src/share/api/remote-global-parameter6-del.json index 545921ef99..7b4041782f 100644 --- a/src/share/api/remote-global-parameter6-del.json +++ b/src/share/api/remote-global-parameter6-del.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter6-get-all.json b/src/share/api/remote-global-parameter6-get-all.json index 12d8541330..5ddfa55203 100644 --- a/src/share/api/remote-global-parameter6-get-all.json +++ b/src/share/api/remote-global-parameter6-get-all.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter6-get.json b/src/share/api/remote-global-parameter6-get.json index 4c3975eabf..0950dc5174 100644 --- a/src/share/api/remote-global-parameter6-get.json +++ b/src/share/api/remote-global-parameter6-get.json @@ -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": [ "{", diff --git a/src/share/api/remote-global-parameter6-set.json b/src/share/api/remote-global-parameter6-set.json index 2f1fc6770a..fb547c4f2c 100644 --- a/src/share/api/remote-global-parameter6-set.json +++ b/src/share/api/remote-global-parameter6-set.json @@ -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": [ "{", |