monitor: Create PowerOffRules class
This class contains a PowerOffCause and a PowerOffAction. It provides a
check() method that takes the FanHealth map which it then checks against
the cause. If the cause is satisfied, it then starts the power off
action. It provides a cancel method that will force cancel a running
action in the case that the object owner detects a system power off and
so doesn't need to run this power off anymore.
The class's configuration data is read from the JSON config file.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I5c0c168591d6d62c894c4d036ec762797fd759af
diff --git a/monitor/test/Makefile.am b/monitor/test/Makefile.am
index 32b8c3b..e6d0f9b 100644
--- a/monitor/test/Makefile.am
+++ b/monitor/test/Makefile.am
@@ -7,7 +7,8 @@
TESTS = $(check_PROGRAMS)
check_PROGRAMS += \
- power_off_cause_test
+ power_off_cause_test \
+ power_off_rule_test
power_off_cause_test_SOURCES = \
power_off_cause_test.cpp
@@ -17,3 +18,18 @@
$(OESDK_TESTCASE_FLAGS)
power_off_cause_test_LDADD = \
$(gtest_ldadd)
+
+power_off_rule_test_SOURCES = \
+ power_off_rule_test.cpp \
+ ../conditions.cpp \
+ ../json_parser.cpp \
+ ../logging.cpp
+power_off_rule_test_CXXFLAGS = \
+ $(gtest_cflags)
+power_off_rule_test_LDFLAGS = \
+ $(OESDK_TESTCASE_FLAGS)
+power_off_rule_test_LDADD = \
+ $(gtest_ldadd) \
+ $(FMT_LIBS) \
+ $(SDBUSPLUS_LIBS) \
+ $(SDEVENTPLUS_LIBS)
diff --git a/monitor/test/mock_power_interface.hpp b/monitor/test/mock_power_interface.hpp
new file mode 100644
index 0000000..c070182
--- /dev/null
+++ b/monitor/test/mock_power_interface.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "../power_interface.hpp"
+
+#include <gmock/gmock.h>
+
+namespace phosphor::fan::monitor
+{
+
+class MockPowerInterface : public PowerInterfaceBase
+{
+ public:
+ MOCK_METHOD(void, softPowerOff, (), (override));
+ MOCK_METHOD(void, hardPowerOff, (), (override));
+};
+
+} // namespace phosphor::fan::monitor
diff --git a/monitor/test/power_off_rule_test.cpp b/monitor/test/power_off_rule_test.cpp
new file mode 100644
index 0000000..b708bb9
--- /dev/null
+++ b/monitor/test/power_off_rule_test.cpp
@@ -0,0 +1,148 @@
+#include "../json_parser.hpp"
+#include "../power_off_rule.hpp"
+#include "mock_power_interface.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::fan::monitor;
+using json = nlohmann::json;
+
+TEST(PowerOffRuleTest, TestRules)
+{
+ sd_event* event;
+ sd_event_default(&event);
+ sdeventplus::Event sdEvent{event};
+
+ const auto faultConfig = R"(
+ {
+ "fault_handling":
+ {
+ "power_off_config": [
+ {
+ "type": "hard",
+ "cause": "missing_fan_frus",
+ "count": 2,
+ "delay": 0,
+ "state": "at_pgood"
+ },
+ {
+ "type": "soft",
+ "cause": "nonfunc_fan_rotors",
+ "count": 3,
+ "delay": 0,
+ "state": "runtime"
+ },
+ {
+ "type": "soft",
+ "cause": "nonfunc_fan_rotors",
+ "count": 4,
+ "delay": 1,
+ "state": "runtime"
+ },
+ {
+ "type": "hard",
+ "cause": "missing_fan_frus",
+ "count": 4,
+ "delay": 1,
+ "state": "runtime"
+ }
+ ]
+ }
+ })"_json;
+
+ std::shared_ptr<PowerInterfaceBase> powerIface =
+ std::make_shared<MockPowerInterface>();
+
+ MockPowerInterface& mockIface =
+ static_cast<MockPowerInterface&>(*powerIface);
+
+ EXPECT_CALL(mockIface, hardPowerOff).Times(1);
+ EXPECT_CALL(mockIface, softPowerOff).Times(1);
+
+ auto rules = getPowerOffRules(faultConfig, powerIface);
+ ASSERT_EQ(rules.size(), 4);
+
+ FanHealth health{{"fan0", {false, {true, true}}},
+ {"fan1", {false, {true, true}}}};
+
+ {
+ // Check rule 0
+
+ // wrong state, won't be active
+ rules[0]->check(PowerRuleState::runtime, health);
+ EXPECT_FALSE(rules[0]->active());
+
+ rules[0]->check(PowerRuleState::atPgood, health);
+ EXPECT_TRUE(rules[0]->active());
+
+ // Run the event loop, since the timeout is 0 it should
+ // run the power off and satisfy the EXPECT_CALL above
+ sdEvent.run(std::chrono::milliseconds(1));
+
+ // It doesn't really make much sense to cancel a rule after it
+ // powered off, but it should at least say it isn't active.
+ rules[0]->cancel();
+ EXPECT_FALSE(rules[0]->active());
+ }
+
+ {
+ // Check the second rule.
+ rules[1]->check(PowerRuleState::runtime, health);
+ EXPECT_FALSE(rules[1]->active());
+
+ // > 2 nonfunc rotors
+ health["fan0"] = {true, {true, false}};
+ health["fan1"] = {true, {false, false}};
+
+ rules[1]->check(PowerRuleState::runtime, health);
+ EXPECT_TRUE(rules[1]->active());
+
+ // Run the event loop, since the timeout is 0 it should
+ // run the power off and satisfy the EXPECT_CALL above
+ sdEvent.run(std::chrono::milliseconds(1));
+ }
+
+ {
+ // Check the third rule. It has a timeout so long we can
+ // cancel it before it runs.
+ health["fan0"] = {true, {false, false}};
+ health["fan1"] = {true, {false, false}};
+
+ rules[2]->check(PowerRuleState::runtime, health);
+ EXPECT_TRUE(rules[2]->active());
+
+ sdEvent.run(std::chrono::milliseconds(1));
+
+ rules[2]->cancel();
+ EXPECT_FALSE(rules[2]->active());
+
+ // This will go past the timeout, it should have been canceled so the
+ // soft power off won't have run and the EXPECT_CALL above
+ // should be happy.
+ sdEvent.run(std::chrono::seconds(1));
+ }
+
+ {
+ // Check the 4th rule. Resolve it before it completes
+ health["fan0"] = {false, {true, true}};
+ health["fan1"] = {false, {true, true}};
+ health["fan2"] = {false, {true, true}};
+ health["fan3"] = {false, {true, true}};
+
+ rules[3]->check(PowerRuleState::runtime, health);
+ EXPECT_TRUE(rules[3]->active());
+
+ // Won't complete yet
+ sdEvent.run(std::chrono::milliseconds(1));
+
+ // Make them present
+ health["fan0"] = {true, {true, true}};
+ health["fan1"] = {true, {true, true}};
+ health["fan2"] = {true, {true, true}};
+ health["fan3"] = {true, {true, true}};
+
+ // It should be inactive now
+ rules[3]->check(PowerRuleState::runtime, health);
+ EXPECT_FALSE(rules[3]->active());
+ }
+}