From: Marcin Siodelski Date: Tue, 10 Jul 2018 15:44:49 +0000 (+0200) Subject: [5674] Implemented HA pause for ordinary states. X-Git-Tag: ha_phase2~54^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6fb64283afe40de4a646ca86a9f36a117f4836c3;p=thirdparty%2Fkea.git [5674] Implemented HA pause for ordinary states. Currently it excludes terminated and syncing states. --- diff --git a/src/hooks/dhcp/high_availability/ha_service.cc b/src/hooks/dhcp/high_availability/ha_service.cc index e55ec2cffa..cb18d91230 100644 --- a/src/hooks/dhcp/high_availability/ha_service.cc +++ b/src/hooks/dhcp/high_availability/ha_service.cc @@ -45,7 +45,7 @@ HAService::HAService(const IOServicePtr& io_service, const NetworkStatePtr& netw 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_(), - query_filter_(config), pending_requests_() { + query_filter_(config), state_machine_control_(config), pending_requests_() { if (server_type == HAServerType::DHCPv4) { communication_state_.reset(new CommunicationState4(io_service_, config)); @@ -130,10 +130,16 @@ HAService::normalStateHandler() { if (doOnEntry()) { query_filter_.serveDefaultScopes(); adjustNetworkState(); + state_machine_control_.notify(getCurrState()); } scheduleHeartbeat(); + if (state_machine_control_.amPaused()) { + postNextEvent(NOP_EVT); + return; + } + // Check if the clock skew is still acceptable. If not, transition to // the terminated state. if (shouldTerminate()) { @@ -180,10 +186,17 @@ HAService::partnerDownStateHandler() { query_filter_.serveDefaultScopes(); } adjustNetworkState(); + + state_machine_control_.notify(getCurrState()); } scheduleHeartbeat(); + if (state_machine_control_.amPaused()) { + postNextEvent(NOP_EVT); + return; + } + // Check if the clock skew is still acceptable. If not, transition to // the terminated state. if (shouldTerminate()) { @@ -220,10 +233,16 @@ HAService::readyStateHandler() { if (doOnEntry()) { query_filter_.serveNoScopes(); adjustNetworkState(); + state_machine_control_.notify(getCurrState()); } scheduleHeartbeat(); + if (state_machine_control_.amPaused()) { + postNextEvent(NOP_EVT); + return; + } + // Check if the clock skew is still acceptable. If not, transition to // the terminated state. if (shouldTerminate()) { @@ -361,6 +380,17 @@ HAService::waitingStateHandler() { if (doOnEntry()) { query_filter_.serveNoScopes(); adjustNetworkState(); + state_machine_control_.notify(getCurrState()); + } + + // Only schedule the heartbeat for non-backup servers. + if (config_->getThisServerConfig()->getRole() != HAConfig::PeerConfig::BACKUP) { + scheduleHeartbeat(); + } + + if (state_machine_control_.amPaused()) { + postNextEvent(NOP_EVT); + return; } // Backup server must remain in its own state. @@ -369,8 +399,6 @@ HAService::waitingStateHandler() { return; } - scheduleHeartbeat(); - // Check if the clock skew is still acceptable. If not, transition to // the terminated state. if (shouldTerminate()) { @@ -477,6 +505,11 @@ HAService::verboseTransition(const unsigned state) { } } +void +HAService::unpause() { + state_machine_control_.unpause(); +} + void HAService::serveDefaultScopes() { query_filter_.serveDefaultScopes(); diff --git a/src/hooks/dhcp/high_availability/ha_service.h b/src/hooks/dhcp/high_availability/ha_service.h index bff04ca006..e1485ec38b 100644 --- a/src/hooks/dhcp/high_availability/ha_service.h +++ b/src/hooks/dhcp/high_availability/ha_service.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -246,6 +247,13 @@ protected: public: + /// @brief Un-pauses the state machine. + /// + /// This is method is invoked when the server receives the 'ha-continue' + /// command which instructs the server to unblock the HA state machine + /// so as it may transition to a next state. + void unpause(); + /// @brief Instructs the HA service to serve default scopes. /// /// This method is mostly useful for unit testing. The scopes need to be @@ -635,6 +643,9 @@ protected: /// @brief Selects queries to be processed/dropped. QueryFilter query_filter_; + /// @brief Controls state machine pausing and unpausing. + HAStateMachineControl state_machine_control_; + /// @brief Map holding a number of scheduled requests for a given packet. /// /// A single callout may send multiple requests at the same time, e.g. diff --git a/src/hooks/dhcp/high_availability/ha_state_machine_control.h b/src/hooks/dhcp/high_availability/ha_state_machine_control.h index 463f6b35bc..887c70124b 100644 --- a/src/hooks/dhcp/high_availability/ha_state_machine_control.h +++ b/src/hooks/dhcp/high_availability/ha_state_machine_control.h @@ -8,6 +8,7 @@ #define HA_STATE_MACHINE_CONTROL_H #include +#include #include namespace isc { @@ -73,6 +74,9 @@ private: std::set visited_states_; }; +/// @brief Shared pointer to the @c HAStateMachineControl. +typedef boost::shared_ptr HAStateMachineControlPtr; + } // end of namespace isc::ha } // end of namespace isc 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 572f71754f..8151ec6bbf 100644 --- a/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc +++ b/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc @@ -3547,6 +3547,71 @@ TEST_F(HAServiceStateMachineTest, syncingTransitionsLoadBalancing) { EXPECT_EQ(HAService::HA_SYNCING_SUCCEEDED_EVT, service_->getLastEvent()); } +// This test verifies that the HA state machine can be paused in certain states +// when the server is operating in load balancing mode. +TEST_F(HAServiceStateMachineTest, stateTransitionsLoadBalancingPause) { + partner_->startup(); + + HAConfigPtr valid_config = createValidConfiguration(); + auto state_configs = valid_config->getStateMachineConfig(); + + for (auto cfg = state_configs.begin(); cfg != state_configs.end(); ++cfg) { + cfg->second->setPausing("always"); + } + + startService(valid_config); + + { + SCOPED_TRACE("LOAD BALANCING state transitions"); + + testTransition(MyState(HA_LOAD_BALANCING_ST), PartnerState(HA_TERMINATED_ST), + FinalState(HA_LOAD_BALANCING_ST)); + + service_->unpause(); + + testTransition(MyState(HA_LOAD_BALANCING_ST), PartnerState(HA_TERMINATED_ST), + FinalState(HA_TERMINATED_ST)); + } + + { + SCOPED_TRACE("PARTNER DOWN state transitions"); + + testTransition(MyState(HA_PARTNER_DOWN_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_PARTNER_DOWN_ST)); + + service_->unpause(); + + testTransition(MyState(HA_PARTNER_DOWN_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_WAITING_ST)); + } + + + { + SCOPED_TRACE("READY state transitions"); + + testTransition(MyState(HA_READY_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_READY_ST)); + + service_->unpause(); + + testTransition(MyState(HA_READY_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_LOAD_BALANCING_ST)); + } + + + { + SCOPED_TRACE("WAITING state transitions"); + + testTransition(MyState(HA_WAITING_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_WAITING_ST)); + + service_->unpause(); + + testTransition(MyState(HA_WAITING_ST), PartnerState(HA_LOAD_BALANCING_ST), + FinalState(HA_SYNCING_ST)); + } +} + // This test verifies that the server takes ownership of the given scopes // and whether the DHCP service is disabled or enabled in certain states. TEST_F(HAServiceStateMachineTest, scopesServingLoadBalancing) {