summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc')
-rw-r--r--src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc814
1 files changed, 814 insertions, 0 deletions
diff --git a/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc
new file mode 100644
index 0000000..6282e77
--- /dev/null
+++ b/src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc
@@ -0,0 +1,814 @@
+// Copyright (C) 2018-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 <config.h>
+
+#include <ha_test.h>
+#include <ha_impl.h>
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/network_state.h>
+#include <hooks/hooks_manager.h>
+#include <testutils/gtest_utils.h>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+#include <string>
+
+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;
+
+namespace {
+
+/// @brief Structure that holds registered hook indexes.
+///
+/// This allows us to park packets.
+struct TestHooks {
+ /// @brief Index of leases4_committed callout.
+ int hook_index_leases4_committed_;
+
+ /// @brief Index of leases6_committed callout.
+ int hook_index_leases6_committed_;
+
+ /// @brief Constructor
+ ///
+ /// The constructor registers hook points for callout tests.
+ TestHooks() {
+ hook_index_leases4_committed_ =
+ HooksManager::registerHook("leases4_committed");
+ hook_index_leases6_committed_ =
+ HooksManager::registerHook("leases6_committed");
+ }
+};
+
+TestHooks test_hooks;
+
+/// @brief Derivation of the @c HAImpl which provides access to protected
+/// methods and members.
+class TestHAImpl : public HAImpl {
+public:
+
+ using HAImpl::config_;
+ using HAImpl::service_;
+};
+
+/// @brief Test fixture class for @c HAImpl.
+class HAImplTest : public HATest {
+public:
+
+ /// @brief Tests handler of a ha-sync command.
+ ///
+ /// It always expects that the error result is returned. The expected
+ /// error text should be provided as function argument.
+ ///
+ /// @param ha_sync_command command provided as text.
+ /// @param expected_response expected text response.
+ void testSynchronizeHandler(const std::string& ha_sync_command,
+ const std::string& expected_response) {
+ 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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ ConstElementPtr command = Element::fromJSON(ha_sync_command);
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("command", command);
+
+ ASSERT_NO_THROW(ha_impl.synchronizeHandler(*callout_handle));
+
+ ConstElementPtr response;
+ callout_handle->getArgument("response", response);
+ ASSERT_TRUE(response);
+
+ checkAnswer(response, CONTROL_RESULT_ERROR, expected_response);
+ }
+};
+
+// Tests that HAService object is created for DHCPv4 service.
+TEST_F(HAImplTest, startService) {
+ // Valid configuration must be provided prior to starting the service.
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration()));
+
+ // Network state is also required.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
+
+ // Start the service for DHCPv4 server.
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ // Make sure that the HA service has been created for the requested
+ // server type.
+ ASSERT_TRUE(ha_impl.service_);
+ EXPECT_EQ(HAServerType::DHCPv4, ha_impl.service_->getServerType());
+}
+
+// Tests that HAService object is created for DHCPv6 service.
+TEST_F(HAImplTest, startService6) {
+ // Valid configuration must be provided prior to starting the service.
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration()));
+
+ // Network state is also required.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
+
+ // Start the service for DHCPv4 server.
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv6));
+
+ // Make sure that the HA service has been created for the requested
+ // server type.
+ ASSERT_TRUE(ha_impl.service_);
+ EXPECT_EQ(HAServerType::DHCPv6, ha_impl.service_->getServerType());
+}
+
+// Tests for buffer4_receive callout implementation.
+TEST_F(HAImplTest, buffer4Receive) {
+ // Use hot-standby mode to make sure that this server instance is selected
+ // to process each received query. This is going to give predictable results.
+ ConstElementPtr ha_config = createValidJsonConfiguration(HAConfig::HOT_STANDBY);
+
+ // Create implementation object and configure it.
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(ha_config));
+
+ // Starting the service is required prior to running any callouts.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ // Initially the HA service is in the waiting state and serves no scopes.
+ // We need to explicitly enable the scope to be served.
+ ha_impl.service_->serveDefaultScopes();
+
+ // Create callout handle to be used for passing arguments to the
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+
+ // Create the BOOTP message. We can use it for testing message parsing
+ // failure case because BOOTP is not supported. We will later turn it
+ // into the DHCP message to test successful parsing.
+ std::vector<uint8_t> msg = {
+ 1, // BOOTREQUEST
+ 1, // ethernet
+ 6, // HW address length = 6
+ 0, // hops = 0
+ 1, 2, 3, 4, // xid
+ 0, 0, // secs = 0
+ 0, 0, // flags
+ 0, 0, 0, 0, // ciaddr = 0
+ 0, 0, 0, 0, // yiaddr = 0
+ 0, 0, 0, 0, // siaddr = 0
+ 0, 0, 0, 0, // giaddr = 0
+ 1, 2, 3, 4, 5, 6, // chaddr
+ };
+
+ // fill chaddr reminder, sname and file with zeros
+ msg.insert(msg.end(), 10 + 128 + 64, 0);
+
+ // Create DHCPv4 message object from the BOOTP message. This should be
+ // successful because the message is not parsed yet.
+ Pkt4Ptr query4(new Pkt4(&msg[0], msg.size()));
+
+ // Set buffer4_receive callout arguments.
+ callout_handle->setArgument("query4", query4);
+
+ // Invoke the buffer4_receive callout.
+ ASSERT_NO_THROW(ha_impl.buffer4Receive(*callout_handle));
+
+ // The BOOTP messages are not supported so trying to unpack the message
+ // should trigger an error. The callout should set the next step to
+ // DROP treating the message as malformed.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, callout_handle->getStatus());
+ // Malformed message should not be classified.
+ EXPECT_TRUE(query4->getClasses().empty());
+
+ // Turn this into the DHCP message by appending a magic cookie and the
+ // options.
+ std::vector<uint8_t> magic_cookie = {
+ 99, 130, 83, 99
+ };
+
+ // Provide DHCP message type option, truncated vendor option and domain name.
+ // Parsing this message should be successful but domain name following the
+ // truncated vendor option should be skipped.
+ std::vector<uint8_t> options = {
+ 53, 1, 1, // Message type = DHCPDISCOVER
+ 125, 6, // vendor options
+ 1, 2, 3, 4, // enterprise id
+ 8, 1, // data len 8 but the actual length is 1 (truncated options)
+ 15, 3, 'a', 'b', 'c' // Domain name = abc
+ };
+
+ // Append the magic cookie and the options to our BOOTP message.
+ msg.insert(msg.end(), magic_cookie.begin(), magic_cookie.end());
+ msg.insert(msg.end(), options.begin(), options.end());
+
+ // Create new query and pass it to the callout.
+ query4.reset(new Pkt4(&msg[0], msg.size()));
+ callout_handle->setArgument("query4", query4);
+
+ // Invoke the callout again.
+ ASSERT_NO_THROW(ha_impl.buffer4Receive(*callout_handle));
+
+ // This time the callout should set the next step to SKIP to indicate to
+ // the DHCP server that the message has been already parsed.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+
+ // The client class should be assigned to the message to indicate that the
+ // server1 should process this message.
+ ASSERT_EQ(2, query4->getClasses().size());
+ EXPECT_TRUE(query4->inClass("ALL"));
+ EXPECT_TRUE(query4->inClass("HA_server1"));
+
+ // Check that the message has been parsed. The DHCP message type should
+ // be set in this case.
+ EXPECT_EQ(DHCPDISCOVER, static_cast<int>(query4->getType()));
+ // Domain name should be skipped because the vendor option was truncated.
+ EXPECT_FALSE(query4->getOption(DHO_DOMAIN_NAME));
+}
+
+// Tests for buffer6_receive callout implementation.
+TEST_F(HAImplTest, buffer6Receive) {
+ // Use hot-standby mode to make sure that this server instance is selected
+ // to process each received query. This is going to give predictable results.
+ ConstElementPtr ha_config = createValidJsonConfiguration(HAConfig::HOT_STANDBY);
+
+ // Create implementation object and configure it.
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(ha_config));
+
+ // Starting the service is required prior to running any callouts.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv6));
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv6));
+
+ // Initially the HA service is in the waiting state and serves no scopes.
+ // We need to explicitly enable the scope to be served.
+ ha_impl.service_->serveDefaultScopes();
+
+ // Create callout handle to be used for passing arguments to the
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+
+ // Create DHCPv6 message. It initially has no transaction id so should be
+ // considered malformed.
+ std::vector<uint8_t> msg = {
+ 1, // Solicit
+ };
+
+ // Create DHCPv4 message object from the BOOTP message. This should be
+ // successful because the message is not parsed yet.
+ Pkt6Ptr query6(new Pkt6(&msg[0], msg.size()));
+
+ // Set buffer6_receive callout arguments.
+ callout_handle->setArgument("query6", query6);
+
+ // Invoke the buffer6_receive callout.
+ ASSERT_NO_THROW(ha_impl.buffer6Receive(*callout_handle));
+
+ // Our DHCP messages contains no transaction id so it should cause
+ // parsing error. The next step is set to DROP for malformed messages.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, callout_handle->getStatus());
+ // Malformed message should not be classified.
+ EXPECT_TRUE(query6->getClasses().empty());
+
+ // Append transaction id (3 bytes, each set to 1).
+ msg.insert(msg.end(), 3, 1);
+
+ // Include 3 options in the DHCPv6 message: ORO, truncated vendor option
+ // and the NIS Domain Name option. This should be parsed correctly but the
+ // last option should be skipped because of the preceding option being
+ // truncated.
+ std::vector<uint8_t> options = {
+ 0, 6, 0, 2, 0, 29, // option ORO requesting option 29
+ 0, 17, // vendor options
+ 0, 9, // option length = 10
+ 1, 2, 3, 4, // enterprise id
+ 0, 1, 0, 10, // code 1, (invalid) length = 10
+ 1, // ONLY 1 byte of data (truncated)
+ 0, 29, 0, 3, 'a', 'b', 'c' // NIS Domain Name = abc
+ };
+
+ msg.insert(msg.end(), options.begin(), options.end());
+
+ // Create new query and pass it to the callout.
+ query6.reset(new Pkt6(&msg[0], msg.size()));
+ callout_handle->setArgument("query6", query6);
+
+ // Invoke the callout again.
+ ASSERT_NO_THROW(ha_impl.buffer6Receive(*callout_handle));
+
+ // This time the callout should set the next step to SKIP to indicate to
+ // the DHCP server that the message has been already parsed.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+
+ // The client class should be assigned to the message to indicate that the
+ // server1 should process this message.
+ ASSERT_EQ(2, query6->getClasses().size());
+ EXPECT_TRUE(query6->inClass("ALL"));
+ EXPECT_TRUE(query6->inClass("HA_server1"));
+
+ // Check that the message has been parsed. The DHCP message type should
+ // be set in this case.
+ EXPECT_EQ(DHCPV6_SOLICIT, static_cast<int>(query6->getType()));
+ // Domain name should be skipped because the vendor option was truncated.
+ EXPECT_FALSE(query6->getOption(D6O_NIS_DOMAIN_NAME));
+}
+
+// Tests leases4_committed callout implementation.
+TEST_F(HAImplTest, leases4Committed) {
+ // Create implementation object and configure it.
+ TestHAImpl 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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ // Make sure we wait for the acks from the backup server to be able to
+ // test the case of sending lease updates even though the service is
+ // in the state in which the lease updates are normally not sent.
+ ha_impl.config_->setWaitBackupAck(true);
+
+ // Create callout handle to be used for passing arguments to the
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+
+ // Set the hook index so we can park packets.
+ callout_handle->setCurrentHook(test_hooks.hook_index_leases4_committed_);
+
+ // query4
+ Pkt4Ptr query4 = createMessage4(DHCPREQUEST, 1, 0, 0);
+ callout_handle->setArgument("query4", query4);
+
+ // leases4
+ Lease4CollectionPtr leases4(new Lease4Collection());
+ callout_handle->setArgument("leases4", leases4);
+
+ // deleted_leases4
+ Lease4CollectionPtr deleted_leases4(new Lease4Collection());
+ callout_handle->setArgument("deleted_leases4", deleted_leases4);
+
+ // Set initial status.
+ callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Park the packet.
+ HooksManager::park("leases4_committed", query4, []{});
+
+ // There are no leases so the callout should return.
+ ASSERT_NO_THROW(ha_impl.leases4Committed(*callout_handle));
+
+ // No updates are generated so the default status should not be modified.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query4));
+
+ // Create a lease and pass it to the callout, but temporarily disable lease
+ // updates.
+ HWAddrPtr hwaddr(new HWAddr(std::vector<uint8_t>(6, 1), HTYPE_ETHER));
+ Lease4Ptr lease4(new Lease4(IOAddress("192.1.2.3"), hwaddr,
+ static_cast<const uint8_t*>(0), 0,
+ 60, 0, 1));
+ leases4->push_back(lease4);
+ callout_handle->setArgument("leases4", leases4);
+
+ ha_impl.config_->setSendLeaseUpdates(false);
+
+ // Park the packet.
+ HooksManager::park("leases4_committed", query4, []{});
+
+ // Run the callout again.
+ ASSERT_NO_THROW(ha_impl.leases4Committed(*callout_handle));
+
+ // No updates are generated so the default status should not be modified.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query4));
+
+ // Enable updates and retry.
+ ha_impl.config_->setSendLeaseUpdates(true);
+ callout_handle->setArgument("leases4", leases4);
+
+ // Park the packet.
+ HooksManager::park("leases4_committed", query4, []{});
+
+ // Run the callout again.
+ ASSERT_NO_THROW(ha_impl.leases4Committed(*callout_handle));
+
+ // This time the lease update should be generated and the status should
+ // be set to "park".
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_PARK, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query4));
+}
+
+// Tests leases6_committed callout implementation.
+TEST_F(HAImplTest, leases6Committed) {
+ // Create implementation object and configure it.
+ TestHAImpl 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::DHCPv6));
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv6));
+
+ // Make sure we wait for the acks from the backup server to be able to
+ // test the case of sending lease updates even though the service is
+ // in the state in which the lease updates are normally not sent.
+ ha_impl.config_->setWaitBackupAck(true);
+
+ // Create callout handle to be used for passing arguments to the
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+
+ // Set the hook index so we can park packets.
+ callout_handle->setCurrentHook(test_hooks.hook_index_leases6_committed_);
+
+ // query6
+ Pkt6Ptr query6 = createMessage6(DHCPV6_REQUEST, 1, 0);
+ callout_handle->setArgument("query6", query6);
+
+ // leases6
+ Lease6CollectionPtr leases6(new Lease6Collection());
+ callout_handle->setArgument("leases6", leases6);
+
+ // deleted_leases6
+ Lease6CollectionPtr deleted_leases6(new Lease6Collection());
+ callout_handle->setArgument("deleted_leases6", deleted_leases6);
+
+ // Set initial status.
+ callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Park the packet.
+ HooksManager::park("leases6_committed", query6, []{});
+
+ // There are no leases so the callout should return.
+ ASSERT_NO_THROW(ha_impl.leases6Committed(*callout_handle));
+
+ // No updates are generated so the default status should not be modified.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query6));
+
+ // Create a lease and pass it to the callout, but temporarily disable lease
+ // updates.
+ DuidPtr duid(new DUID(std::vector<uint8_t>(8, 2)));
+ Lease6Ptr lease6(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::cafe"), duid,
+ 1234, 50, 60, 1));
+ leases6->push_back(lease6);
+ callout_handle->setArgument("leases6", leases6);
+
+ ha_impl.config_->setSendLeaseUpdates(false);
+
+ // Park the packet.
+ HooksManager::park("leases6_committed", query6, []{});
+
+ // Run the callout again.
+ ASSERT_NO_THROW(ha_impl.leases6Committed(*callout_handle));
+
+ // No updates are generated so the default status should not be modified.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query6));
+
+ // Enable updates and retry.
+ ha_impl.config_->setSendLeaseUpdates(true);
+ callout_handle->setArgument("leases6", leases6);
+
+ // Park the packet.
+ HooksManager::park("leases6_committed", query6, []{});
+
+ // Run the callout again.
+ ASSERT_NO_THROW(ha_impl.leases6Committed(*callout_handle));
+
+ // This time the lease update should be generated and the status should
+ // be set to "park".
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_PARK, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query6));
+}
+
+// Tests ha-sync command handler with correct and incorrect arguments.
+TEST_F(HAImplTest, synchronizeHandler) {
+ {
+ // This syntax is correct. The error returned is simply a result of
+ // trying to connect to the server which is offline, which should
+ // result in connection refused error.
+ SCOPED_TRACE("Correct syntax");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\","
+ " \"arguments\": {"
+ " \"server-name\": \"server2\""
+ " }"
+ "}", "Connection refused");
+ }
+
+ {
+ SCOPED_TRACE("No arguments");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\""
+ "}", "arguments not found in the 'ha-sync' command");
+ }
+
+ {
+ SCOPED_TRACE("No server name");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\","
+ " \"arguments\": {"
+ " \"max-period\": 20"
+ " }"
+ "}", "'server-name' is mandatory for the 'ha-sync' command");
+ }
+
+ {
+ SCOPED_TRACE("Server name is not a string");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\","
+ " \"arguments\": {"
+ " \"server-name\": 20"
+ " }"
+ "}", "'server-name' must be a string in the 'ha-sync' command");
+ }
+
+ {
+ SCOPED_TRACE("Max period is not a number");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\","
+ " \"arguments\": {"
+ " \"server-name\": \"server2\","
+ " \"max-period\": \"20\""
+ " }"
+ "}", "'max-period' must be a positive integer in the 'ha-sync'"
+ " command");
+ }
+
+ {
+ SCOPED_TRACE("Max period must be positive");
+ testSynchronizeHandler("{"
+ " \"command\": \"ha-sync\","
+ " \"arguments\": {"
+ " \"server-name\": \"server2\","
+ " \"max-period\": \"20\""
+ " }"
+ "}", "'max-period' must be a positive integer in the 'ha-sync'"
+ " command");
+ }
+
+}
+
+// Tests ha-continue command handler.
+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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ ConstElementPtr command = Element::fromJSON("{ \"command\": \"ha-continue\" }");
+
+ 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 status-get command processed handler.
+TEST_F(HAImplTest, statusGet) {
+ 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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ std::string name = "status-get";
+ ConstElementPtr response =
+ Element::fromJSON("{ \"arguments\": { \"pid\": 1 }, \"result\": 0 }");
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("name", name);
+ callout_handle->setArgument("response", response);
+
+ ASSERT_NO_THROW(ha_impl.commandProcessed(*callout_handle));
+
+ ConstElementPtr got;
+ callout_handle->getArgument("response", got);
+ ASSERT_TRUE(got);
+
+ std::string expected =
+ "{"
+ " \"arguments\": {"
+ " \"high-availability\": ["
+ " {"
+ " \"ha-mode\": \"load-balancing\","
+ " \"ha-servers\": {"
+ " \"local\": {"
+ " \"role\": \"primary\","
+ " \"scopes\": [ ],"
+ " \"state\": \"waiting\""
+ " },"
+ " \"remote\": {"
+ " \"age\": 0,"
+ " \"in-touch\": false,"
+ " \"last-scopes\": [ ],"
+ " \"last-state\": \"\","
+ " \"role\": \"secondary\","
+ " \"communication-interrupted\": false,"
+ " \"connecting-clients\": 0,"
+ " \"unacked-clients\": 0,"
+ " \"unacked-clients-left\": 0,"
+ " \"analyzed-packets\": 0"
+ " }"
+ " }"
+ " }"
+ " ],"
+ " \"pid\": 1"
+ " },"
+ " \"result\": 0"
+ "}";
+ EXPECT_TRUE(isEquivalent(got, Element::fromJSON(expected)));
+}
+
+// Tests status-get command processed handler for backup server.
+TEST_F(HAImplTest, statusGetBackupServer) {
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(createValidJsonConfiguration()));
+ ha_impl.config_->setThisServerName("server3");
+
+ // Starting the service is required prior to running any callouts.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ std::string name = "status-get";
+ ConstElementPtr response =
+ Element::fromJSON("{ \"arguments\": { \"pid\": 1 }, \"result\": 0 }");
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("name", name);
+ callout_handle->setArgument("response", response);
+
+ ASSERT_NO_THROW(ha_impl.commandProcessed(*callout_handle));
+
+ ConstElementPtr got;
+ callout_handle->getArgument("response", got);
+ ASSERT_TRUE(got);
+
+ std::string expected =
+ "{"
+ " \"arguments\": {"
+ " \"high-availability\": ["
+ " {"
+ " \"ha-mode\": \"load-balancing\","
+ " \"ha-servers\": {"
+ " \"local\": {"
+ " \"role\": \"backup\","
+ " \"scopes\": [ ],"
+ " \"state\": \"backup\""
+ " }"
+ " }"
+ " }"
+ " ],"
+ " \"pid\": 1"
+ " },"
+ " \"result\": 0"
+ "}";
+ EXPECT_TRUE(isEquivalent(got, Element::fromJSON(expected)));
+}
+
+// Tests status-get command processed handler for primary server being in the
+// passive-backup state.
+TEST_F(HAImplTest, statusGetPassiveBackup) {
+ TestHAImpl ha_impl;
+ ASSERT_NO_THROW(ha_impl.configure(createValidPassiveBackupJsonConfiguration()));
+
+ // Starting the service is required prior to running any callouts.
+ NetworkStatePtr network_state(new NetworkState(NetworkState::DHCPv4));
+ ASSERT_NO_THROW(ha_impl.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ std::string name = "status-get";
+ ConstElementPtr response =
+ Element::fromJSON("{ \"arguments\": { \"pid\": 1 }, \"result\": 0 }");
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("name", name);
+ callout_handle->setArgument("response", response);
+
+ ASSERT_NO_THROW(ha_impl.commandProcessed(*callout_handle));
+
+ ConstElementPtr got;
+ callout_handle->getArgument("response", got);
+ ASSERT_TRUE(got);
+
+ std::string expected =
+ "{"
+ " \"arguments\": {"
+ " \"high-availability\": ["
+ " {"
+ " \"ha-mode\": \"passive-backup\","
+ " \"ha-servers\": {"
+ " \"local\": {"
+ " \"role\": \"primary\","
+ " \"scopes\": [ \"server1\" ],"
+ " \"state\": \"passive-backup\""
+ " }"
+ " }"
+ " }"
+ " ],"
+ " \"pid\": 1"
+ " },"
+ " \"result\": 0"
+ "}";
+ EXPECT_TRUE(isEquivalent(got, Element::fromJSON(expected)));
+}
+
+// Test ha-maintenance-notify command handler.
+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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ ConstElementPtr command = Element::fromJSON(
+ "{"
+ " \"command\": \"ha-maintenance-notify\","
+ " \"arguments\": {"
+ " \"cancel\": false"
+ " }"
+ "}"
+ );
+
+ 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-reset command handler.
+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.startService(io_service_, network_state,
+ HAServerType::DHCPv4));
+
+ ConstElementPtr command = Element::fromJSON(
+ "{"
+ " \"command\": \"ha-reset\""
+ "}"
+ );
+
+ 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.");
+}
+
+}