diff --git a/doc/examples/kea4/all-keys.json b/doc/examples/kea4/all-keys.json index 4289c8d958..098b9a3906 100644 --- a/doc/examples/kea4/all-keys.json +++ b/doc/examples/kea4/all-keys.json @@ -523,6 +523,22 @@ // When the maximum count is 0 the maximum age (in seconds) applies. "statistic-default-sample-age": 60, + // By default Kea processes packets on a single thread (default 'false' + // value for this option). To enable multi-threading, this option can be + // set ('true' value). + "enable-multi-threading": false, + + // When multi-threading is enabled, Kea will process packets on a number of + // multiple threads configurable through this option. The value must be a + // positive integer (0 means auto detect). + "packet-thread-pool-size": 0, + + // When multi-threading is enabled, Kea will read packets from the interface + // and append a working item to the thread pool. This option configures the + // maximum number of items that can be queued for each processing thread. + // The value must be a positive integer (0 means unlimited). + "packet-thread-queue-size": 0, + // Governs how the Kea DHCPv4 server should deal with the invalid // data received from the client. "sanity-checks": { diff --git a/doc/examples/kea6/all-keys.json b/doc/examples/kea6/all-keys.json index 34a8ab0675..7b87fbeac1 100644 --- a/doc/examples/kea6/all-keys.json +++ b/doc/examples/kea6/all-keys.json @@ -464,6 +464,22 @@ // When the maximum count is 0 the maximum age (in seconds) applies. "statistic-default-sample-age": 60, + // By default Kea processes packets on a single thread (default 'false' + // value for this option). To enable multi-threading, this option can be + // set ('true' value). + "enable-multi-threading": false, + + // When multi-threading is enabled, Kea will process packets on a number of + // multiple threads configurable through this option. The value must be a + // positive integer (0 means auto detect). + "packet-thread-pool-size": 0, + + // When multi-threading is enabled, Kea will read packets from the interface + // and append a working item to the thread pool. This option configures the + // maximum number of items that can be queued for each processing thread. + // The value must be a positive integer (0 means unlimited). + "packet-thread-queue-size": 0, + // Governs how the Kea DHCPv6 server should deal with the invalid // data received from the client. "sanity-checks": { diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index ebbb15ecd7..25554103a6 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -169,17 +169,20 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) { "processCommand(\"config-set\", json)"); } + // command line parameters overwrite file and database configuration bool enabled = false; - if (srv_thread_count >= 0) { + if (Dhcpv4Srv::srv_thread_count_ >= 0) { enabled = true; } if (enabled) { - CfgMgr::instance().getCurrentCfg()->setServerThreadCount(srv_thread_count); - CfgMgr::instance().getCurrentCfg()->setServerMaxThreadQueueSize(4); + CfgMgr::instance().getCurrentCfg()->setPktThreadPoolSize(Dhcpv4Srv::srv_thread_count_); + CfgMgr::instance().getCurrentCfg()->setPktThreadQueueSize(0); LOG_FATAL(dhcp4_logger, DHCP4_MULTI_THREADING_WARNING); + } else { + enabled = CfgMgr::instance().getCurrentCfg()->getEnableMultiThreading(); } MultiThreadingMgr::instance().apply(enabled, - CfgMgr::instance().getCurrentCfg()->getServerThreadCount()); + CfgMgr::instance().getCurrentCfg()->getPktThreadPoolSize()); // Now check is the returned result is successful (rcode=0) or not // (see @ref isc::config::parseAnswer). @@ -204,7 +207,7 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) { LOG_WARN(dhcp4_logger, DHCP4_MULTI_THREADING_INFO) .arg(MultiThreadingMgr::instance().getMode() ? "yes" : "no") .arg(MultiThreadingMgr::instance().getPktThreadPoolSize()) - .arg(CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize()); + .arg(CfgMgr::instance().getCurrentCfg()->getPktThreadQueueSize()); return (result); } diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll index 3ac2fbd4d2..688325daff 100644 --- a/src/bin/dhcp4/dhcp4_lexer.ll +++ b/src/bin/dhcp4/dhcp4_lexer.ll @@ -1418,6 +1418,33 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } +\"enable-multi-threading\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_ENABLE_MULTI_THREADING(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("enable-multi-threading", driver.loc_); + } +} + +\"packet-thread-pool-size\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_PACKET_THREAD_POOL_SIZE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("packet-thread-pool-size", driver.loc_); + } +} + +\"packet-thread-queue-size\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_PACKET_THREAD_QUEUE_SIZE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("packet-thread-queue-size", driver.loc_); + } +} + \"control-socket\" { switch(driver.ctx_) { case isc::dhcp::Parser4Context::DHCP4: @@ -1845,8 +1872,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } - - {JSONString} { /* A string has been matched. It contains the actual string and single quotes. We need to get those quotes out of the way and just use its content, e.g. diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy index 6d26fabb61..8d185696af 100644 --- a/src/bin/dhcp4/dhcp4_parser.yy +++ b/src/bin/dhcp4/dhcp4_parser.yy @@ -187,6 +187,10 @@ using namespace std; DHCP4O6_PORT "dhcp4o6-port" + ENABLE_MULTI_THREADING "enable-multi-threading" + PACKET_THREAD_POOL_SIZE "packet-thread-pool-size" + PACKET_THREAD_QUEUE_SIZE "packet-thread-queue-size" + CONTROL_SOCKET "control-socket" SOCKET_TYPE "socket-type" SOCKET_NAME "socket-name" @@ -507,6 +511,9 @@ global_param: valid_lifetime | statistic_default_sample_count | statistic_default_sample_age | unknown_map_entry + | enable_multi_threading + | packet_thread_pool_size + | packet_thread_queue_size ; valid_lifetime: VALID_LIFETIME COLON INTEGER { @@ -1029,6 +1036,21 @@ flex_id: FLEX_ID { ctx.stack_.back()->add(flex_id); }; +enable_multi_threading: ENABLE_MULTI_THREADING COLON BOOLEAN { + ElementPtr b(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("enable-multi-threading", b); +}; + +packet_thread_pool_size: PACKET_THREAD_POOL_SIZE COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("packet-thread-pool-size", prf); +}; + +packet_thread_queue_size: PACKET_THREAD_QUEUE_SIZE COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("packet-thread-queue-size", prf); +}; + hooks_libraries: HOOKS_LIBRARIES { ElementPtr l(new ListElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("hooks-libraries", l); @@ -1995,8 +2017,6 @@ only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN { // --- end of client classes --------------------------------- -// was server-id but in is DHCPv6-only - dhcp4o6_port: DHCP4O6_PORT COLON INTEGER { ElementPtr time(new IntElement($3, ctx.loc2pos(@3))); ctx.stack_.back()->set("dhcp4o6-port", time); @@ -2277,6 +2297,8 @@ control_agent_json_object: CONTROL_AGENT { ctx.leave(); }; +// Config control information element + config_control: CONFIG_CONTROL { ElementPtr i(new MapElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("config-control", i); @@ -2472,7 +2494,6 @@ pattern: PATTERN { ctx.leave(); }; - %% void diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index b6041748ae..9e8e1d5201 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -588,6 +588,8 @@ void Dhcpv4Exchange::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) { const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_"); +int Dhcpv4Srv::srv_thread_count_ = -1; + Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port, const bool use_bcast, const bool direct_response_desired) : io_service_(new IOService()), server_port_(server_port), @@ -937,12 +939,13 @@ Dhcpv4Srv::run_one() { try { bool read_pkt = true; - // Do not read more packets from socket if there are enough - // packets to be processed in the packet thread pool queue - const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize(); + // Do not read more packets from socket if there are enough packets to + // be processed in the packet thread pool queue + // max_queue_size = 0 means no limit + const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getPktThreadQueueSize(); const int thread_count = MultiThreadingMgr::instance().getPktThreadPoolSize(); size_t pkt_queue_size = MultiThreadingMgr::instance().getPktThreadPool().count(); - if (thread_count && (pkt_queue_size >= thread_count * max_queue_size)) { + if (thread_count && max_queue_size && (pkt_queue_size >= thread_count * max_queue_size)) { read_pkt = false; } diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 106a3de794..29db9b61a3 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -1061,6 +1061,12 @@ protected: CBControlDHCPv4Ptr cb_control_; public: + /// @brief command line parameter thread count + /// when parameter is not specified, the default value is used + /// the default value is: -1 means disabled (single-threaded), + /// 0 means auto-detect, other values set thread count explicitly + static int srv_thread_count_; + /// Class methods for DHCPv4-over-DHCPv6 handler /// @brief Updates statistics for received packets diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index 0cec538b4f..6f89b9f3c0 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -10,16 +10,17 @@ #include #include #include +#include +#include #include #include #include -#include -#include #include #include #include #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -39,21 +39,20 @@ #include #include +#include #include #include -#include -#include #include -#include +#include +#include #include #include -#include using namespace std; using namespace isc; -using namespace isc::dhcp; using namespace isc::data; +using namespace isc::dhcp; using namespace isc::asiolink; using namespace isc::hooks; using namespace isc::process; @@ -103,6 +102,18 @@ public: uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port"); cfg->setDhcp4o6Port(dhcp4o6_port); + // Set enable multi threading flag. + bool enable_multi_threading = getBoolean(global, "enable-multi-threading"); + cfg->setEnableMultiThreading(enable_multi_threading); + + // Set packet thread pool size. + uint32_t packet_thread_pool_size = getUint32(global, "packet-thread-pool-size"); + cfg->setPktThreadPoolSize(packet_thread_pool_size); + + // Set packet thread queue size. + uint32_t packet_thread_queue_size = getUint32(global, "packet-thread-queue-size"); + cfg->setPktThreadQueueSize(packet_thread_queue_size); + // Set the global user context. ConstElementPtr user_context = global->get("user-context"); if (user_context) { @@ -297,7 +308,6 @@ void configureCommandChannel() { } } - isc::data::ConstElementPtr configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, bool check_only) { @@ -336,13 +346,13 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, // have to be restored to global storages. bool rollback = false; // config_pair holds the details of the current parser when iterating over - // the parsers. It is declared outside the loops so in case of an error, - // the name of the failing parser can be retrieved in the "catch" clause. + // the parsers. It is declared outside the loop so in case of error, the + // name of the failing parser can be retrieved within the "catch" clause. ConfigPair config_pair; ElementPtr mutable_cfg; SrvConfigPtr srv_cfg; try { - // Get the staging configuration + // Get the staging configuration. srv_cfg = CfgMgr::instance().getStagingCfg(); // This is a way to convert ConstElementPtr to ElementPtr. @@ -353,7 +363,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, // Relocate dhcp-ddns parameters that have moved to global scope. // Rule is that a global value overrides the dhcp-ddns value, so // we need to do this before we apply global defaults. - // Note this is done for backward compatibilty. + // Note this is done for backward compatibility. srv_cfg->moveDdnsParams(mutable_cfg); // Set all default values if not specified by the user. @@ -505,7 +515,6 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, } if (config_pair.first == "shared-networks") { - /// We need to create instance of SharedNetworks4ListParser /// and parse the list of the shared networks into the /// CfgSharedNetworks4 object. One additional step is then to @@ -578,11 +587,13 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, (config_pair.first == "ddns-qualifying-suffix") || (config_pair.first == "store-extended-info") || (config_pair.first == "statistic-default-sample-count") || - (config_pair.first == "statistic-default-sample-age")) { + (config_pair.first == "statistic-default-sample-age") || + (config_pair.first == "enable-multi-threading") || + (config_pair.first == "packet-thread-pool-size") || + (config_pair.first == "packet-thread-queue-size")) { CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first, config_pair.second); continue; - } // Nothing to configure for the user-context. @@ -604,7 +615,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, // defined as part of shared networks. global_parser.sanityChecks(srv_cfg, mutable_cfg); - // Validate D2 client confuguration. + // Validate D2 client configuration. if (!d2_client_cfg) { d2_client_cfg.reset(new D2ClientConfig()); } @@ -655,7 +666,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig(); CfgMgr::instance().setD2ClientConfig(cfg); - // This occurs last as if it succeeds, there is no easy way + // This occurs last as if it succeeds, there is no easy way to // revert it. As a result, the failure to commit a subsequent // change causes problems when trying to roll back. const HooksConfig& libraries = @@ -665,12 +676,14 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, catch (const isc::Exception& ex) { LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what()); answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what()); + // An error occurred, so make sure to restore the original data. rollback = true; } catch (...) { // For things like bad_cast in boost::lexical_cast LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION); answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration" " parsing error"); + // An error occurred, so make sure to restore the original data. rollback = true; } } @@ -687,6 +700,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, err << "during update from config backend database: " << ex.what(); LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str()); answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()); + // An error occurred, so make sure to restore the original data. rollback = true; } catch (...) { // For things like bad_cast in boost::lexical_cast @@ -695,6 +709,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, << "undefined configuration parsing error"; LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str()); answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()); + // An error occurred, so make sure to restore the original data. rollback = true; } } @@ -716,7 +731,5 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, return (answer); } -int srv_thread_count = -1; - } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp4/json_config_parser.h b/src/bin/dhcp4/json_config_parser.h index 7d6d00a4cf..73fae45ac1 100644 --- a/src/bin/dhcp4/json_config_parser.h +++ b/src/bin/dhcp4/json_config_parser.h @@ -60,8 +60,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set, bool check_only = false); -extern int srv_thread_count; - } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 98d3b40fb7..111a4bdab6 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -149,7 +149,7 @@ main(int argc, char* argv[]) { << "], 0-65535 allowed." << endl; usage(); } else { - srv_thread_count = thread_count; + Dhcpv4Srv::srv_thread_count_ = thread_count; } break; diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 7114459825..3492c24564 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -57,6 +57,7 @@ using namespace isc::hooks; using namespace std; namespace { + const char* PARSER_CONFIGS[] = { // CONFIGURATION 0: one subnet with one pool, no user contexts "{" @@ -1483,7 +1484,6 @@ TEST_F(Dhcp4ParserTest, nextServerNegative) { " \"subnet\": \"192.0.2.0/24\" } ]," "\"valid-lifetime\": 4000 }"; - ConstElementPtr json1; ASSERT_NO_THROW(json1 = parseDHCP4(config_bogus1)); ConstElementPtr json2; @@ -2763,7 +2763,6 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) { EXPECT_EQ(170, def->getCode()); EXPECT_EQ(OPT_STRING_TYPE, def->getType()); EXPECT_FALSE(def->getArrayType()); - } // Goal of this test is to verify that global option data is configured @@ -3487,7 +3486,6 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) { testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected)); } - // Verify that empty option name is rejected in the configuration. TEST_F(Dhcp4ParserTest, optionNameEmpty) { // Empty option names not allowed. @@ -3851,7 +3849,6 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) { " } ]" "}"; - ASSERT_NO_THROW(json = parseDHCP4(config)); extractConfig(config); @@ -4009,7 +4006,6 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) { ASSERT_FALSE(desc2.option_); } - // Tests of the hooks libraries configuration. All tests have the pre- // condition (checked in the test fixture's SetUp() method) that no hooks // libraries are loaded at the start of the tests. @@ -4080,7 +4076,6 @@ buildHooksLibrariesConfig(const char* library1 = NULL, return (buildHooksLibrariesConfig(libraries)); } - // The goal of this test is to verify the configuration of hooks libraries if // none are specified. TEST_F(Dhcp4ParserTest, NoHooksLibraries) { @@ -4617,7 +4612,6 @@ TEST_F(Dhcp4ParserTest, subnetRelayInfoList) { EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("192.0.3.124"))); } - // Goal of this test is to verify that multiple subnets can be configured // with defined client classes. TEST_F(Dhcp4ParserTest, classifySubnets) { @@ -4964,7 +4958,6 @@ TEST_F(Dhcp4ParserTest, reservations) { EXPECT_FALSE(hosts_cfg->get4(234, Host::IDENT_CIRCUIT_ID, &circuit_id[0], circuit_id.size())); - // Repeat the test for the DUID based reservation in this subnet. std::vector duid_r(duid.rbegin(), duid.rend()); host = hosts_cfg->get4(542, Host::IDENT_DUID, &duid_r[0], duid_r.size()); @@ -5321,6 +5314,41 @@ TEST_F(Dhcp4ParserTest, hostReservationGlobal) { EXPECT_EQ(Network::HR_OUT_OF_POOL, subnet->getHostReservationMode()); } +/// Check that the multi-threading settings have a default value when not +/// specified. +TEST_F(Dhcp4ParserTest, multiThreadingDefaultSettings) { + ConstElementPtr status; + + string config = "{ " + genIfaceConfig() + "," + + "\"subnet4\": [ ]" + "}"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + extractConfig(config); + + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json)); + + // returned value should be 0 (success) + checkResult(status, 0); + + // The value of enable-multi-threading must be equal to the default value + // (false). The default value is defined in GLOBAL4_DEFAULTS in + // simple_parser4.cc. + EXPECT_EQ(false, + CfgMgr::instance().getStagingCfg()->getEnableMultiThreading()); + + // The value of packet-thread-pool-size must be equal to the default value + // (0). The default value is defined in GLOBAL4_DEFAULTS in + // simple_parser4.cc. + EXPECT_EQ(0, CfgMgr::instance().getStagingCfg()->getPktThreadPoolSize()); + + // The value of packet-thread-queue-size must be equal to the default value + // (4). The default value is defined in GLOBAL4_DEFAULTS in + // simple_parser4.cc. + EXPECT_EQ(4, CfgMgr::instance().getStagingCfg()->getPktThreadQueueSize()); +} + /// Check that the decline-probation-period has a default value when not /// specified. TEST_F(Dhcp4ParserTest, declineTimerDefault) { @@ -5340,8 +5368,8 @@ TEST_F(Dhcp4ParserTest, declineTimerDefault) { checkResult(status, 0); // The value of decline-probation-period must be equal to the - // default value (86400). The default value is defined in GLOBAL6_DEFAULTS in - // simple_parser6.cc. + // default value (86400). The default value is defined in GLOBAL4_DEFAULTS in + // simple_parser4.cc. EXPECT_EQ(86400, CfgMgr::instance().getStagingCfg()->getDeclinePeriod()); } @@ -5368,7 +5396,6 @@ TEST_F(Dhcp4ParserTest, dhcp4o6portDefault) { EXPECT_EQ(0, CfgMgr::instance().getStagingCfg()->getDhcp4o6Port()); } - /// Check that the decline-probation-period value can be set properly. TEST_F(Dhcp4ParserTest, declineTimer) { ConstElementPtr status; @@ -5489,7 +5516,6 @@ TEST_F(Dhcp4ParserTest, expiredLeasesProcessingError) { EXPECT_TRUE(errorContainsPosition(status, "")); } - // Checks if the DHCPv4 is able to parse the configuration without 4o6 parameters // and does not set 4o6 fields at all. TEST_F(Dhcp4ParserTest, 4o6default) { @@ -5620,7 +5646,6 @@ TEST_F(Dhcp4ParserTest, 4o6subnetBogus) { checkResult(status, 1); } - // Checks if the DHCPv4 is able to parse the configuration with 4o6 network // interface defined. TEST_F(Dhcp4ParserTest, 4o6iface) { @@ -5763,7 +5788,6 @@ TEST_F(Dhcp4ParserTest, validClientClassDictionary) { " } ] \n" "} \n"; - ConstElementPtr json; ASSERT_NO_THROW(json = parseDHCP4(config)); extractConfig(config); @@ -6217,7 +6241,6 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) { " \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n" " }\n" " ]\n" - " } ]\n" "} \n"; @@ -6900,8 +6923,10 @@ TEST_F(Dhcp4ParserTest, dhcpQueueControl) { // Fetch the queue control info. staged_control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl(); + // Make sure the staged queue config exists. ASSERT_TRUE(staged_control); + // Now build the expected queue control content. if (scenario.json_.empty()) { exp_control = Element::createMap(); @@ -7169,5 +7194,35 @@ TEST_F(Dhcp4ParserTest, statsDefaultLimits) { EXPECT_EQ("00:00:05", util::durationToText(stats_mgr.getMaxSampleAgeDefault(), 0)); } + +/// Check that the multi threading settings can be set properly. +TEST_F(Dhcp4ParserTest, multiThreadingSettings) { + ConstElementPtr status; + + string config = "{ " + genIfaceConfig() + "," + + "\"enable-multi-threading\": true," + "\"packet-thread-pool-size\": 256," + "\"packet-thread-queue-size\": 256," + "\"subnet4\": [ ]" + "}"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP4(config)); + extractConfig(config); + + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json)); + + // returned value should be 0 (success) + checkResult(status, 0); + + // The value of multi-threading settings must be equal to the specified + // values + EXPECT_EQ(true, + CfgMgr::instance().getStagingCfg()->getEnableMultiThreading()); + EXPECT_EQ(256, + CfgMgr::instance().getStagingCfg()->getPktThreadPoolSize()); + EXPECT_EQ(256, + CfgMgr::instance().getStagingCfg()->getPktThreadQueueSize()); +} } diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index c54256b15a..0aed882902 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -140,17 +140,20 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { "processCommand(\"config-set\", json)"); } + // command line parameters overwrite file and database configuration bool enabled = false; - if (srv_thread_count >= 0) { + if (Dhcpv6Srv::srv_thread_count_ >= 0) { enabled = true; } if (enabled) { - CfgMgr::instance().getCurrentCfg()->setServerThreadCount(srv_thread_count); - CfgMgr::instance().getCurrentCfg()->setServerMaxThreadQueueSize(4); + CfgMgr::instance().getCurrentCfg()->setPktThreadPoolSize(Dhcpv6Srv::srv_thread_count_); + CfgMgr::instance().getCurrentCfg()->setPktThreadQueueSize(0); LOG_FATAL(dhcp6_logger, DHCP6_MULTI_THREADING_WARNING); + } else { + enabled = CfgMgr::instance().getCurrentCfg()->getEnableMultiThreading(); } MultiThreadingMgr::instance().apply(enabled, - CfgMgr::instance().getCurrentCfg()->getServerThreadCount()); + CfgMgr::instance().getCurrentCfg()->getPktThreadPoolSize()); // Now check is the returned result is successful (rcode=0) or not // (see @ref isc::config::parseAnswer). @@ -175,7 +178,7 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { LOG_WARN(dhcp6_logger, DHCP6_MULTI_THREADING_INFO) .arg(MultiThreadingMgr::instance().getMode() ? "yes" : "no") .arg(MultiThreadingMgr::instance().getPktThreadPoolSize()) - .arg(CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize()); + .arg(CfgMgr::instance().getCurrentCfg()->getPktThreadQueueSize()); return (result); } diff --git a/src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll index c2425e5e82..a084532801 100644 --- a/src/bin/dhcp6/dhcp6_lexer.ll +++ b/src/bin/dhcp6/dhcp6_lexer.ll @@ -1647,7 +1647,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } - \"parameters\" { switch(driver.ctx_) { case isc::dhcp::Parser6Context::HOOKS_LIBRARIES: @@ -1810,6 +1809,33 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } +\"enable-multi-threading\" { + switch(driver.ctx_) { + case isc::dhcp::Parser6Context::DHCP6: + return isc::dhcp::Dhcp6Parser::make_ENABLE_MULTI_THREADING(driver.loc_); + default: + return isc::dhcp::Dhcp6Parser::make_STRING("enable-multi-threading", driver.loc_); + } +} + +\"packet-thread-pool-size\" { + switch(driver.ctx_) { + case isc::dhcp::Parser6Context::DHCP6: + return isc::dhcp::Dhcp6Parser::make_PACKET_THREAD_POOL_SIZE(driver.loc_); + default: + return isc::dhcp::Dhcp6Parser::make_STRING("packet-thread-pool-size", driver.loc_); + } +} + +\"packet-thread-queue-size\" { + switch(driver.ctx_) { + case isc::dhcp::Parser6Context::DHCP6: + return isc::dhcp::Dhcp6Parser::make_PACKET_THREAD_QUEUE_SIZE(driver.loc_); + default: + return isc::dhcp::Dhcp6Parser::make_STRING("packet-thread-queue-size", driver.loc_); + } +} + \"control-socket\" { switch(driver.ctx_) { case isc::dhcp::Parser6Context::DHCP6: @@ -1909,7 +1935,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } - {JSONString} { /* A string has been matched. It contains the actual string and single quotes. We need to get those quotes out of the way and just use its content, e.g. diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy index 3cd1780f13..723ecff235 100644 --- a/src/bin/dhcp6/dhcp6_parser.yy +++ b/src/bin/dhcp6/dhcp6_parser.yy @@ -50,6 +50,7 @@ using namespace std; NULL_TYPE "null" DHCP6 "Dhcp6" + DATA_DIRECTORY "data-directory" CONFIG_CONTROL "config-control" CONFIG_DATABASES "config-databases" @@ -191,6 +192,10 @@ using namespace std; DHCP4O6_PORT "dhcp4o6-port" + ENABLE_MULTI_THREADING "enable-multi-threading" + PACKET_THREAD_POOL_SIZE "packet-thread-pool-size" + PACKET_THREAD_QUEUE_SIZE "packet-thread-queue-size" + CONTROL_SOCKET "control-socket" SOCKET_TYPE "socket-type" SOCKET_NAME "socket-name" @@ -417,7 +422,8 @@ syntax_map: LCURLY_BRACKET { ctx.require("Dhcp6", ctx.loc2pos(@1), ctx.loc2pos(@4)); }; -// This represents top-level entries: Dhcp6, Dhcp4, DhcpDdns, Logging +// This represents top-level entries: Control-agent, Dhcp6, Dhcp4, +// DhcpDdns, Logging global_objects: global_object | global_objects COMMA global_object ; @@ -513,6 +519,9 @@ global_param: data_directory | statistic_default_sample_count | statistic_default_sample_age | unknown_map_entry + | enable_multi_threading + | packet_thread_pool_size + | packet_thread_queue_size ; data_directory: DATA_DIRECTORY { @@ -725,7 +734,6 @@ re_detect: RE_DETECT COLON BOOLEAN { ctx.stack_.back()->set("re-detect", b); }; - lease_database: LEASE_DATABASE { ElementPtr i(new MapElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("lease-database", i); @@ -1039,6 +1047,21 @@ relay_supplied_options: RELAY_SUPPLIED_OPTIONS { ctx.leave(); }; +enable_multi_threading: ENABLE_MULTI_THREADING COLON BOOLEAN { + ElementPtr b(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("enable-multi-threading", b); +}; + +packet_thread_pool_size: PACKET_THREAD_POOL_SIZE COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("packet-thread-pool-size", prf); +}; + +packet_thread_queue_size: PACKET_THREAD_QUEUE_SIZE COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("packet-thread-queue-size", prf); +}; + hooks_libraries: HOOKS_LIBRARIES { ElementPtr l(new ListElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("hooks-libraries", l); @@ -1328,7 +1351,6 @@ rapid_commit: RAPID_COMMIT COLON BOOLEAN { ctx.stack_.back()->set("rapid-commit", rc); }; - // ---- shared-networks --------------------- shared_networks: SHARED_NETWORKS { @@ -1670,6 +1692,7 @@ sub_pool6: LCURLY_BRACKET { } pool_params RCURLY_BRACKET { // The pool parameter is required. ctx.require("pool", ctx.loc2pos(@1), ctx.loc2pos(@4)); + // parsing completed }; pool_params: pool_param @@ -2158,6 +2181,7 @@ socket_name: SOCKET_NAME { ctx.leave(); }; + // --- dhcp-queue-control --------------------------------------------- dhcp_queue_control: DHCP_QUEUE_CONTROL { @@ -2368,6 +2392,7 @@ dep_hostname_char_replacement: HOSTNAME_CHAR_REPLACEMENT { ctx.leave(); }; + // JSON entries for Dhcp4 and DhcpDdns dhcp4_json_object: DHCP4 { diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index abcfe4b25c..6d0be0cfbe 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -204,6 +204,8 @@ namespace dhcp { const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_"); +int Dhcpv6Srv::srv_thread_count_ = -1; + Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port) : io_service_(new IOService()), server_port_(server_port), client_port_(client_port), serverid_(), shutdown_(true), @@ -523,12 +525,13 @@ void Dhcpv6Srv::run_one() { try { bool read_pkt = true; - // Do not read more packets from socket if there are enough - // packets to be processed in the packet thread pool queue - const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize(); + // Do not read more packets from socket if there are enough packets to + // be processed in the packet thread pool queue + // max_queue_size = 0 means no limit + const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getPktThreadQueueSize(); const int thread_count = MultiThreadingMgr::instance().getPktThreadPoolSize(); size_t pkt_queue_size = MultiThreadingMgr::instance().getPktThreadPool().count(); - if (thread_count && (pkt_queue_size >= thread_count * max_queue_size)) { + if (thread_count && max_queue_size && (pkt_queue_size >= thread_count * max_queue_size)) { read_pkt = false; } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 80b8d2a141..a9571d2f5c 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -1061,6 +1061,12 @@ protected: uint16_t client_port_; public: + /// @brief command line parameter thread count + /// when parameter is not specified, the default value is used + /// the default value is: -1 means disabled (single-threaded), + /// 0 means auto-detect, other values set thread count explicitly + static int srv_thread_count_; + /// @note used by DHCPv4-over-DHCPv6 so must be public and static /// @brief Updates statistics for transmitted packets diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 7ee22b648a..fbe5a10c01 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -11,11 +11,11 @@ #include #include #include +#include #include -#include #include #include -#include +#include #include #include #include @@ -40,14 +40,13 @@ #include #include #include + #include #include #include #include #include -#include -#include #include #include @@ -55,14 +54,15 @@ #include #include -#include - using namespace std; using namespace isc; using namespace isc::data; using namespace isc::dhcp; using namespace isc::asiolink; using namespace isc::hooks; +using namespace isc::process; +using namespace isc::config; +using namespace isc::db; namespace { @@ -188,6 +188,18 @@ public: uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port"); srv_config->setDhcp4o6Port(dhcp4o6_port); + // Set enable multi threading flag. + bool enable_multi_threading = getBoolean(global, "enable-multi-threading"); + srv_config->setEnableMultiThreading(enable_multi_threading); + + // Set packet thread pool size. + uint32_t packet_thread_pool_size = getUint32(global, "packet-thread-pool-size"); + srv_config->setPktThreadPoolSize(packet_thread_pool_size); + + // Set packet thread queue size. + uint32_t packet_thread_queue_size = getUint32(global, "packet-thread-queue-size"); + srv_config->setPktThreadQueueSize(packet_thread_queue_size); + // Set the global user context. ConstElementPtr user_context = global->get("user-context"); if (user_context) { @@ -311,7 +323,6 @@ public: } } - if (iface.empty()) { iface = (*subnet)->getIface(); continue; @@ -349,8 +360,6 @@ public: } } - - }; } // anonymous namespace @@ -627,12 +636,11 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, } if (config_pair.first == "shared-networks") { - /// We need to create instance of SharedNetworks4ListParser + /// We need to create instance of SharedNetworks6ListParser /// and parse the list of the shared networks into the - /// CfgSharedNetworks4 object. One additional step is then to + /// CfgSharedNetworks6 object. One additional step is then to /// add subnets from the CfgSharedNetworks6 into CfgSubnets6 /// as well. - SharedNetworks6ListParser parser; CfgSharedNetworks6Ptr cfg = srv_config->getCfgSharedNetworks6(); parser.parse(cfg, config_pair.second); @@ -697,7 +705,10 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, (config_pair.first == "ddns-qualifying-suffix") || (config_pair.first == "store-extended-info") || (config_pair.first == "statistic-default-sample-count") || - (config_pair.first == "statistic-default-sample-age")) { + (config_pair.first == "statistic-default-sample-age") || + (config_pair.first == "enable-multi-threading") || + (config_pair.first == "packet-thread-pool-size") || + (config_pair.first == "packet-thread-queue-size")) { CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first, config_pair.second); continue; @@ -728,7 +739,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, // defined as part of shared networks. global_parser.sanityChecks(srv_config, mutable_cfg); - // Validate D2 client confuguration. + // Validate D2 client configuration. if (!d2_client_cfg) { d2_client_cfg.reset(new D2ClientConfig()); } @@ -844,7 +855,5 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, return (answer); } -int srv_thread_count = -1; - } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp6/json_config_parser.h b/src/bin/dhcp6/json_config_parser.h index 981ed8b9b8..8230d3550e 100644 --- a/src/bin/dhcp6/json_config_parser.h +++ b/src/bin/dhcp6/json_config_parser.h @@ -44,8 +44,6 @@ isc::data::ConstElementPtr configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, bool check_only = false); -extern int srv_thread_count; - } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc index 2f8b5940a4..e6f8cdb6c8 100644 --- a/src/bin/dhcp6/main.cc +++ b/src/bin/dhcp6/main.cc @@ -149,7 +149,7 @@ main(int argc, char* argv[]) { << "], 0-65535 allowed." << endl; usage(); } else { - srv_thread_count = thread_count; + Dhcpv6Srv::srv_thread_count_ = thread_count; } break; diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index a3cd8b5ca2..1a3421e072 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -510,8 +510,8 @@ public: /// @brief Create the simple configuration with single option. /// /// This function allows to set one of the parameters that configure - /// option value. These parameters are: "name", "code", "data" and - /// "csv-format". + /// option value. These parameters are: "name", "code", "data", + /// "csv-format" and "space". /// /// @param param_value string holding option parameter value to be /// injected into the configuration string. @@ -784,7 +784,6 @@ public: return (ReturnType()); } - /// @brief Test invalid option parameter value. /// /// This test function constructs the simple configuration @@ -1203,7 +1202,6 @@ TEST_F(Dhcp6ParserTest, emptySubnet) { ConstElementPtr status; EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); - // returned value should be 0 (success) checkResult(status, 0); } @@ -1311,7 +1309,7 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) { } while (++cnt < 10); } -// This checks that it is possible to assign arbitrary ids for subnets. +// This test checks that it is possible to assign arbitrary ids for subnets. TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) { ConstElementPtr x; // Four subnets with arbitrary subnet ids. @@ -1407,7 +1405,6 @@ TEST_F(Dhcp6ParserTest, multipleSubnetsOverlappingIDs) { EXPECT_TRUE(errorContainsPosition(x, "")); } - // Goal of this test is to verify that a previously configured subnet can be // deleted in subsequent reconfiguration. TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) { @@ -1541,8 +1538,6 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) { EXPECT_EQ(4, subnets->at(2)->getID()); } - - // This test checks if it is possible to override global values // on a per subnet basis. TEST_F(Dhcp6ParserTest, subnetLocal) { @@ -1658,7 +1653,6 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) { EXPECT_FALSE(subnet); } - // This test checks if it is not allowed to define global interface // parameter. TEST_F(Dhcp6ParserTest, interfaceGlobal) { @@ -1686,7 +1680,6 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) { EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError); } - // This test checks if it is possible to define a subnet with an // interface-id option defined. TEST_F(Dhcp6ParserTest, subnetInterfaceId) { @@ -1734,7 +1727,6 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) { EXPECT_TRUE(selector.interface_id_->equals(subnet->getInterfaceId())); } - // This test checks if it is not allowed to define global interface // parameter. TEST_F(Dhcp6ParserTest, interfaceIdGlobal) { @@ -1958,7 +1950,6 @@ TEST_F(Dhcp6ParserTest, multiplePools) { EXPECT_TRUE(subnets->at(0)->getPools(Lease::TYPE_PD).empty()); } - // Test verifies that a subnet with pool values that do not belong to that // pool are rejected. TEST_F(Dhcp6ParserTest, poolOutOfSubnet) { @@ -1972,7 +1963,6 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) { " \"subnet\": \"2001:db8:1::/64\" } ]," "\"valid-lifetime\": 4000 }"; - ConstElementPtr json; ASSERT_NO_THROW(json = parseDHCP6(config)); @@ -2409,7 +2399,6 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) { EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress()); } - // Goal of this test is check for proper handling of invalid prefix delegation // pool configuration. It uses an array of invalid configurations to check // a variety of configuration errors. @@ -2903,7 +2892,6 @@ TEST_F(Dhcp6ParserTest, optionIntegerTypes) { checkResult(status, 0); } - /// The goal of this test is to verify that the invalid encapsulated /// option space name is not accepted. TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) { @@ -3640,7 +3628,6 @@ TEST_F(Dhcp6ParserTest, optionDataMultiplePools) { testOption(*range3.first, D6O_SUBSCRIBER_ID, subscriber_id_expected2, sizeof(subscriber_id_expected2)); - pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::300")); ASSERT_TRUE(pool); pool6 = boost::dynamic_pointer_cast(pool); @@ -3957,7 +3944,6 @@ TEST_F(Dhcp6ParserTest, rdnssOption) { EXPECT_EQ("example.com.", optionCustom->readFqdn(4)); } - // This test checks if vendor options can be specified in the config file // (in hex format), and later retrieved from configured subnet TEST_F(Dhcp6ParserTest, vendorOptionsHex) { @@ -4224,7 +4210,6 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) { // condition (checked in the test fixture's SetUp() method) that no hooks // libraries are loaded at the start of the tests. - // Helper function to return a configuration containing an arbitrary number // of hooks libraries. std::string @@ -4294,7 +4279,6 @@ buildHooksLibrariesConfig(const char* library1 = NULL, return (buildHooksLibrariesConfig(libraries)); } - // The goal of this test is to verify the configuration of hooks libraries if // none are specified. TEST_F(Dhcp6ParserTest, NoHooksLibraries) { @@ -4364,7 +4348,6 @@ TEST_F(Dhcp6ParserTest, LibrariesSpecified) { } - // This test verifies that it is possible to select subset of interfaces on // which server should listen. TEST_F(Dhcp6ParserTest, selectedInterfaces) { @@ -4382,7 +4365,6 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) { "\"renew-timer\": 1000, " "\"valid-lifetime\": 4000 }"; - ConstElementPtr json; ASSERT_NO_THROW(json = parseDHCP6(config)); extractConfig(config); @@ -4421,7 +4403,6 @@ TEST_F(Dhcp6ParserTest, allInterfaces) { "\"renew-timer\": 1000, " "\"valid-lifetime\": 4000 }"; - ConstElementPtr json; ASSERT_NO_THROW(json = parseDHCP6(config)); extractConfig(config); @@ -4437,7 +4418,6 @@ TEST_F(Dhcp6ParserTest, allInterfaces) { EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET6)); } - // This test checks if it is possible to specify relay information TEST_F(Dhcp6ParserTest, subnetRelayInfo) { @@ -5233,7 +5213,6 @@ TEST_F(Dhcp6ParserTest, reservations) { ASSERT_TRUE(opt_prf); EXPECT_EQ(11, static_cast(opt_prf->getValue())); - // The HW address used for one of the reservations in the subnet 542 // consists of numbers from 6 to 1. So, let's just reverse the order // of the address from the previous test. @@ -5532,7 +5511,6 @@ TEST_F(Dhcp6ParserTest, macSources2) { EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, sources[2]); } - /// The goal of this test is to verify that empty MAC sources configuration /// is rejected. If specified, this has to have at least one value. TEST_F(Dhcp6ParserTest, macSourcesEmpty) { @@ -5911,6 +5889,41 @@ TEST_F(Dhcp6ParserTest, testDataDir) { EXPECT_NE(original_datadir, string(CfgMgr::instance().getDataDir())); } +/// Check that the multi-threading settings have a default value when not +/// specified. +TEST_F(Dhcp6ParserTest, multiThreadingDefaultSettings) { + ConstElementPtr status; + + string config = "{ " + genIfaceConfig() + "," + + "\"subnet6\": [ ]" + "}"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP6(config)); + extractConfig(config); + + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + + // returned value should be 0 (success) + checkResult(status, 0); + + // The value of enable-multi-threading must be equal to the default value + // (false). The default value is defined in GLOBAL6_DEFAULTS in + // simple_parser6.cc. + EXPECT_EQ(false, + CfgMgr::instance().getStagingCfg()->getEnableMultiThreading()); + + // The value of packet-thread-pool-size must be equal to the default value + // (0). The default value is defined in GLOBAL6_DEFAULTS in + // simple_parser6.cc. + EXPECT_EQ(0, CfgMgr::instance().getStagingCfg()->getPktThreadPoolSize()); + + // The value of packet-thread-queue-size must be equal to the default value + // (4). The default value is defined in GLOBAL6_DEFAULTS in + // simple_parser6.cc. + EXPECT_EQ(4, CfgMgr::instance().getStagingCfg()->getPktThreadQueueSize()); +} + /// Check that the decline-probation-period value has a default value if not /// specified explicitly. TEST_F(Dhcp6ParserTest, declineTimerDefault) { @@ -6077,7 +6090,6 @@ TEST_F(Dhcp6ParserTest, expiredLeasesProcessingError) { // Verifies that simple list of valid classes parses and // is staged for commit. TEST_F(Dhcp6ParserTest, validClientClassDictionary) { - string config = "{ " + genIfaceConfig() + "," "\"preferred-lifetime\": 3000, \n" "\"rebind-timer\": 2000, \n" @@ -6826,7 +6838,6 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveInterfaces) { EXPECT_EQ("", s->getIface().get()); } - // It is not allowed to have different values for interfaces names is subnets // in the same shared network. TEST_F(Dhcp6ParserTest, sharedNetworksInterfacesMixed) { @@ -6894,7 +6905,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) { CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg() ->getCfgSharedNetworks6(); - // Two shared networks are expeced. + // Two shared networks are expected. ASSERT_TRUE(cfg_net); const SharedNetwork6Collection* nets = cfg_net->getAll(); ASSERT_TRUE(nets); @@ -7118,7 +7129,7 @@ TEST_F(Dhcp6ParserTest, comments) { ASSERT_TRUE(ctx_opt_desc->get("comment")); EXPECT_EQ("\"Set option value\"", ctx_opt_desc->get("comment")->str()); - // And there there are some client classes. + // And there are some client classes. const ClientClassDictionaryPtr& dict = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); ASSERT_TRUE(dict); @@ -7428,6 +7439,7 @@ TEST_F(Dhcp6ParserTest, configControlInfo) { registerBackendType(ConfigBackendDHCPv6Mgr::instance(), "mysql")); + // Should parse ok, now that the factory has been registered. configure(config, CONTROL_RESULT_SUCCESS, ""); // Make sure the config control info is there. @@ -7454,7 +7466,6 @@ TEST_F(Dhcp6ParserTest, configControlInfo) { // Check whether it is possible to configure server-tag TEST_F(Dhcp6ParserTest, serverTag) { - // Config without server-tag string config_no_tag = "{ " + genIfaceConfig() + "," + "\"subnet6\": [ ] " @@ -7617,7 +7628,7 @@ TEST_F(Dhcp6ParserTest, dhcpQueueControlInvalid) { "expecting boolean" }, { - "queue type not a string", + "queue enabled, type not a string", "{ \n" " \"enable-queue\": true, \n" " \"queue-type\": 7777 \n" @@ -7752,5 +7763,34 @@ TEST_F(Dhcp6ParserTest, statsDefaultLimits) { util::durationToText(stats_mgr.getMaxSampleAgeDefault(), 0)); } +/// Check that the multi threading settings can be set properly. +TEST_F(Dhcp6ParserTest, multiThreadingSettings) { + ConstElementPtr status; + + string config = "{ " + genIfaceConfig() + "," + + "\"enable-multi-threading\": true," + "\"packet-thread-pool-size\": 256," + "\"packet-thread-queue-size\": 256," + "\"subnet6\": [ ]" + "}"; + + ConstElementPtr json; + ASSERT_NO_THROW(json = parseDHCP6(config)); + extractConfig(config); + + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + + // returned value should be 0 (success) + checkResult(status, 0); + + // The value of multi-threading settings must be equal to the specified + // values + EXPECT_EQ(true, + CfgMgr::instance().getStagingCfg()->getEnableMultiThreading()); + EXPECT_EQ(256, + CfgMgr::instance().getStagingCfg()->getPktThreadPoolSize()); + EXPECT_EQ(256, + CfgMgr::instance().getStagingCfg()->getPktThreadQueueSize()); +} } diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc index 0b38c7c0fb..b86087e607 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser4.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc @@ -85,7 +85,10 @@ const SimpleKeywords SimpleParser4::GLOBAL4_PARAMETERS = { { "ddns-qualifying-suffix", Element::string }, { "store-extended-info", Element::boolean }, { "statistic-default-sample-count", Element::integer }, - { "statistic-default-sample-age", Element::integer } + { "statistic-default-sample-age", Element::integer }, + { "enable-multi-threading", Element::boolean }, + { "packet-thread-pool-size", Element::integer }, + { "packet-thread-queue-size", Element::integer } }; /// @brief This table defines default global values for DHCPv4 @@ -118,7 +121,10 @@ const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = { { "hostname-char-replacement", Element::string, "" }, { "store-extended-info", Element::boolean, "false" }, { "statistic-default-sample-count", Element::integer, "20" }, - { "statistic-default-sample-age", Element::integer, "0" } + { "statistic-default-sample-age", Element::integer, "0" }, + { "enable-multi-threading", Element::boolean, "false" }, + { "packet-thread-pool-size", Element::integer, "0" }, + { "packet-thread-queue-size", Element::integer, "4" } }; /// @brief This table defines all option definition parameters. @@ -474,5 +480,5 @@ size_t SimpleParser4::deriveParameters(ElementPtr global) { return (cnt); } -}; -}; +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc index 78f19ab940..96375bf4cb 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser6.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc @@ -85,7 +85,10 @@ const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = { { "ddns-qualifying-suffix", Element::string }, { "store-extended-info", Element::boolean }, { "statistic-default-sample-count", Element::integer }, - { "statistic-default-sample-age", Element::integer } + { "statistic-default-sample-age", Element::integer }, + { "enable-multi-threading", Element::boolean }, + { "packet-thread-pool-size", Element::integer }, + { "packet-thread-queue-size", Element::integer } }; /// @brief This table defines default global values for DHCPv6 @@ -113,7 +116,10 @@ const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = { { "hostname-char-replacement", Element::string, "" }, { "store-extended-info", Element::boolean, "false" }, { "statistic-default-sample-count", Element::integer, "20" }, - { "statistic-default-sample-age", Element::integer, "0" } + { "statistic-default-sample-age", Element::integer, "0" }, + { "enable-multi-threading", Element::boolean, "false" }, + { "packet-thread-pool-size", Element::integer, "0" }, + { "packet-thread-queue-size", Element::integer, "4" } }; /// @brief This table defines all option definition parameters. @@ -476,5 +482,5 @@ size_t SimpleParser6::deriveParameters(ElementPtr global) { return (cnt); } -}; -}; +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index d5be04a4f1..391fc396e3 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -42,8 +42,8 @@ SrvConfig::SrvConfig() cfg_host_operations6_(CfgHostOperations::createConfig6()), class_dictionary_(new ClientClassDictionary()), decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0), - server_threads_(0), - server_max_thread_queue_size_(0), + enable_multi_threading_(false), + pkt_thread_pool_size_(0), pkt_thread_queue_size_(0), d2_client_config_(new D2ClientConfig()), configured_globals_(Element::createMap()), cfg_consist_(new CfgConsistency()) { @@ -62,8 +62,8 @@ SrvConfig::SrvConfig(const uint32_t sequence) cfg_host_operations6_(CfgHostOperations::createConfig6()), class_dictionary_(new ClientClassDictionary()), decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0), - server_threads_(0), - server_max_thread_queue_size_(0), + enable_multi_threading_(false), + pkt_thread_pool_size_(0), pkt_thread_queue_size_(0), d2_client_config_(new D2ClientConfig()), configured_globals_(Element::createMap()), cfg_consist_(new CfgConsistency()) { diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index 3107ffeb2e..81308f495c 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -705,32 +705,46 @@ public: return (dhcp4o6_port_); } - /// @brief Sets the server thread count. + /// @brief Sets the enable multi threading flag. /// - /// @param threads value of the server thread count - void setServerThreadCount(uint32_t threads) { - server_threads_ = threads; + /// @param size value of the enable multi threading flag + void setEnableMultiThreading(bool enabled) { + enable_multi_threading_ = enabled; } - /// @brief Retrieves the server thread count. + /// @brief Retrieves the enable multi threading flag. /// - /// @return value of the server thread count - uint32_t getServerThreadCount() const { - return (server_threads_); + /// @return value of the enable multi threading flag + uint32_t getEnableMultiThreading() const { + return (enable_multi_threading_); } - /// @brief Sets the server max thread queue size. + /// @brief Sets the packet thread pool size. /// - /// @param size max thread queue size - void setServerMaxThreadQueueSize(uint32_t size) { - server_max_thread_queue_size_ = size; + /// @param size value of the packet thread pool size + void setPktThreadPoolSize(uint32_t size) { + pkt_thread_pool_size_ = size; } - /// @brief Retrieves the server max thread queue size. + /// @brief Retrieves the packet thread pool size. /// - /// @return value of the max thread queue size - uint32_t getServerMaxThreadQueueSize() const { - return (server_max_thread_queue_size_); + /// @return value of the packet thread pool size + uint32_t getPktThreadPoolSize() const { + return (pkt_thread_pool_size_); + } + + /// @brief Sets the packet thread queue size. + /// + /// @param size value of the packet thread queue size + void setPktThreadQueueSize(uint32_t size) { + pkt_thread_queue_size_ = size; + } + + /// @brief Retrieves the packet thread queue size. + /// + /// @return value of the packet thread queue size + uint32_t getPktThreadQueueSize() const { + return (pkt_thread_queue_size_); } /// @brief Returns pointer to the D2 client configuration @@ -951,11 +965,14 @@ private: /// this socket is bound and connected to this port and port + 1 uint16_t dhcp4o6_port_; - /// @brief The server thread count. - uint32_t server_threads_; + /// @brief The enable multi threading flag. + bool enable_multi_threading_; - /// @brief The server max thread queue size. - uint32_t server_max_thread_queue_size_; + /// @brief The packet thread pool size. + uint32_t pkt_thread_pool_size_; + + /// @brief The packet thread queue size. + uint32_t pkt_thread_queue_size_; /// @brief Stores D2 client configuration D2ClientConfigPtr d2_client_config_;