summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAppaRao Puli <apparao.puli@linux.intel.com>2020-04-09 19:06:51 +0300
committerAppaRao Puli <apparao.puli@linux.intel.com>2020-04-24 14:06:38 +0300
commitb52664e2f47512c4eb7ce8f036eacf7a4b161320 (patch)
treefe99c05de61815bca4d9a202760b06ac9253f06b
parent6193231855a08068dc74c33ae95efd23782d4157 (diff)
downloadbmcweb-b52664e2f47512c4eb7ce8f036eacf7a4b161320.tar.xz
EventService: Manager and subscriptions support
Add EventService Manager which will manage all the EventService configuration and subscriptions. This includes API for add or update or delete subscriptions along with other supported API support. Also includes http connection open and send event code using "push style eventing". Added BMCWEB_INSECURE_HTTP_PUSH_STYLE_EVENTING build flag to enable/disable http push style eventing which is not a secure channel. Tested: - Tested along with other patches such as http client and Event log support, SubmitTestEvent and its works. - Ran Redfish validation successfully. Change-Id: Ie4687e4cbfabd525b7a8ad4e615510f034edc6e9 Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
-rw-r--r--CMakeLists.txt7
-rw-r--r--redfish-core/include/event_service_manager.hpp213
-rw-r--r--redfish-core/lib/event_service.hpp224
3 files changed, 320 insertions, 124 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9d4e4e97ea..be9a3742ce 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,6 +127,11 @@ option (
)
option (
+ BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING
+ "Enable HTTP push style eventing feature" OFF
+)
+
+option (
BMCWEB_ENABLE_VALIDATION_UNSECURE_FEATURE
"Enables unsecure features required by validation. Note: must
be turned off for production images."
@@ -416,6 +421,8 @@ target_compile_definitions (
-DBMCWEB_INSECURE_UNRESTRICTED_SENSOR_OVERRIDE>
$<$<BOOL:${BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE}>:
-DBMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE>
+ $<$<BOOL:${BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING}>:
+ -DBMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING>
)
# configure and install systemd unit files
diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
new file mode 100644
index 0000000000..9b907ba459
--- /dev/null
+++ b/redfish-core/include/event_service_manager.hpp
@@ -0,0 +1,213 @@
+/*
+// 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 <cstdlib>
+#include <ctime>
+#include <error_messages.hpp>
+#include <http_client.hpp>
+#include <memory>
+#include <utils/json_utils.hpp>
+#include <variant>
+
+namespace redfish
+{
+class Subscription
+{
+ public:
+ std::string id;
+ 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
+
+ Subscription(const Subscription&) = delete;
+ Subscription& operator=(const Subscription&) = delete;
+ Subscription(Subscription&&) = delete;
+ Subscription& operator=(Subscription&&) = delete;
+
+ Subscription(const std::string& inHost, const std::string& inPort,
+ const std::string& inPath, const std::string& inUriProto) :
+ host(inHost),
+ port(inPort), path(inPath), uriProto(inUriProto)
+ {
+ conn = std::make_shared<crow::HttpClient>(
+ crow::connections::systemBus->get_io_context(), host, port);
+ }
+ ~Subscription()
+ {
+ }
+
+ void sendEvent(const std::string& msg)
+ {
+ std::vector<std::pair<std::string, std::string>> reqHeaders;
+ for (const auto& header : httpHeaders)
+ {
+ for (const auto& item : header.items())
+ {
+ std::string key = item.key();
+ std::string val = item.value();
+ reqHeaders.emplace_back(std::pair(key, val));
+ }
+ }
+ conn->setHeaders(reqHeaders);
+ conn->doConnectAndSend(path, msg);
+ }
+
+ private:
+ std::string host;
+ std::string port;
+ std::string path;
+ std::string uriProto;
+ std::shared_ptr<crow::HttpClient> conn;
+};
+
+class EventServiceManager
+{
+ private:
+ EventServiceManager(const EventServiceManager&) = delete;
+ EventServiceManager& operator=(const EventServiceManager&) = delete;
+ EventServiceManager(EventServiceManager&&) = delete;
+ EventServiceManager& operator=(EventServiceManager&&) = delete;
+
+ EventServiceManager()
+ {
+ // TODO: Read the persistent data from store and populate.
+ // Populating with default.
+ enabled = true;
+ retryAttempts = 3;
+ retryTimeoutInterval = 30; // seconds
+ }
+
+ boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
+ subscriptionsMap;
+
+ public:
+ bool enabled;
+ uint32_t retryAttempts;
+ uint32_t retryTimeoutInterval;
+
+ static EventServiceManager& getInstance()
+ {
+ static EventServiceManager handler;
+ return handler;
+ }
+
+ void updateSubscriptionData()
+ {
+ // Persist the config and subscription data.
+ // TODO: subscriptionsMap & configData need to be
+ // written to Persist store.
+ return;
+ }
+
+ std::shared_ptr<Subscription> getSubscription(const std::string& id)
+ {
+ auto obj = subscriptionsMap.find(id);
+ if (obj == subscriptionsMap.end())
+ {
+ BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id;
+ return nullptr;
+ }
+ std::shared_ptr<Subscription> subValue = obj->second;
+ return subValue;
+ }
+
+ std::string addSubscription(const std::shared_ptr<Subscription> subValue)
+ {
+ 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)
+ {
+ BMCWEB_LOG_ERROR << "Failed to generate random number";
+ return std::string("");
+ }
+
+ updateSubscriptionData();
+ return id;
+ }
+
+ bool isSubscriptionExist(const std::string& id)
+ {
+ auto obj = subscriptionsMap.find(id);
+ if (obj == subscriptionsMap.end())
+ {
+ return false;
+ }
+ return true;
+ }
+
+ void deleteSubscription(const std::string& id)
+ {
+ auto obj = subscriptionsMap.find(id);
+ if (obj != subscriptionsMap.end())
+ {
+ subscriptionsMap.erase(obj);
+ updateSubscriptionData();
+ }
+ }
+
+ size_t getNumberOfSubscriptions()
+ {
+ return subscriptionsMap.size();
+ }
+
+ std::vector<std::string> getAllIDs()
+ {
+ std::vector<std::string> idList;
+ for (const auto& it : subscriptionsMap)
+ {
+ idList.emplace_back(it.first);
+ }
+ return idList;
+ }
+
+ bool isDestinationExist(const std::string& destUrl)
+ {
+ for (const auto& it : subscriptionsMap)
+ {
+ std::shared_ptr<Subscription> entry = it.second;
+ if (entry->destinationUrl == destUrl)
+ {
+ BMCWEB_LOG_ERROR << "Destination exist already" << destUrl;
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+} // namespace redfish
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
index 62654919dc..43517ddc63 100644
--- a/redfish-core/lib/event_service.hpp
+++ b/redfish-core/lib/event_service.hpp
@@ -14,12 +14,7 @@
// 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>
+#include "event_service_manager.hpp"
namespace redfish
{
@@ -33,52 +28,11 @@ static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
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"}}},
@@ -104,11 +58,12 @@ class EventService : public Node
{"@odata.id", "/redfish/v1/EventService"}};
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
- asyncResp->res.jsonValue["ServiceEnabled"] = configData.enabled;
+ asyncResp->res.jsonValue["ServiceEnabled"] =
+ EventServiceManager::getInstance().enabled;
asyncResp->res.jsonValue["DeliveryRetryAttempts"] =
- configData.retryAttempts;
+ EventServiceManager::getInstance().retryAttempts;
asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] =
- configData.retryTimeoutInterval;
+ EventServiceManager::getInstance().retryTimeoutInterval;
asyncResp->res.jsonValue["EventFormatTypes"] = supportedEvtFormatTypes;
asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes;
}
@@ -131,7 +86,7 @@ class EventService : public Node
if (serviceEnabled)
{
- configData.enabled = *serviceEnabled;
+ EventServiceManager::getInstance().enabled = *serviceEnabled;
}
if (retryAttemps)
@@ -145,7 +100,8 @@ class EventService : public Node
}
else
{
- configData.retryAttempts = *retryAttemps;
+ EventServiceManager::getInstance().retryAttempts =
+ *retryAttemps;
}
}
@@ -160,11 +116,12 @@ class EventService : public Node
}
else
{
- configData.retryTimeoutInterval = *retryInterval;
+ EventServiceManager::getInstance().retryTimeoutInterval =
+ *retryInterval;
}
}
- updateSubscriptionData();
+ EventServiceManager::getInstance().updateSubscriptionData();
}
};
@@ -196,15 +153,17 @@ class EventDestinationCollection : public Node
{"Name", "Event Destination Collections"}};
nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
+
+ std::vector<std::string> subscripIds =
+ EventServiceManager::getInstance().getAllIDs();
memberArray = nlohmann::json::array();
- asyncResp->res.jsonValue["Members@odata.count"] =
- subscriptionsMap.size();
+ asyncResp->res.jsonValue["Members@odata.count"] = subscripIds.size();
- for (auto& it : subscriptionsMap)
+ for (const std::string& id : subscripIds)
{
memberArray.push_back(
{{"@odata.id",
- "/redfish/v1/EventService/Subscriptions/" + it.first}});
+ "/redfish/v1/EventService/Subscriptions/" + id}});
}
}
@@ -213,7 +172,8 @@ class EventDestinationCollection : public Node
{
auto asyncResp = std::make_shared<AsyncResp>(res);
- if (subscriptionsMap.size() >= maxNoOfSubscriptions)
+ if (EventServiceManager::getInstance().getNumberOfSubscriptions() >=
+ maxNoOfSubscriptions)
{
messages::eventSubscriptionLimitExceeded(asyncResp->res);
return;
@@ -238,19 +198,58 @@ class EventDestinationCollection : public Node
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))
+ // Format: <protocol>://<host>:<port>/<uri>
+ // protocol: http/https
+ // host: Exclude ' ', ':', '#', '?'
+ // port: Empty or numeric value with ':' seperator.
+ // uri: Start with '/' and Exclude '#', ' '
+ // Can include query params(ex: '/event?test=1')
+ // TODO: Need to validate hostname extensively(as per rfc)
+ const std::regex urlRegex(
+ "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/"
+ "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)");
+ std::cmatch match;
+ if (!std::regex_match(destUrl.c_str(), match, urlRegex))
{
messages::propertyValueFormatError(asyncResp->res, destUrl,
"Destination");
return;
}
- subValue.destinationUrl = destUrl;
+
+ std::string uriProto = std::string(match[1].first, match[1].second);
+ if (uriProto == "http")
+ {
+#ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING
+ messages::propertyValueFormatError(asyncResp->res, destUrl,
+ "Destination");
+ return;
+#endif
+ }
+
+ std::string host = std::string(match[2].first, match[2].second);
+ std::string port = std::string(match[3].first, match[3].second);
+ std::string path = std::string(match[4].first, match[4].second);
+ if (port.empty())
+ {
+ if (uriProto == "http")
+ {
+ port = "80";
+ }
+ else
+ {
+ port = "443";
+ }
+ }
+ if (path.empty())
+ {
+ path = "/";
+ }
+
+ std::shared_ptr<Subscription> subValue =
+ std::make_shared<Subscription>(host, port, path, uriProto);
+
+ subValue->destinationUrl = destUrl;
if (subscriptionType)
{
@@ -260,11 +259,11 @@ class EventDestinationCollection : public Node
asyncResp->res, *subscriptionType, "SubscriptionType");
return;
}
- subValue.subscriptionType = *subscriptionType;
+ subValue->subscriptionType = *subscriptionType;
}
else
{
- subValue.subscriptionType = "RedfishEvent"; // Default
+ subValue->subscriptionType = "RedfishEvent"; // Default
}
if (protocol != "Redfish")
@@ -273,7 +272,7 @@ class EventDestinationCollection : public Node
"Protocol");
return;
}
- subValue.protocol = protocol;
+ subValue->protocol = protocol;
if (eventFormatType)
{
@@ -285,22 +284,22 @@ class EventDestinationCollection : public Node
asyncResp->res, *eventFormatType, "EventFormatType");
return;
}
- subValue.eventFormatType = *eventFormatType;
+ subValue->eventFormatType = *eventFormatType;
}
else
{
// If not specified, use default "Event"
- subValue.eventFormatType.assign({"Event"});
+ subValue->eventFormatType.assign({"Event"});
}
if (context)
{
- subValue.customText = *context;
+ subValue->customText = *context;
}
if (headers)
{
- subValue.httpHeaders = *headers;
+ subValue->httpHeaders = *headers;
}
if (regPrefixes)
@@ -316,14 +315,14 @@ class EventDestinationCollection : public Node
return;
}
}
- subValue.registryPrefixes = *regPrefixes;
+ subValue->registryPrefixes = *regPrefixes;
}
if (msgIds)
{
// Do we need to loop-up MessageRegistry and validate
// data for authenticity??? Not mandate, i believe.
- subValue.registryMsgIds = *msgIds;
+ subValue->registryMsgIds = *msgIds;
}
if (retryPolicy)
@@ -336,37 +335,22 @@ class EventDestinationCollection : public Node
"DeliveryRetryPolicy");
return;
}
- subValue.retryPolicy = *retryPolicy;
+ subValue->retryPolicy = *retryPolicy;
}
else
{
// Default "TerminateAfterRetries"
- subValue.retryPolicy = "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)
+ std::string id =
+ EventServiceManager::getInstance().addSubscription(subValue);
+ if (id.empty())
{
messages::internalError(asyncResp->res);
return;
}
- updateSubscriptionData();
-
messages::created(asyncResp->res);
asyncResp->res.addHeader(
"Location", "/redfish/v1/EventService/Subscriptions/" + id);
@@ -400,16 +384,15 @@ class EventDestination : public Node
return;
}
- const std::string& id = params[0];
- auto obj = subscriptionsMap.find(id);
- if (obj == subscriptionsMap.end())
+ std::shared_ptr<Subscription> subValue =
+ EventServiceManager::getInstance().getSubscription(params[0]);
+ if (subValue == nullptr)
{
res.result(boost::beast::http::status::not_found);
res.end();
return;
}
-
- EventSrvSubscription& subValue = obj->second;
+ const std::string& id = params[0];
res.jsonValue = {
{"@odata.type", "#EventDestination.v1_7_0.EventDestination"},
@@ -418,16 +401,16 @@ class EventDestination : public Node
"/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["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;
+ 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;
+ subValue->registryPrefixes;
+ asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds;
+ asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy;
}
void doPatch(crow::Response& res, const crow::Request& req,
@@ -440,9 +423,9 @@ class EventDestination : public Node
return;
}
- const std::string& id = params[0];
- auto obj = subscriptionsMap.find(id);
- if (obj == subscriptionsMap.end())
+ std::shared_ptr<Subscription> subValue =
+ EventServiceManager::getInstance().getSubscription(params[0]);
+ if (subValue == nullptr)
{
res.result(boost::beast::http::status::not_found);
res.end();
@@ -460,16 +443,14 @@ class EventDestination : public Node
return;
}
- EventSrvSubscription& subValue = obj->second;
-
if (context)
{
- subValue.customText = *context;
+ subValue->customText = *context;
}
if (headers)
{
- subValue.httpHeaders = *headers;
+ subValue->httpHeaders = *headers;
}
if (retryPolicy)
@@ -482,10 +463,10 @@ class EventDestination : public Node
"DeliveryRetryPolicy");
return;
}
- subValue.retryPolicy = *retryPolicy;
+ subValue->retryPolicy = *retryPolicy;
}
- updateSubscriptionData();
+ EventServiceManager::getInstance().updateSubscriptionData();
}
void doDelete(crow::Response& res, const crow::Request& req,
@@ -499,18 +480,13 @@ class EventDestination : public Node
return;
}
- const std::string& id = params[0];
- auto obj = subscriptionsMap.find(id);
- if (obj == subscriptionsMap.end())
+ if (!EventServiceManager::getInstance().isSubscriptionExist(params[0]))
{
res.result(boost::beast::http::status::not_found);
res.end();
return;
}
-
- subscriptionsMap.erase(obj);
-
- updateSubscriptionData();
+ EventServiceManager::getInstance().deleteSubscription(params[0]);
}
};