EventService: Move subscription persistent data
This commit resolves https://github.com/openbmc/bmcweb/issues/168
Current store mechanism makes it very difficult to keep in sync with
the existing files, and has caused several bugs because the path it
uses different than the existing bmcweb_persistent_data.json, and it's
missing several error checks.
If there has old config in /var/lib/bmcweb/eventservice_config.json.
Restart bmcweb will move old config to bmcweb_presistent_data.json and
delete the old config.
Tested:
- Create new Subscription via POST
https://${bmc}/redfish/v1/EventService/Subscriptions/
The subscription is successfully created and GET succussfully.
Restart bmcweb or reboot.
The subscription will restore.
- Delete the Subscription via DELETE
https://${bmc}/redfish/v1/EventService/Subscriptions/${subscription_id}
The subscription is successfully delete.
bmcweb_persistent_data.json will delete subscription content.
- Modify EventService config via PATCH
https://{{bmc}}/redfish/v1/EventService
GET https://{{bmc}}/redfish/v1/EventService and the changes applied.
bmcweb_persistent_data.json will apply modification after PATCH.
Restart bmcweb or reboot
The config maintains the changed.
Signed-off-by: JunLin Chen <Jun-Lin.Chen@quantatw.com>
Change-Id: Ic29385ea8231ba976bbf415af2803df2d30cb10a
diff --git a/include/event_service_store.hpp b/include/event_service_store.hpp
new file mode 100644
index 0000000..d51adad
--- /dev/null
+++ b/include/event_service_store.hpp
@@ -0,0 +1,264 @@
+#pragma once
+#include "logging.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <nlohmann/json.hpp>
+
+namespace persistent_data
+{
+
+struct UserSubscription
+{
+ std::string id;
+ std::string destinationUrl;
+ std::string protocol;
+ std::string retryPolicy;
+ std::string customText;
+ std::string eventFormatType;
+ std::string subscriptionType;
+ std::vector<std::string> registryMsgIds;
+ std::vector<std::string> registryPrefixes;
+ std::vector<std::string> resourceTypes;
+ std::vector<nlohmann::json> httpHeaders; // key-value pair
+ std::vector<std::string> metricReportDefinitions;
+
+ static std::shared_ptr<UserSubscription>
+ fromJson(const nlohmann::json& j, const bool loadFromOldConfig = false)
+ {
+ std::shared_ptr<UserSubscription> subvalue =
+ std::make_shared<UserSubscription>();
+ for (const auto& element : j.items())
+ {
+ if (element.key() == "Id")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->id = *value;
+ }
+ else if (element.key() == "Destination")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->destinationUrl = *value;
+ }
+ else if (element.key() == "Protocol")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->protocol = *value;
+ }
+ else if (element.key() == "DeliveryRetryPolicy")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->retryPolicy = *value;
+ }
+ else if (element.key() == "Context")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->customText = *value;
+ }
+ else if (element.key() == "EventFormatType")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->eventFormatType = *value;
+ }
+ else if (element.key() == "SubscriptionType")
+ {
+ const std::string* value =
+ element.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->subscriptionType = *value;
+ }
+ else if (element.key() == "MessageIds")
+ {
+ const auto& obj = element.value();
+ for (const auto& val : obj.items())
+ {
+ const std::string* value =
+ val.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->registryMsgIds.emplace_back(*value);
+ }
+ }
+ else if (element.key() == "RegistryPrefixes")
+ {
+ const auto& obj = element.value();
+ for (const auto& val : obj.items())
+ {
+ const std::string* value =
+ val.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->registryPrefixes.emplace_back(*value);
+ }
+ }
+ else if (element.key() == "ResourceTypes")
+ {
+ const auto& obj = element.value();
+ for (const auto& val : obj.items())
+ {
+ const std::string* value =
+ val.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->resourceTypes.emplace_back(*value);
+ }
+ }
+ else if (element.key() == "HttpHeaders")
+ {
+ const auto& obj = element.value();
+ for (const auto& val : obj.items())
+ {
+ const auto value =
+ val.value().get_ptr<const nlohmann::json::object_t*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->httpHeaders.emplace_back(*value);
+ }
+ }
+ else if (element.key() == "MetricReportDefinitions")
+ {
+ const auto& obj = element.value();
+ for (const auto& val : obj.items())
+ {
+ const std::string* value =
+ val.value().get_ptr<const std::string*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ subvalue->metricReportDefinitions.emplace_back(*value);
+ }
+ }
+ else
+ {
+ BMCWEB_LOG_ERROR
+ << "Got unexpected property reading persistent file: "
+ << element.key();
+ continue;
+ }
+ }
+
+ if ((subvalue->id.empty() && !loadFromOldConfig) ||
+ subvalue->destinationUrl.empty() || subvalue->protocol.empty() ||
+ subvalue->retryPolicy.empty() || subvalue->customText.empty() ||
+ subvalue->eventFormatType.empty() ||
+ subvalue->subscriptionType.empty() ||
+ (subvalue->registryPrefixes.empty() &&
+ subvalue->registryMsgIds.empty()))
+ {
+ BMCWEB_LOG_ERROR << "Subscription missing required field "
+ "information, refusing to restore";
+ return nullptr;
+ }
+
+ return subvalue;
+ }
+};
+
+struct EventServiceConfig
+{
+ bool enabled = true;
+ uint32_t retryAttempts = 3;
+ uint32_t retryTimeoutInterval = 30;
+
+ void fromJson(const nlohmann::json& j)
+ {
+ for (const auto& element : j.items())
+ {
+ if (element.key() == "ServiceEnabled")
+ {
+ const bool* value = element.value().get_ptr<const bool*>();
+ if (value == nullptr)
+ {
+ continue;
+ }
+ enabled = *value;
+ }
+ else if (element.key() == "DeliveryRetryAttempts")
+ {
+ const uint64_t* value =
+ element.value().get_ptr<const uint64_t*>();
+ if ((value == nullptr) ||
+ (*value < std::numeric_limits<uint32_t>::lowest()) ||
+ (*value > std::numeric_limits<uint32_t>::max()))
+ {
+ continue;
+ }
+ retryAttempts = static_cast<uint32_t>(*value);
+ }
+ else if (element.key() == "DeliveryRetryIntervalSeconds")
+ {
+ const uint64_t* value =
+ element.value().get_ptr<const uint64_t*>();
+ if ((value == nullptr) ||
+ (*value < std::numeric_limits<uint32_t>::lowest()) ||
+ (*value > std::numeric_limits<uint32_t>::max()))
+ {
+ continue;
+ }
+ retryTimeoutInterval = static_cast<uint32_t>(*value);
+ }
+ }
+ }
+};
+
+class EventServiceStore
+{
+ public:
+ boost::container::flat_map<std::string, std::shared_ptr<UserSubscription>>
+ subscriptionsConfigMap;
+ EventServiceConfig eventServiceConfig;
+
+ static EventServiceStore& getInstance()
+ {
+ static EventServiceStore eventServiceStore;
+ return eventServiceStore;
+ }
+
+ EventServiceConfig& getEventServiceConfig()
+ {
+ return eventServiceConfig;
+ }
+};
+
+} // namespace persistent_data