summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc')
-rw-r--r--src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc833
1 files changed, 833 insertions, 0 deletions
diff --git a/src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc b/src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc
new file mode 100644
index 0000000..6c14a6d
--- /dev/null
+++ b/src/hooks/dhcp/high_availability/tests/communication_state_unittest.cc
@@ -0,0 +1,833 @@
+// 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 <asiolink/asio_wrapper.h>
+#include <communication_state.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <exceptions/exceptions.h>
+#include <http/date_time.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <limits>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::ha;
+using namespace isc::ha::test;
+using namespace isc::http;
+using namespace isc::util;
+
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
+
+namespace {
+
+
+/// @brief Test fixture class for @c CommunicationState class.
+class CommunicationStateTest : public HATest {
+public:
+
+ /// @brief Constructor.
+ CommunicationStateTest()
+ : state_(io_service_, createValidConfiguration()),
+ state6_(io_service_, createValidConfiguration()) {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~CommunicationStateTest() {
+ MultiThreadingMgr::instance().setMode(false);
+ io_service_->poll();
+ }
+
+ /// @brief Verifies that the partner state is set and retrieved correctly.
+ void partnerStateTest();
+
+ /// @brief Verifies that the partner's scopes are set and retrieved correctly.
+ void partnerScopesTest();
+
+ /// @brief Verifies that the object is poked right after construction.
+ void initialDurationTest();
+
+ /// @brief Verifies that poking the state updates the returned duration.
+ void pokeTest();
+
+ /// @brief Test that heartbeat function is triggered.
+ void heartbeatTest();
+
+ /// @brief Test that invalid values provided to startHeartbeat are rejected.
+ void startHeartbeatInvalidValuesTest();
+
+ /// @brief Test that failure detection works properly for DHCPv4 case.
+ void detectFailureV4Test();
+
+ /// @brief This test verifies that it is possible to disable analysis of the DHCPv4
+ /// packets in which case the partner's failure is assumed when there is
+ /// no connection over the control channel.
+ void failureDetectionDisabled4Test();
+
+ /// @brief Test that failure detection works properly for DHCPv6 case.
+ void detectFailureV6Test();
+
+ /// @brief This test verifies that it is possible to disable analysis of the DHCPv6
+ /// packets in which case the partner's failure is assumed when there is
+ /// no connection over the control channel.
+ void failureDetectionDisabled6Test();
+
+ /// @brief This test verifies that the clock skew is checked properly by the
+ /// clockSkewShouldWarn and clockSkewShouldTerminate functions.
+ void clockSkewTest();
+
+ /// @brief This test verifies that the clock skew value is formatted correctly
+ /// for logging.
+ void logFormatClockSkewTest();
+
+ /// @brief Tests that the communication state report is correct.
+ void getReportTest();
+
+ /// @brief Tests unusual values used to create the report.
+ void getReportDefaultValuesTest();
+
+ /// @brief Tests that unsent updates count can be incremented and fetched.
+ void getUnsentUpdateCountTest();
+
+ /// @brief Tests that unsent updates count from partner can be set and
+ /// a difference from previous value detected.
+ void hasPartnerNewUnsentUpdatesTest();
+
+ /// @brief Returns test heartbeat implementation.
+ ///
+ /// @return Pointer to heartbeat implementation function under test.
+ std::function<void()> getHeartbeatImpl() {
+ return (std::bind(&CommunicationStateTest::heartbeatImpl, this));
+ }
+
+ /// @brief Test heartbeat implementation.
+ ///
+ /// It simply pokes the communication state object. Note that the real
+ /// implementation would send an actual heartbeat command prior to
+ /// poking the state.
+ void heartbeatImpl() {
+ state_.poke();
+ }
+
+ /// @brief Communication state object used throughout the tests.
+ NakedCommunicationState4 state_;
+
+ /// @brief Communication state for IPv6 used throughout the tests.
+ NakedCommunicationState6 state6_;
+};
+
+// Verifies that the partner state is set and retrieved correctly.
+void
+CommunicationStateTest::partnerStateTest() {
+ // Initially the state is unknown.
+ EXPECT_LT(state_.getPartnerState(), 0);
+
+ state_.setPartnerState("hot-standby");
+ EXPECT_EQ(HA_HOT_STANDBY_ST, state_.getPartnerState());
+
+ state_.setPartnerState("load-balancing");
+ EXPECT_EQ(HA_LOAD_BALANCING_ST, state_.getPartnerState());
+
+ state_.setPartnerState("partner-down");
+ EXPECT_EQ(HA_PARTNER_DOWN_ST, state_.getPartnerState());
+
+ state_.setPartnerState("ready");
+ EXPECT_EQ(HA_READY_ST, state_.getPartnerState());
+
+ state_.setPartnerState("syncing");
+ EXPECT_EQ(HA_SYNCING_ST, state_.getPartnerState());
+
+ state_.setPartnerState("terminated");
+ EXPECT_EQ(HA_TERMINATED_ST, state_.getPartnerState());
+
+ state_.setPartnerState("waiting");
+ EXPECT_EQ(HA_WAITING_ST, state_.getPartnerState());
+
+ state_.setPartnerState("unavailable");
+ EXPECT_EQ(HA_UNAVAILABLE_ST, state_.getPartnerState());
+
+ // An attempt to set unsupported value should result in exception.
+ EXPECT_THROW(state_.setPartnerState("unsupported"), BadValue);
+}
+
+// Verifies that the partner's scopes are set and retrieved correctly.
+void
+CommunicationStateTest::partnerScopesTest() {
+ // Initially, the scopes should be empty.
+ ASSERT_TRUE(state_.getPartnerScopes().empty());
+
+ // Set new partner scopes.
+ ASSERT_NO_THROW(
+ state_.setPartnerScopes(Element::fromJSON("[ \"server1\", \"server2\" ]"))
+ );
+
+ // Get them back.
+ auto returned = state_.getPartnerScopes();
+ EXPECT_EQ(2, returned.size());
+ EXPECT_EQ(1, returned.count("server1"));
+ EXPECT_EQ(1, returned.count("server2"));
+
+ // Override the scopes.
+ ASSERT_NO_THROW(
+ state_.setPartnerScopes(Element::fromJSON("[ \"server1\" ]"))
+ );
+ returned = state_.getPartnerScopes();
+ EXPECT_EQ(1, returned.size());
+ EXPECT_EQ(1, returned.count("server1"));
+
+ // Clear the scopes.
+ ASSERT_NO_THROW(
+ state_.setPartnerScopes(Element::fromJSON("[ ]"))
+ );
+ returned = state_.getPartnerScopes();
+ EXPECT_TRUE(returned.empty());
+
+ // An attempt to set invalid JSON should fail.
+ EXPECT_THROW(state_.setPartnerScopes(Element::fromJSON("{ \"not-a-list\": 1 }")),
+ BadValue);
+}
+
+// Verifies that the object is poked right after construction.
+void
+CommunicationStateTest::initialDurationTest() {
+ EXPECT_TRUE(state_.isPoked());
+}
+
+// Verifies that poking the state updates the returned duration.
+void
+CommunicationStateTest::pokeTest() {
+ state_.modifyPokeTime(-30);
+ ASSERT_GE(state_.getDurationInMillisecs(), 30000);
+ ASSERT_TRUE(state_.isCommunicationInterrupted());
+ ASSERT_NO_THROW(state_.poke());
+ EXPECT_TRUE(state_.isPoked());
+ EXPECT_FALSE(state_.isCommunicationInterrupted());
+}
+
+// Test that heartbeat function is triggered.
+void
+CommunicationStateTest::heartbeatTest() {
+ // Set poke time to the past and expect that the object is considered
+ // not poked.
+ state_.modifyPokeTime(-30);
+ EXPECT_FALSE(state_.isPoked());
+
+ // Run heartbeat every 1 second.
+ ASSERT_NO_THROW(state_.startHeartbeat(1, getHeartbeatImpl()));
+ runIOService(1200);
+
+ // After > than 1 second the state should have been poked.
+ EXPECT_TRUE(state_.isPoked());
+
+ // Repeat the test.
+ state_.modifyPokeTime(-30);
+ EXPECT_FALSE(state_.isPoked());
+ ASSERT_NO_THROW(state_.startHeartbeat(1, getHeartbeatImpl()));
+ runIOService(1200);
+ EXPECT_TRUE(state_.isPoked());
+}
+
+// Test that invalid values provided to startHeartbeat are rejected.
+void
+CommunicationStateTest::startHeartbeatInvalidValuesTest() {
+ EXPECT_THROW(state_.startHeartbeat(-1, getHeartbeatImpl()), BadValue);
+ EXPECT_THROW(state_.startHeartbeat(0, getHeartbeatImpl()), BadValue);
+ EXPECT_THROW(state_.startHeartbeat(1, 0), BadValue);
+}
+
+// Test that failure detection works properly for DHCPv4 case.
+void
+CommunicationStateTest::detectFailureV4Test() {
+ // Initially, there should be no unacked clients recorded.
+ ASSERT_FALSE(state_.failureDetected());
+ EXPECT_EQ(0, state_.getUnackedClientsCount());
+ EXPECT_EQ(0, state_.getConnectingClientsCount());
+ EXPECT_EQ(0, state_.getAnalyzedMessagesCount());
+
+ // The maximum number of unacked clients is 10. Let's provide 10
+ // DHCPDISCOVER messages with the "secs" value of 15 which exceeds
+ // the threshold of 10. All these clients should be recorded as
+ // unacked.
+ for (uint8_t i = 0; i < 10; ++i) {
+ // Some of the requests have no client identifier to test that
+ // we don't fall over if the client identifier is null.
+ const uint8_t client_id_seed = (i < 5 ? i : 0);
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, i,
+ client_id_seed,
+ 15)));
+ // We don't exceed the maximum of number of unacked clients so the
+ // partner failure shouldn't be reported.
+ ASSERT_FALSE(state_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i);
+ }
+ EXPECT_EQ(10, state_.getUnackedClientsCount());
+ EXPECT_EQ(10, state_.getConnectingClientsCount());
+ EXPECT_EQ(10, state_.getAnalyzedMessagesCount());
+
+ // Let's provide similar set of requests but this time the "secs" field is
+ // below the threshold. They should not be counted as failures. Also,
+ // all of these requests have client identifier.
+ for (uint8_t i = 0; i < 10; ++i) {
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, i, i,
+ 9)));
+ ASSERT_FALSE(state_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i);
+ }
+ EXPECT_EQ(10, state_.getUnackedClientsCount());
+ EXPECT_EQ(15, state_.getConnectingClientsCount());
+ EXPECT_EQ(20, state_.getAnalyzedMessagesCount());
+
+ // Let's create a message from a new (not recorded yet) client with the
+ // "secs" field value below the threshold. It should not be counted as failure.
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 10, 10, 6)));
+
+ // Still no failure.
+ ASSERT_FALSE(state_.failureDetected());
+ EXPECT_EQ(10, state_.getUnackedClientsCount());
+ EXPECT_EQ(16, state_.getConnectingClientsCount());
+ EXPECT_EQ(21, state_.getAnalyzedMessagesCount());
+
+ // Let's repeat one of the requests which already have been recorded as
+ // unacked but with a greater value of "secs" field. This should not
+ // be counted because only new clients count.
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 3, 3, 20)));
+ ASSERT_FALSE(state_.failureDetected());
+ EXPECT_EQ(10, state_.getUnackedClientsCount());
+ EXPECT_EQ(16, state_.getConnectingClientsCount());
+ EXPECT_EQ(22, state_.getAnalyzedMessagesCount());
+
+ // This time let's simulate a client with a MAC address already recorded but
+ // with a client identifier. This should be counted as a new unacked request.
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 7, 7, 15)));
+ ASSERT_TRUE(state_.failureDetected());
+ EXPECT_EQ(11, state_.getUnackedClientsCount());
+ EXPECT_EQ(16, state_.getConnectingClientsCount());
+ EXPECT_EQ(23, state_.getAnalyzedMessagesCount());
+
+ // Poking should cause all counters to reset as it is an indication that the
+ // control connection has been re-established.
+ ASSERT_NO_THROW(state_.poke());
+
+ // We're back to no failure state.
+ EXPECT_FALSE(state_.failureDetected());
+ EXPECT_EQ(0, state_.getUnackedClientsCount());
+ EXPECT_EQ(0, state_.getConnectingClientsCount());
+ EXPECT_EQ(0, state_.getAnalyzedMessagesCount());
+
+ // Send 11 DHCPDISCOVER messages with the "secs" field bytes swapped. Swapping
+ // bytes was reported for some misbehaving Windows clients. The server should
+ // detect bytes swapping when second byte is 0 and the first byte is non-zero.
+ // However, the first byte is equal to 5 which is below our threshold so none
+ // of the requests below should count as unacked.
+ for (uint8_t i = 0; i < 11; ++i) {
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, i, i,
+ 0x0500)));
+ ASSERT_FALSE(state_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i)
+ << " when testing swapped secs field bytes";
+ }
+ EXPECT_EQ(0, state_.getUnackedClientsCount());
+ EXPECT_EQ(11, state_.getConnectingClientsCount());
+ EXPECT_EQ(11, state_.getAnalyzedMessagesCount());
+
+ // Repeat the same test, but this time either the first byte exceeds the
+ // secs threshold or the second byte is non-zero. All should be counted
+ // as unacked.
+ for (uint8_t i = 0; i < 10; ++i) {
+ uint16_t secs = (i % 2 == 0 ? 0x0F00 : 0x0501);
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, i, i,
+ secs)));
+ ASSERT_FALSE(state_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i)
+ << " when testing swapped secs field bytes";
+ }
+
+ // This last message should cause the failure state.
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 11, 11,
+ 0x30)));
+ EXPECT_TRUE(state_.failureDetected());
+ EXPECT_EQ(11, state_.getUnackedClientsCount());
+ EXPECT_EQ(12, state_.getConnectingClientsCount());
+ EXPECT_EQ(22, state_.getAnalyzedMessagesCount());
+}
+
+// This test verifies that it is possible to disable analysis of the DHCPv4
+// packets in which case the partner's failure is assumed when there is
+// no connection over the control channel.
+void
+CommunicationStateTest::failureDetectionDisabled4Test() {
+ state_.config_->setMaxUnackedClients(0);
+ EXPECT_TRUE(state_.failureDetected());
+}
+
+// Test that failure detection works properly for DHCPv6 case.
+void
+CommunicationStateTest::detectFailureV6Test() {
+ // Initially, there should be no unacked clients recorded.
+ ASSERT_FALSE(state6_.failureDetected());
+ EXPECT_EQ(0, state6_.getUnackedClientsCount());
+ EXPECT_EQ(0, state6_.getConnectingClientsCount());
+ EXPECT_EQ(0, state6_.getAnalyzedMessagesCount());
+
+ // The maximum number of unacked clients is 10. Let's provide 10
+ // Solicit messages with the "elapsed time" value of 1500 which exceeds
+ // the threshold of 10000ms. Note that the elapsed time value is provided
+ // in 1/100s of 1 second. All these clients should be recorded as
+ // unacked.
+ for (uint8_t i = 0; i < 10; ++i) {
+ ASSERT_NO_THROW(state6_.analyzeMessage(createMessage6(DHCPV6_SOLICIT, i,
+ 1500)));
+ // We don't exceed the maximum number of unacked clients so the
+ // partner failure shouldn't be reported.
+ ASSERT_FALSE(state6_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i);
+ }
+ EXPECT_EQ(10, state6_.getUnackedClientsCount());
+ EXPECT_EQ(10, state6_.getConnectingClientsCount());
+ EXPECT_EQ(10, state6_.getAnalyzedMessagesCount());
+
+ // Let's provide similar set of requests but this time the "elapsed time" is
+ // below the threshold. This should not reduce the number of unacked or new
+ // clients.
+ for (uint8_t i = 0; i < 10; ++i) {
+ ASSERT_NO_THROW(state6_.analyzeMessage(createMessage6(DHCPV6_SOLICIT, i,
+ 900)));
+ ASSERT_FALSE(state6_.failureDetected())
+ << "failure detected for the request number "
+ << static_cast<int>(i);
+ }
+ EXPECT_EQ(10, state6_.getUnackedClientsCount());
+ EXPECT_EQ(10, state6_.getConnectingClientsCount());
+ EXPECT_EQ(20, state6_.getAnalyzedMessagesCount());
+
+ // Let's create a message from a new (not recorded yet) client with the
+ // "elapsed time" value below the threshold. It should not count as failure.
+ ASSERT_NO_THROW(state6_.analyzeMessage(createMessage6(DHCPV6_SOLICIT, 10, 600)));
+
+ // Still no failure.
+ ASSERT_FALSE(state6_.failureDetected());
+ EXPECT_EQ(10, state6_.getUnackedClientsCount());
+ EXPECT_EQ(11, state6_.getConnectingClientsCount());
+ EXPECT_EQ(21, state6_.getAnalyzedMessagesCount());
+
+ // Let's repeat one of the requests which already have been recorded as
+ // unacked but with a greater value of "elapsed time". This should not
+ // be counted because only new clients count.
+ ASSERT_NO_THROW(state6_.analyzeMessage(createMessage6(DHCPV6_SOLICIT, 3, 2000)));
+ ASSERT_FALSE(state6_.failureDetected());
+ EXPECT_EQ(10, state6_.getUnackedClientsCount());
+ EXPECT_EQ(11, state6_.getConnectingClientsCount());
+ EXPECT_EQ(22, state6_.getAnalyzedMessagesCount());
+
+ // New unacked client should cause failure to be detected.
+ ASSERT_NO_THROW(state6_.analyzeMessage(createMessage6(DHCPV6_SOLICIT, 11, 1500)));
+ ASSERT_TRUE(state6_.failureDetected());
+ EXPECT_EQ(11, state6_.getUnackedClientsCount());
+ EXPECT_EQ(12, state6_.getConnectingClientsCount());
+ EXPECT_EQ(23, state6_.getAnalyzedMessagesCount());
+
+ // Poking should cause all counters to reset as it is an indication that the
+ // control connection has been re-established.
+ ASSERT_NO_THROW(state6_.poke());
+
+ // We're back to no failure state.
+ EXPECT_FALSE(state6_.failureDetected());
+ EXPECT_EQ(0, state6_.getUnackedClientsCount());
+ EXPECT_EQ(0, state6_.getConnectingClientsCount());
+ EXPECT_EQ(0, state6_.getAnalyzedMessagesCount());
+}
+
+// This test verifies that it is possible to disable analysis of the DHCPv6
+// packets in which case the partner's failure is assumed when there is
+// no connection over the control channel.
+void
+CommunicationStateTest::failureDetectionDisabled6Test() {
+ state6_.config_->setMaxUnackedClients(0);
+ EXPECT_TRUE(state6_.failureDetected());
+}
+
+// This test verifies that the clock skew is checked properly by the
+// clockSkewShouldWarn and clockSkewShouldTerminate functions.
+void
+CommunicationStateTest::clockSkewTest() {
+ // Default clock skew is 0.
+ EXPECT_FALSE(state_.clockSkewShouldWarn());
+ EXPECT_FALSE(state_.clockSkewShouldTerminate());
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+
+ // Partner time is ahead by 15s (no warning).
+ state_.clock_skew_ += boost::posix_time::time_duration(0, 0, 15);
+ EXPECT_FALSE(state_.clockSkewShouldWarn());
+ EXPECT_FALSE(state_.clockSkewShouldTerminate());
+
+ // Partner time is behind by 15s (no warning).
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+ state_.clock_skew_ -= boost::posix_time::time_duration(0, 0, 15);
+ EXPECT_FALSE(state_.clockSkewShouldWarn());
+ EXPECT_FALSE(state_.clockSkewShouldTerminate());
+
+ // Partner time is ahead by 35s (warning).
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+ state_.clock_skew_ += boost::posix_time::time_duration(0, 0, 35);
+ EXPECT_TRUE(state_.clockSkewShouldWarn());
+ EXPECT_FALSE(state_.clockSkewShouldTerminate());
+
+ // Partner time is behind by 35s (warning).
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+ state_.clock_skew_ -= boost::posix_time::time_duration(0, 0, 35);
+ state_.last_clock_skew_warn_ = boost::posix_time::ptime();
+ EXPECT_TRUE(state_.clockSkewShouldWarn());
+ EXPECT_FALSE(state_.clockSkewShouldTerminate());
+
+ // Due to the gating mechanism this should not return true the second
+ // time.
+ EXPECT_FALSE(state_.clockSkewShouldWarn());
+
+ // But should warn if the warning was issued more than 60 seconds ago.
+ state_.last_clock_skew_warn_ -= boost::posix_time::time_duration(0, 1, 30);
+ EXPECT_TRUE(state_.clockSkewShouldWarn());
+
+ // Partner time is ahead by 65s (warning and terminate).
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+ state_.clock_skew_ += boost::posix_time::time_duration(0, 1, 5);
+ state_.last_clock_skew_warn_ = boost::posix_time::ptime();
+ EXPECT_TRUE(state_.clockSkewShouldWarn());
+ EXPECT_TRUE(state_.clockSkewShouldTerminate());
+
+ // Partner time is behind by 65s (warning and terminate).
+ state_.setPartnerTime(HttpDateTime().rfc1123Format());
+ state_.clock_skew_ -= boost::posix_time::time_duration(0, 1, 5);
+ state_.last_clock_skew_warn_ = boost::posix_time::ptime();
+ EXPECT_TRUE(state_.clockSkewShouldWarn());
+ EXPECT_TRUE(state_.clockSkewShouldTerminate());
+}
+
+// This test verifies that the clock skew value is formatted correctly
+// for logging.
+void
+CommunicationStateTest::logFormatClockSkewTest() {
+ // Make sure logFormatClockSkew() does not throw if called prior
+ // the first call to setPartnerTime().
+ std::string log;
+ ASSERT_NO_THROW(log = state_.logFormatClockSkew());
+ EXPECT_EQ(std::string("skew not initialized"), log);
+
+ // Get current time.
+ boost::posix_time::ptime now = HttpDateTime().getPtime();
+
+ // Partner time is ahead by 15s.
+ boost::posix_time::time_duration offset(0,0,15);
+ state_.setPartnerTime(HttpDateTime(now + offset).rfc1123Format());
+ ASSERT_NO_THROW(log = state_.logFormatClockSkew());
+
+ // The logFormatClockSkew uses the clock_skew_ value which is computed
+ // at the time when setPartnerTime() is called. Therefore, we can't
+ // just assume that it is 15s because it may be already slightly off.
+ // Let's compare the output with the actual clock_skew_ value remembered
+ // in the state_ instance.
+ ASSERT_FALSE(state_.clock_skew_.is_special());
+ ASSERT_FALSE(state_.clock_skew_.is_negative());
+ std::ostringstream s;
+ s << state_.clock_skew_.seconds() << "s ahead";
+ EXPECT_TRUE(log.find(s.str()) != std::string::npos) <<
+ " log content wrong: " << log;
+
+ // Partner time is behind by 15s.
+ state_.setPartnerTime(HttpDateTime(now - offset).rfc1123Format());
+ ASSERT_NO_THROW(log = state_.logFormatClockSkew());
+
+ // Again, extract the actual clock skew remembered in the state_ instance.
+ ASSERT_FALSE(state_.clock_skew_.is_special());
+ auto skew = state_.clock_skew_;
+
+ // It must be negative this time.
+ ASSERT_TRUE(skew.is_negative());
+ // Convert it to positive value so we can use to to build the expected string.
+ skew = -skew;
+ std::ostringstream s2;
+ s2 << skew.seconds() << "s behind";
+ EXPECT_TRUE(log.find(s2.str()) != std::string::npos) <<
+ " log content wrong: " << log;
+
+ offset = hours(18) + minutes(37) + seconds(15);
+ ptime mytime(date(2019, Jul, 23), offset);
+
+ state_.my_time_at_skew_ = mytime;
+ state_.partner_time_at_skew_ = mytime + seconds(25);
+ state_.clock_skew_ = seconds(25);
+ ASSERT_NO_THROW(log = state_.logFormatClockSkew());
+ std::string expected("my time: 2019-07-23 18:37:15, "
+ "partner's time: 2019-07-23 18:37:40, "
+ "partner's clock is 25s ahead");
+ EXPECT_EQ(expected, log);
+}
+
+// Tests that the communication state report is correct.
+void
+CommunicationStateTest::getReportTest() {
+ state_.setPartnerState("waiting");
+
+ auto scopes = Element::createList();
+ scopes->add(Element::create("server1"));
+ state_.setPartnerScopes(scopes);
+
+ state_.poke();
+
+ // Simulate the communications interrupted state.
+ state_.modifyPokeTime(-100);
+
+ // Send two DHCP packets of which one has secs value beyond the threshold and
+ // the other one lower than the threshold.
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 0, 0, 5)));
+ ASSERT_NO_THROW(state_.analyzeMessage(createMessage4(DHCPDISCOVER, 1, 0, 15)));
+
+ // Get the report.
+ auto report = state_.getReport();
+ ASSERT_TRUE(report);
+
+ // Compare with the expected output.
+ std::string expected = "{"
+ " \"age\": 100,"
+ " \"in-touch\": true,"
+ " \"last-scopes\": [ \"server1\" ],"
+ " \"last-state\": \"waiting\","
+ " \"communication-interrupted\": true,"
+ " \"connecting-clients\": 2,"
+ " \"unacked-clients\": 1,"
+ " \"unacked-clients-left\": 10,"
+ " \"analyzed-packets\": 2"
+ "}";
+ EXPECT_TRUE(isEquivalent(Element::fromJSON(expected), report));
+}
+
+// Tests unusual values used to create the report.
+void
+CommunicationStateTest::getReportDefaultValuesTest() {
+ auto report = state_.getReport();
+ ASSERT_TRUE(report);
+
+ // Compare with the expected output.
+ std::string expected = "{"
+ " \"age\": 0,"
+ " \"in-touch\": false,"
+ " \"last-scopes\": [ ],"
+ " \"last-state\": \"\","
+ " \"communication-interrupted\": false,"
+ " \"connecting-clients\": 0,"
+ " \"unacked-clients\": 0,"
+ " \"unacked-clients-left\": 0,"
+ " \"analyzed-packets\": 0"
+ "}";
+ EXPECT_TRUE(isEquivalent(Element::fromJSON(expected), report));
+}
+
+void
+CommunicationStateTest::getUnsentUpdateCountTest() {
+ // Initially the count should be 0.
+ EXPECT_EQ(0, state_.getUnsentUpdateCount());
+
+ // Increasing the value by 1 several times.
+ EXPECT_NO_THROW(state_.increaseUnsentUpdateCount());
+ EXPECT_EQ(1, state_.getUnsentUpdateCount());
+ EXPECT_NO_THROW(state_.increaseUnsentUpdateCount());
+ EXPECT_EQ(2, state_.getUnsentUpdateCount());
+ EXPECT_NO_THROW(state_.increaseUnsentUpdateCount());
+ EXPECT_EQ(3, state_.getUnsentUpdateCount());
+
+ // Test that the method under test protects against an overflow
+ // resetting the value to 0.
+ state_.unsent_update_count_ = std::numeric_limits<uint64_t>::max();
+ EXPECT_NO_THROW(state_.increaseUnsentUpdateCount());
+ EXPECT_EQ(1, state_.getUnsentUpdateCount());
+}
+
+void
+CommunicationStateTest::hasPartnerNewUnsentUpdatesTest() {
+ // Initially the counts should be 0.
+ EXPECT_FALSE(state_.hasPartnerNewUnsentUpdates());
+
+ // Set a positive value. It should be noticed.
+ EXPECT_NO_THROW(state_.setPartnerUnsentUpdateCount(5));
+ EXPECT_TRUE(state_.hasPartnerNewUnsentUpdates());
+
+ // No change, no new unsent updates.
+ EXPECT_NO_THROW(state_.setPartnerUnsentUpdateCount(5));
+ EXPECT_FALSE(state_.hasPartnerNewUnsentUpdates());
+
+ // Change it again. New updates.
+ EXPECT_NO_THROW(state_.setPartnerUnsentUpdateCount(10));
+ EXPECT_TRUE(state_.hasPartnerNewUnsentUpdates());
+
+ // Set it to 0 to simulate restart. No updates.
+ EXPECT_NO_THROW(state_.setPartnerUnsentUpdateCount(0));
+ EXPECT_FALSE(state_.hasPartnerNewUnsentUpdates());
+}
+
+TEST_F(CommunicationStateTest, partnerStateTest) {
+ partnerStateTest();
+}
+
+TEST_F(CommunicationStateTest, partnerStateTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ partnerStateTest();
+}
+
+TEST_F(CommunicationStateTest, partnerScopesTest) {
+ partnerScopesTest();
+}
+
+TEST_F(CommunicationStateTest, partnerScopesTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ partnerScopesTest();
+}
+
+TEST_F(CommunicationStateTest, initialDurationTest) {
+ initialDurationTest();
+}
+
+TEST_F(CommunicationStateTest, initialDurationTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ initialDurationTest();
+}
+
+TEST_F(CommunicationStateTest, pokeTest) {
+ pokeTest();
+}
+
+TEST_F(CommunicationStateTest, pokeTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ pokeTest();
+}
+
+TEST_F(CommunicationStateTest, heartbeatTest) {
+ heartbeatTest();
+}
+
+TEST_F(CommunicationStateTest, heartbeatTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ heartbeatTest();
+}
+
+TEST_F(CommunicationStateTest, startHeartbeatInvalidValuesTest) {
+ startHeartbeatInvalidValuesTest();
+}
+
+TEST_F(CommunicationStateTest, startHeartbeatInvalidValuesTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ startHeartbeatInvalidValuesTest();
+}
+
+TEST_F(CommunicationStateTest, detectFailureV4Test) {
+ detectFailureV4Test();
+}
+
+TEST_F(CommunicationStateTest, detectFailureV4TestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ detectFailureV4Test();
+}
+
+TEST_F(CommunicationStateTest, failureDetectionDisabled4Test) {
+ failureDetectionDisabled4Test();
+}
+
+TEST_F(CommunicationStateTest, failureDetectionDisabled4TestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ failureDetectionDisabled4Test();
+}
+
+TEST_F(CommunicationStateTest, detectFailureV6Test) {
+ detectFailureV6Test();
+}
+
+TEST_F(CommunicationStateTest, detectFailureV6TestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ detectFailureV6Test();
+}
+
+TEST_F(CommunicationStateTest, failureDetectionDisabled6Test) {
+ failureDetectionDisabled6Test();
+}
+
+TEST_F(CommunicationStateTest, failureDetectionDisabled6TestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ failureDetectionDisabled6Test();
+}
+
+TEST_F(CommunicationStateTest, clockSkewTest) {
+ clockSkewTest();
+}
+
+TEST_F(CommunicationStateTest, clockSkewTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ clockSkewTest();
+}
+
+TEST_F(CommunicationStateTest, logFormatClockSkewTest) {
+ logFormatClockSkewTest();
+}
+
+TEST_F(CommunicationStateTest, logFormatClockSkewTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ logFormatClockSkewTest();
+}
+
+TEST_F(CommunicationStateTest, getReportTest) {
+ getReportTest();
+}
+
+TEST_F(CommunicationStateTest, getReportTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ getReportTest();
+}
+
+TEST_F(CommunicationStateTest, getReportDefaultValuesTest) {
+ getReportDefaultValuesTest();
+}
+
+TEST_F(CommunicationStateTest, getReportDefaultValuesTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ getReportDefaultValuesTest();
+}
+
+TEST_F(CommunicationStateTest, getUnsentUpdateCountTest) {
+ getUnsentUpdateCountTest();
+}
+
+TEST_F(CommunicationStateTest, getUnsentUpdateCountTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ getUnsentUpdateCountTest();
+}
+
+TEST_F(CommunicationStateTest, hasPartnerNewUnsentUpdatesTest) {
+ hasPartnerNewUnsentUpdatesTest();
+}
+
+TEST_F(CommunicationStateTest, hasPartnerNewUnsentUpdatesTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ hasPartnerNewUnsentUpdatesTest();
+}
+
+}