/* // 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 "event_service_manager.hpp" #include #include #include #include #include #include #include namespace redfish { static constexpr const std::array supportedEvtFormatTypes = { eventFormatType, metricReportFormatType}; static constexpr const std::array supportedRegPrefixes = { "Base", "OpenBMC", "TaskEvent"}; static constexpr const std::array supportedRetryPolicies = { "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE static constexpr const std::array supportedResourceTypes = { "IBMConfigFile", "Task"}; #else static constexpr const std::array supportedResourceTypes = { "Task"}; #endif static constexpr const uint8_t maxNoOfSubscriptions = 20; inline void requestRoutesEventService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/EventService/") .privileges(redfish::privileges::getEventService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService"; asyncResp->res.jsonValue["@odata.type"] = "#EventService.v1_5_0.EventService"; asyncResp->res.jsonValue["Id"] = "EventService"; asyncResp->res.jsonValue["Name"] = "Event Service"; asyncResp->res.jsonValue["Subscriptions"]["@odata.id"] = "/redfish/v1/EventService/Subscriptions"; asyncResp->res .jsonValue["Actions"]["#EventService.SubmitTestEvent"]["target"] = "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent"; const persistent_data::EventServiceConfig eventServiceConfig = persistent_data::EventServiceStore::getInstance() .getEventServiceConfig(); asyncResp->res.jsonValue["Status"]["State"] = (eventServiceConfig.enabled ? "Enabled" : "Disabled"); asyncResp->res.jsonValue["ServiceEnabled"] = eventServiceConfig.enabled; asyncResp->res.jsonValue["DeliveryRetryAttempts"] = eventServiceConfig.retryAttempts; asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] = eventServiceConfig.retryTimeoutInterval; asyncResp->res.jsonValue["EventFormatTypes"] = supportedEvtFormatTypes; asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes; asyncResp->res.jsonValue["ResourceTypes"] = supportedResourceTypes; nlohmann::json supportedSSEFilters = { {"EventFormatType", true}, {"MessageId", true}, {"MetricReportDefinition", true}, {"RegistryPrefix", true}, {"OriginResource", false}, {"ResourceType", false}}; asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] = supportedSSEFilters; }); BMCWEB_ROUTE(app, "/redfish/v1/EventService/") .privileges(redfish::privileges::patchEventService) .methods(boost::beast::http::verb::patch)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } std::optional serviceEnabled; std::optional retryAttemps; std::optional retryInterval; if (!json_util::readJsonPatch( req, asyncResp->res, "ServiceEnabled", serviceEnabled, "DeliveryRetryAttempts", retryAttemps, "DeliveryRetryIntervalSeconds", retryInterval)) { return; } persistent_data::EventServiceConfig eventServiceConfig = persistent_data::EventServiceStore::getInstance() .getEventServiceConfig(); if (serviceEnabled) { eventServiceConfig.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 { eventServiceConfig.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 { eventServiceConfig.retryTimeoutInterval = *retryInterval; } } EventServiceManager::getInstance().setEventServiceConfig( eventServiceConfig); }); } inline void requestRoutesSubmitTestEvent(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/") .privileges(redfish::privileges::postEventService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } if (!EventServiceManager::getInstance().sendTestEventLog()) { messages::serviceDisabled(asyncResp->res, "/redfish/v1/EventService/"); return; } asyncResp->res.result(boost::beast::http::status::no_content); }); } inline void requestRoutesEventDestinationCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") .privileges(redfish::privileges::getEventDestinationCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#EventDestinationCollection.EventDestinationCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService/Subscriptions"; asyncResp->res.jsonValue["Name"] = "Event Destination Collections"; nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; std::vector subscripIds = EventServiceManager::getInstance().getAllIDs(); memberArray = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = subscripIds.size(); for (const std::string& id : subscripIds) { nlohmann::json::object_t member; member["@odata.id"] = "/redfish/v1/EventService/Subscriptions/" + id; memberArray.push_back(std::move(member)); } }); BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") .privileges(redfish::privileges::postEventDestinationCollection) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } if (EventServiceManager::getInstance().getNumberOfSubscriptions() >= maxNoOfSubscriptions) { messages::eventSubscriptionLimitExceeded(asyncResp->res); return; } std::string destUrl; std::string protocol; std::optional context; std::optional subscriptionType; std::optional eventFormatType2; std::optional retryPolicy; std::optional> msgIds; std::optional> regPrefixes; std::optional> resTypes; std::optional> headers; std::optional> mrdJsonArray; if (!json_util::readJsonPatch( req, asyncResp->res, "Destination", destUrl, "Context", context, "Protocol", protocol, "SubscriptionType", subscriptionType, "EventFormatType", eventFormatType2, "HttpHeaders", headers, "RegistryPrefixes", regPrefixes, "MessageIds", msgIds, "DeliveryRetryPolicy", retryPolicy, "MetricReportDefinitions", mrdJsonArray, "ResourceTypes", resTypes)) { return; } if (regPrefixes && msgIds) { if (!regPrefixes->empty() && !msgIds->empty()) { messages::propertyValueConflict(asyncResp->res, "MessageIds", "RegistryPrefixes"); return; } } std::string host; std::string urlProto; uint16_t port = 0; std::string path; if (!crow::utility::validateAndSplitUrl(destUrl, urlProto, host, port, path)) { BMCWEB_LOG_WARNING << "Failed to validate and split destination url"; messages::propertyValueFormatError(asyncResp->res, destUrl, "Destination"); return; } if (path.empty()) { path = "/"; } std::shared_ptr subValue = std::make_shared(host, port, path, urlProto); 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 (eventFormatType2) { if (std::find(supportedEvtFormatTypes.begin(), supportedEvtFormatTypes.end(), *eventFormatType2) == supportedEvtFormatTypes.end()) { messages::propertyValueNotInList( asyncResp->res, *eventFormatType2, "EventFormatType"); return; } subValue->eventFormatType = *eventFormatType2; } else { // If not specified, use default "Event" subValue->eventFormatType = "Event"; } if (context) { subValue->customText = *context; } if (headers) { for (const nlohmann::json& headerChunk : *headers) { for (const auto& item : headerChunk.items()) { const std::string* value = item.value().get_ptr(); if (value == nullptr) { messages::propertyValueFormatError( asyncResp->res, item.value().dump(2, 1), "HttpHeaders/" + item.key()); return; } subValue->httpHeaders.set(item.key(), *value); } } } 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 (resTypes) { for (const std::string& it : *resTypes) { if (std::find(supportedResourceTypes.begin(), supportedResourceTypes.end(), it) == supportedResourceTypes.end()) { messages::propertyValueNotInList(asyncResp->res, it, "ResourceTypes"); return; } } subValue->resourceTypes = *resTypes; } if (msgIds) { std::vector registryPrefix; // If no registry prefixes are mentioned, consider all // supported prefixes if (subValue->registryPrefixes.empty()) { registryPrefix.assign(supportedRegPrefixes.begin(), supportedRegPrefixes.end()); } else { registryPrefix = subValue->registryPrefixes; } for (const std::string& id : *msgIds) { bool validId = false; // Check for Message ID in each of the selected Registry for (const std::string& it : registryPrefix) { const std::span registry = redfish::registries::getRegistryFromPrefix(it); if (std::any_of( registry.begin(), registry.end(), [&id](const redfish::registries::MessageEntry& messageEntry) { return id == messageEntry.first; })) { validId = true; break; } } if (!validId) { messages::propertyValueNotInList(asyncResp->res, id, "MessageIds"); return; } } 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"; } if (mrdJsonArray) { for (nlohmann::json& mrdObj : *mrdJsonArray) { std::string mrdUri; if (!json_util::readJson(mrdObj, asyncResp->res, "@odata.id", mrdUri)) { return; } subValue->metricReportDefinitions.emplace_back(mrdUri); } } std::string id = EventServiceManager::getInstance().addSubscription(subValue); if (id.empty()) { messages::internalError(asyncResp->res); return; } messages::created(asyncResp->res); asyncResp->res.addHeader( "Location", "/redfish/v1/EventService/Subscriptions/" + id); }); } inline void requestRoutesEventDestination(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions//") .privileges(redfish::privileges::getEventDestination) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } std::shared_ptr subValue = EventServiceManager::getInstance().getSubscription(param); if (subValue == nullptr) { asyncResp->res.result(boost::beast::http::status::not_found); return; } const std::string& id = param; asyncResp->res.jsonValue["@odata.type"] = "#EventDestination.v1_7_0.EventDestination"; asyncResp->res.jsonValue["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"] = nlohmann::json::array(); asyncResp->res.jsonValue["EventFormatType"] = subValue->eventFormatType; asyncResp->res.jsonValue["RegistryPrefixes"] = subValue->registryPrefixes; asyncResp->res.jsonValue["ResourceTypes"] = subValue->resourceTypes; asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds; asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy; nlohmann::json::array_t mrdJsonArray; for (const auto& mdrUri : subValue->metricReportDefinitions) { nlohmann::json::object_t mdr; mdr["@odata.id"] = mdrUri; mrdJsonArray.emplace_back(std::move(mdr)); } asyncResp->res.jsonValue["MetricReportDefinitions"] = mrdJsonArray; }); BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions//") // The below privilege is wrong, it should be ConfigureManager OR // ConfigureSelf // https://github.com/openbmc/bmcweb/issues/220 //.privileges(redfish::privileges::patchEventDestination) .privileges({{"ConfigureManager"}}) .methods(boost::beast::http::verb::patch)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } std::shared_ptr subValue = EventServiceManager::getInstance().getSubscription(param); if (subValue == nullptr) { asyncResp->res.result(boost::beast::http::status::not_found); return; } std::optional context; std::optional retryPolicy; std::optional> headers; if (!json_util::readJsonPatch(req, asyncResp->res, "Context", context, "DeliveryRetryPolicy", retryPolicy, "HttpHeaders", headers)) { return; } if (context) { subValue->customText = *context; } if (headers) { boost::beast::http::fields fields; for (const nlohmann::json& headerChunk : *headers) { for (auto& it : headerChunk.items()) { const std::string* value = it.value().get_ptr(); if (value == nullptr) { messages::propertyValueFormatError( asyncResp->res, it.value().dump(2, ' ', true), "HttpHeaders/" + it.key()); return; } fields.set(it.key(), *value); } } subValue->httpHeaders = fields; } if (retryPolicy) { if (std::find(supportedRetryPolicies.begin(), supportedRetryPolicies.end(), *retryPolicy) == supportedRetryPolicies.end()) { messages::propertyValueNotInList(asyncResp->res, *retryPolicy, "DeliveryRetryPolicy"); return; } subValue->retryPolicy = *retryPolicy; subValue->updateRetryPolicy(); } EventServiceManager::getInstance().updateSubscriptionData(); }); BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions//") // The below privilege is wrong, it should be ConfigureManager OR // ConfigureSelf // https://github.com/openbmc/bmcweb/issues/220 //.privileges(redfish::privileges::deleteEventDestination) .privileges({{"ConfigureManager"}}) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } if (!EventServiceManager::getInstance().isSubscriptionExist(param)) { asyncResp->res.result(boost::beast::http::status::not_found); return; } EventServiceManager::getInstance().deleteSubscription(param); }); } } // namespace redfish