diff --git a/src/hooks/dhcp/high_availability/command_creator.cc b/src/hooks/dhcp/high_availability/command_creator.cc index 447bdc4069..25395e0f14 100644 --- a/src/hooks/dhcp/high_availability/command_creator.cc +++ b/src/hooks/dhcp/high_availability/command_creator.cc @@ -65,15 +65,21 @@ CommandCreator::createDHCPEnable(const unsigned int origin, } ConstElementPtr -CommandCreator::createHAReset(const HAServerType& server_type) { - ConstElementPtr command = config::createCommand("ha-reset"); +CommandCreator::createHAReset(const std::string& server_name, + const HAServerType& server_type) { + auto args = Element::createMap(); + args->set("server-name", Element::create(server_name)); + ConstElementPtr command = config::createCommand("ha-reset", args); insertService(command, server_type); return (command); } ConstElementPtr -CommandCreator::createHeartbeat(const HAServerType& server_type) { - ConstElementPtr command = config::createCommand("ha-heartbeat"); +CommandCreator::createHeartbeat(const std::string& server_name, + const HAServerType& server_type) { + auto args = Element::createMap(); + args->set("server-name", Element::create(server_name)); + ConstElementPtr command = config::createCommand("ha-heartbeat", args); insertService(command, server_type); return (command); } @@ -250,8 +256,11 @@ CommandCreator::createMaintenanceNotify(const bool cancel, const HAServerType& s } ConstElementPtr -CommandCreator::createSyncCompleteNotify(const HAServerType& server_type) { - auto command = config::createCommand("ha-sync-complete-notify"); +CommandCreator::createSyncCompleteNotify(const std::string& server_name, + const HAServerType& server_type) { + auto args = Element::createMap(); + args->set("server-name", Element::create(server_name)); + auto command = config::createCommand("ha-sync-complete-notify", args); insertService(command, server_type); return (command); } diff --git a/src/hooks/dhcp/high_availability/command_creator.h b/src/hooks/dhcp/high_availability/command_creator.h index a9ec33b72a..04c81acd0b 100644 --- a/src/hooks/dhcp/high_availability/command_creator.h +++ b/src/hooks/dhcp/high_availability/command_creator.h @@ -49,16 +49,22 @@ public: /// @brief Creates ha-reset command. /// + /// @param server_name name of the server sending the command allowing + /// for associating the command with the relationship. /// @param server_type type of the DHCP server, i.e. v4 or v6. /// @return Pointer to the JSON representation of the command. static data::ConstElementPtr - createHAReset(const HAServerType& server_type); + createHAReset(const std::string& server_name, + const HAServerType& server_type); /// @brief Creates ha-heartbeat command for DHCP server. /// + /// @param server_name name of the server sending the command allowing + /// for associating the command with the relationship. /// @return Pointer to the JSON representation of the command. static data::ConstElementPtr - createHeartbeat(const HAServerType& server_type); + createHeartbeat(const std::string& server_name, + const HAServerType& server_type); /// @brief Creates lease4-update command. /// @@ -174,10 +180,13 @@ public: /// @brief Creates ha-sync-complete-notify command. /// + /// @param server_name name of the server sending the command allowing + /// for associating the command with the relationship. /// @param server_type type of the DHCP server, i.e. v4 or v6. /// @return Pointer to the JSON representation of the command. static data::ConstElementPtr - createSyncCompleteNotify(const HAServerType& server_type); + createSyncCompleteNotify(const std::string& server_name, + const HAServerType& server_type); /// @brief List of commands used by the High Availability in v4. static std::unordered_set ha_commands4_; diff --git a/src/hooks/dhcp/high_availability/ha_impl.cc b/src/hooks/dhcp/high_availability/ha_impl.cc index 7ab7701fe2..af0fdb1f2d 100644 --- a/src/hooks/dhcp/high_availability/ha_impl.cc +++ b/src/hooks/dhcp/high_availability/ha_impl.cc @@ -319,22 +319,43 @@ HAImpl::commandProcessed(hooks::CalloutHandle& callout_handle) { ElementPtr mutable_resp_args = boost::const_pointer_cast(resp_args); - /// @todo Today we support only one HA relationship per Kea server. - /// In the future there will be more of them. Therefore we enclose - /// our sole relationship in a list. + // Process the status get command for each HA service. auto ha_relationships = Element::createList(); - auto ha_relationship = Element::createMap(); - ConstElementPtr ha_servers = services_->get()->processStatusGet(); - ha_relationship->set("ha-servers", ha_servers); - ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->get()->getHAMode()))); - ha_relationships->add(ha_relationship); - mutable_resp_args->set("high-availability", ha_relationships); + for (auto service : services_->getAll()) { + auto ha_relationship = Element::createMap(); + ConstElementPtr ha_servers = service->processStatusGet(); + ha_relationship->set("ha-servers", ha_servers); + ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->get()->getHAMode()))); + ha_relationships->add(ha_relationship); + mutable_resp_args->set("high-availability", ha_relationships); + } } } void HAImpl::heartbeatHandler(CalloutHandle& callout_handle) { - ConstElementPtr response = services_->get()->processHeartbeat(); + // Command must always be provided. + ConstElementPtr command; + callout_handle.getArgument("command", command); + + // Retrieve arguments. + ConstElementPtr args; + static_cast(parseCommand(args, command)); + + HAServicePtr service; + try { + service = getHAServiceByServerName("ha-heartbeat", args); + + } catch (const std::exception& ex) { + // There was an error while parsing command arguments. Return an error status + // code to notify the user. + ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + callout_handle.setArgument("response", response); + return; + } + + // Command parsing was successful, so let's process the command. + ConstElementPtr response = service->processHeartbeat(); callout_handle.setArgument("response", response); } @@ -351,6 +372,7 @@ HAImpl::synchronizeHandler(hooks::CalloutHandle& callout_handle) { ConstElementPtr server_name; unsigned int max_period_value = 0; + HAServicePtr service; try { // Arguments are required for the ha-sync command. if (!args) { @@ -386,6 +408,8 @@ HAImpl::synchronizeHandler(hooks::CalloutHandle& callout_handle) { max_period_value = static_cast(max_period->intValue()); } + service = getHAServiceByServerName("ha-sync", args); + } catch (const std::exception& ex) { // There was an error while parsing command arguments. Return an error status // code to notify the user. @@ -395,8 +419,8 @@ HAImpl::synchronizeHandler(hooks::CalloutHandle& callout_handle) { } // Command parsing was successful, so let's process the command. - ConstElementPtr response = services_->get()->processSynchronize(server_name->stringValue(), - max_period_value); + ConstElementPtr response = service->processSynchronize(server_name->stringValue(), + max_period_value); callout_handle.setArgument("response", response); } @@ -410,8 +434,8 @@ HAImpl::scopesHandler(hooks::CalloutHandle& callout_handle) { ConstElementPtr args; static_cast(parseCommand(args, command)); + HAServicePtr service; std::vector scopes_vector; - try { // Arguments must be present. if (!args) { @@ -444,6 +468,8 @@ HAImpl::scopesHandler(hooks::CalloutHandle& callout_handle) { scopes_vector.push_back(scope->stringValue()); } + service = getHAServiceByServerName("ha-sync", args); + } catch (const std::exception& ex) { // There was an error while parsing command arguments. Return an error status // code to notify the user. @@ -453,13 +479,32 @@ HAImpl::scopesHandler(hooks::CalloutHandle& callout_handle) { } // Command parsing was successful, so let's process the command. - ConstElementPtr response = services_->get()->processScopes(scopes_vector); + ConstElementPtr response = service->processScopes(scopes_vector); callout_handle.setArgument("response", response); } void HAImpl::continueHandler(hooks::CalloutHandle& callout_handle) { - ConstElementPtr response = services_->get()->processContinue(); + // Command must always be provided. + ConstElementPtr command; + callout_handle.getArgument("command", command); + + // Retrieve arguments. + ConstElementPtr args; + static_cast(parseCommand(args, command)); + + HAServicePtr service; + try { + service = getHAServiceByServerName("ha-continue", args); + + } catch (const std::exception& ex) { + // There was an error while parsing command arguments. Return an error status + // code to notify the user. + ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + callout_handle.setArgument("response", response); + return; + } + ConstElementPtr response = service->processContinue(); callout_handle.setArgument("response", response); } @@ -469,46 +514,139 @@ HAImpl::maintenanceNotifyHandler(hooks::CalloutHandle& callout_handle) { ConstElementPtr command; callout_handle.getArgument("command", command); - // Retrieve arguments. - ConstElementPtr args; - static_cast(parseCommandWithArgs(args, command)); + HAServicePtr service; + try { + // Retrieve arguments. + ConstElementPtr args; + static_cast(parseCommandWithArgs(args, command)); - ConstElementPtr cancel_op = args->get("cancel"); - if (!cancel_op) { - isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command"); + ConstElementPtr cancel_op = args->get("cancel"); + if (!cancel_op) { + isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command"); + } + + if (cancel_op->getType() != Element::boolean) { + isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command"); + } + + service = getHAServiceByServerName("ha-maintenance-notify", args); + + ConstElementPtr response = service->processMaintenanceNotify(cancel_op->boolValue()); + callout_handle.setArgument("response", response); + + } catch (const std::exception& ex) { + // There was an error while parsing command arguments. Return an error status + // code to notify the user. + ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + callout_handle.setArgument("response", response); } - - if (cancel_op->getType() != Element::boolean) { - isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command"); - } - - ConstElementPtr response = services_->get()->processMaintenanceNotify(cancel_op->boolValue()); - callout_handle.setArgument("response", response); } void HAImpl::maintenanceStartHandler(hooks::CalloutHandle& callout_handle) { - ConstElementPtr response = services_->get()->processMaintenanceStart(); + ConstElementPtr response; + for (auto service : services_->getAll()) { + response = service->processMaintenanceStart(); + int rcode = CONTROL_RESULT_SUCCESS; + static_cast(parseAnswer(rcode, response)); + if (rcode != CONTROL_RESULT_SUCCESS) { + break; + } + + } callout_handle.setArgument("response", response); } void HAImpl::maintenanceCancelHandler(hooks::CalloutHandle& callout_handle) { - ConstElementPtr response = services_->get()->processMaintenanceCancel(); + ConstElementPtr response; + for (auto service : services_->getAll()) { + response = service->processMaintenanceCancel(); + } callout_handle.setArgument("response", response); } void HAImpl::haResetHandler(hooks::CalloutHandle& callout_handle) { + // Command must always be provided. + ConstElementPtr command; + callout_handle.getArgument("command", command); + + // Retrieve arguments. + ConstElementPtr args; + static_cast(parseCommand(args, command)); + + HAServicePtr service; + try { + service = getHAServiceByServerName("ha-reset", args); + + } catch (const std::exception& ex) { + // There was an error while parsing command arguments. Return an error status + // code to notify the user. + ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + callout_handle.setArgument("response", response); + return; + } + ConstElementPtr response = services_->get()->processHAReset(); callout_handle.setArgument("response", response); } void HAImpl::syncCompleteNotifyHandler(hooks::CalloutHandle& callout_handle) { + // Command must always be provided. + ConstElementPtr command; + callout_handle.getArgument("command", command); + + // Retrieve arguments. + ConstElementPtr args; + static_cast(parseCommand(args, command)); + + HAServicePtr service; + try { + service = getHAServiceByServerName("ha-sync-complete-notify", args); + + } catch (const std::exception& ex) { + // There was an error while parsing command arguments. Return an error status + // code to notify the user. + ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + callout_handle.setArgument("response", response); + return; + } + ConstElementPtr response = services_->get()->processSyncCompleteNotify(); callout_handle.setArgument("response", response); } +HAServicePtr +HAImpl::getHAServiceByServerName(const std::string& command_name, ConstElementPtr args) const { + HAServicePtr service; + if (args) { + // Arguments must be a map. + if (args->getType() != Element::map) { + isc_throw(BadValue, "arguments in the '" << command_name << "' command are not a map"); + } + + auto server_name = args->get("server-name"); + + if (server_name) { + if (server_name->getType() != Element::string) { + isc_throw(BadValue, "'server-name' must be a string in the '" << command_name << "' command"); + } + service = services_->get(server_name->stringValue()); + if (!service) { + isc_throw(BadValue, server_name->stringValue() << " matches no configured" + << " 'server-name'"); + } + } + } + + if (!service) { + service = services_->get(); + } + + return (service); +} + } // end of namespace isc::ha } // end of namespace isc diff --git a/src/hooks/dhcp/high_availability/ha_impl.h b/src/hooks/dhcp/high_availability/ha_impl.h index a4c360d3b7..1cdc93ff8b 100644 --- a/src/hooks/dhcp/high_availability/ha_impl.h +++ b/src/hooks/dhcp/high_availability/ha_impl.h @@ -178,6 +178,20 @@ public: /// @param callout_handle Callout handle provided to the callout. void syncCompleteNotifyHandler(hooks::CalloutHandle& callout_handle); + /// @brief Attempts to get an @c HAService by server name. + /// + /// The function expects that the arguments contain the "server-name" + /// parameter. If the parameter is not specified, a default @c HAService + /// name is returned. + /// + /// @param command_name command name. + /// @param args command arguments or null. + /// @return Pointer to an @c HAService instance. + /// @throw BadValue if the specified server-name doesn't exist or if the + /// server-name wasn't specified and more than one @c HAService exists. + HAServicePtr getHAServiceByServerName(const std::string& command_name, + data::ConstElementPtr args) const; + protected: /// @brief Holds parsed configuration. diff --git a/src/hooks/dhcp/high_availability/ha_service.cc b/src/hooks/dhcp/high_availability/ha_service.cc index 9dc44a37b5..30605bc722 100644 --- a/src/hooks/dhcp/high_availability/ha_service.cc +++ b/src/hooks/dhcp/high_availability/ha_service.cc @@ -1692,7 +1692,8 @@ HAService::asyncSendHeartbeat() { (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(), HostHttpHeader(partner_config->getUrl().getStrippedHostname())); partner_config->addBasicAuthHttpHeader(request); - request->setBodyAsJson(CommandCreator::createHeartbeat(server_type_)); + request->setBodyAsJson(CommandCreator::createHeartbeat(config_->getThisServerConfig()->getName(), + server_type_)); request->finalize(); // Response object should also be created because the HTTP client needs @@ -2507,7 +2508,8 @@ void HAService::asyncSendHAReset(HttpClient& http_client, const HAConfig::PeerConfigPtr& config, PostRequestCallback post_request_action) { - ConstElementPtr command = CommandCreator::createHAReset(server_type_); + ConstElementPtr command = CommandCreator::createHAReset(config_->getThisServerConfig()->getName(), + server_type_); // Create HTTP/1.1 request including our command. PostHttpRequestJsonPtr request = boost::make_shared @@ -2867,7 +2869,8 @@ HAService::asyncSyncCompleteNotify(HttpClient& http_client, HostHttpHeader(remote_config->getUrl().getStrippedHostname())); remote_config->addBasicAuthHttpHeader(request); - request->setBodyAsJson(CommandCreator::createSyncCompleteNotify(server_type_)); + request->setBodyAsJson(CommandCreator::createSyncCompleteNotify(config_->getThisServerConfig()->getName(), + server_type_)); request->finalize(); // Response object should also be created because the HTTP client needs diff --git a/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc b/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc index d2bf2f4afe..3deeb7f66b 100644 --- a/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc +++ b/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc @@ -191,14 +191,24 @@ TEST(CommandCreatorTest, createDHCPEnable4) { // This test verifies that the ha-reset command sent to DHCPv4 server is correct. TEST(CommandCreatorTest, createHAReset4) { - ConstElementPtr command = CommandCreator::createHAReset(HAServerType::DHCPv4); - ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-reset", "dhcp4")); + ConstElementPtr command = CommandCreator::createHAReset("server1", HAServerType::DHCPv4); + ConstElementPtr arguments; + ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-reset", "dhcp4", arguments)); + auto server_name = arguments->get("server-name"); + ASSERT_TRUE(server_name); + ASSERT_EQ(Element::string, server_name->getType()); + EXPECT_EQ("server1", server_name->stringValue()); } // This test verifies that the ha-heartbeat command is correct. TEST(CommandCreatorTest, createHeartbeat4) { - ConstElementPtr command = CommandCreator::createHeartbeat(HAServerType::DHCPv4); - ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-heartbeat", "dhcp4")); + ConstElementPtr command = CommandCreator::createHeartbeat("server1", HAServerType::DHCPv4); + ConstElementPtr arguments; + ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-heartbeat", "dhcp4", arguments)); + auto server_name = arguments->get("server-name"); + ASSERT_TRUE(server_name); + ASSERT_EQ(Element::string, server_name->getType()); + EXPECT_EQ("server1", server_name->stringValue()); } // This test verifies that the command generated for the lease update @@ -326,8 +336,13 @@ TEST(CommandCreatorTest, createDHCPEnable6) { // This test verifies that the ha-reset command sent to DHCPv6 server is correct. TEST(CommandCreatorTest, createHAReset6) { - ConstElementPtr command = CommandCreator::createHAReset(HAServerType::DHCPv6); - ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-reset", "dhcp6")); + ConstElementPtr arguments; + ConstElementPtr command = CommandCreator::createHAReset("server1", HAServerType::DHCPv6); + ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-reset", "dhcp6", arguments)); + auto server_name = arguments->get("server-name"); + ASSERT_TRUE(server_name); + ASSERT_EQ(Element::string, server_name->getType()); + EXPECT_EQ("server1", server_name->stringValue()); } // This test verifies that the command generated for the lease update @@ -520,16 +535,25 @@ TEST(CommandCreatorTest, createMaintenanceNotify6) { // This test verifies that the ha-sync-complete-notify command sent to a // DHCPv4 server is correct. TEST(CommandCreatorTest, createSyncCompleteNotify4) { - ConstElementPtr command = CommandCreator::createSyncCompleteNotify(HAServerType::DHCPv4); - ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-sync-complete-notify", "dhcp4")); + ConstElementPtr command = CommandCreator::createSyncCompleteNotify("server1", HAServerType::DHCPv4); + ConstElementPtr arguments; + ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-sync-complete-notify", "dhcp4", arguments)); + auto server_name = arguments->get("server-name"); + ASSERT_TRUE(server_name); + ASSERT_EQ(Element::string, server_name->getType()); + EXPECT_EQ("server1", server_name->stringValue()); } // This test verifies that the ha-sync-complete-notify command sent to a // DHCPv4 server is correct. TEST(CommandCreatorTest, createSyncCompleteNotify6) { - ConstElementPtr command = CommandCreator::createSyncCompleteNotify(HAServerType::DHCPv6); + ConstElementPtr command = CommandCreator::createSyncCompleteNotify("server1", HAServerType::DHCPv6); ConstElementPtr arguments; - ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-sync-complete-notify", "dhcp6")); + ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "ha-sync-complete-notify", "dhcp6", arguments)); + auto server_name = arguments->get("server-name"); + ASSERT_TRUE(server_name); + ASSERT_EQ(Element::string, server_name->getType()); + EXPECT_EQ("server1", server_name->stringValue()); } } diff --git a/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc index 3798e02aca..311f42932e 100644 --- a/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc +++ b/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc @@ -571,13 +571,53 @@ TEST_F(HAImplTest, synchronizeHandler) { " command"); } + { + SCOPED_TRACE("Server name must be valid"); + testSynchronizeHandler("{" + " \"command\": \"ha-sync\"," + " \"arguments\": {" + " \"server-name\": \"server5\"," + " \"max-period\": 20" + " }" + "}", "server5 matches no configured 'server-name'"); + } + } -// Tests ha-continue command handler. +// Tests ha-continue command handler with a specified server name. TEST_F(HAImplTest, continueHandler) { HAImpl ha_impl; ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON("{" + "\"command\": \"ha-continue\"," + "\"arguments\": {" + " \"server-name\": \"server1\"" + "}" + "}"); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.continueHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA state machine is not paused."); +} + +// Tests ha-continue command handler without a server name. +TEST_F(HAImplTest, continueHandlerWithNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, @@ -597,6 +637,35 @@ TEST_F(HAImplTest, continueHandler) { checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA state machine is not paused."); } +// Tests ha-continue command handler with wrong server name. +TEST_F(HAImplTest, continueHandlerWithWrongServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON("{" + "\"command\": \"ha-continue\"," + "\"arguments\": {" + " \"server-name\": \"server5\"" + "}" + "}"); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.continueHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + // Tests status-get command processed handler. TEST_F(HAImplTest, statusGet) { HAImpl ha_impl; @@ -752,11 +821,43 @@ TEST_F(HAImplTest, statusGetPassiveBackup) { EXPECT_TRUE(isEquivalent(got, Element::fromJSON(expected))); } -// Test ha-maintenance-notify command handler. +// Test ha-maintenance-notify command handler with server name. TEST_F(HAImplTest, maintenanceNotify) { HAImpl ha_impl; ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-maintenance-notify\"," + " \"arguments\": {" + " \"cancel\": false," + " \"server-name\": \"server1\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.maintenanceNotifyHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "Server is in-maintenance state."); +} + +// Test ha-maintenance-notify command handler without server name. +TEST_F(HAImplTest, maintenanceNotifyNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, @@ -783,11 +884,75 @@ TEST_F(HAImplTest, maintenanceNotify) { checkAnswer(response, CONTROL_RESULT_SUCCESS, "Server is in-maintenance state."); } -// Test ha-reset command handler. +// Test ha-maintenance-notify command handler without server name. +TEST_F(HAImplTest, maintenanceNotifyBadServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-maintenance-notify\"," + " \"arguments\": {" + " \"cancel\": false," + " \"server-name\": \"server5\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.maintenanceNotifyHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + + +// Test ha-reset command handler with a specified server name. TEST_F(HAImplTest, haReset) { HAImpl ha_impl; ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-reset\"," + " \"arguments\": {" + " \"server-name\": \"server1\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.haResetHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA state machine already in WAITING state."); +} + +// Test ha-reset command handler without a specified server name. +TEST_F(HAImplTest, haResetNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + // Starting the service is required prior to running any callouts. NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, @@ -811,4 +976,313 @@ TEST_F(HAImplTest, haReset) { checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA state machine already in WAITING state."); } +// Test ha-reset command handler with a wrong server name. +TEST_F(HAImplTest, haResetBadServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-reset\"," + " \"arguments\": {" + " \"server-name\": \"server5\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.haResetHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + +// Test ha-heartbeat command handler with a specified server name. +TEST_F(HAImplTest, haHeartbeat) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-heartbeat\"," + " \"arguments\": {" + " \"server-name\": \"server1\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.heartbeatHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA peer status returned."); +} + +// Test ha-heartbeat command handler without a specified server name. +TEST_F(HAImplTest, haHeartbeatNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-heartbeat\"" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.heartbeatHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "HA peer status returned."); +} + +// Test ha-heartbeat command handler with a wrong server name. +TEST_F(HAImplTest, haHeartbeatBadServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-heartbeat\"," + " \"arguments\": {" + " \"server-name\": \"server5\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.heartbeatHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + +// Test ha-sync-complete-notify command handler with a specified server name. +TEST_F(HAImplTest, haSyncCompleteNotify) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-sync-complete-notify\"," + " \"arguments\": {" + " \"server-name\": \"server1\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.syncCompleteNotifyHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, + "Server successfully notified about the synchronization completion."); +} + +// Test ha-sync-complete-notify command handler without a specified server name. +TEST_F(HAImplTest, haSyncCompleteNotifyNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-sync-complete-notify\"" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.syncCompleteNotifyHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, + "Server successfully notified about the synchronization completion."); +} + +// Test ha-sync-complete-notify command handler with a wrong server name. +TEST_F(HAImplTest, haSyncCompleteNotifyBadServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-sync-complete-notify\"," + " \"arguments\": {" + " \"server-name\": \"server5\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.syncCompleteNotifyHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + +// Test ha-scopes command handler with a specified server name. +TEST_F(HAImplTest, haScopes) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-scopes\"," + " \"arguments\": {" + " \"scopes\": [ \"server1\", \"server2\" ]," + " \"server-name\": \"server1\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.scopesHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "New HA scopes configured."); +} + +// Test ha-scopes command handler without a specified server name. +TEST_F(HAImplTest, haScopesNoServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-scopes\"," + " \"arguments\": {" + " \"scopes\": [ \"server1\", \"server2\" ]" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.scopesHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_SUCCESS, "New HA scopes configured."); +} + +// Test ha-scopes command handler with a wrong server name. +TEST_F(HAImplTest, haScopesBadServerName) { + HAImpl ha_impl; + ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration())); + + // Starting the service is required prior to running any callouts. + NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4)); + ASSERT_NO_THROW(ha_impl.startServices(io_service_, network_state, + HAServerType::DHCPv4)); + + ConstElementPtr command = Element::fromJSON( + "{" + " \"command\": \"ha-scopes\"," + " \"arguments\": {" + " \"scopes\": [ \"server1\", \"server2\" ]," + " \"server-name\": \"server5\"" + " }" + "}" + ); + + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + callout_handle->setArgument("command", command); + + ASSERT_NO_THROW(ha_impl.scopesHandler(*callout_handle)); + + ConstElementPtr response; + callout_handle->getArgument("response", response); + ASSERT_TRUE(response); + + checkAnswer(response, CONTROL_RESULT_ERROR, "server5 matches no configured 'server-name'"); +} + + }