blob: 62654919dc551734ff722689506c47f98bc41541 [file] [log] [blame]
AppaRao Pulie5aaf042020-03-20 01:05:52 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17#include "node.hpp"
18
19#include <boost/container/flat_map.hpp>
20#include <error_messages.hpp>
21#include <utils/json_utils.hpp>
22#include <variant>
23
24namespace redfish
25{
26
27static constexpr const std::array<const char*, 1> supportedEvtFormatTypes = {
28 "Event"};
29static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
30 "Base", "OpenBMC", "Task"};
31static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
32 "TerminateAfterRetries", "SuspendRetries", "RetryForever"};
33
34static constexpr const uint8_t maxNoOfSubscriptions = 20;
35
36struct EventSrvConfig
37{
38 bool enabled;
39 uint32_t retryAttempts;
40 uint32_t retryTimeoutInterval;
41};
42
43struct EventSrvSubscription
44{
45 std::string destinationUrl;
46 std::string protocol;
47 std::string retryPolicy;
48 std::string customText;
49 std::string eventFormatType;
50 std::string subscriptionType;
51 std::vector<std::string> registryMsgIds;
52 std::vector<std::string> registryPrefixes;
53 std::vector<nlohmann::json> httpHeaders; // key-value pair
54};
55
56EventSrvConfig configData;
57boost::container::flat_map<std::string, EventSrvSubscription> subscriptionsMap;
58
59inline void initEventSrvStore()
60{
61 // TODO: Read the persistent data from store and populate.
62 // Populating with default.
63 configData.enabled = true;
64 configData.retryAttempts = 3;
65 configData.retryTimeoutInterval = 30; // seconds
66}
67
68inline void updateSubscriptionData()
69{
70 // Persist the config and subscription data.
71 // TODO: subscriptionsMap & configData need to be
72 // written to Persist store.
73 return;
74}
75class EventService : public Node
76{
77 public:
78 EventService(CrowApp& app) : Node(app, "/redfish/v1/EventService/")
79 {
80 initEventSrvStore();
81
82 entityPrivileges = {
83 {boost::beast::http::verb::get, {{"Login"}}},
84 {boost::beast::http::verb::head, {{"Login"}}},
85 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
86 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
87 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
88 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
89 }
90
91 private:
92 void doGet(crow::Response& res, const crow::Request& req,
93 const std::vector<std::string>& params) override
94 {
95 auto asyncResp = std::make_shared<AsyncResp>(res);
96 res.jsonValue = {
97 {"@odata.type", "#EventService.v1_5_0.EventService"},
98 {"Id", "EventService"},
99 {"Name", "Event Service"},
100 {"ServerSentEventUri",
101 "/redfish/v1/EventService/Subscriptions/SSE"},
102 {"Subscriptions",
103 {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}},
104 {"@odata.id", "/redfish/v1/EventService"}};
105
106 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
107 asyncResp->res.jsonValue["ServiceEnabled"] = configData.enabled;
108 asyncResp->res.jsonValue["DeliveryRetryAttempts"] =
109 configData.retryAttempts;
110 asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] =
111 configData.retryTimeoutInterval;
112 asyncResp->res.jsonValue["EventFormatTypes"] = supportedEvtFormatTypes;
113 asyncResp->res.jsonValue["RegistryPrefixes"] = supportedRegPrefixes;
114 }
115
116 void doPatch(crow::Response& res, const crow::Request& req,
117 const std::vector<std::string>& params) override
118 {
119 auto asyncResp = std::make_shared<AsyncResp>(res);
120
121 std::optional<bool> serviceEnabled;
122 std::optional<uint32_t> retryAttemps;
123 std::optional<uint32_t> retryInterval;
124
125 if (!json_util::readJson(req, res, "ServiceEnabled", serviceEnabled,
126 "DeliveryRetryAttempts", retryAttemps,
127 "DeliveryRetryIntervalSeconds", retryInterval))
128 {
129 return;
130 }
131
132 if (serviceEnabled)
133 {
134 configData.enabled = *serviceEnabled;
135 }
136
137 if (retryAttemps)
138 {
139 // Supported range [1-3]
140 if ((*retryAttemps < 1) || (*retryAttemps > 3))
141 {
142 messages::queryParameterOutOfRange(
143 asyncResp->res, std::to_string(*retryAttemps),
144 "DeliveryRetryAttempts", "[1-3]");
145 }
146 else
147 {
148 configData.retryAttempts = *retryAttemps;
149 }
150 }
151
152 if (retryInterval)
153 {
154 // Supported range [30 - 180]
155 if ((*retryInterval < 30) || (*retryInterval > 180))
156 {
157 messages::queryParameterOutOfRange(
158 asyncResp->res, std::to_string(*retryInterval),
159 "DeliveryRetryIntervalSeconds", "[30-180]");
160 }
161 else
162 {
163 configData.retryTimeoutInterval = *retryInterval;
164 }
165 }
166
167 updateSubscriptionData();
168 }
169};
170
171class EventDestinationCollection : public Node
172{
173 public:
174 EventDestinationCollection(CrowApp& app) :
175 Node(app, "/redfish/v1/EventService/Subscriptions/")
176 {
177 entityPrivileges = {
178 {boost::beast::http::verb::get, {{"Login"}}},
179 {boost::beast::http::verb::head, {{"Login"}}},
180 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
181 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
182 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
183 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
184 }
185
186 private:
187 void doGet(crow::Response& res, const crow::Request& req,
188 const std::vector<std::string>& params) override
189 {
190 auto asyncResp = std::make_shared<AsyncResp>(res);
191
192 res.jsonValue = {
193 {"@odata.type",
194 "#EventDestinationCollection.EventDestinationCollection"},
195 {"@odata.id", "/redfish/v1/EventService/Subscriptions"},
196 {"Name", "Event Destination Collections"}};
197
198 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
199 memberArray = nlohmann::json::array();
200 asyncResp->res.jsonValue["Members@odata.count"] =
201 subscriptionsMap.size();
202
203 for (auto& it : subscriptionsMap)
204 {
205 memberArray.push_back(
206 {{"@odata.id",
207 "/redfish/v1/EventService/Subscriptions/" + it.first}});
208 }
209 }
210
211 void doPost(crow::Response& res, const crow::Request& req,
212 const std::vector<std::string>& params) override
213 {
214 auto asyncResp = std::make_shared<AsyncResp>(res);
215
216 if (subscriptionsMap.size() >= maxNoOfSubscriptions)
217 {
218 messages::eventSubscriptionLimitExceeded(asyncResp->res);
219 return;
220 }
221 std::string destUrl;
222 std::string protocol;
223 std::optional<std::string> context;
224 std::optional<std::string> subscriptionType;
225 std::optional<std::string> eventFormatType;
226 std::optional<std::string> retryPolicy;
227 std::optional<std::vector<std::string>> msgIds;
228 std::optional<std::vector<std::string>> regPrefixes;
229 std::optional<std::vector<nlohmann::json>> headers;
230
231 if (!json_util::readJson(
232 req, res, "Destination", destUrl, "Context", context,
233 "Protocol", protocol, "SubscriptionType", subscriptionType,
234 "EventFormatType", eventFormatType, "HttpHeaders", headers,
235 "RegistryPrefixes", regPrefixes, "MessageIds", msgIds,
236 "DeliveryRetryPolicy", retryPolicy))
237 {
238 return;
239 }
240
241 EventSrvSubscription subValue;
242
243 // Validate the URL using regex expression
244 // Format: <protocol>://<host>:<port>/uri
245 // protocol: http/https, uri: can include params.
246 const std::regex urlRegex("(http|https)://([^/ :]+):?.*");
247 if (!std::regex_match(destUrl, urlRegex))
248 {
249 messages::propertyValueFormatError(asyncResp->res, destUrl,
250 "Destination");
251 return;
252 }
253 subValue.destinationUrl = destUrl;
254
255 if (subscriptionType)
256 {
257 if (*subscriptionType != "RedfishEvent")
258 {
259 messages::propertyValueNotInList(
260 asyncResp->res, *subscriptionType, "SubscriptionType");
261 return;
262 }
263 subValue.subscriptionType = *subscriptionType;
264 }
265 else
266 {
267 subValue.subscriptionType = "RedfishEvent"; // Default
268 }
269
270 if (protocol != "Redfish")
271 {
272 messages::propertyValueNotInList(asyncResp->res, protocol,
273 "Protocol");
274 return;
275 }
276 subValue.protocol = protocol;
277
278 if (eventFormatType)
279 {
280 if (std::find(supportedEvtFormatTypes.begin(),
281 supportedEvtFormatTypes.end(),
282 *eventFormatType) == supportedEvtFormatTypes.end())
283 {
284 messages::propertyValueNotInList(
285 asyncResp->res, *eventFormatType, "EventFormatType");
286 return;
287 }
288 subValue.eventFormatType = *eventFormatType;
289 }
290 else
291 {
292 // If not specified, use default "Event"
293 subValue.eventFormatType.assign({"Event"});
294 }
295
296 if (context)
297 {
298 subValue.customText = *context;
299 }
300
301 if (headers)
302 {
303 subValue.httpHeaders = *headers;
304 }
305
306 if (regPrefixes)
307 {
308 for (const std::string& it : *regPrefixes)
309 {
310 if (std::find(supportedRegPrefixes.begin(),
311 supportedRegPrefixes.end(),
312 it) == supportedRegPrefixes.end())
313 {
314 messages::propertyValueNotInList(asyncResp->res, it,
315 "RegistryPrefixes");
316 return;
317 }
318 }
319 subValue.registryPrefixes = *regPrefixes;
320 }
321
322 if (msgIds)
323 {
324 // Do we need to loop-up MessageRegistry and validate
325 // data for authenticity??? Not mandate, i believe.
326 subValue.registryMsgIds = *msgIds;
327 }
328
329 if (retryPolicy)
330 {
331 if (std::find(supportedRetryPolicies.begin(),
332 supportedRetryPolicies.end(),
333 *retryPolicy) == supportedRetryPolicies.end())
334 {
335 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
336 "DeliveryRetryPolicy");
337 return;
338 }
339 subValue.retryPolicy = *retryPolicy;
340 }
341 else
342 {
343 // Default "TerminateAfterRetries"
344 subValue.retryPolicy = "TerminateAfterRetries";
345 }
346
347 std::srand(static_cast<uint32_t>(std::time(0)));
348 std::string id;
349
350 int retry = 3;
351 while (retry)
352 {
353 id = std::to_string(std::rand());
354 auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
355 if (inserted.second)
356 {
357 break;
358 }
359 retry--;
360 };
361
362 if (retry <= 0)
363 {
364 messages::internalError(asyncResp->res);
365 return;
366 }
367
368 updateSubscriptionData();
369
370 messages::created(asyncResp->res);
371 asyncResp->res.addHeader(
372 "Location", "/redfish/v1/EventService/Subscriptions/" + id);
373 }
374};
375
376class EventDestination : public Node
377{
378 public:
379 EventDestination(CrowApp& app) :
380 Node(app, "/redfish/v1/EventService/Subscriptions/<str>/",
381 std::string())
382 {
383 entityPrivileges = {
384 {boost::beast::http::verb::get, {{"Login"}}},
385 {boost::beast::http::verb::head, {{"Login"}}},
386 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
387 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
388 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
389 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
390 }
391
392 private:
393 void doGet(crow::Response& res, const crow::Request& req,
394 const std::vector<std::string>& params) override
395 {
396 auto asyncResp = std::make_shared<AsyncResp>(res);
397 if (params.size() != 1)
398 {
399 messages::internalError(asyncResp->res);
400 return;
401 }
402
403 const std::string& id = params[0];
404 auto obj = subscriptionsMap.find(id);
405 if (obj == subscriptionsMap.end())
406 {
407 res.result(boost::beast::http::status::not_found);
408 res.end();
409 return;
410 }
411
412 EventSrvSubscription& subValue = obj->second;
413
414 res.jsonValue = {
415 {"@odata.type", "#EventDestination.v1_7_0.EventDestination"},
416 {"Protocol", "Redfish"}};
417 asyncResp->res.jsonValue["@odata.id"] =
418 "/redfish/v1/EventService/Subscriptions/" + id;
419 asyncResp->res.jsonValue["Id"] = id;
420 asyncResp->res.jsonValue["Name"] = "Event Destination " + id;
421 asyncResp->res.jsonValue["Destination"] = subValue.destinationUrl;
422 asyncResp->res.jsonValue["Context"] = subValue.customText;
423 asyncResp->res.jsonValue["SubscriptionType"] =
424 subValue.subscriptionType;
425 asyncResp->res.jsonValue["HttpHeaders"] = subValue.httpHeaders;
426 asyncResp->res.jsonValue["EventFormatType"] = subValue.eventFormatType;
427 asyncResp->res.jsonValue["RegistryPrefixes"] =
428 subValue.registryPrefixes;
429 asyncResp->res.jsonValue["MessageIds"] = subValue.registryMsgIds;
430 asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue.retryPolicy;
431 }
432
433 void doPatch(crow::Response& res, const crow::Request& req,
434 const std::vector<std::string>& params) override
435 {
436 auto asyncResp = std::make_shared<AsyncResp>(res);
437 if (params.size() != 1)
438 {
439 messages::internalError(asyncResp->res);
440 return;
441 }
442
443 const std::string& id = params[0];
444 auto obj = subscriptionsMap.find(id);
445 if (obj == subscriptionsMap.end())
446 {
447 res.result(boost::beast::http::status::not_found);
448 res.end();
449 return;
450 }
451
452 std::optional<std::string> context;
453 std::optional<std::string> retryPolicy;
454 std::optional<std::vector<nlohmann::json>> headers;
455
456 if (!json_util::readJson(req, res, "Context", context,
457 "DeliveryRetryPolicy", retryPolicy,
458 "HttpHeaders", headers))
459 {
460 return;
461 }
462
463 EventSrvSubscription& subValue = obj->second;
464
465 if (context)
466 {
467 subValue.customText = *context;
468 }
469
470 if (headers)
471 {
472 subValue.httpHeaders = *headers;
473 }
474
475 if (retryPolicy)
476 {
477 if (std::find(supportedRetryPolicies.begin(),
478 supportedRetryPolicies.end(),
479 *retryPolicy) == supportedRetryPolicies.end())
480 {
481 messages::propertyValueNotInList(asyncResp->res, *retryPolicy,
482 "DeliveryRetryPolicy");
483 return;
484 }
485 subValue.retryPolicy = *retryPolicy;
486 }
487
488 updateSubscriptionData();
489 }
490
491 void doDelete(crow::Response& res, const crow::Request& req,
492 const std::vector<std::string>& params) override
493 {
494 auto asyncResp = std::make_shared<AsyncResp>(res);
495
496 if (params.size() != 1)
497 {
498 messages::internalError(asyncResp->res);
499 return;
500 }
501
502 const std::string& id = params[0];
503 auto obj = subscriptionsMap.find(id);
504 if (obj == subscriptionsMap.end())
505 {
506 res.result(boost::beast::http::status::not_found);
507 res.end();
508 return;
509 }
510
511 subscriptionsMap.erase(obj);
512
513 updateSubscriptionData();
514 }
515};
516
517} // namespace redfish