| /* |
| // 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 |