From: Thomas Markwalder Date: Wed, 14 Apr 2021 15:06:14 +0000 (-0400) Subject: [#1736] Initial commit with HA+MT integration X-Git-Tag: Kea-1.9.7~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4d19c8bc43539cd6771f91c7c0cffff572e92dba;p=thirdparty%2Fkea.git [#1736] Initial commit with HA+MT integration HA+MT fully integrated and functional. src/hooks/dhcp/high_availability/ha_service.* HAService - changed client_ from instance to pointer - added listener_ HAService::startClientAndListener() - new method which instantiates client_ and listener_ instances based on config, and starts them HAService::stopClientAndListener() - new method that stops client_ and listener_ instances. HAService::HAService() - invokes startClientAndListener() HAService::~HAService() - invokes stopClientAndListener() src/hooks/dhcp/high_availability/tests/Makefile.am Added ha_mt_unittest.c src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc New file with HA+MT related tests src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc Modified tests to ensure use of ST HAService src/lib/config/cmd_http_listener.h Added CmdHttpListener commentary --- diff --git a/src/hooks/dhcp/high_availability/ha_service.cc b/src/hooks/dhcp/high_availability/ha_service.cc index 2f9f1644eb..2d7d42a15d 100644 --- a/src/hooks/dhcp/high_availability/ha_service.cc +++ b/src/hooks/dhcp/high_availability/ha_service.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,7 @@ const int HAService::HA_CONTROL_RESULT_MAINTENANCE_NOT_ALLOWED; HAService::HAService(const IOServicePtr& io_service, const NetworkStatePtr& network_state, const HAConfigPtr& config, const HAServerType& server_type) : io_service_(io_service), network_state_(network_state), config_(config), - server_type_(server_type), client_(*io_service), communication_state_(), + server_type_(server_type), client_(), listener_(), communication_state_(), query_filter_(config), mutex_(), pending_requests_(), lease_update_backlog_(config->getDelayedUpdatesLimit()) { @@ -67,12 +68,18 @@ HAService::HAService(const IOServicePtr& io_service, const NetworkStatePtr& netw startModel(HA_WAITING_ST); + // Start client and/or listener. + startClientAndListener(); + LOG_INFO(ha_logger, HA_SERVICE_STARTED) .arg(HAConfig::HAModeToString(config->getHAMode())) .arg(HAConfig::PeerConfig::roleToString(config->getThisServerConfig()->getRole())); } HAService::~HAService() { + // Stop client and/or listener. + stopClientAndListener(); + network_state_->reset(NetworkState::Origin::HA_COMMAND); } @@ -989,7 +996,7 @@ HAService::adjustNetworkState() { bool HAService::shouldPartnerDown() const { - // Checking whether the communication with the partner is ok is the + // Checking whether the communication with the partner is OK is the // first step towards verifying if the server is up. if (communication_state_->isCommunicationInterrupted()) { // If the communication is interrupted, we also have to check @@ -1336,7 +1343,7 @@ HAService::asyncSendLeaseUpdate(const QueryPtrType& query, } } else { // This was a response from the backup server and we're configured to - // not wait for their ackowledgments, so there is nothing more to do. + // not wait for their acknowledgments, so there is nothing more to do. return; } @@ -1356,7 +1363,7 @@ HAService::asyncSendLeaseUpdate(const QueryPtrType& query, ); // The number of pending requests is the number of requests for which we - // expect an acknowledgement prior to responding to the DHCP clients. If + // expect an acknowledgment prior to responding to the DHCP clients. If // we're configured to wait for the acks from the backups or it is not // a backup increase the number of pending requests. if (config_->amWaitingBackupAck() || (config->getRole() != HAConfig::PeerConfig::BACKUP)) { @@ -1856,7 +1863,7 @@ HAService::asyncSyncLeases() { dhcp_disable_timeout = 1; } - asyncSyncLeases(client_, config_->getFailoverPeerConfig()->getName(), + asyncSyncLeases(*client_, config_->getFailoverPeerConfig()->getName(), dhcp_disable_timeout, LeasePtr(), null_action); } @@ -2404,7 +2411,7 @@ HAService::processMaintenanceNotify(const bool cancel) { // have a way to distinguish between the errors caused by the communication // issues and the cases when there is no communication error but the server // is not allowed to enter the in-maintenance state. In the former case, the - // parter would go to partner-down. In the case signaled by the special + // partner would go to partner-down. In the case signaled by the special // result code entering the maintenance state is not allowed. return (createAnswer(HA_CONTROL_RESULT_MAINTENANCE_NOT_ALLOWED, "Unable to transition the server from the " @@ -2704,7 +2711,7 @@ HAService::verifyAsyncResponse(const HttpResponsePtr& response, int& rcode) { bool HAService::clientConnectHandler(const boost::system::error_code& ec, int tcp_native_fd) { - // If things look ok register the socket with Interface Manager. Note + // If things look OK register the socket with Interface Manager. Note // we don't register if the FD is < 0 to avoid an exception throw. // It is unlikely that this will occur but we want to be liberal // and avoid issues. @@ -2712,7 +2719,7 @@ HAService::clientConnectHandler(const boost::system::error_code& ec, int tcp_nat && (tcp_native_fd >= 0)) { // External socket callback is a NOP. Ready events handlers are // run by an explicit call IOService ready in kea-dhcp code. - // We are registerin the socket only to interrupt main-thread + // We are registering the socket only to interrupt main-thread // select(). IfaceMgr::instance().addExternalSocket(tcp_native_fd, std::bind(&HAService::socketReadyHandler, this, ph::_1) @@ -2732,7 +2739,7 @@ HAService::socketReadyHandler(int tcp_native_fd) { // ongoing transactions, we close it. This will unregister it from // IfaceMgr and ensure the client starts over with a fresh connection // if it needs to do so. - client_.closeIfOutOfBand(tcp_native_fd); + client_->closeIfOutOfBand(tcp_native_fd); } void @@ -2773,6 +2780,55 @@ HAService::getPendingRequestInternal(const QueryPtrType& query) { } } +void +HAService::startClientAndListener() { + // If we're not configured for multi-threading, then we start + // a client in ST mode and return. + if (!config_->getEnableMultiThreading()) { + client_.reset(new HttpClient(*io_service_, 0)); + return; + } + + // Start a client in MT mode. + client_.reset(new HttpClient(*io_service_, + config_->getHttpClientThreads())); + + // If we're configured to use our own listener create and start it. + if (config_->getHttpDedicatedListener()) { + // Get the server address and port from this server's URL. + auto my_url = config_->getThisServerConfig()->getUrl(); + IOAddress server_address(IOAddress::IPV4_ZERO_ADDRESS()); + try { + // Since we do not currently support hostname resolution, + // we need to make sure we have an IP address here. + server_address = IOAddress(my_url.getStrippedHostname()); + } catch (const std::exception& ex) { + isc_throw(Unexpected, "server Url:" << my_url.getStrippedHostname() + << " is not a valid IP address"); + } + + // Fetch how many threads the listener will use. + uint32_t listener_threads = config_->getHttpListenerThreads(); + + // Instantiate the listener. + listener_.reset(new CmdHttpListener(server_address, my_url.getPort(), + listener_threads)); + // Start the listener listening. + listener_->start(); + } +} + +void +HAService::stopClientAndListener() { + if (client_) { + client_->stop(); + } + + if (listener_) { + listener_->stop(); + } +} + // Explicit instantiations. template int HAService::getPendingRequest(const Pkt4Ptr&); template int HAService::getPendingRequest(const Pkt6Ptr&); diff --git a/src/hooks/dhcp/high_availability/ha_service.h b/src/hooks/dhcp/high_availability/ha_service.h index 8b33498f58..41d9724c1b 100644 --- a/src/hooks/dhcp/high_availability/ha_service.h +++ b/src/hooks/dhcp/high_availability/ha_service.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -89,7 +90,8 @@ public: /// @brief Constructor. /// /// It clears the DHCP state using origin HA internal command and starts the - /// state model in waiting state. + /// state model in waiting state. Creates and starts the client and the + /// listener (if one). /// /// @param io_service Pointer to the IO service used by the DHCP server. /// @param config Parsed HA hook library configuration. @@ -103,7 +105,8 @@ public: /// @brief Destructor. /// - /// It clears the DHCP state using origin HA internal command. + /// Stops the client and listener (if one). It clears the DHCP + /// state using origin HA internal command. virtual ~HAService(); /// @brief Returns HA server type used in object construction. @@ -353,7 +356,7 @@ protected: /// @param state the new value to assign to the current state. void verboseTransition(const unsigned state); - /// @brief Returns normal operation state for the current configruation. + /// @brief Returns normal operation state for the current configuration. /// /// @return "load-balancing" for active servers in load balancing mode, /// "hot-standby" for active servers in hot-standby mode, "backup" for @@ -895,7 +898,7 @@ protected: /// entering the load-balancing state. It ensures that all outstanding lease /// updates are sent to the partner before the server can continue normal /// operation in the load-balancing state. In order to prevent collisions - /// between new allocations and oustanding updates this method is synchronous. + /// between new allocations and outstanding updates this method is synchronous. /// /// This method creates its own instances of the HttpClient and IOService and /// invokes IOService::run(). @@ -944,12 +947,12 @@ public: /// @brief Processes ha-maintenance-notify command and returns a response. /// - /// This command attempts to tramsition the server to the in-maintenance state + /// This command attempts to transition the server to the in-maintenance state /// if the cancel flag is set to false. Such transition is not allowed if /// the server is currently in one of the following states: /// - backup: because maintenance is not supported for backup servers, /// - partner-in-maintenance: because only one server is in maintenance while - /// the partner must be in parter-in-maintenance state, + /// the partner must be in partner-in-maintenance state, /// - terminated: because the only way to resume HA service is by shutting /// down the server, fixing the clock skew and restarting. /// @@ -1000,6 +1003,9 @@ public: data::ConstElementPtr processMaintenanceCancel(); protected: + void startClientAndListener(); + + void stopClientAndListener(); /// @brief Checks if the response is valid or contains an error. /// @@ -1020,7 +1026,7 @@ protected: /// /// @param ec Error status of the ASIO connect /// @param tcp_native_fd socket descriptor to register - /// @return always true. Registeration cannot fail, and if ec indicates a real + /// @return always true. Registration cannot fail, and if ec indicates a real /// error we want Connection logic to process it. bool clientConnectHandler(const boost::system::error_code& ec, int tcp_native_fd); @@ -1073,8 +1079,12 @@ protected: /// @brief DHCP server type. HAServerType server_type_; - /// @brief HTTP client instance used to send lease updates. - http::HttpClient client_; + /// @brief HTTP client instance used to send HA commands and lease updates. + http::HttpClientPtr client_; + + /// @brief HTTP listener instance used to receive and respond to HA commands + /// and lease updates. + config::CmdHttpListenerPtr listener_; /// @brief Holds communication state with a peer. CommunicationStatePtr communication_state_; diff --git a/src/hooks/dhcp/high_availability/tests/Makefile.am b/src/hooks/dhcp/high_availability/tests/Makefile.am index 275963dc5a..322992a4bc 100644 --- a/src/hooks/dhcp/high_availability/tests/Makefile.am +++ b/src/hooks/dhcp/high_availability/tests/Makefile.am @@ -32,6 +32,7 @@ ha_unittests_SOURCES += ha_config_unittest.cc ha_unittests_SOURCES += ha_impl_unittest.cc ha_unittests_SOURCES += ha_service_unittest.cc ha_unittests_SOURCES += ha_test.cc ha_test.h +ha_unittests_SOURCES += ha_mt_unittest.cc ha_unittests_SOURCES += lease_update_backlog_unittest.cc ha_unittests_SOURCES += query_filter_unittest.cc ha_unittests_SOURCES += run_unittests.cc diff --git a/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc new file mode 100644 index 0000000000..3c47568fcc --- /dev/null +++ b/src/hooks/dhcp/high_availability/tests/ha_mt_unittest.cc @@ -0,0 +1,417 @@ +// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::config; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::ha; +using namespace isc::ha::test; +using namespace isc::hooks; +using namespace isc::http; +using namespace isc::util; + +namespace { + +/// @brief IP address to which HTTP service is bound. +const std::string SERVER_ADDRESS = "127.0.0.1"; + +/// @brief Port number to which HTTP service is bound. +const unsigned short SERVER_PORT = 18123; + +/// @brief Request Timeout used in most of the tests (ms). +const long REQUEST_TIMEOUT = 10000; + +/// @brief Persistent connection idle timeout used in most of the tests (ms). +const long IDLE_TIMEOUT = 10000; + +/// @brief Test timeout (ms). +const long TEST_TIMEOUT = 10000; + +/// @brief Generates IPv4 leases to be used by the tests. +/// +/// @param [out] leases reference to the container where leases are stored. +void generateTestLeases(std::vector& leases) { + for (uint8_t i = 1; i <= 10; ++i) { + uint32_t lease_address = 0xC0000201 + 256 * i; + std::vector hwaddr(6, i); + Lease4Ptr lease(new Lease4(IOAddress(lease_address), + HWAddrPtr(new HWAddr(hwaddr, HTYPE_ETHER)), + ClientIdPtr(), + 60, + static_cast(1000 + i), + SubnetID(i))); + leases.push_back(lease); + } +} + +/// @brief Generates IPv6 leases to be used by the tests. +/// +/// @param [out] leases reference to the container where leases are stored. +void generateTestLeases(std::vector& leases) { + std::vector address_bytes = IOAddress("2001:db8:1::1").toBytes(); + for (uint8_t i = 1; i <= 10; ++i) { + DuidPtr duid(new DUID(std::vector(10, i))); + address_bytes[6] += i; + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, + IOAddress::fromBytes(AF_INET6, &address_bytes[0]), + duid, 1, 50, 60, SubnetID(i))); + leases.push_back(lease); + } +} + +/// @brief Returns generated leases in JSON format. +/// +/// @tparam LeasesVec vector of IPv4 or IPv6 lease pointers. +/// @param leases reference to the container holding leases to be +/// converted to JSON format. +template +ConstElementPtr getLeasesAsJson(const LeasesVec& leases) { + ElementPtr leases_json = Element::createList(); + for (auto l = leases.begin(); l != leases.end(); ++l) { + leases_json->add((*l)->toElement()); + } + return (leases_json); +} + +/// @brief Derivation of the @c HAService which provides access to +/// protected methods and members. +class TestHAService : public HAService { +public: + + /// @brief Constructor. + /// + /// @param io_service Pointer to the IO service used by the DHCP server. + /// @param network_state Object holding state of the DHCP service + /// (enabled/disabled). + /// @param config Parsed HA hook library configuration. + /// @param server_type Server type, i.e. DHCPv4 or DHCPv6 server. + TestHAService(const IOServicePtr& io_service, + const NetworkStatePtr& network_state, + const HAConfigPtr& config, + const HAServerType& server_type = HAServerType::DHCPv4) + : HAService(io_service, network_state, config, server_type) { + } + + /// @brief Test version of the @c HAService::runModel. + /// + /// The original implementation of this method returns control when + /// @c NOP_EVT is found. This implementation runs a + /// single handler to allow the tests to verify if the state machine + /// transitions to an expected state before it is run again. + virtual void runModel(unsigned int event) { + try { + postNextEvent(event); + getState(getCurrState())->run(); + + } catch (const std::exception& ex) { + abortModel(ex.what()); + } + } + + /// @brief Schedules asynchronous "dhcp-disable" command to the specified + /// server. + /// + /// This variant of the method uses default HTTP client for communication. + /// + /// @param server_name name of the server to which the command should be + /// sent. + /// @param max_period maximum number of seconds for which the DHCP service + /// should be disabled. + /// @param post_request_action pointer to the function to be executed when + /// the request is completed. + void asyncDisableDHCPService(const std::string& server_name, + const unsigned int max_period, + const PostRequestCallback& post_request_action) { + HAService::asyncDisableDHCPService(*client_, server_name, max_period, + post_request_action); + } + + /// @brief Schedules asynchronous "dhcp-enable" command to the specified + /// server. + /// + /// This variant of the method uses default HTTP client for communication. + /// + /// @param server_name name of the server to which the command should be + /// sent. + /// @param post_request_action pointer to the function to be executed when + /// the request is completed. + void asyncEnableDHCPService(const std::string& server_name, + const PostRequestCallback& post_request_action) { + HAService::asyncEnableDHCPService(*client_, server_name, post_request_action); + } + + using HAService::asyncSendHeartbeat; + using HAService::asyncSyncLeases; + using HAService::postNextEvent; + using HAService::transition; + using HAService::verboseTransition; + using HAService::shouldSendLeaseUpdates; + using HAService::shouldQueueLeaseUpdates; + using HAService::pendingRequestSize; + using HAService::getPendingRequest; + using HAService::network_state_; + using HAService::config_; + using HAService::communication_state_; + using HAService::query_filter_; + using HAService::lease_update_backlog_; + using HAService::client_; + using HAService::listener_; +}; + +/// @brief Pointer to the @c TestHAService. +typedef boost::shared_ptr TestHAServicePtr; + +/// @brief Test fixture class for @c HAService multi-threading. +class HAMtServiceTest : public HATest { +public: + + /// @brief Constructor. + HAMtServiceTest() + : HATest() { + MultiThreadingMgr::instance().setMode(true); + } + + /// @brief Destructor. + /// + /// Stops all test servers. + ~HAMtServiceTest() { + io_service_->get_io_service().reset(); + io_service_->poll(); + MultiThreadingMgr::instance().setMode(false); + } + + /// @brief Callback function invoke upon test timeout. + /// + /// It stops the IO service and reports test timeout. + /// + /// @param fail_on_timeout Specifies if test failure should be reported. + void timeoutHandler(const bool fail_on_timeout) { + if (fail_on_timeout) { + ADD_FAILURE() << "Timeout occurred while running the test!"; + } + io_service_->stop(); + } +}; + +// Verifies permuations of HA+MT configuration and start-up. +TEST_F(HAMtServiceTest, multiThreadingStartup) { + + // Structure describing a test scenario. + struct Scenario { + std::string desc_; // Description of the scenario. + std::string mt_json_; // multi-threading config to use. + bool dhcp_mt_enabled_; // True if DHCP multi-threading is enabled. + uint32_t dhcp_threads_; // Value of DHCP thread-pool-size. + bool exp_ha_mt_enabled_; // If HA+MT should be enabled + bool exp_listener_; // if HA+MT should use dedicated listener. + uint32_t exp_listener_threads_; // Expected number of listener threads. + uint32_t exp_client_threads_; // Expected number of client threads. + }; + + // Mnemonic constants. + bool dhcp_mt = true; + bool ha_mt = true; + bool listener = true; + + // Number of threads the system reports as supported. + uint32_t sys_threads = MultiThreadingMgr::detectThreadCount(); + + std::vector scenarios { + { + "1 no ha+mt/default", + "", + dhcp_mt, 4, + !ha_mt, !listener, 0, 0 + }, + { + "2 dhcp mt enabled, ha mt disabled", + makeHAMtJson(!ha_mt, !listener, 0, 0), + dhcp_mt, 4, + !ha_mt, !listener, 0, 0 + }, + { + "3 dhcp mt disabled, mt enabled", + makeHAMtJson(ha_mt, listener, 0, 0), + !dhcp_mt, 4, + !ha_mt, !listener, 0, 0 + }, + { + "4 dhcp mt enabled, mt enabled, listener disabled", + makeHAMtJson(ha_mt, !listener, 0, 0), + dhcp_mt, 4, + ha_mt, !listener, 4, 4 + }, + { + "5 dhcp mt enabled, mt enabled, listener enabled", + makeHAMtJson(ha_mt, !listener, 0, 0), + dhcp_mt, 4, + ha_mt, !listener, 4, 4 + }, + { + "6 explicit DHCP threads, explicit thread values", + makeHAMtJson(ha_mt, listener, 5, 6), + dhcp_mt, 4, + ha_mt, listener, 5, 6 + } + , + { + "7 explicit DHCP threads, zero thread values", + makeHAMtJson(ha_mt, listener, 0, 0), + dhcp_mt, 8, + ha_mt, listener, 8, 8 + }, + { + "8 DHCP auto detect threads, zero thread values", + // Special case: if system reports supported threads as 0 + // then HA+MT should be disabled. Otherwise it should + // be enabled with listener and client threads set to the + // reported value. + makeHAMtJson(ha_mt, listener, 0, 0), + dhcp_mt, 0, + (sys_threads > 0), listener, sys_threads, sys_threads + } + }; + + // Iterate over the scenarios. + for (auto const& scenario : scenarios) { + SCOPED_TRACE(scenario.desc_); + + // Build the HA JSON configuration. + std::stringstream ss; + ss << + "[" + " {" + " \"this-server-name\": \"server1\"," + " \"mode\": \"passive-backup\"," + " \"wait-backup-ack\": true," + " \"peers\": [" + " {" + " \"name\": \"server1\"," + " \"url\": \"http://127.0.0.1:8080/\"," + " \"role\": \"primary\"" + " }," + " {" + " \"name\": \"server2\"," + " \"url\": \"http://127.0.0.1:8081/\"," + " \"role\": \"backup\"" + " }" + " ]"; + + if (!scenario.mt_json_.empty()) { + ss << "," << scenario.mt_json_; + } + + ss << "}]"; + ConstElementPtr config_json; + ASSERT_NO_THROW_LOG(config_json = Element::fromJSON(ss.str())); + + // Set DHCP multi-threading configuration in CfgMgr. + setDHCPMultiThreadingConfig(scenario.dhcp_mt_enabled_, scenario.dhcp_threads_); + /// @todo this is a hack... we have chicken-egg... CmdHttpListener won't + /// start if MT is not enabled BUT that happens after config hook point + MultiThreadingMgr::instance().setMode(scenario.dhcp_mt_enabled_); + + // Create the HA configuration + HAConfigPtr ha_config(new HAConfig()); + HAConfigParser parser; + ASSERT_NO_THROW_LOG(parser.parse(ha_config, config_json)); + + // Instanatiate the service. + TestHAServicePtr service; + ASSERT_NO_THROW_LOG(service.reset(new TestHAService(io_service_, network_state_, + ha_config))); + // Verify the configuration is as expected. + if (!scenario.exp_ha_mt_enabled_) { + // When HA+MT is disabled, client should be single-threaded. + ASSERT_TRUE(service->client_); + EXPECT_FALSE(service->client_->getThreadIOService()); + EXPECT_EQ(service->client_->getThreadPoolSize(), 0); + EXPECT_EQ(service->client_->getThreadCount(), 0); + + // Listener should not exist. + ASSERT_FALSE(service->listener_); + continue; + } + + // Multi-threading should be enabled. + ASSERT_TRUE(ha_config->getEnableMultiThreading()); + + // When HA+MT is enabled, client should be multi-threaded. + ASSERT_TRUE(service->client_); + EXPECT_TRUE(service->client_->getThreadIOService()); + EXPECT_EQ(service->client_->getThreadPoolSize(), scenario.exp_client_threads_); + // Currently thread count should be the same as thread pool size. This might + // change if we go to so some sort of dynamic thread instance management. + EXPECT_EQ(service->client_->getThreadCount(), scenario.exp_client_threads_); + + if (!scenario.exp_listener_) { + // We should not have a listener. + ASSERT_FALSE(service->listener_); + continue; + } + + // We sould have a listening listener with the expected number of threads. + ASSERT_TRUE(service->listener_); + EXPECT_TRUE(service->listener_->isListening()); + EXPECT_EQ(service->listener_->getThreadPoolSize(), scenario.exp_listener_threads_); + // Currently thread count should be the same as thread pool size. This might + // change if we go to so some sort of dynamic thread instance management. + EXPECT_EQ(service->listener_->getThreadCount(), scenario.exp_listener_threads_); + } +} + +TEST_F(HAMtServiceTest, twoServicesFun) { +} + +} // end of anonymous namespace diff --git a/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc index 995ea3b047..5abea871df 100644 --- a/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc +++ b/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -59,6 +61,10 @@ using namespace isc::hooks; using namespace isc::http; using namespace isc::util; +/// @file The tests herein were created prior to HA+MT but are very valuable +/// for testing HA single-threaded operation and overall HA behavior. +/// HA+MT testing is done elsewhere. + namespace { /// @brief IP address to which HTTP service is bound. @@ -171,7 +177,7 @@ public: void asyncDisableDHCPService(const std::string& server_name, const unsigned int max_period, const PostRequestCallback& post_request_action) { - HAService::asyncDisableDHCPService(client_, server_name, max_period, + HAService::asyncDisableDHCPService(*client_, server_name, max_period, post_request_action); } @@ -186,7 +192,7 @@ public: /// the request is completed. void asyncEnableDHCPService(const std::string& server_name, const PostRequestCallback& post_request_action) { - HAService::asyncEnableDHCPService(client_, server_name, post_request_action); + HAService::asyncEnableDHCPService(*client_, server_name, post_request_action); } using HAService::asyncSendHeartbeat; @@ -203,6 +209,8 @@ public: using HAService::communication_state_; using HAService::query_filter_; using HAService::lease_update_backlog_; + using HAService::client_; + using HAService::listener_; }; /// @brief Pointer to the @c TestHAService. @@ -626,6 +634,25 @@ public: io_service_->stop(); } + /// @brief Creates a TestHAService instance with HA+MT disabled. + /// + /// @param network_state Object holding state of the DHCP service + /// (enabled/disabled). + /// @param config Parsed HA hook library configuration. + /// @param server_type server type, i.e. DHCPv4 or DHCPv6. + void createSTService( + const NetworkStatePtr& state, + const HAConfigPtr config, + const HAServerType& server_type = HAServerType::DHCPv4) { + + ASSERT_FALSE(config->getEnableMultiThreading()); + ASSERT_NO_THROW_LOG(service_.reset(new TestHAService(io_service_, state, + config, server_type))); + ASSERT_TRUE(service_->client_); + ASSERT_FALSE(service_->client_->getThreadIOService()); + ASSERT_FALSE(service_->listener_); + } + /// @brief Generates IPv4 leases to be used by the tests. void generateTestLeases4() { generateTestLeases(leases4_); @@ -820,7 +847,7 @@ public: state->modifyPokeTime(-30); // Create HA service and schedule lease updates. - service_.reset(new TestHAService(io_service_, network_state_, config_storage)); + createSTService(network_state_, config_storage); service_->communication_state_ = state; service_->transition(my_state.state_, HAService::NOP_EVT); @@ -922,8 +949,7 @@ public: state->modifyPokeTime(-30); // Create HA service and schedule lease updates. - service_.reset(new TestHAService(io_service_, network_state_, config_storage, - HAServerType::DHCPv6)); + createSTService(network_state_, config_storage, HAServerType::DHCPv6); service_->communication_state_ = state; service_->transition(my_state.state_, HAService::NOP_EVT); @@ -4763,7 +4789,7 @@ class HAServiceStateMachineTest : public HAServiceTest { public: /// @brief Constructor. HAServiceStateMachineTest() - : HAServiceTest(), service_(), state_(), + : HAServiceTest(), state_(), partner_(new HAPartner(listener2_, factory2_)) { } @@ -4778,8 +4804,7 @@ public: const HAServerType& server_type = HAServerType::DHCPv4) { config->setHeartbeatDelay(1); config->setSyncPageLimit(1000); - service_.reset(new TestHAService(io_service_, network_state_, config, - server_type)); + createSTService(network_state_, config, server_type); // Replace default communication state with custom state which exposes // protected members and methods. state_.reset(new NakedCommunicationState4(io_service_, config)); @@ -5121,8 +5146,6 @@ public: return (isDoingHeartbeat()); } - /// @brief Pointer to the HA service under test. - TestHAServicePtr service_; /// @brief Pointer to the communication state used in the tests. NakedCommunicationState4Ptr state_; /// @brief Pointer to the partner used in some tests. @@ -7411,4 +7434,4 @@ TEST_F(HAServiceStateMachineTest, shouldSendLeaseUpdatesPassiveBackup) { EXPECT_TRUE(expectLeaseUpdates(MyState(HA_WAITING_ST), peer_config)); } -} +} // end of anonymous namespace diff --git a/src/lib/config/cmd_http_listener.h b/src/lib/config/cmd_http_listener.h index a9191bb51e..9473076722 100644 --- a/src/lib/config/cmd_http_listener.h +++ b/src/lib/config/cmd_http_listener.h @@ -16,6 +16,18 @@ namespace isc { namespace config { +/// @brief A multi-threaded HTTP listener that can process API commands +/// requests. +/// +/// This class will listen for Command API client requests on a given +/// IP address and port. It uses its own IOService instance to drive +/// a thread-pool which can service multiple connections concurrently. +/// The number of concurrent connections is currently limited to the +/// configured thread pool size. +/// +/// @note This class is NOT compatible with Kea core single-threading. +/// It is incumbant upon the owner to ensure the Kea core multi-threading +/// is (or will be) enabled when creating instances of this class. class CmdHttpListener { public: /// @brief Constructor