From: Tomek Mrugalski Date: Fri, 25 Nov 2016 17:09:22 +0000 (+0100) Subject: [5014_phase2] SimpleParser implemented, 4 parsers converted X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=813f8529dad949c7c63be11645d7955e36478a78;p=thirdparty%2Fkea.git [5014_phase2] SimpleParser implemented, 4 parsers converted - SimpleParser concept implemented - Converted 4 parsers (option data, option data list, option defintion, option definition list) - updated unit-tests - converted other parsers (HostReservationParser{4,6}, ClientClassDefParser) to use those new parsers - converted kea-dhcp{4,6} to use those new parsers --- diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index 52b554972b..e4edf89027 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -190,8 +191,7 @@ protected: parser = new Pools4ListParser(config_id, pools_); } else if (config_id.compare("relay") == 0) { parser = new RelayInfoParser(config_id, relay_info_, Option::V4); - } else if (config_id.compare("option-data") == 0) { - parser = new OptionDataListParser(config_id, options_, AF_INET); + // option-data has been converted to SimpleParser already. } else if (config_id.compare("match-client-id") == 0) { parser = new BooleanParser(config_id, boolean_values_); } else if (config_id.compare("4o6-subnet") == 0) { @@ -424,10 +424,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id, parser = new IfacesConfigParser4(); } else if (config_id.compare("subnet4") == 0) { parser = new Subnets4ListConfigParser(config_id); - } else if (config_id.compare("option-data") == 0) { - parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET); - } else if (config_id.compare("option-def") == 0) { - parser = new OptionDefListParser(config_id, globalContext()); + // option-data and option-def have been converted to SimpleParser already. } else if ((config_id.compare("version") == 0) || (config_id.compare("next-server") == 0)) { parser = new StringParser(config_id, @@ -538,7 +535,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Please do not change this order! ParserCollection independent_parsers; ParserPtr subnet_parser; - ParserPtr option_parser; ParserPtr iface_parser; ParserPtr leases_parser; ParserPtr client_classes_parser; @@ -567,10 +563,43 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // the name of the failing parser can be retrieved in the "catch" clause. ConfigPair config_pair; try { + + // This is a way to convert ConstElementPtr to ElementPtr. + // We need a config that can be edited, because we will insert + // default values and will insert derived values as well. + std::map values; + config_set->getValue(values); + ElementPtr mutable_cfg(new MapElement()); + mutable_cfg->setValue(values); + + // Set all default values if not specified by the user. + SimpleParser::setAllDefaults(mutable_cfg, false); + + // We need definitions first + ConstElementPtr option_defs = mutable_cfg->get("option-def"); + if (option_defs) { + OptionDefListParser parser(AF_INET); + CfgOptionDefPtr cfg_option_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef(); + parser.parse(cfg_option_def, option_defs); + } + // Make parsers grouping. const std::map& values_map = - config_set->mapValue(); + mutable_cfg->mapValue(); BOOST_FOREACH(config_pair, values_map) { + + if (config_pair.first == "option-def") { + // This is converted to SimpleParser and is handled already above. + continue; + } + + if (config_pair.first == "option-data") { + OptionDataListParser parser(AF_INET); + CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption(); + parser.parse(cfg_option, config_pair.second); + continue; + } + ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first, config_pair.second)); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED) @@ -579,8 +608,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { subnet_parser = parser; } else if (config_pair.first == "lease-database") { leases_parser = parser; - } else if (config_pair.first == "option-data") { - option_parser = parser; } else if (config_pair.first == "interfaces-config") { // The interface parser is independent from any other // parser and can be run here before any other parsers. @@ -606,15 +633,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { } } - // The option values parser is the next one to be run. - std::map::const_iterator option_config = - values_map.find("option-data"); - if (option_config != values_map.end()) { - config_pair.first = "option-data"; - option_parser->build(option_config->second); - option_parser->commit(); - } - // The class definitions parser is the next one to be run. std::map::const_iterator cc_config = values_map.find("client-classes"); diff --git a/src/bin/dhcp4/tests/host_options_unittest.cc b/src/bin/dhcp4/tests/host_options_unittest.cc index b2c86e0660..38dd9c68c8 100644 --- a/src/bin/dhcp4/tests/host_options_unittest.cc +++ b/src/bin/dhcp4/tests/host_options_unittest.cc @@ -203,7 +203,7 @@ const char* HOST_CONFIGS[] = { "\"valid-lifetime\": 600," "\"option-data\": [ {" " \"name\": \"vivso-suboptions\"," - " \"data\": 4491" + " \"data\": \"4491\"" "}," "{" " \"name\": \"tftp-servers\"," @@ -221,7 +221,7 @@ const char* HOST_CONFIGS[] = { " \"ip-address\": \"10.0.0.7\"," " \"option-data\": [ {" " \"name\": \"vivso-suboptions\"," - " \"data\": 4491" + " \"data\": \"4491\"" " }," " {" " \"name\": \"tftp-servers\"," diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 2f30470326..69ba1d66f9 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -183,11 +184,12 @@ public: uint32_values_)); parser = code_parser; } else if (entry == "option-data") { - OptionDataListParserPtr option_parser(new OptionDataListParser(entry, - options_, - AF_INET6)); - parser = option_parser; + OptionDataListParser opts_parser(AF_INET6); + opts_parser.parse(options_, param.second); + // OptionDataListParser is converted to SimpleParser already, + // no need to go through build/commit phases. + continue; } else { isc_throw(DhcpConfigError, "unsupported parameter: " << entry << " (" << param.second->getPosition() << ")"); @@ -411,8 +413,7 @@ protected: parser = new RelayInfoParser(config_id, relay_info_, Option::V6); } else if (config_id.compare("pd-pools") == 0) { parser = new PdPoolListParser(config_id, pools_); - } else if (config_id.compare("option-data") == 0) { - parser = new OptionDataListParser(config_id, options_, AF_INET6); + // option-data was here, but it is now converted to SimpleParser } else if (config_id.compare("rapid-commit") == 0) { parser = new BooleanParser(config_id, boolean_values_); } else { @@ -688,11 +689,9 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id, parser = new IfacesConfigParser6(); } else if (config_id.compare("subnet6") == 0) { parser = new Subnets6ListConfigParser(config_id); - } else if (config_id.compare("option-data") == 0) { - parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET6); - } else if (config_id.compare("option-def") == 0) { - parser = new OptionDefListParser(config_id, globalContext()); - } else if (config_id.compare("version") == 0) { + // option-data and option-def are no longer needed here. They're now + // converted to SimpleParser and are handled in configureDhcp6Server + } else if (config_id.compare("version") == 0) { parser = new StringParser(config_id, globalContext()->string_values_); } else if (config_id.compare("lease-database") == 0) { @@ -793,7 +792,6 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { // Please do not change this order! ParserCollection independent_parsers; ParserPtr subnet_parser; - ParserPtr option_parser; ParserPtr iface_parser; ParserPtr leases_parser; ParserPtr client_classes_parser; @@ -823,10 +821,42 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { ConfigPair config_pair; try { + // This is a way to convert ConstElementPtr to ElementPtr. + // We need a config that can be edited, because we will insert + // default values and will insert derived values as well. + std::map values; + config_set->getValue(values); + ElementPtr mutable_cfg(new MapElement()); + mutable_cfg->setValue(values); + + SimpleParser::setAllDefaults(mutable_cfg, true); + // Make parsers grouping. const std::map& values_map = - config_set->mapValue(); + mutable_cfg->mapValue(); + + // We need definitions first + ConstElementPtr option_defs = mutable_cfg->get("option-def"); + if (option_defs) { + OptionDefListParser parser(AF_INET6); + CfgOptionDefPtr cfg_option_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef(); + parser.parse(cfg_option_def, option_defs); + } + BOOST_FOREACH(config_pair, values_map) { + + if (config_pair.first == "option-def") { + // This is converted to SimpleParser and is handled already above. + continue; + } + + if (config_pair.first == "option-data") { + OptionDataListParser parser(AF_INET6); + CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption(); + parser.parse(cfg_option, config_pair.second); + continue; + } + ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first, config_pair.second)); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED) @@ -835,9 +865,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { subnet_parser = parser; } else if (config_pair.first == "lease-database") { leases_parser = parser; - } else if (config_pair.first == "option-data") { + } /* else if (config_pair.first == "option-data") { option_parser = parser; - } else if (config_pair.first == "hooks-libraries") { + } */ else if (config_pair.first == "hooks-libraries") { // Executing the commit will alter currently loaded hooks // libraries. Check if the supplied libraries are valid, // but defer the commit until after everything else has @@ -863,15 +893,6 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { } } - // The option values parser is the next one to be run. - std::map::const_iterator option_config = - values_map.find("option-data"); - if (option_config != values_map.end()) { - config_pair.first = "option-data"; - option_parser->build(option_config->second); - option_parser->commit(); - } - // The class definitions parser is the next one to be run. std::map::const_iterator cc_config = values_map.find("client-classes"); diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 99e83b774b..f43d5d3d99 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -2023,10 +2023,12 @@ TEST_F(Dhcpv6SrvTest, rsooOverride) { " \"option-def\": [ {" " \"name\": \"foo\"," " \"code\": 120," + " \"csv-format\": false," " \"type\": \"binary\"" " } ]," " \"option-data\": [ {" " \"code\": 120," + " \"csv-format\": false," " \"data\": \"05\"" " } ]," " \"preferred-lifetime\": 3000," diff --git a/src/bin/dhcp6/tests/host_unittest.cc b/src/bin/dhcp6/tests/host_unittest.cc index 4d0bba3971..12440cd7d5 100644 --- a/src/bin/dhcp6/tests/host_unittest.cc +++ b/src/bin/dhcp6/tests/host_unittest.cc @@ -229,7 +229,7 @@ const char* CONFIGS[] = { "\"renew-timer\": 1000, " "\"option-data\": [ {" " \"name\": \"vendor-opts\"," - " \"data\": 4491" + " \"data\": \"4491\"" "}," "{" " \"name\": \"tftp-servers\"," @@ -247,7 +247,7 @@ const char* CONFIGS[] = { " \"ip-addresses\": [ \"2001:db8:1::2\" ]," " \"option-data\": [ {" " \"name\": \"vendor-opts\"," - " \"data\": 4491" + " \"data\": \"4491\"" " }," " {" " \"name\": \"tftp-servers\"," diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index f763340e9d..a28e3a8bf8 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -32,6 +32,8 @@ EXTRA_DIST = EXTRA_DIST += parsers/client_class_def_parser.cc EXTRA_DIST += parsers/client_class_def_parser.h EXTRA_DIST += parsers/dhcp_config_parser.h +EXTRA_DIST += parsers/simple_parser.cc +EXTRA_DIST += parsers/simple_parser.h EXTRA_DIST += parsers/dbaccess_parser.cc EXTRA_DIST += parsers/dbaccess_parser.h EXTRA_DIST += parsers/dhcp_parsers.cc @@ -161,6 +163,8 @@ libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.h libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.cc libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.h +libkea_dhcpsrv_la_SOURCES += parsers/simple_parser.cc +libkea_dhcpsrv_la_SOURCES += parsers/simple_parser.h libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.h libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.cc diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc index c939a4eaa4..679f12b922 100644 --- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc @@ -91,12 +91,16 @@ ClientClassDefParser::build(ConstElementPtr class_def_cfg) { global_context_)); parser = exp_parser; } else if (entry == "option-data") { - OptionDataListParserPtr opts_parser; + uint16_t family = (global_context_->universe_ == Option::V4 ? AF_INET : AF_INET6); - opts_parser.reset(new OptionDataListParser(entry, options_, family)); - parser = opts_parser; + OptionDataListParser opts_parser(family); + opts_parser.parse(options_, param.second); + + // OptionDataListParser is converted to SimpleParser already, + // no need to go through build/commit phases. + continue; } else if (entry == "next-server") { StringParserPtr str_parser(new StringParser(entry, string_values_)); parser = str_parser; diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index b96416cf4b..e37e2a8b6e 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -376,75 +376,30 @@ HooksLibrariesParser::getLibraries(isc::hooks::HookLibsCollection& libraries, } // **************************** OptionDataParser ************************* -OptionDataParser::OptionDataParser(const std::string&, const CfgOptionPtr& cfg, - const uint16_t address_family) - : boolean_values_(new BooleanStorage()), - string_values_(new StringStorage()), uint32_values_(new Uint32Storage()), - option_descriptor_(false), cfg_(cfg), - address_family_(address_family) { - // If configuration not specified, then it is a global configuration - // scope. - if (!cfg_) { - cfg_ = CfgMgr::instance().getStagingCfg()->getCfgOption(); - } +OptionDataParser::OptionDataParser(const uint16_t address_family) + : address_family_(address_family) { } -void -OptionDataParser::build(ConstElementPtr option_data_entries) { - BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) { - ParserPtr parser; - if (param.first == "name" || param.first == "data" || - param.first == "space") { - StringParserPtr name_parser(new StringParser(param.first, - string_values_)); - parser = name_parser; - } else if (param.first == "code") { - Uint32ParserPtr code_parser(new Uint32Parser(param.first, - uint32_values_)); - parser = code_parser; - } else if (param.first == "csv-format") { - BooleanParserPtr value_parser(new BooleanParser(param.first, - boolean_values_)); - parser = value_parser; - } else { - isc_throw(DhcpConfigError, - "option-data parameter not supported: " << param.first - << " (" << param.second->getPosition() << ")"); - } - - parser->build(param.second); - // Before we can create an option we need to get the data from - // the child parsers. The only way to do it is to invoke commit - // on them so as they store the values in appropriate storages - // that this class provided to them. Note that this will not - // modify values stored in the global storages so the configuration - // will remain consistent even parsing fails somewhere further on. - parser->commit(); - } +std::pair +OptionDataParser::parse(isc::data::ConstElementPtr single_option) { // Try to create the option instance. - createOption(option_data_entries); + std::pair opt = createOption(single_option); - if (!option_descriptor_.option_) { + if (!opt.first.option_) { isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and" " thus there is nothing to commit. Has build() been called?"); } - cfg_->add(option_descriptor_.option_, option_descriptor_.persistent_, - option_space_); -} - -void -OptionDataParser::commit() { - // Does nothing + return (opt); } OptionalValue OptionDataParser::extractCode(ConstElementPtr parent) const { uint32_t code; try { - code = uint32_values_->getParam("code"); + code = getInteger(parent, "code"); } catch (const exception&) { // The code parameter was not found. Return an unspecified @@ -454,14 +409,14 @@ OptionDataParser::extractCode(ConstElementPtr parent) const { if (code == 0) { isc_throw(DhcpConfigError, "option code must not be zero " - "(" << uint32_values_->getPosition("code", parent) << ")"); + "(" << getPosition("code", parent) << ")"); } else if (address_family_ == AF_INET && code > std::numeric_limits::max()) { isc_throw(DhcpConfigError, "invalid option code '" << code << "', it must not be greater than '" << static_cast(std::numeric_limits::max()) - << "' (" << uint32_values_->getPosition("code", parent) + << "' (" << getPosition("code", parent) << ")"); } else if (address_family_ == AF_INET6 && @@ -469,7 +424,7 @@ OptionDataParser::extractCode(ConstElementPtr parent) const { isc_throw(DhcpConfigError, "invalid option code '" << code << "', it must not exceed '" << std::numeric_limits::max() - << "' (" << uint32_values_->getPosition("code", parent) + << "' (" << getPosition("code", parent) << ")"); } @@ -481,7 +436,7 @@ OptionalValue OptionDataParser::extractName(ConstElementPtr parent) const { std::string name; try { - name = string_values_->getParam("name"); + name = getString(parent, "name"); } catch (...) { return (OptionalValue()); @@ -490,17 +445,17 @@ OptionDataParser::extractName(ConstElementPtr parent) const { if (name.find(" ") != std::string::npos) { isc_throw(DhcpConfigError, "invalid option name '" << name << "', space character is not allowed (" - << string_values_->getPosition("name", parent) << ")"); + << getPosition("name", parent) << ")"); } return (OptionalValue(name, OptionalValueState(true))); } std::string -OptionDataParser::extractData() const { +OptionDataParser::extractData(ConstElementPtr parent) const { std::string data; try { - data = string_values_->getParam("data"); + data = getString(parent, "data"); } catch (...) { // The "data" parameter was not found. Return an empty value. @@ -511,10 +466,10 @@ OptionDataParser::extractData() const { } OptionalValue -OptionDataParser::extractCSVFormat() const { +OptionDataParser::extractCSVFormat(ConstElementPtr parent) const { bool csv_format = true; try { - csv_format = boolean_values_->getParam("csv-format"); + csv_format = getBoolean(parent, "csv-format"); } catch (...) { return (OptionalValue(csv_format)); @@ -524,10 +479,10 @@ OptionDataParser::extractCSVFormat() const { } std::string -OptionDataParser::extractSpace() const { +OptionDataParser::extractSpace(ConstElementPtr parent) const { std::string space = address_family_ == AF_INET ? "dhcp4" : "dhcp6"; try { - space = string_values_->getParam("space"); + space = getString(parent, "space"); } catch (...) { return (space); @@ -555,7 +510,7 @@ OptionDataParser::extractSpace() const { // should never get here. Therefore, it is ok to call getPosition for // the space parameter here as this parameter will always be specified. isc_throw(DhcpConfigError, ex.what() << " (" - << string_values_->getPosition("space") << ")"); + << getPosition("space", parent) << ")"); } return (space); @@ -593,16 +548,16 @@ OptionDataParser::findOptionDefinition(const std::string& option_space, return (def); } -void +std::pair OptionDataParser::createOption(ConstElementPtr option_data) { const Option::Universe universe = address_family_ == AF_INET ? Option::V4 : Option::V6; OptionalValue code_param = extractCode(option_data); OptionalValue name_param = extractName(option_data); - OptionalValue csv_format_param = extractCSVFormat(); - std::string data_param = extractData(); - std::string space_param = extractSpace(); + OptionalValue csv_format_param = extractCSVFormat(option_data); + std::string data_param = extractData(option_data); + std::string space_param = extractSpace(option_data); // Require that option code or option name is specified. if (!code_param.isSpecified() && !name_param.isSpecified()) { @@ -627,7 +582,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) { << space_param << "." << name_param << "' having code '" << code_param << "' does not exist (" - << string_values_->getPosition("name", option_data) + << getPosition("name", option_data) << ")"); // If there is no option definition and the option code is not specified @@ -636,7 +591,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) { isc_throw(DhcpConfigError, "definition for the option '" << space_param << "." << name_param << "' does not exist (" - << string_values_->getPosition("name", option_data) + << getPosition("name", option_data) << ")"); } } @@ -669,12 +624,14 @@ OptionDataParser::createOption(ConstElementPtr option_data) { isc_throw(DhcpConfigError, "option data is not a valid" << " string of hexadecimal digits: " << data_param << " (" - << string_values_->getPosition("data", option_data) + << getPosition("data", option_data) << ")"); } } OptionPtr option; + OptionDescriptor desc(false); + if (!def) { // @todo We have a limited set of option definitions initalized at // the moment. In the future we want to initialize option definitions @@ -683,13 +640,9 @@ OptionDataParser::createOption(ConstElementPtr option_data) { // ok to create generic option if definition does not exist. OptionPtr option(new Option(universe, static_cast(code_param), binary)); - // The created option is stored in option_descriptor_ class member - // until the commit stage when it is inserted into the main storage. - // If an option with the same code exists in main storage already the - // old option is replaced. - option_descriptor_.option_ = option; - option_descriptor_.persistent_ = false; + desc.option_ = option; + desc.persistent_ = false; } else { // Option name is specified it should match the name in the definition. @@ -698,7 +651,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) { << name_param << "' does not match the " << "option definition: '" << space_param << "." << def->getName() << "' (" - << string_values_->getPosition("name", option_data) + << getPosition("name", option_data) << ")"); } @@ -709,143 +662,65 @@ OptionDataParser::createOption(ConstElementPtr option_data) { !csv_format_param.isSpecified() || csv_format_param ? def->optionFactory(universe, def->getCode(), data_tokens) : def->optionFactory(universe, def->getCode(), binary); - OptionDescriptor desc(option, false); - option_descriptor_.option_ = option; - option_descriptor_.persistent_ = false; - + desc.option_ = option; + desc.persistent_ = false; } catch (const isc::Exception& ex) { isc_throw(DhcpConfigError, "option data does not match" << " option definition (space: " << space_param << ", code: " << def->getCode() << "): " << ex.what() << " (" - << string_values_->getPosition("data", option_data) + << getPosition("data", option_data) << ")"); } } // All went good, so we can set the option space name. - option_space_ = space_param; + return make_pair(desc, space_param); } // **************************** OptionDataListParser ************************* -OptionDataListParser::OptionDataListParser(const std::string&, - const CfgOptionPtr& cfg, +OptionDataListParser::OptionDataListParser(//const std::string&, + //const CfgOptionPtr& cfg, const uint16_t address_family) - : cfg_(cfg), address_family_(address_family) { + : address_family_(address_family) { } -void -OptionDataListParser::build(ConstElementPtr option_data_list) { - BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) { - boost::shared_ptr - parser(new OptionDataParser("option-data", cfg_, address_family_)); - - parser->build(option_value); - parsers_.push_back(parser); - } -} -void -OptionDataListParser::commit() { - BOOST_FOREACH(ParserPtr parser, parsers_) { - parser->commit(); - } - // Append suboptions to the top-level options - if (cfg_) { - cfg_->encapsulate(); - } else { - CfgMgr::instance().getStagingCfg()->getCfgOption()->encapsulate(); +void OptionDataListParser::parse(const CfgOptionPtr& cfg, + isc::data::ConstElementPtr option_data_list) { + OptionDataParser option_parser(address_family_); + BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) { + std::pair option = + option_parser.parse(data); + cfg->add(option.first.option_, option.first.persistent_, option.second); + cfg->encapsulate(); } } // ******************************** OptionDefParser **************************** -OptionDefParser::OptionDefParser(const std::string&, - ParserContextPtr global_context) - : boolean_values_(new BooleanStorage()), - string_values_(new StringStorage()), - uint32_values_(new Uint32Storage()), - global_context_(global_context) { +OptionDefParser::OptionDefParser(const uint16_t address_family) + : address_family_(address_family) { } -void -OptionDefParser::build(ConstElementPtr option_def) { - // Parse the elements that make up the option definition. - BOOST_FOREACH(ConfigPair param, option_def->mapValue()) { - std::string entry(param.first); - ParserPtr parser; - if (entry == "name" || entry == "type" || entry == "record-types" - || entry == "space" || entry == "encapsulate") { - StringParserPtr str_parser(new StringParser(entry, - string_values_)); - parser = str_parser; - } else if (entry == "code") { - Uint32ParserPtr code_parser(new Uint32Parser(entry, - uint32_values_)); - parser = code_parser; - } else if (entry == "array") { - BooleanParserPtr array_parser(new BooleanParser(entry, - boolean_values_)); - parser = array_parser; - } else { - isc_throw(DhcpConfigError, "invalid parameter '" << entry - << "' (" << param.second->getPosition() << ")"); - } +std::pair +OptionDefParser::parse(ConstElementPtr option_def) { - parser->build(param.second); - parser->commit(); - } - // Create an instance of option definition. - createOptionDef(option_def); + // Get mandatory parameters. + std::string name = getString(option_def, "name"); + uint32_t code = getInteger(option_def, "code"); + std::string type = getString(option_def, "type"); - try { - CfgMgr::instance().getStagingCfg()->getCfgOptionDef()-> - add(option_definition_, option_space_name_); - - } catch (const std::exception& ex) { - // Append position if there is a failure. - isc_throw(DhcpConfigError, ex.what() << " (" - << option_def->getPosition() << ")"); - } - - // All definitions have been prepared. Put them as runtime options into - // the libdhcp++. - const OptionDefSpaceContainer& container = - CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->getContainer(); - LibDHCP::setRuntimeOptionDefs(container); -} - -void -OptionDefParser::commit() { - // Do nothing. -} - -void -OptionDefParser::createOptionDef(ConstElementPtr option_def_element) { - // Check if mandatory parameters have been specified. - std::string name; - uint32_t code; - std::string type; - try { - name = string_values_->getParam("name"); - code = uint32_values_->getParam("code"); - type = string_values_->getParam("type"); - } catch (const std::exception& ex) { - isc_throw(DhcpConfigError, ex.what() << " (" - << option_def_element->getPosition() << ")"); - } - - bool array_type = boolean_values_->getOptionalParam("array", false); - std::string record_types = - string_values_->getOptionalParam("record-types", ""); - std::string space = string_values_->getOptionalParam("space", - global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6"); - std::string encapsulates = - string_values_->getOptionalParam("encapsulate", ""); + // Get optional parameters. Whoever called this parser, should have + // called SimpleParser::setDefaults first. + bool array_type = getBoolean(option_def, "array"); + std::string record_types = getString(option_def, "record-types"); + std::string space = getString(option_def, "space"); + std::string encapsulates = getString(option_def, "encapsulate"); if (!OptionSpace::validateName(space)) { isc_throw(DhcpConfigError, "invalid option space name '" << space << "' (" - << string_values_->getPosition("space") << ")"); + << getPosition("space", option_def) << ")"); } // Create option definition. @@ -858,14 +733,14 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) { isc_throw(DhcpConfigError, "option '" << space << "." << "name" << "', comprising an array of data" << " fields may not encapsulate any option space (" - << option_def_element->getPosition() << ")"); + << option_def->getPosition() << ")"); } else if (encapsulates == space) { isc_throw(DhcpConfigError, "option must not encapsulate" << " an option space it belongs to: '" << space << "." << name << "' is set to" << " encapsulate '" << space << "' (" - << option_def_element->getPosition() << ")"); + << option_def->getPosition() << ")"); } else { def.reset(new OptionDefinition(name, code, type, @@ -892,7 +767,7 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) { isc_throw(DhcpConfigError, "invalid record type values" << " specified for the option definition: " << ex.what() << " (" - << string_values_->getPosition("record-types") << ")"); + << getPosition("record-types", option_def) << ")"); } } @@ -901,38 +776,43 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) { def->validate(); } catch (const std::exception& ex) { isc_throw(DhcpConfigError, ex.what() - << " (" << option_def_element->getPosition() << ")"); + << " (" << option_def->getPosition() << ")"); } // Option definition has been created successfully. - option_space_name_ = space; - option_definition_ = def; + return make_pair(def, space); } // ******************************** OptionDefListParser ************************ -OptionDefListParser::OptionDefListParser(const std::string&, - ParserContextPtr global_context) - : global_context_(global_context) { +OptionDefListParser::OptionDefListParser(const uint16_t address_family) + : address_family_(address_family) { } void -OptionDefListParser::build(ConstElementPtr option_def_list) { +OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_list) { if (!option_def_list) { isc_throw(DhcpConfigError, "parser error: a pointer to a list of" << " option definitions is NULL (" << option_def_list->getPosition() << ")"); } + OptionDefParser parser(address_family_); BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) { - boost::shared_ptr - parser(new OptionDefParser("single-option-def", global_context_)); - parser->build(option_def); + OptionDefinitionTuple def; + + def = parser.parse(option_def); + try { + storage->add(def.first, def.second); + } catch (const std::exception& ex) { + // Append position if there is a failure. + isc_throw(DhcpConfigError, ex.what() << " (" + << option_def->getPosition() << ")"); + } } -} -void -OptionDefListParser::commit() { - // Do nothing. + // All definitions have been prepared. Put them as runtime options into + // the libdhcp++. + LibDHCP::setRuntimeOptionDefs(storage->getContainer()); } //****************************** RelayInfoParser ******************************** @@ -1132,13 +1012,9 @@ PoolParser::build(ConstElementPtr pool_structure) { " address pools"); } - OptionDataListParserPtr option_parser(new OptionDataListParser("option-data", - options_, - address_family_)); - option_parser->build(option_data); - option_parser->commit(); - options_->copyTo(*pool->getCfgOption());; - + CfgOptionPtr cfg = pool->getCfgOption(); + OptionDataListParser option_parser(address_family_); + option_parser.parse(cfg, option_data); } catch (const std::exception& ex) { isc_throw(isc::dhcp::DhcpConfigError, ex.what() << " (" << option_data->getPosition() << ")"); @@ -1188,6 +1064,13 @@ SubnetConfigParser::build(ConstElementPtr subnet) { continue; } + if (param.first == "option-data") { + uint16_t family = global_context_->universe_ == Option::V4 ? AF_INET : AF_INET6; + OptionDataListParser opt_parser(family); + opt_parser.parse(options_, param.second); + continue; + } + ParserPtr parser; // When unsupported parameter is specified, the function called // below will thrown an exception. We have to catch this exception diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 67f62074c1..f5cbea23b0 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -534,39 +536,30 @@ private: /// an option the configuration will not be accepted. If parsing /// is successful then an instance of an option is created and /// added to the storage provided by the calling class. -class OptionDataParser : public DhcpConfigParser { +class OptionDataParser : public SimpleParser { public: /// @brief Constructor. /// - /// @param dummy first argument is ignored, all Parser constructors - /// accept string as first argument. - /// @param [out] cfg Pointer to the configuration object where parsed option - /// should be stored or NULL if this is a global option. /// @param address_family Address family: @c AF_INET or @c AF_INET6. - /// @throw isc::dhcp::DhcpConfigError if options or global_context are null. - OptionDataParser(const std::string& dummy, const CfgOptionPtr& cfg, - const uint16_t address_family); + OptionDataParser(const uint16_t address_family); - /// @brief Parses the single option data. + /// @brief Parses ElementPtr containing option definition /// - /// This method parses the data of a single option from the configuration. - /// The option data includes option name, option code and data being - /// carried by this option. Eventually it creates the instance of the - /// option and adds it to the Configuration Manager. + /// This method parses ElementPtr containing the option defintion, + /// instantiates the option for it and then returns a pair + /// of option descritor (that holds that new option) and + /// a string that specifies the option space. /// - /// @param option_data_entries collection of entries that define value - /// for a particular option. - /// @throw DhcpConfigError if invalid parameter specified in - /// the configuration. - /// @throw isc::InvalidOperation if failed to set storage prior to - /// calling build. - virtual void build(isc::data::ConstElementPtr option_data_entries); - - /// @brief Does nothing. - virtual void commit(); - - /// @brief virtual destructor to ensure orderly destruction of derivations. - virtual ~OptionDataParser(){}; + /// Note: ElementPtr is expected to contain all fields. If your + /// ElementPtr does not have them, please use + /// @ref SimpleParser::setOptionDefaults to fill the missing fields + /// with default values. + /// + /// @param single_option ElementPtr containing option defintion + /// @return Option object wrapped in option description and an option + /// space + std::pair + parse(isc::data::ConstElementPtr single_option); private: /// @brief Finds an option definition within an option space @@ -594,22 +587,19 @@ private: /// options storage. If the option data parsed by \ref build function /// are invalid or insufficient this function emits an exception. /// - /// @warning this function does not check if options_ storage pointer - /// is intitialized but this check is not needed here because it is done - /// in the \ref build function. - /// /// @param option_data An element holding data for a single option being /// created. /// + /// @return created option descriptor + /// /// @throw DhcpConfigError if parameters provided in the configuration /// are invalid. - void createOption(isc::data::ConstElementPtr option_data); + std::pair + createOption(isc::data::ConstElementPtr option_data); /// @brief Retrieves parsed option code as an optional value. /// /// @param parent A data element holding full option data configuration. - /// It is used here to log a position if the element holding a code - /// is not specified and its position is therefore unavailable. /// /// @return Option code, possibly unspecified. /// @throw DhcpConfigError if option code is invalid. @@ -619,8 +609,6 @@ private: /// @brief Retrieves parsed option name as an optional value. /// /// @param parent A data element holding full option data configuration. - /// It is used here to log a position if the element holding a name - /// is not specified and its position is therefore unavailable. /// /// @return Option name, possibly unspecified. /// @throw DhcpConfigError if option name is invalid. @@ -630,13 +618,14 @@ private: /// @brief Retrieves csv-format parameter as an optional value. /// /// @return Value of the csv-format parameter, possibly unspecified. - util::OptionalValue extractCSVFormat() const; + util::OptionalValue extractCSVFormat(data::ConstElementPtr parent) const; /// @brief Retrieves option data as a string. /// + /// @param parent A data element holding full option data configuration. /// @return Option data as a string. It will return empty string if /// option data is unspecified. - std::string extractData() const; + std::string extractData(data::ConstElementPtr parent) const; /// @brief Retrieves option space name. /// @@ -644,27 +633,10 @@ private: /// 'dhcp4' or 'dhcp6' option space name is returned, depending on /// the universe specified in the parser context. /// + /// @param parent A data element holding full option data configuration. + /// /// @return Option space name. - std::string extractSpace() const; - - /// Storage for boolean values. - BooleanStoragePtr boolean_values_; - - /// Storage for string values (e.g. option name or data). - StringStoragePtr string_values_; - - /// Storage for uint32 values (e.g. option code). - Uint32StoragePtr uint32_values_; - - /// Option descriptor holds newly configured option. - OptionDescriptor option_descriptor_; - - /// Option space name where the option belongs to. - std::string option_space_; - - /// @brief Configuration holding option being parsed or NULL if the option - /// is global. - CfgOptionPtr cfg_; + std::string extractSpace(data::ConstElementPtr parent) const; /// @brief Address family: @c AF_INET or @c AF_INET6. uint16_t address_family_; @@ -680,97 +652,52 @@ typedef OptionDataParser *OptionDataParserFactory(const std::string&, /// data for a particular subnet and creates a collection of options. /// If parsing is successful, all these options are added to the Subnet /// object. -class OptionDataListParser : public DhcpConfigParser { +class OptionDataListParser : public SimpleParser { public: /// @brief Constructor. /// - /// @param dummy nominally would be param name, this is always ignored. - /// @param [out] cfg Pointer to the configuration object where options - /// should be stored or NULL if this is global option scope. /// @param address_family Address family: @c AF_INET or AF_INET6 - OptionDataListParser(const std::string& dummy, const CfgOptionPtr& cfg, - const uint16_t address_family); + OptionDataListParser(const uint16_t address_family); - /// @brief Parses entries that define options' data for a subnet. + /// @brief Parses a list of options, instantiates them and stores in cfg /// - /// This method iterates over all entries that define option data - /// for options within a single subnet and creates options' instances. - /// - /// @param option_data_list pointer to a list of options' data sets. - /// @throw DhcpConfigError if option parsing failed. - void build(isc::data::ConstElementPtr option_data_list); - - /// @brief Commit all option values. + /// This method expects to get a list of options in option_data_list, + /// iterates over them, creates option objects, wraps them with + /// option descriptor and stores in specified cfg. /// - /// This function invokes commit for all option values - /// and append suboptions to the top-level options. - void commit(); - + /// @param cfg created options will be stored here + /// @param option_data_list configuration that describes the options + void parse(const CfgOptionPtr& cfg, + isc::data::ConstElementPtr option_data_list); private: - - /// Collection of parsers; - ParserCollection parsers_; - - /// @brief Pointer to a configuration where options are stored. - CfgOptionPtr cfg_; - /// @brief Address family: @c AF_INET or @c AF_INET6 uint16_t address_family_; - }; -typedef boost::shared_ptr OptionDataListParserPtr; - +typedef std::pair OptionDefinitionTuple; /// @brief Parser for a single option definition. /// /// This parser creates an instance of a single option definition. -class OptionDefParser : public DhcpConfigParser { +class OptionDefParser : public SimpleParser { public: /// @brief Constructor. /// - /// @param dummy first argument is ignored, all Parser constructors - /// accept string as first argument. - /// @param global_context is a pointer to the global context which - /// stores global scope parameters, options, option defintions. - OptionDefParser(const std::string& dummy, ParserContextPtr global_context); + /// @param address_family Address family: @c AF_INET or AF_INET6 + OptionDefParser(const uint16_t address_family); /// @brief Parses an entry that describes single option definition. /// /// @param option_def a configuration entry to be parsed. + /// @return tuple (option definition, option space) of the parsed structure /// /// @throw DhcpConfigError if parsing was unsuccessful. - void build(isc::data::ConstElementPtr option_def); - - /// @brief Stores the parsed option definition in a storage. - void commit(); + OptionDefinitionTuple + parse(isc::data::ConstElementPtr option_def); private: - - /// @brief Create option definition from the parsed parameters. - /// - /// @param option_def_element A data element holding the configuration - /// for an option definition. - void createOptionDef(isc::data::ConstElementPtr option_def_element); - - /// Instance of option definition being created by this parser. - OptionDefinitionPtr option_definition_; - - /// Name of the space the option definition belongs to. - std::string option_space_name_; - - /// Storage for boolean values. - BooleanStoragePtr boolean_values_; - - /// Storage for string values. - StringStoragePtr string_values_; - - /// Storage for uint32 values. - Uint32StoragePtr uint32_values_; - - /// Parsing context which contains global values, options and option - /// definitions. - ParserContextPtr global_context_; + /// @brief Address family: @c AF_INET or @c AF_INET6 + uint16_t address_family_; }; /// @brief Parser for a list of option definitions. @@ -779,7 +706,7 @@ private: /// option definitions and creates instances of these definitions. /// If the parsing is successful, the collection of created definitions /// is put into the provided storage. -class OptionDefListParser : public DhcpConfigParser { +class OptionDefListParser : public SimpleParser { public: /// @brief Constructor. /// @@ -787,29 +714,24 @@ public: /// accept string as first argument. /// @param global_context is a pointer to the global context which /// stores global scope parameters, options, option defintions. - OptionDefListParser(const std::string& dummy, - ParserContextPtr global_context); + OptionDefListParser(const uint16_t address_family); + //const std::string& dummy, + //ParserContextPtr global_context); - /// @brief Parse configuration entries. + /// @brief Parses a list of option defintions, create them and store in cfg /// - /// This function parses configuration entries, creates instances - /// of option definitions and tries to add them to the Configuration - /// Manager. + /// This method iterates over def_list, which is a JSON list of option defintions, + /// then creates corresponding option defintions and store them in the + /// configuration structure. /// - /// @param option_def_list pointer to an element that holds entries - /// that define option definitions. - /// @throw DhcpConfigError if configuration parsing fails. - void build(isc::data::ConstElementPtr option_def_list); + /// @param def_list JSON list describing option definitions + /// @param cfg parsed option definitions will be stored here + void parse(CfgOptionDefPtr cfg, isc::data::ConstElementPtr def_list); - /// @brief Commits option definitions. - /// - /// Currently this function is no-op, because option definitions are - /// added to the Configuration Manager in the @c build method. - void commit(); +protected: - /// Parsing context which contains global values, options and option - /// definitions. - ParserContextPtr global_context_; + /// @brief Address family: @c AF_INET or @c AF_INET6 + uint16_t address_family_; }; /// @brief a collection of pools diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc index 7a4055ee1c..49c046edb3 100644 --- a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc +++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc @@ -196,8 +196,12 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) { // surround it with try-catch. if (element.first == "option-data") { CfgOptionPtr cfg_option = host_->getCfgOption4(); - OptionDataListParser parser(element.first, cfg_option, AF_INET); - parser.build(element.second); + + // This parser is converted to SimpleParser already. It + // parses the Element structure immediately, there's no need + // to go through build/commit phases. + OptionDataListParser parser(AF_INET); + parser.parse(cfg_option, element.second); // Everything else should be surrounded with try-catch to append // position. @@ -255,8 +259,12 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) { // appended). if (element.first == "option-data") { CfgOptionPtr cfg_option = host_->getCfgOption6(); - OptionDataListParser parser(element.first, cfg_option, AF_INET6); - parser.build(element.second); + + // This parser is converted to SimpleParser already. It + // parses the Element structure immediately, there's no need + // to go through build/commit phases. + OptionDataListParser parser(AF_INET6); + parser.parse(cfg_option, element.second); } else if (element.first == "ip-addresses" || element.first == "prefixes") { BOOST_FOREACH(ConstElementPtr prefix_element, diff --git a/src/lib/dhcpsrv/parsers/simple_parser.cc b/src/lib/dhcpsrv/parsers/simple_parser.cc new file mode 100644 index 0000000000..9ae9d3d942 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser.cc @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc::data; + +namespace isc { +namespace dhcp { + +/// This table defines default values for option definitions in DHCPv4 +const SimpleDefaults OPTION4_DEF_DEFAULTS{ + { "record-types", Element::string, ""}, + { "space", Element::string, "dhcp4"}, + { "array", Element::boolean, "false"}, + { "encapsulate", Element::string, "" } +}; + +/// This table defines default values for option definitions in DHCPv6 +const SimpleDefaults OPTION6_DEF_DEFAULTS{ + { "record-types", Element::string, ""}, + { "space", Element::string, "dhcp6"}, + { "array", Element::boolean, "false"}, + { "encapsulate", Element::string, "" } +}; + +/// This table defines default values for options in DHCPv4 +const SimpleDefaults OPTION4_DEFAULTS({ + { "space", Element::string, "dhcp4"}, + { "csv-format", Element::boolean, "true"}, + { "encapsulate", Element::string, "" } +}); + +/// This table defines default values for options in DHCPv6 +const SimpleDefaults OPTION6_DEFAULTS({ + { "space", Element::string, "dhcp6"}, + { "csv-format", Element::boolean, "true"}, + { "encapsulate", Element::string, "" } +}); + +/// This table defines default values for both DHCPv4 and DHCPv6 +const SimpleDefaults GLOBAL_DEFAULTS = { + { "renew-timer", Element::integer, "900" }, + { "rebind-timer", Element::integer, "1800" }, + { "preferred-lifetime", Element::integer, "3600" }, + { "valid-lifetime", Element::integer, "7200" } +}; + +std::string +SimpleParser::getString(isc::data::ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(BadValue, "Element " << name << " not found"); + } + if (x->getType() != Element::string) { + isc_throw(BadValue, "Element " << name << " found, but is not a string"); + } + + return (x->stringValue()); +} + +int64_t +SimpleParser::getInteger(isc::data::ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(BadValue, "Element " << name << " not found"); + } + if (x->getType() != Element::integer) { + isc_throw(BadValue, "Element " << name << " found, but is not an integer"); + } + + return (x->intValue()); +} + +bool +SimpleParser::getBoolean(isc::data::ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(BadValue, "Element " << name << " not found"); + } + if (x->getType() != Element::boolean) { + isc_throw(BadValue, "Element " << name << " found, but is not a boolean"); + } + + return (x->boolValue()); +} + +const data::Element::Position& +SimpleParser::getPosition(const std::string& name, const data::ConstElementPtr parent) const { + if (!parent) { + return (data::Element::ZERO_POSITION()); + } + ConstElementPtr elem = parent->get(name); + if (!elem) { + return (data::Element::ZERO_POSITION()); + } + return (elem->getPosition()); +} + +size_t SimpleParser::setDefaults(isc::data::ElementPtr scope, + const SimpleDefaults& default_values) { + size_t cnt = 0; + + // This is the position representing a default value. As the values + // we're inserting here are not present in whatever the config file + // came from, we need to make sure it's clearly labeled as default. + const Element::Position pos("", 0, 0); + + // Let's go over all parameters we have defaults for. + BOOST_FOREACH(SimpleDefault def_value, default_values) { + + // Try if such a parameter is there. If it is, let's + // skip it, because user knows best *cough*. + ConstElementPtr x = scope->get(string(def_value.name_)); + if (x) { + // There is such a value already, skip it. + continue; + } + + // There isn't such a value defined, let's create the default + // value... + switch (def_value.type_) { + case Element::string: { + x.reset(new StringElement(def_value.value_, pos)); + break; + } + case Element::integer: { + int int_value = boost::lexical_cast(def_value.value_); + x.reset(new IntElement(int_value, pos)); + break; + } + case Element::boolean: { + bool bool_value; + if (def_value.value_ == string("true")) { + bool_value = true; + } else if (def_value.value_ == string("false")) { + bool_value = false; + } else { + isc_throw(BadValue, "Internal error. Boolean value specified as " + << def_value.value_ << ", expected true or false"); + } + x.reset(new BoolElement(bool_value, pos)); + break; + } + case Element::real: { + double dbl_value = boost::lexical_cast(def_value.value_); + x.reset(new DoubleElement(dbl_value, pos)); + break; + } + default: + // No default values for null, list or map + isc_throw(BadValue, "Internal error. Incorrect default value type."); + } + + // ... and insert it into the provided Element tree. + scope->set(def_value.name_, x); + cnt++; + } + + return (cnt); +} + +size_t SimpleParser::setGlobalDefaults(isc::data::ElementPtr global) { + return (setDefaults(global, GLOBAL_DEFAULTS)); +} + +size_t SimpleParser::setOptionDefaults(isc::data::ElementPtr option, bool v6) { + return (setDefaults(option, v6?OPTION6_DEFAULTS : OPTION4_DEFAULTS)); +} + +size_t SimpleParser::setOptionListDefaults(isc::data::ElementPtr option_list, bool v6) { + size_t cnt = 0; + BOOST_FOREACH(ElementPtr single_option, option_list->listValue()) { + cnt += setOptionDefaults(single_option, v6); + } + return (cnt); +} + +size_t SimpleParser::setOptionDefDefaults(isc::data::ElementPtr option_def, bool v6) { + return (setDefaults(option_def, v6? OPTION6_DEF_DEFAULTS : OPTION4_DEF_DEFAULTS)); +} + +size_t SimpleParser::setOptionDefListDefaults(isc::data::ElementPtr option_def_list, + bool v6) { + size_t cnt = 0; + BOOST_FOREACH(ElementPtr single_def, option_def_list->listValue()) { + cnt += setOptionDefDefaults(single_def, v6); + } + return (cnt); +} + + +size_t SimpleParser::setAllDefaults(isc::data::ElementPtr global, bool v6) { + size_t cnt = 0; + + // Set global defaults first. + //cnt = setGlobalDefaults(global); + + // Now set option defintion defaults for each specified option definition + ConstElementPtr option_defs = global->get("option-def"); + if (option_defs) { + BOOST_FOREACH(ElementPtr single_def, option_defs->listValue()) { + cnt += setOptionDefDefaults(single_def, v6); + } + } + + ConstElementPtr options = global->get("option-data"); + if (options) { + BOOST_FOREACH(ElementPtr single_option, options->listValue()) { + cnt += setOptionDefaults(single_option, v6); + } + //setOptionListDefaults(options); + } + + return (cnt); +} + +size_t +SimpleParser::deriveParams(isc::data::ConstElementPtr parent, + isc::data::ElementPtr child, + const ParamsList& params) { + if ( (parent->getType() != Element::map) || + (child->getType() != Element::map)) { + return (0); + } + + size_t cnt = 0; + BOOST_FOREACH(string param, params) { + ConstElementPtr x = parent->get(param); + if (!x) { + // Parent doesn't define this parameter, so there's + // nothing to derive from + continue; + } + + if (child->get(param)) { + // Child defines this parameter already. There's + // nothing to do here. + continue; + } + + // Copy the parameters to the child scope. + child->set(param, x); + cnt++; + } + + return (cnt); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcpsrv/parsers/simple_parser.h b/src/lib/dhcpsrv/parsers/simple_parser.h new file mode 100644 index 0000000000..14fc804d52 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser.h @@ -0,0 +1,189 @@ +#ifndef SIMPLE_PARSER_H +#define SIMPLE_PARSER_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// This array defines a single entry of default values +struct SimpleDefault { + SimpleDefault(const char* name, isc::data::Element::types type, const char* value) + :name_(name), type_(type), value_(value) {} + std::string name_; + const isc::data::Element::types type_; + const char* value_; +}; + +/// This specifies all default values in a given scope (e.g. a subnet) +typedef std::vector SimpleDefaults; + +/// This defines a list of all parameters that are derived (or inherited) between +/// contexts +typedef std::vector ParamsList; + + +/// @brief A simple parser +/// +/// This class is intended to be a simpler replacement for @ref DhcpConfigParser. +/// The simplification comes from several factors: +/// - no build/commit nonsense. There's a single step: +/// CfgStorage parse(ConstElementPtr json) +/// that converts JSON configuration into an object and returns it. +/// - almost no state kept. The only state kept in most cases is whether the +/// parsing is done in v4 or v6 context. This greatly simplifies the +/// parsers (no contexts, no child parsers list, no separate storage for +/// uint32, strings etc. In fact, there's so little state kept, that this +/// parser is mostly a collection of static methods. +/// - no optional parameters (all are mandatory). This simplifies the parser, +/// but introduces a new step before parsing where we insert the default +/// values into client configuration before parsing. This is actually a good +/// thing, because we now have a clear picture of the default parameters as +/// they're defined in a single place (the DhcpConfigParser had the defaults +/// spread out in multiple files in multiple directories). +class SimpleParser { + public: + + /// @brief Derives (inherits) parameters from parent scope to a child + /// + /// This method derives parameters from the parent scope to the child, + /// if there are no values specified in the child scope. For example, + /// this method can be used to derive timers from global scope (e.g. for + /// the whole DHCPv6 server) to a subnet scope. This method checks + /// if the child scope doesn't have more specific values defined. If + /// it doesn't, then the value from parent scope is copied over. + /// + /// @param parent scope to copy from (e.g. global) + /// @param child scope to copy from (e.g. subnet) + /// @param params names of the parameters to copy + /// @return number of parameters copied + static size_t deriveParams(isc::data::ConstElementPtr parent, + isc::data::ElementPtr child, + const ParamsList& params); + + /// @brief Sets the default values + /// + /// This method sets the default values for parameters that are not + /// defined. The list of default values is specified by default_values. + /// If not present, those will be inserted into the scope. If + /// a parameter is already present, the default value will not + /// be inserted. + /// + /// @param scope default values will be inserted here + /// @param default_values list of default values + /// @return number of parameters inserted + static size_t setDefaults(isc::data::ElementPtr scope, + const SimpleDefaults& default_values); + + /// @brief Sets global defaults + /// + /// This method sets global defaults. Note it does not set the + /// defaults for any scopes lower than global. See @ref setAllDefaults + /// for that. + /// + /// @param global scope to be filled in with defaults. + /// @return number of default values added + static size_t setGlobalDefaults(isc::data::ElementPtr global); + + /// @brief Sets option defaults for a single option + /// + /// This method sets default values for a single option. + /// + /// @param option an option data to be filled in with defaults. + /// @param v6 is it v6 (true) or v4 (false) option? + /// @return number of default values added + static size_t setOptionDefaults(isc::data::ElementPtr option, bool v6); + + /// @brief Sets option defaults for the whole options list + /// + /// This method sets default values for a list of options. + /// + /// @param option_list an option data to be filled in with defaults. + /// @param v6 is it v6 (true) or v4 (false) option? + /// @return number of default values added + static size_t setOptionListDefaults(isc::data::ElementPtr option_list, bool v6); + + /// @brief Sets option defaults for a single option definition + /// + /// This method sets default values for a single option definition. + /// + /// @param option_def an option defintion to be filled in with defaults. + /// @param v6 is it v6 (true) or v4 (false) option defintion? + /// @return number of default values added + static size_t setOptionDefDefaults(isc::data::ElementPtr option_def, bool v6); + + /// @brief Sets option defaults for the whole option definitions list + /// + /// This method sets default values for a list of option definitions. + /// + /// @param option_def_list a list of option defintions to be filled in with defaults. + /// @param v6 is it v6 (true) or v4 (false) option? + /// @return number of default values added + static size_t setOptionDefListDefaults(isc::data::ElementPtr option_def_list, + bool v6); + + /// @brief Sets defaults for the whole configuration + /// + /// This method sets global, options and option definition defaults. Note + /// it does set the defaults for scopes lower than global. If you want to + /// set the global defaults only, see @ref setGlobalDefaults for that. + /// + /// @param global scope to be filled in with defaults. + /// @return number of default values added + static size_t setAllDefaults(isc::data::ElementPtr global, bool v6); + + /// @brief Utility method that returns position of an element + /// + /// It's mostly useful for logging. + /// + /// @param name position of that element will be returned + /// @param parent parent element (optional) + /// @return position of the element specified. + const data::Element::Position& + getPosition(const std::string& name, const data::ConstElementPtr parent = + data::ConstElementPtr()) const; + + /// @destructor + /// + /// It's really simple, isn't it? + virtual ~SimpleParser() {} + + protected: + + /// @brief Returns a string parameter from a scope + /// + /// Unconditionally returns a parameter. If the parameter is not there or + /// is not of appropriate type, BadValue exception is thrown. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a string value of the parameter + static std::string getString(isc::data::ConstElementPtr scope, const std::string& name); + + /// @brief Returns an integer parameter from a scope + /// + /// Unconditionally returns a parameter. If the parameter is not there or + /// is not of appropriate type, BadValue exception is thrown. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return an integer value of the parameter + static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string& name); + + /// @brief Returns a boolean parameter from a scope + /// + /// Unconditionally returns a parameter. If the parameter is not there or + /// is not of appropriate type, BadValue exception is thrown. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a boolean value of the parameter + static bool getBoolean(isc::data::ConstElementPtr scope, const std::string& name); +}; + +}; +}; + +#endif diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index 6cff215133..452f4df038 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -291,7 +292,7 @@ public: /// @return returns an ConstElementPtr containing the numeric result /// code and outcome comment. isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr - config_set) { + config_set) { // Answer will hold the result. ConstElementPtr answer; if (!config_set) { @@ -309,6 +310,16 @@ public: const std::map& values_map = config_set->mapValue(); BOOST_FOREACH(config_pair, values_map) { + + // These are the simple parsers. No need to go through + // the ParserPtr hooplas with them. + if ( (config_pair.first == "option-data") || + (config_pair.first == "option-def")) { + continue; + } + // Remaining ones are old style parsers. Need go do + // the build/commit dance with them. + // Create the parser based on element name. ParserPtr parser(createConfigParser(config_pair.first)); // Options must be parsed last @@ -321,12 +332,27 @@ public: } } + int family = parser_context_->universe_ == Option::V4 + ? AF_INET : AF_INET6; + + // The option definition parser is the next one to be run. + std::map::const_iterator + def_config = values_map.find("option-def"); + if (def_config != values_map.end()) { + + CfgOptionDefPtr cfg_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef(); + OptionDefListParser def_list_parser(family); + def_list_parser.parse(cfg_def, def_config->second); + } + // The option values parser is the next one to be run. std::map::const_iterator option_config = values_map.find("option-data"); if (option_config != values_map.end()) { - option_parser->build(option_config->second); - option_parser->commit(); + CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption(); + + OptionDataListParser option_list_parser(family); + option_list_parser.parse(cfg_option, option_config->second); } // Everything was fine. Configuration is successful. @@ -358,17 +384,9 @@ public: /// @throw throws NotImplemented if element name isn't supported. ParserPtr createConfigParser(const std::string& config_id) { ParserPtr parser; - int family = parser_context_->universe_ == Option::V4 ? - AF_INET : AF_INET6; - if (config_id.compare("option-data") == 0) { - parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(), - family)); - - } else if (config_id.compare("option-def") == 0) { - parser.reset(new OptionDefListParser(config_id, - parser_context_)); - - } else if (config_id.compare("hooks-libraries") == 0) { + // option-data and option-def converted to SimpleParser, so they + // are no longer here. + if (config_id.compare("hooks-libraries") == 0) { parser.reset(new HooksLibrariesParser(config_id)); hooks_libraries_parser_ = boost::dynamic_pointer_cast(parser); @@ -391,13 +409,15 @@ public: /// /// @return retuns 0 if the configuration parsed successfully, /// non-zero otherwise failure. - int parseConfiguration(const std::string& config) { + int parseConfiguration(const std::string& config, bool v6 = false) { int rcode_ = 1; // Turn config into elements. // Test json just to make sure its valid. ElementPtr json = Element::fromJSON(config); EXPECT_TRUE(json); if (json) { + SimpleParser::setAllDefaults(json, v6); + ConstElementPtr status = parseElementSet(json); ConstElementPtr comment = parseAnswer(rcode_, status); error_text_ = comment->stringValue(); @@ -565,7 +585,7 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) { "}"; // Verify that the configuration string parses. - int rcode = parseConfiguration(config); + int rcode = parseConfiguration(config, true); ASSERT_EQ(0, rcode); @@ -783,7 +803,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { " } ]" "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_NE(0, rcode); CfgMgr::instance().clear(); @@ -799,7 +819,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { " \"data\": \"0\"" " } ]" "}"; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_NE(0, rcode); CfgMgr::instance().clear(); @@ -814,7 +834,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { " \"data\": \"0\"" " } ]" "}"; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); ASSERT_EQ(0, rcode); OptionPtr opt = getOptionPtr("dhcp6", 25000); ASSERT_TRUE(opt); @@ -830,10 +850,11 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { " \"name\": \"foo-name\"," " \"space\": \"dhcp6\"," " \"code\": 25000," + " \"csv-format\": false," " \"data\": \"123456\"" " } ]" "}"; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); opt = getOptionPtr("dhcp6", 25000); ASSERT_TRUE(opt); @@ -854,7 +875,7 @@ TEST_F(ParseConfigTest, optionDataNoName) { " } ]" "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); Option6AddrLstPtr opt = boost::dynamic_pointer_cast< Option6AddrLst>(getOptionPtr("dhcp6", 23)); @@ -874,7 +895,7 @@ TEST_F(ParseConfigTest, optionDataNoCode) { " } ]" "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); Option6AddrLstPtr opt = boost::dynamic_pointer_cast< Option6AddrLst>(getOptionPtr("dhcp6", 23)); @@ -893,7 +914,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) { " } ]" "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); Option6AddrLstPtr opt = boost::dynamic_pointer_cast< Option6AddrLst>(getOptionPtr("dhcp6", 23)); @@ -910,7 +931,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) { " } ]" "}"; rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); opt = boost::dynamic_pointer_cast(getOptionPtr("dhcp6", 23)); @@ -939,7 +960,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) { "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); Option6AddrLstPtr opt = boost::dynamic_pointer_cast< Option6AddrLst>(getOptionPtr("dhcp6", 2345)); @@ -965,7 +986,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) { "}"; rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); opt = boost::dynamic_pointer_cast(getOptionPtr("dhcp6", 2345)); @@ -986,7 +1007,7 @@ TEST_F(ParseConfigTest, emptyOptionData) { "}"; int rcode = 0; - ASSERT_NO_THROW(rcode = parseConfiguration(config)); + ASSERT_NO_THROW(rcode = parseConfiguration(config, true)); EXPECT_EQ(0, rcode); const Option6AddrLstPtr opt = boost::dynamic_pointer_cast< Option6AddrLst>(getOptionPtr("dhcp6", D6O_DHCPV4_O_DHCPV6_SERVER));