monitor: Create PowerOffAction class hierarchy
The PowerOffAction base class and its derived classes will be used to
power off a system due to fan failures.
There are 3 types of power offs:
1. HardPowerOff - Do a hard power off after a delay
2. SoftPowerOff - Do a soft power off after a delay
3. EpowPowerOff - This isn't fully defined yet, but it will involve
powering off after setting an early power off warning
somehow and then waiting through 2 delays.
The code that makes the D-Bus calls to do the power offs is in a
standalone class so that it can be be mocked in testcases.
This code also makes use of the Logger class for logging, so this commit
brings that in as a singleton.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I83118963df4ec0b4f89619572f6935329eec3adb
diff --git a/configure.ac b/configure.ac
index 3b16f84..2af3e09 100644
--- a/configure.ac
+++ b/configure.ac
@@ -207,6 +207,12 @@
["$PYTHON \${top_srcdir}/monitor/gen-fan-monitor-defs.py \
-m $FAN_MONITOR_YAML_FILE"])
])
+
+ AC_ARG_VAR(NUM_MONITOR_LOG_ENTRIES, [Maximum number of entries in the message log])
+ AS_IF([test "x$NUM_MONITOR_LOG_ENTRIES" == "x"], [NUM_MONITOR_LOG_ENTRIES=75])
+ AC_DEFINE_UNQUOTED([NUM_MONITOR_LOG_ENTRIES], [$NUM_MONITOR_LOG_ENTRIES],
+ [Maximum number of entries in the message log])
+
AC_CONFIG_FILES([monitor/Makefile])
])
diff --git a/monitor/Makefile.am b/monitor/Makefile.am
index ecfab71..2c7a7fa 100644
--- a/monitor/Makefile.am
+++ b/monitor/Makefile.am
@@ -7,6 +7,8 @@
phosphor_fan_monitor_SOURCES = \
argument.cpp \
fan.cpp \
+ power_interface.cpp \
+ logging.cpp \
main.cpp \
tach_sensor.cpp \
conditions.cpp \
diff --git a/monitor/logging.cpp b/monitor/logging.cpp
new file mode 100644
index 0000000..1463bc6
--- /dev/null
+++ b/monitor/logging.cpp
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "logging.hpp"
+
+namespace phosphor::fan::monitor
+{
+
+Logger& getLogger()
+{
+ static Logger logger{NUM_MONITOR_LOG_ENTRIES};
+
+ return logger;
+}
+
+} // namespace phosphor::fan::monitor
diff --git a/monitor/logging.hpp b/monitor/logging.hpp
new file mode 100644
index 0000000..dbd2b8c
--- /dev/null
+++ b/monitor/logging.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "logger.hpp"
+
+namespace phosphor::fan::monitor
+{
+/**
+ * @brief Returns the singleton Logger class
+ *
+ * @return Logger& - The logger
+ */
+Logger& getLogger();
+} // namespace phosphor::fan::monitor
diff --git a/monitor/power_interface.cpp b/monitor/power_interface.cpp
new file mode 100644
index 0000000..372993f
--- /dev/null
+++ b/monitor/power_interface.cpp
@@ -0,0 +1,41 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "power_interface.hpp"
+
+#include "sdbusplus.hpp"
+
+namespace phosphor::fan::monitor
+{
+
+constexpr auto systemdService = "org.freedesktop.systemd1";
+constexpr auto systemdPath = "/org/freedesktop/systemd1";
+constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
+
+void PowerInterface::softPowerOff()
+{
+ util::SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
+ "StartUnit", "obmc-host-shutdown@0.target",
+ "replace");
+}
+
+void PowerInterface::hardPowerOff()
+{
+ util::SDBusPlus::callMethod(
+ systemdService, systemdPath, systemdMgrIface, "StartUnit",
+ "obmc-chassis-hard-poweroff@0.target", "replace");
+}
+
+} // namespace phosphor::fan::monitor
diff --git a/monitor/power_interface.hpp b/monitor/power_interface.hpp
new file mode 100644
index 0000000..1a339c5
--- /dev/null
+++ b/monitor/power_interface.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+namespace phosphor::fan::monitor
+{
+
+/**
+ * @class PowerInterfaceBase
+ *
+ * The base class that contains the APIs to do power offs.
+ * This is required so it can be mocked in testcases.
+ */
+class PowerInterfaceBase
+{
+ public:
+ PowerInterfaceBase() = default;
+ virtual ~PowerInterfaceBase() = default;
+ PowerInterfaceBase(const PowerInterfaceBase&) = delete;
+ PowerInterfaceBase& operator=(const PowerInterfaceBase&) = delete;
+ PowerInterfaceBase(PowerInterfaceBase&&) = delete;
+ PowerInterfaceBase& operator=(PowerInterfaceBase&&) = delete;
+
+ /**
+ * @brief Perform a soft power off
+ */
+ virtual void softPowerOff() = 0;
+
+ /**
+ * @brief Perform a hard power off
+ */
+ virtual void hardPowerOff() = 0;
+};
+
+/**
+ * @class PowerInterface
+ *
+ * Concrete class to perform power offs
+ */
+class PowerInterface : public PowerInterfaceBase
+{
+ public:
+ PowerInterface() = default;
+ ~PowerInterface() = default;
+ PowerInterface(const PowerInterface&) = delete;
+ PowerInterface& operator=(const PowerInterface&) = delete;
+ PowerInterface(PowerInterface&&) = delete;
+ PowerInterface& operator=(PowerInterface&&) = delete;
+
+ /**
+ * @brief Perform a soft power off
+ */
+ void softPowerOff() override;
+
+ /**
+ * @brief Perform a hard power off
+ */
+ void hardPowerOff() override;
+};
+
+} // namespace phosphor::fan::monitor
diff --git a/monitor/power_off_action.hpp b/monitor/power_off_action.hpp
new file mode 100644
index 0000000..05d4ff6
--- /dev/null
+++ b/monitor/power_off_action.hpp
@@ -0,0 +1,327 @@
+#pragma once
+
+#include "logging.hpp"
+#include "power_interface.hpp"
+
+#include <fmt/format.h>
+
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+#include <chrono>
+
+namespace phosphor::fan::monitor
+{
+
+/**
+ * @class PowerOffAction
+ *
+ * This is the base class for a power off action, which is
+ * used by the PowerOffRule class to do different types of
+ * power offs based on fan failures.
+ *
+ * The power off is started with the start() method, and the
+ * derived class may or may not allow it to be stopped with
+ * the cancel() method, which is really only useful when
+ * there is a delay before the power off.
+ *
+ * It uses the PowerInterfaceBase object pointer to perform
+ * the D-Bus call to do the power off, so it can be mocked
+ * for testing.
+ */
+class PowerOffAction
+{
+ public:
+ PowerOffAction() = delete;
+ virtual ~PowerOffAction() = default;
+ PowerOffAction(const PowerOffAction&) = delete;
+ PowerOffAction& operator=(const PowerOffAction&) = delete;
+ PowerOffAction(PowerOffAction&&) = delete;
+ PowerOffAction& operator=(PowerOffAction&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] name - The action name. Used for tracing.
+ * powerInterface - The object used to invoke the power off.
+ */
+ PowerOffAction(const std::string& name,
+ std::shared_ptr<PowerInterfaceBase> powerInterface) :
+ _name(name),
+ _powerIface(std::move(powerInterface)),
+ _event(sdeventplus::Event::get_default())
+ {}
+
+ /**
+ * @brief Starts the power off.
+ *
+ * Though this occurs in the child class, usually this
+ * involves starting a timer and then powering off when it
+ * times out.
+ */
+ virtual void start() = 0;
+
+ /**
+ * @brief Attempts to cancel the power off, if the derived
+ * class allows it, and assuming the power off hasn't
+ * already happened.
+ *
+ * The 'force' parameter is mainly for use when something else
+ * powered off the system so this action doesn't need to run
+ * anymore even if it isn't usually cancelable.
+ *
+ * @param[in] force - If the cancel should be forced
+ *
+ * @return bool - If the cancel was allowed/successful
+ */
+ virtual bool cancel(bool force) = 0;
+
+ /**
+ * @brief If the power off action is currently in progress, which
+ * usually means it's still in the delay time before the
+ * power off D-Bus command is executed.
+ *
+ * @return bool - If the action is active
+ */
+ bool active() const
+ {
+ return _active;
+ }
+
+ /**
+ * @brief Returns the name of the action
+ *
+ * @return const std::string& - The name
+ */
+ const std::string& name() const
+ {
+ return _name;
+ }
+
+ protected:
+ /**
+ * @brief The name of the action, which is set by the
+ * derived class.
+ */
+ const std::string _name;
+
+ /**
+ * @brief If the action is currently active or not.
+ */
+ bool _active = false;
+
+ /**
+ * @brief The object used to invoke the power off with.
+ */
+ std::shared_ptr<PowerInterfaceBase> _powerIface;
+
+ /**
+ * @brief The event loop object. Needed by timers.
+ */
+ sdeventplus::Event _event;
+};
+
+/**
+ * @class HardPowerOff
+ *
+ * This class is derived from the PowerOffAction class
+ * and will execute a hard power off after some delay.
+ */
+class HardPowerOff : public PowerOffAction
+{
+ public:
+ HardPowerOff() = delete;
+ ~HardPowerOff() = default;
+ HardPowerOff(const HardPowerOff&) = delete;
+ HardPowerOff& operator=(const HardPowerOff&) = delete;
+ HardPowerOff(HardPowerOff&&) = delete;
+ HardPowerOff& operator=(HardPowerOff&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] delay - The amount of time in seconds to wait before
+ * doing the power off
+ * @param[in] powerInterface - The object to use to do the power off
+ */
+ HardPowerOff(uint32_t delay,
+ std::shared_ptr<PowerInterfaceBase> powerInterface) :
+ PowerOffAction("Hard Power Off: " + std::to_string(delay) + "s",
+ powerInterface),
+ _delay(delay),
+ _timer(_event, std::bind(std::mem_fn(&HardPowerOff::powerOff), this))
+ {}
+
+ /**
+ * @brief Starts a timer upon the expiration of which the
+ * hard power off will be done.
+ */
+ void start() override
+ {
+ _timer.restartOnce(_delay);
+ }
+
+ /**
+ * @brief Cancels the timer. This is always allowed.
+ *
+ * @param[in] force - If the cancel should be forced or not
+ * (not checked in this case)
+ * @return bool - Always returns true
+ */
+ bool cancel(bool) override
+ {
+ if (_timer.isEnabled())
+ {
+ _timer.setEnabled(false);
+ }
+
+ // Can always be canceled
+ return true;
+ }
+
+ /**
+ * @brief Performs the hard power off.
+ */
+ void powerOff()
+ {
+ getLogger().log(
+ fmt::format("Action '{}' executing hard power off", name()));
+ _powerIface->hardPowerOff();
+ }
+
+ private:
+ /**
+ * @brief The number of seconds to wait between starting the
+ * action and doing the power off.
+ */
+ std::chrono::seconds _delay;
+
+ /**
+ * @brief The Timer object used to handle the delay.
+ */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _timer;
+};
+
+/**
+ * @class SoftPowerOff
+ *
+ * This class is derived from the PowerOffAction class
+ * and will execute a soft power off after some delay.
+ */
+class SoftPowerOff : public PowerOffAction
+{
+ public:
+ SoftPowerOff() = delete;
+ ~SoftPowerOff() = default;
+ SoftPowerOff(const SoftPowerOff&) = delete;
+ SoftPowerOff& operator=(const SoftPowerOff&) = delete;
+ SoftPowerOff(SoftPowerOff&&) = delete;
+ SoftPowerOff& operator=(SoftPowerOff&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] delay - The amount of time in seconds to wait before
+ * doing the power off
+ * @param[in] powerInterface - The object to use to do the power off
+ */
+ SoftPowerOff(uint32_t delay,
+ std::shared_ptr<PowerInterfaceBase> powerInterface) :
+ PowerOffAction("Soft Power Off: " + std::to_string(delay) + "s",
+ powerInterface),
+ _delay(delay),
+ _timer(_event, std::bind(std::mem_fn(&SoftPowerOff::powerOff), this))
+ {}
+
+ /**
+ * @brief Starts a timer upon the expiration of which the
+ * soft power off will be done.
+ */
+ void start() override
+ {
+ _timer.restartOnce(_delay);
+ }
+
+ /**
+ * @brief Cancels the timer. This is always allowed.
+ *
+ * @param[in] force - If the cancel should be forced or not
+ * (not checked in this case)
+ * @return bool - Always returns true
+ */
+ bool cancel(bool) override
+ {
+ if (_timer.isEnabled())
+ {
+ _timer.setEnabled(false);
+ }
+
+ // Can always be canceled
+ return true;
+ }
+
+ /**
+ * @brief Performs the soft power off.
+ */
+ void powerOff()
+ {
+ getLogger().log(
+ fmt::format("Action '{}' executing soft power off", name()));
+ _powerIface->softPowerOff();
+ }
+
+ private:
+ /**
+ * @brief The number of seconds to wait between starting the
+ * action and doing the power off.
+ */
+ std::chrono::seconds _delay;
+
+ /**
+ * @brief The Timer object used to handle the delay.
+ */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _timer;
+};
+
+/**
+ * @class EpowPowerOff
+ *
+ * Still TODO, but has a cancelable service mode delay followed
+ * by an uncancelable meltdown delay followed by a hard power off, with
+ * some sort of EPOW alert in there as well.
+ */
+class EpowPowerOff : public PowerOffAction
+{
+ public:
+ EpowPowerOff() = delete;
+ ~EpowPowerOff() = default;
+ EpowPowerOff(const EpowPowerOff&) = delete;
+ EpowPowerOff& operator=(const EpowPowerOff&) = delete;
+ EpowPowerOff(EpowPowerOff&&) = delete;
+ EpowPowerOff& operator=(EpowPowerOff&&) = delete;
+
+ EpowPowerOff(uint32_t serviceModeDelay, uint32_t meltdownDelay,
+ std::shared_ptr<PowerInterfaceBase> powerInterface) :
+ PowerOffAction("EPOW Power Off: " + std::to_string(serviceModeDelay) +
+ "s/" + std::to_string(meltdownDelay) + "s",
+ powerInterface),
+ _serviceModeDelay(serviceModeDelay), _meltdownDelay(meltdownDelay)
+ {}
+
+ void start() override
+ {
+ // TODO
+ }
+
+ bool cancel(bool) override
+ {
+ // TODO
+ return true;
+ }
+
+ private:
+ std::chrono::seconds _serviceModeDelay;
+ std::chrono::seconds _meltdownDelay;
+};
+} // namespace phosphor::fan::monitor