Implement Subscription Heartbeat Logic
This implements the subscription heartbeat logic which will send the
message `RedfishServiceFunctional` periodically with the interval of
`HeartbeatIntervalMinutes` specified in subscription property [1][2], if
`SendHeartbeat` is enabled..
Note the heartbeat enablement is per event destination as DMTF specifies
[3] like
```
... This message shall only be sent if specifically requested by an event
destination during the creation of a subscription...
```
This also add `HeartbeatEvent` to supported registry prefixes like
```
curl -k -X GET https://${bmc}/redfish/v1/EventService/
{
...
"RegistryPrefixes": [
"Base",
"OpenBMC",
"TaskEvent",
"HeartbeatEvent"
],
"ResourceTypes": [
"Task",
"Heartbeat"
],
```
Tested:
1) A single subscription and heartbeat via Redfish Event Listener
- Create a subscription via Redfish Event Listener
- PATCH `SendHeartbeat=true` and `HeartbeatIntervalMinutes` like
```
curl -k -X PATCH https://${bmc}/redfish/v1/EventService/Subscriptions/${SUBID} \
-H "Content-Type: application/json" \
-d '{"SendHeartbeat":true, "HeartbeatIntervalMinutes":1}'
```
- Monitor the Redfish Event Listener and check the following heartbeat
messages periodically (per HeartbeatIntervalMinutes)
```
response_type: POST
headers: {'Host': '9.3.62.209', 'Content-Length': '230'}
response={
"@odata.type": "#Event.v1_4_0.Event",
"Events": [
{
"@odata.type": "#Message.v1_1_1.Message",
"EventId": "HeartbeatId",
"EventTimestamp": "2024-11-21T12:21:47+00:00",
"MemberId": "0",
"Message": "Redfish service is functional.",
"MessageArgs": [],
"MessageId": "HeartbeatEvent.1.0.1.RedfishServiceFunctional",
"MessageSeverity": "OK",
"OriginOfCondition": "/redfish/v1/EventService/Subscriptions/1521743607",
"Resolution": "None."
}
],
"Id": "HeartbeatId",
"Name": "Event Log"
}
```
- Change `SendHeartbeat` to false and see whether the heartbeat message
is stopped.
2) Multiple sbscribers with the different heartbeat setups
- create 2 event listeners with 2 different destinations (e.g., port
8080 and 8081).
- Patch sendheartbeat=true to only one subscriber.
- Check whether the only subscriber that enables `SendHeartbeat` is
receiving the heartbeat messages.
3) Redfish Service Validator passes
[1] https://github.com/openbmc/bmcweb/blob/02ea923f13de196726ac2f022766a6f80bee1c0a/redfish-core/schema/dmtf/json-schema/EventDestination.v1_15_0.json#L356
[2] https://redfish.dmtf.org/registries/HeartbeatEvent.1.0.1.json
[3] https://github.com/DMTF/Redfish/blob/d9e54fc8393d8930bd42e8b134741f5051a2680f/registries/HeartbeatEvent.1.0.1.json#L14
Change-Id: I8682e05f4459940913ba189f1ed016874e38dd4a
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
index 04ca735..57385a2 100644
--- a/redfish-core/lib/event_service.hpp
+++ b/redfish-core/lib/event_service.hpp
@@ -45,13 +45,13 @@
static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
eventFormatType, metricReportFormatType};
-static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
- "Base", "OpenBMC", "TaskEvent"};
+static constexpr const std::array<const char*, 4> supportedRegPrefixes = {
+ "Base", "OpenBMC", "TaskEvent", "HeartbeatEvent"};
static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
"TerminateAfterRetries", "SuspendRetries", "RetryForever"};
-static constexpr const std::array<const char*, 1> supportedResourceTypes = {
- "Task"};
+static constexpr const std::array<const char*, 2> supportedResourceTypes = {
+ "Task", "Heartbeat"};
inline void requestRoutesEventService(App& app)
{
@@ -708,6 +708,12 @@
messages::created(asyncResp->res);
asyncResp->res.addHeader(
"Location", "/redfish/v1/EventService/Subscriptions/" + id);
+
+ // schedule a heartbeat
+ if (subValue->userSub->sendHeartbeat)
+ {
+ subValue->scheduleNextHeartbeatEvent();
+ }
});
}
@@ -875,6 +881,14 @@
subValue->userSub->hbIntervalMinutes = *hbIntervalMinutes;
}
+ if (hbIntervalMinutes || sendHeartbeat)
+ {
+ // if Heartbeat interval or send heart were changed, cancel
+ // the heartbeat timer if running and start a new heartbeat
+ // if needed
+ subValue->heartbeatParametersChanged();
+ }
+
if (verifyCertificate)
{
subValue->userSub->verifyCertificate = *verifyCertificate;
diff --git a/redfish-core/lib/message_registries.hpp b/redfish-core/lib/message_registries.hpp
index c41b144..a6c3674 100644
--- a/redfish-core/lib/message_registries.hpp
+++ b/redfish-core/lib/message_registries.hpp
@@ -19,6 +19,7 @@
#include "query.hpp"
#include "registries.hpp"
#include "registries/base_message_registry.hpp"
+#include "registries/heartbeat_event_message_registry.hpp"
#include "registries/openbmc_message_registry.hpp"
#include "registries/privilege_registry.hpp"
#include "registries/resource_event_message_registry.hpp"
@@ -49,17 +50,21 @@
asyncResp->res.jsonValue["Name"] = "MessageRegistryFile Collection";
asyncResp->res.jsonValue["Description"] =
"Collection of MessageRegistryFiles";
- asyncResp->res.jsonValue["Members@odata.count"] = 5;
nlohmann::json& members = asyncResp->res.jsonValue["Members"];
- for (const char* memberName : std::to_array(
- {"Base", "TaskEvent", "ResourceEvent", "OpenBMC", "Telemetry"}))
+
+ static constexpr const auto registryFiles = std::to_array(
+ {"Base", "TaskEvent", "ResourceEvent", "OpenBMC", "Telemetry",
+ "HeartbeatEvent"});
+
+ for (const char* memberName : registryFiles)
{
nlohmann::json::object_t member;
member["@odata.id"] =
boost::urls::format("/redfish/v1/Registries/{}", memberName);
members.emplace_back(std::move(member));
}
+ asyncResp->res.jsonValue["Members@odata.count"] = members.size();
}
inline void requestRoutesMessageRegistryFileCollection(App& app)
@@ -111,6 +116,11 @@
header = ®istries::telemetry::header;
url = registries::telemetry::url;
}
+ else if (registry == "HeartbeatEvent")
+ {
+ header = ®istries::heartbeat_event::header;
+ url = registries::heartbeat_event::url;
+ }
else
{
messages::resourceNotFound(asyncResp->res, "MessageRegistryFile",
@@ -209,6 +219,15 @@
registryEntries.emplace_back(&entry);
}
}
+ else if (registry == "HeartbeatEvent")
+ {
+ header = ®istries::heartbeat_event::header;
+ for (const registries::MessageEntry& entry :
+ registries::heartbeat_event::registry)
+ {
+ registryEntries.emplace_back(&entry);
+ }
+ }
else
{
messages::resourceNotFound(asyncResp->res, "MessageRegistryFile",