blob: 62654919dc551734ff722689506c47f98bc41541 [file] [log] [blame]
/*
// 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