summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/perfmon/tests/alarm_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/perfmon/tests/alarm_unittests.cc')
-rw-r--r--src/hooks/dhcp/perfmon/tests/alarm_unittests.cc328
1 files changed, 328 insertions, 0 deletions
diff --git a/src/hooks/dhcp/perfmon/tests/alarm_unittests.cc b/src/hooks/dhcp/perfmon/tests/alarm_unittests.cc
new file mode 100644
index 0000000..0814dc4
--- /dev/null
+++ b/src/hooks/dhcp/perfmon/tests/alarm_unittests.cc
@@ -0,0 +1,328 @@
+// Copyright (C) 2024 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 <alarm.h>
+#include <dhcp/dhcp6.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <unordered_set>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfmon;
+using namespace boost::posix_time;
+
+namespace {
+
+// Verifies Alarm construction.
+TEST(Alarm, validConstructors) {
+ AlarmPtr alarm;
+
+ auto start_time = PktEvent::now();
+
+ // Create valid v4 alarm, verify contents and label.
+ Duration low_water(milliseconds(50));
+ Duration high_water(milliseconds(250));
+ ASSERT_NO_THROW_LOG(alarm.reset(new Alarm(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL,
+ low_water, high_water)));
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ(alarm->getFamily(), AF_INET);
+ EXPECT_EQ(alarm->getQueryType(), DHCPDISCOVER);
+ EXPECT_EQ(alarm->getResponseType(), DHCPOFFER);
+ EXPECT_EQ(alarm->getStartEventLabel(), "process_started");
+ EXPECT_EQ(alarm->getStopEventLabel(), "process_completed");
+ EXPECT_EQ(alarm->getSubnetId(), SUBNET_ID_GLOBAL);
+ EXPECT_EQ("DHCPDISCOVER-DHCPOFFER.process_started-process_completed.0", alarm->getLabel());
+ EXPECT_EQ(alarm->getSubnetId(), SUBNET_ID_GLOBAL);
+ EXPECT_EQ(alarm->getLowWater(), low_water);
+ EXPECT_EQ(alarm->getHighWater(), high_water);
+ EXPECT_EQ(alarm->getState(), Alarm::CLEAR);
+ EXPECT_GE(alarm->getStosTime(), start_time);
+
+ start_time = PktEvent::now();
+
+ // Create valid v6 key and use that to create an alarm. Verify contents and label.
+ DurationKeyPtr key;
+ ASSERT_NO_THROW_LOG(key.reset(new DurationKey(AF_INET6, DHCPV6_SOLICIT, DHCPV6_ADVERTISE,
+ "mt_queued", "process_started", 77)));
+
+ ASSERT_NO_THROW_LOG(alarm.reset(new Alarm(*key, low_water, high_water, false)));
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ(alarm->getFamily(), AF_INET6);
+ EXPECT_EQ(alarm->getQueryType(), DHCPV6_SOLICIT);
+ EXPECT_EQ(alarm->getResponseType(), DHCPV6_ADVERTISE);
+ EXPECT_EQ(alarm->getStartEventLabel(), "mt_queued");
+ EXPECT_EQ(alarm->getStopEventLabel(), "process_started");
+ EXPECT_EQ(alarm->getSubnetId(), 77);
+ EXPECT_EQ("SOLICIT-ADVERTISE.mt_queued-process_started.77", alarm->getLabel());
+ EXPECT_EQ(alarm->getLowWater(), low_water);
+ EXPECT_EQ(alarm->getHighWater(), high_water);
+ EXPECT_EQ(alarm->getState(), Alarm::DISABLED);
+ EXPECT_GE(alarm->getStosTime(), start_time);
+}
+
+// Verifies Alarm invalid construction.
+TEST(Alarm, invalidConstructors) {
+ AlarmPtr alarm;
+
+ // Make sure we catch an invalid message pairing.
+ Duration low_water(milliseconds(50));
+ Duration high_water(milliseconds(250));
+ ASSERT_THROW_MSG(alarm.reset(new Alarm(AF_INET, DHCPDISCOVER, DHCPDISCOVER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL, low_water, high_water)),
+ BadValue,
+ "Response type: DHCPDISCOVER not valid for query type: DHCPDISCOVER");
+
+ // Low water too high, should throw.
+ ASSERT_THROW_MSG(alarm.reset(new Alarm(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL, high_water, low_water)),
+ BadValue,
+ "low water: 00:00:00.250000, must be less than high water:"
+ " 00:00:00.050000");
+
+ // Create valid v6 key.
+ DurationKeyPtr key;
+ ASSERT_NO_THROW_LOG(key.reset(new DurationKey(AF_INET6, DHCPV6_SOLICIT, DHCPV6_ADVERTISE,
+ "mt_queued", "process_started", 77)));
+
+ // Low water too high, should throw.
+ ASSERT_THROW_MSG(alarm.reset(new Alarm(*key, high_water, low_water)),
+ BadValue,
+ "low water: 00:00:00.250000, must be less than high water:"
+ " 00:00:00.050000");
+}
+
+TEST(Alarm, lowWaterHighWaterSetters) {
+ // Create valid v4 alarm.
+ Duration low_water(milliseconds(50));
+ Duration high_water(milliseconds(250));
+ AlarmPtr alarm;
+ ASSERT_NO_THROW_LOG(alarm.reset(new Alarm(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL,
+ low_water, high_water)));
+
+ // Should be able to set thresholds to new, valid values.
+ low_water += milliseconds(50);
+ high_water -= milliseconds(100);
+ ASSERT_NO_THROW(alarm->setLowWater(low_water));
+ EXPECT_EQ(alarm->getLowWater(), low_water);
+ ASSERT_NO_THROW(alarm->setHighWater(high_water));
+ EXPECT_EQ(alarm->getHighWater(), high_water);
+
+ // Setting low too high should fail and leave Alarm intact.
+ ASSERT_THROW_MSG(alarm->setLowWater(high_water), BadValue,
+ "low water: 00:00:00.150000, must be less than high water: 00:00:00.150000");
+ EXPECT_EQ(alarm->getLowWater(), low_water);
+
+ // Setting high too low should fail and leave Alarm intact.
+ ASSERT_THROW_MSG(alarm->setHighWater(low_water), BadValue,
+ "high water: 00:00:00.100000, must be greater than low water: 00:00:00.100000");
+ EXPECT_EQ(alarm->getHighWater(), high_water);
+}
+
+TEST(Alarm, clearAndDisable) {
+ auto start_time = PktEvent::now();
+ AlarmPtr alarm;
+ ASSERT_NO_THROW_LOG(alarm.reset(new Alarm(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL, milliseconds(100), milliseconds(200))));
+
+ // Initial state should be CLEAR, stos_time_ should be close to now, no report time.
+ EXPECT_EQ(alarm->getState(), Alarm::CLEAR);
+ EXPECT_GE(alarm->getStosTime(), start_time);
+ EXPECT_EQ(alarm->getLastHighWaterReport(), PktEvent::EMPTY_TIME());
+
+ // Save stos then nap.
+ auto prev_time = alarm->getStosTime();
+ usleep(100);
+
+ // Change the state to DISABLED. Should have a later stos_time_.
+ ASSERT_NO_THROW(alarm->disable());
+ EXPECT_EQ(alarm->getState(), Alarm::DISABLED);
+ EXPECT_GE(alarm->getStosTime(), prev_time);
+ EXPECT_EQ(alarm->getLastHighWaterReport(), PktEvent::EMPTY_TIME());
+
+ // While we're disabled verify operations that are not allowed.
+ ASSERT_THROW_MSG(alarm->checkSample(milliseconds(75), seconds(60)), InvalidOperation,
+ "Alarm::checkSample() - should not be called when alarm is DISABLED");
+
+ // Save stos then nap.
+ prev_time = alarm->getStosTime();
+ usleep(100);
+
+ // Restore the alarm to CLEAR.
+ ASSERT_NO_THROW(alarm->clear());
+ EXPECT_EQ(alarm->getState(), Alarm::CLEAR);
+ EXPECT_GE(alarm->getStosTime(), prev_time);
+ EXPECT_EQ(alarm->getLastHighWaterReport(), PktEvent::EMPTY_TIME());
+}
+
+// Verifies the result of Alarm::checkSample() over the range of scenarios.
+// The alarm is created in either the CLEAR or TRIGGERED state and then checkSample()
+// is invoked. The scenarios tested are described by the table below:
+//
+// ```
+// INPUT | OUTPUT
+// Test sample relationship Input Report Int.|
+// to the thresholds State Elapsed | Report State Stos Last Report
+// -------------------------------------------------|----------------------------------
+// sample < low_water C false | false C - -
+// sample < low_water C true | false C - -
+// sample < low_water T false | true C updated reset
+// sample < low_water T true | true C updated reset
+// |
+// sample == low_water C false | false C - -
+// sample == low_water C true | false C - -
+// sample == low_water T false | false T - -
+// sample == low_water T true | true T updated
+// |
+// low_water < sample < high_water C false | false C - -
+// low_water < sample < high_water C true | false C - -
+// low_water < sample < high_water T false | false T - -
+// low_water < sample < high_water T true | true T - updated
+// |
+// sample == high water C false | false C - -
+// sample == high water C true | false C - -
+// sample == high water T false | false T - -
+// sample == high water T true | true T - updated
+// |
+// sample > high water C false | true T updated set
+// sample > high water C true | true T updated set
+// sample > high water T false | false T - -
+// sample > high water T true | true T - updated
+// ```
+TEST(Alarm, checkSample) {
+ // Create mnemonic constants.
+ Duration low_water(milliseconds(100));
+ Duration high_water(milliseconds(200));
+ Duration lt_low_water(milliseconds(50));
+ Duration eq_low_water = low_water;
+ Duration mid_range(milliseconds(150));
+ Duration eq_high_water = high_water;
+ Duration gt_high_water(milliseconds(250));
+ Duration report_interval(milliseconds(25));
+
+ bool report_elapsed = true;
+ bool should_report = true;
+
+ // Enumerates possible outcomes for last_high_water_report.
+ enum TimeChange {
+ none, // no change
+ set, // from empty time to time
+ updated, // updated to a more recent time
+ reset // reset to empty time
+ };
+
+ // Embodies a test scenario based on the table in the commentary. It does not
+ // include a column for stos_time_ changes as they are easily inferred.
+ struct Scenario {
+ Duration sample_; // duration to test the alarm with
+ Alarm::State input_state_; // Starting state of the Alarm (CLEAR or TRIGGERED)
+ bool report_interval_elapsed_; // True if report interval has elapsed
+ bool should_report_; // True if checkSample() should return true
+ Alarm::State output_state_; // Alarm state after calling checkSample()
+ TimeChange last_report_chg_; // Expected change to last_high_water_report_
+ };
+
+ // Scenarios as described in the commentary.
+ std::list<Scenario> scenarios = {
+ { lt_low_water, Alarm::CLEAR, !report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { lt_low_water, Alarm::CLEAR, report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { lt_low_water, Alarm::TRIGGERED, !report_elapsed, should_report, Alarm::CLEAR, TimeChange::reset },
+ { lt_low_water, Alarm::TRIGGERED, report_elapsed, should_report, Alarm::CLEAR, TimeChange::reset },
+
+ { eq_low_water, Alarm::CLEAR, !report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { eq_low_water, Alarm::CLEAR, report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { eq_low_water, Alarm::TRIGGERED, !report_elapsed, !should_report, Alarm::TRIGGERED, TimeChange::none },
+ { eq_low_water, Alarm::TRIGGERED, report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::updated },
+
+ { mid_range, Alarm::CLEAR, !report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { mid_range, Alarm::CLEAR, report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { mid_range, Alarm::TRIGGERED, !report_elapsed, !should_report, Alarm::TRIGGERED, TimeChange::none },
+ { mid_range, Alarm::TRIGGERED, report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::updated },
+
+ { eq_high_water, Alarm::CLEAR, !report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { eq_high_water, Alarm::CLEAR, report_elapsed, !should_report, Alarm::CLEAR, TimeChange::none },
+ { eq_high_water, Alarm::TRIGGERED, !report_elapsed, !should_report, Alarm::TRIGGERED, TimeChange::none },
+ { eq_high_water, Alarm::TRIGGERED, report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::updated },
+
+ { gt_high_water, Alarm::CLEAR, !report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::set },
+ { gt_high_water, Alarm::CLEAR, report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::set },
+ { gt_high_water, Alarm::TRIGGERED, !report_elapsed, !should_report, Alarm::TRIGGERED, TimeChange::none },
+ { gt_high_water, Alarm::TRIGGERED, report_elapsed, should_report, Alarm::TRIGGERED, TimeChange::updated },
+ };
+
+ AlarmPtr alarm;
+ DurationKey key(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed", SUBNET_ID_GLOBAL);
+ size_t pass = 0;
+ for (auto const& scenario : scenarios) {
+ std::ostringstream oss;
+ oss << "scenario: " << pass++;
+ SCOPED_TRACE(oss.str());
+
+ auto start_time = PktEvent::now();
+
+ // Create an Alarm with the scenario starting characteristics.
+ ASSERT_NO_THROW_LOG(alarm.reset(new Alarm(key, low_water, high_water)));
+ if (scenario.input_state_ == Alarm::TRIGGERED) {
+ alarm->setState(Alarm::TRIGGERED);
+ alarm->setLastHighWaterReport(!scenario.report_interval_elapsed_ ?
+ PktEvent::now() : start_time - (report_interval * 2));
+ }
+
+ // Save the current timestamps.
+ auto prev_stos_time = alarm->getStosTime();
+ auto prev_report_time = alarm->getLastHighWaterReport();
+
+ // Take a little nap.
+ usleep(50);
+
+ // Invoke checkSample() with the scenario sample duration. It should not throw.
+ bool should_report;
+ ASSERT_NO_THROW_LOG(should_report = alarm->checkSample(scenario.sample_, report_interval));
+
+ // Verify that we returned the expected value for a reportable event (or not).
+ EXPECT_EQ(should_report, scenario.should_report_);
+
+ // Verify we ended up in the expected state.
+ ASSERT_EQ(alarm->getState(), scenario.output_state_);
+
+ // If the state changed, stos_time_ should have been updated.
+ if (scenario.input_state_ != scenario.output_state_) {
+ EXPECT_GT(alarm->getStosTime(), prev_stos_time);
+ } else {
+ EXPECT_EQ(alarm->getStosTime(), prev_stos_time);
+ }
+
+ // Verify the last_high_water_report_ outcome.
+ switch(scenario.last_report_chg_) {
+ case TimeChange::none:
+ EXPECT_EQ(alarm->getLastHighWaterReport(), prev_report_time);
+ break;
+ case TimeChange::set:
+ EXPECT_EQ(prev_report_time, PktEvent::EMPTY_TIME());
+ EXPECT_GE(alarm->getLastHighWaterReport(), alarm->getStosTime());
+ break;
+ case TimeChange::updated:
+ EXPECT_GT(alarm->getLastHighWaterReport(), prev_report_time);
+ break;
+ case TimeChange::reset:
+ EXPECT_EQ(alarm->getLastHighWaterReport(), PktEvent::EMPTY_TIME());
+ break;
+ }
+ }
+}
+
+} // end of anonymous namespace