watchdog: add support for systemd watchdog
Add interfaces and handling for systemd watchdog petting.
Systemd service files can specify a watchdog timeout and systemd will
expect the application to periodically poke a software watchdog, or
else the service will be restarted. This is enabled with the
'WatchdogSec=' service file directive. Add primitives for
interacting with the watchdog APIs.
Enable automatic support in the `async::context` for this watchdog
handling, such that if the watchdog is required (by checking
sd_watchdog_enabled) the daemon will automatically pet at the
appropriate rate, assuming that the `async::context` is functioning
correctly.
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I68caf7b2c7166ca402b07ecee3db65f75365aa72
diff --git a/test/async/watchdog.cpp b/test/async/watchdog.cpp
new file mode 100644
index 0000000..9ab9604
--- /dev/null
+++ b/test/async/watchdog.cpp
@@ -0,0 +1,100 @@
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/test/sdbus_mock.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace sdbusplus::async
+{
+struct WatchdogMock : public sdbusplus::SdBusImpl
+{
+ WatchdogMock() : sdbusplus::SdBusImpl()
+ {
+ sd_bus_open(&busp);
+ }
+
+ ~WatchdogMock()
+ {
+ sd_bus_unref(busp);
+ }
+
+ MOCK_METHOD(int, sd_notify, (int, const char*), (override));
+
+ MOCK_METHOD(int, sd_watchdog_enabled, (int, uint64_t* usec), (override));
+
+ sdbusplus::bus::busp_t busp = nullptr;
+};
+
+struct WatchdogContext : public testing::Test
+{
+ WatchdogMock sdbusMock;
+ sdbusplus::async::context ctx;
+
+ WatchdogContext() : ctx(sdbusplus::bus_t(sdbusMock.busp, &sdbusMock)) {}
+
+ ~WatchdogContext() noexcept override = default;
+
+ void TearDown() override {}
+
+ void spawnStop()
+ {
+ ctx.spawn(stdexec::just() |
+ stdexec::then([this]() { ctx.request_stop(); }));
+ }
+
+ void runToStop()
+ {
+ spawnStop();
+ ctx.run();
+ }
+};
+
+TEST_F(WatchdogContext, WatchdogEnabledAndHeartbeat)
+{
+ using namespace testing;
+ using namespace std::literals;
+
+ // Expect sd_watchdog_enabled to be called once and return .1 second
+ EXPECT_CALL(sdbusMock, sd_watchdog_enabled(_, _))
+ .WillOnce([](int, uint64_t* usec) {
+ *usec = 100000; // .1 second
+ return 0;
+ });
+
+ // Expect sd_notify to be called at least once for heartbeat
+ // The watchdog_loop divides the time by 2, so it should be
+ // called every 0.05 seconds.
+ EXPECT_CALL(sdbusMock, sd_notify(_, StrEq("WATCHDOG=1")))
+ .WillRepeatedly(Return(0));
+
+ // Run the context for a short period to allow watchdog_loop to execute
+ // and send a handful of heartbeats.
+ ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s));
+ runToStop();
+
+ // The EXPECT_CALL for sd_notify will verify if it was called as expected.
+ // If the test passes, it means sd_watchdog_enabled was called,
+ // and sd_notify was called for heartbeats.
+}
+
+TEST_F(WatchdogContext, WatchdogDisabled)
+{
+ using namespace testing;
+ using namespace std::literals;
+
+ // Expect sd_watchdog_enabled to be called once and return 0 (disabled)
+ EXPECT_CALL(sdbusMock, sd_watchdog_enabled(_, _))
+ .WillOnce([](int, uint64_t* usec) {
+ *usec = 0; // Watchdog disabled
+ return 0;
+ });
+
+ // Expect sd_notify will NOT be called
+ EXPECT_CALL(sdbusMock, sd_notify(_, _)).Times(0);
+
+ // Run the context. No sleep is needed as watchdog should exit immediately.
+ runToStop();
+}
+
+} // namespace sdbusplus::async
diff --git a/test/meson.build b/test/meson.build
index 4fe9b15..300c3bc 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -24,6 +24,7 @@
'async/task',
'async/timer',
'async/fdio',
+ 'async/watchdog',
'bus/exception',
'bus/list_names',
'bus/match',