/* // 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 #include #include #include namespace redfish { static constexpr const std::array supportedEvtFormatTypes = { "Event"}; static constexpr const std::array supportedRegPrefixes = { "Base", "OpenBMC", "Task"}; static constexpr const std::array 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 registryMsgIds; std::vector registryPrefixes; std::vector httpHeaders; // key-value pair }; EventSrvConfig configData; boost::container::flat_map 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& params) override { auto asyncResp = std::make_shared(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& params) override { auto asyncResp = std::make_shared(res); std::optional serviceEnabled; std::optional retryAttemps; std::optional 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& params) override { auto asyncResp = std::make_shared(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& params) override { auto asyncResp = std::make_shared(res); if (subscriptionsMap.size() >= maxNoOfSubscriptions) { messages::eventSubscriptionLimitExceeded(asyncResp->res); return; } std::string destUrl; std::string protocol; std::optional context; std::optional subscriptionType; std::optional eventFormatType; std::optional retryPolicy; std::optional> msgIds; std::optional> regPrefixes; std::optional> 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: ://:/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(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//", 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& params) override { auto asyncResp = std::make_shared(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& params) override { auto asyncResp = std::make_shared(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 context; std::optional retryPolicy; std::optional> 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& params) override { auto asyncResp = std::make_shared(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