diff options
Diffstat (limited to 'redfish-core/lib/event_service.hpp')
-rw-r--r-- | redfish-core/lib/event_service.hpp | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp new file mode 100644 index 0000000000..62654919dc --- /dev/null +++ b/redfish-core/lib/event_service.hpp @@ -0,0 +1,517 @@ +/* +// Copyright (c) 2020 Intel 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. +*/ +#pragma once +#include "node.hpp" + +#include <boost/container/flat_map.hpp> +#include <error_messages.hpp> +#include <utils/json_utils.hpp> +#include <variant> + +namespace redfish +{ + +static constexpr const std::array<const char*, 1> supportedEvtFormatTypes = { + "Event"}; +static constexpr const std::array<const char*, 3> supportedRegPrefixes = { + "Base", "OpenBMC", "Task"}; +static constexpr const std::array<const char*, 3> supportedRetryPolicies = { + "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; + +static constexpr const uint8_t maxNoOfSubscriptions = 20; + +struct EventSrvConfig +{ + bool enabled; + uint32_t retryAttempts; + uint32_t retryTimeoutInterval; +}; + +struct EventSrvSubscription +{ + 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<nlohmann::json> httpHeaders; // key-value pair +}; + +EventSrvConfig configData; +boost::container::flat_map<std::string, EventSrvSubscription> subscriptionsMap; + +inline void initEventSrvStore() +{ + // TODO: Read the persistent data from store and populate. + // Populating with default. + configData.enabled = true; + configData.retryAttempts = 3; + configData.retryTimeoutInterval = 30; // seconds +} + +inline void updateSubscriptionData() +{ + // Persist the config and subscription data. + // TODO: subscriptionsMap & configData need to be + // written to Persist store. + return; +} +class EventService : public Node +{ + public: + EventService(CrowApp& app) : Node(app, "/redfish/v1/EventService/") + { + initEventSrvStore(); + + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + res.jsonValue = { + {"@odata.type", "#EventService.v1_5_0.EventService"}, + {"Id", "EventService"}, + {"Name", "Event Service"}, + {"ServerSentEventUri", + "/redfish/v1/EventService/Subscriptions/SSE"}, + {"Subscriptions", + {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}}, + {"@odata.id", "/redfish/v1/EventService"}}; + + asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; + asyncResp->res.jsonValue["ServiceEnabled"] = configData.enabled; + asyncResp->res.jsonValue["DeliveryRetryAttempts"] = + configData.retryAttempts; + asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] = + configData.retryTimeoutInterval; + asyncResp->res.jsonValue["EventFormatTypes"] = supportedEvtFormatTypes; + asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes; + } + + void doPatch(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + + std::optional<bool> serviceEnabled; + std::optional<uint32_t> retryAttemps; + std::optional<uint32_t> retryInterval; + + if (!json_util::readJson(req, res, "ServiceEnabled", serviceEnabled, + "DeliveryRetryAttempts", retryAttemps, + "DeliveryRetryIntervalSeconds", retryInterval)) + { + return; + } + + if (serviceEnabled) + { + configData.enabled = *serviceEnabled; + } + + if (retryAttemps) + { + // Supported range [1-3] + if ((*retryAttemps < 1) || (*retryAttemps > 3)) + { + messages::queryParameterOutOfRange( + asyncResp->res, std::to_string(*retryAttemps), + "DeliveryRetryAttempts", "[1-3]"); + } + else + { + configData.retryAttempts = *retryAttemps; + } + } + + if (retryInterval) + { + // Supported range [30 - 180] + if ((*retryInterval < 30) || (*retryInterval > 180)) + { + messages::queryParameterOutOfRange( + asyncResp->res, std::to_string(*retryInterval), + "DeliveryRetryIntervalSeconds", "[30-180]"); + } + else + { + configData.retryTimeoutInterval = *retryInterval; + } + } + + updateSubscriptionData(); + } +}; + +class EventDestinationCollection : public Node +{ + public: + EventDestinationCollection(CrowApp& app) : + Node(app, "/redfish/v1/EventService/Subscriptions/") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + + res.jsonValue = { + {"@odata.type", + "#EventDestinationCollection.EventDestinationCollection"}, + {"@odata.id", "/redfish/v1/EventService/Subscriptions"}, + {"Name", "Event Destination Collections"}}; + + nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; + memberArray = nlohmann::json::array(); + asyncResp->res.jsonValue["Members@odata.count"] = + subscriptionsMap.size(); + + for (auto& it : subscriptionsMap) + { + memberArray.push_back( + {{"@odata.id", + "/redfish/v1/EventService/Subscriptions/" + it.first}}); + } + } + + void doPost(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + + if (subscriptionsMap.size() >= maxNoOfSubscriptions) + { + messages::eventSubscriptionLimitExceeded(asyncResp->res); + return; + } + std::string destUrl; + std::string protocol; + std::optional<std::string> context; + std::optional<std::string> subscriptionType; + std::optional<std::string> eventFormatType; + std::optional<std::string> retryPolicy; + std::optional<std::vector<std::string>> msgIds; + std::optional<std::vector<std::string>> regPrefixes; + std::optional<std::vector<nlohmann::json>> headers; + + if (!json_util::readJson( + req, res, "Destination", destUrl, "Context", context, + "Protocol", protocol, "SubscriptionType", subscriptionType, + "EventFormatType", eventFormatType, "HttpHeaders", headers, + "RegistryPrefixes", regPrefixes, "MessageIds", msgIds, + "DeliveryRetryPolicy", retryPolicy)) + { + return; + } + + EventSrvSubscription subValue; + + // Validate the URL using regex expression + // Format: <protocol>://<host>:<port>/uri + // protocol: http/https, uri: can include params. + const std::regex urlRegex("(http|https)://([^/ :]+):?.*"); + if (!std::regex_match(destUrl, urlRegex)) + { + messages::propertyValueFormatError(asyncResp->res, destUrl, + "Destination"); + return; + } + subValue.destinationUrl = destUrl; + + if (subscriptionType) + { + if (*subscriptionType != "RedfishEvent") + { + messages::propertyValueNotInList( + asyncResp->res, *subscriptionType, "SubscriptionType"); + return; + } + subValue.subscriptionType = *subscriptionType; + } + else + { + subValue.subscriptionType = "RedfishEvent"; // Default + } + + if (protocol != "Redfish") + { + messages::propertyValueNotInList(asyncResp->res, protocol, + "Protocol"); + return; + } + subValue.protocol = protocol; + + if (eventFormatType) + { + if (std::find(supportedEvtFormatTypes.begin(), + supportedEvtFormatTypes.end(), + *eventFormatType) == supportedEvtFormatTypes.end()) + { + messages::propertyValueNotInList( + asyncResp->res, *eventFormatType, "EventFormatType"); + return; + } + subValue.eventFormatType = *eventFormatType; + } + else + { + // If not specified, use default "Event" + subValue.eventFormatType.assign({"Event"}); + } + + if (context) + { + subValue.customText = *context; + } + + if (headers) + { + subValue.httpHeaders = *headers; + } + + if (regPrefixes) + { + for (const std::string& it : *regPrefixes) + { + if (std::find(supportedRegPrefixes.begin(), + supportedRegPrefixes.end(), + it) == supportedRegPrefixes.end()) + { + messages::propertyValueNotInList(asyncResp->res, it, + "RegistryPrefixes"); + return; + } + } + subValue.registryPrefixes = *regPrefixes; + } + + if (msgIds) + { + // Do we need to loop-up MessageRegistry and validate + // data for authenticity??? Not mandate, i believe. + subValue.registryMsgIds = *msgIds; + } + + if (retryPolicy) + { + if (std::find(supportedRetryPolicies.begin(), + supportedRetryPolicies.end(), + *retryPolicy) == supportedRetryPolicies.end()) + { + messages::propertyValueNotInList(asyncResp->res, *retryPolicy, + "DeliveryRetryPolicy"); + return; + } + subValue.retryPolicy = *retryPolicy; + } + else + { + // Default "TerminateAfterRetries" + subValue.retryPolicy = "TerminateAfterRetries"; + } + + std::srand(static_cast<uint32_t>(std::time(0))); + std::string id; + + int retry = 3; + while (retry) + { + id = std::to_string(std::rand()); + auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); + if (inserted.second) + { + break; + } + retry--; + }; + + if (retry <= 0) + { + messages::internalError(asyncResp->res); + return; + } + + updateSubscriptionData(); + + messages::created(asyncResp->res); + asyncResp->res.addHeader( + "Location", "/redfish/v1/EventService/Subscriptions/" + id); + } +}; + +class EventDestination : public Node +{ + public: + EventDestination(CrowApp& app) : + Node(app, "/redfish/v1/EventService/Subscriptions/<str>/", + std::string()) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + if (params.size() != 1) + { + messages::internalError(asyncResp->res); + return; + } + + const std::string& id = params[0]; + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + EventSrvSubscription& subValue = obj->second; + + res.jsonValue = { + {"@odata.type", "#EventDestination.v1_7_0.EventDestination"}, + {"Protocol", "Redfish"}}; + asyncResp->res.jsonValue["@odata.id"] = + "/redfish/v1/EventService/Subscriptions/" + id; + asyncResp->res.jsonValue["Id"] = id; + asyncResp->res.jsonValue["Name"] = "Event Destination " + id; + asyncResp->res.jsonValue["Destination"] = subValue.destinationUrl; + asyncResp->res.jsonValue["Context"] = subValue.customText; + asyncResp->res.jsonValue["SubscriptionType"] = + subValue.subscriptionType; + asyncResp->res.jsonValue["HttpHeaders"] = subValue.httpHeaders; + asyncResp->res.jsonValue["EventFormatType"] = subValue.eventFormatType; + asyncResp->res.jsonValue["RegistryPrefixes"] = + subValue.registryPrefixes; + asyncResp->res.jsonValue["MessageIds"] = subValue.registryMsgIds; + asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue.retryPolicy; + } + + void doPatch(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + if (params.size() != 1) + { + messages::internalError(asyncResp->res); + return; + } + + const std::string& id = params[0]; + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + std::optional<std::string> context; + std::optional<std::string> retryPolicy; + std::optional<std::vector<nlohmann::json>> headers; + + if (!json_util::readJson(req, res, "Context", context, + "DeliveryRetryPolicy", retryPolicy, + "HttpHeaders", headers)) + { + return; + } + + EventSrvSubscription& subValue = obj->second; + + if (context) + { + subValue.customText = *context; + } + + if (headers) + { + subValue.httpHeaders = *headers; + } + + if (retryPolicy) + { + if (std::find(supportedRetryPolicies.begin(), + supportedRetryPolicies.end(), + *retryPolicy) == supportedRetryPolicies.end()) + { + messages::propertyValueNotInList(asyncResp->res, *retryPolicy, + "DeliveryRetryPolicy"); + return; + } + subValue.retryPolicy = *retryPolicy; + } + + updateSubscriptionData(); + } + + void doDelete(crow::Response& res, const crow::Request& req, + const std::vector<std::string>& params) override + { + auto asyncResp = std::make_shared<AsyncResp>(res); + + if (params.size() != 1) + { + messages::internalError(asyncResp->res); + return; + } + + const std::string& id = params[0]; + auto obj = subscriptionsMap.find(id); + if (obj == subscriptionsMap.end()) + { + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + subscriptionsMap.erase(obj); + + updateSubscriptionData(); + } +}; + +} // namespace redfish |