]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3106] Added server-name to HA commands
authorMarcin Siodelski <marcin@isc.org>
Mon, 16 Oct 2023 10:40:16 +0000 (12:40 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 29 Nov 2023 19:58:55 +0000 (20:58 +0100)
src/hooks/dhcp/high_availability/command_creator.cc
src/hooks/dhcp/high_availability/command_creator.h
src/hooks/dhcp/high_availability/ha_impl.cc
src/hooks/dhcp/high_availability/ha_impl.h
src/hooks/dhcp/high_availability/ha_service.cc
src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc
src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc

index 447bdc40696a85595e05a183c6fa10c50401c5a0..25395e0f144f816b8ca435513b00985c8cd037d4 100644 (file)
@@ -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);
 }
index a9ec33b72aea423cbcbd6f0401acd094c03e5369..04c81acd0b1e521aed27dbe5ca810966bc9f2598 100644 (file)
@@ -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<std::string> ha_commands4_;
index 7ab7701fe26bcac6c8e06651f61049cdc8babb11..af0fdb1f2d6621119a4b0d405873cc04f12659ed 100644 (file)
@@ -319,22 +319,43 @@ HAImpl::commandProcessed(hooks::CalloutHandle& callout_handle) {
         ElementPtr mutable_resp_args =
             boost::const_pointer_cast<Element>(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<void>(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<unsigned int>(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<void>(parseCommand(args, command));
 
+    HAServicePtr service;
     std::vector<std::string> 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<void>(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<void>(parseCommandWithArgs(args, command));
+    HAServicePtr service;
+    try {
+        // Retrieve arguments.
+        ConstElementPtr args;
+        static_cast<void>(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");
-    }
+        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);
+        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);
+    }
 }
 
 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<void>(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<void>(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<void>(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
index a4c360d3b7eb07df9b677dc2191055f5496bb73d..1cdc93ff8b285a154d0efc395e608fcaaf936304 100644 (file)
@@ -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.
index 9dc44a37b54bff11c60d2d90bd762cbbb71ebbcb..30605bc722906464263dc983240f231832408f05 100644 (file)
@@ -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<PostHttpRequestJson>
@@ -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
index d2bf2f4afe6106968384133f63449bd16c7be42f..3deeb7f66b103294bd6532d75074862d403b5323 100644 (file)
@@ -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());
 }
 
 }
index 3798e02aca74853e4017ee7c51f2038a64cb552f..311f42932e9bf8e73cc892a12acbb2365614c792 100644 (file)
@@ -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'");
+}
+
+
 }