1 /* 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 24 namespace redfish 25 { 26 27 static constexpr const std::array<const char*, 1> supportedEvtFormatTypes = { 28 "Event"}; 29 static constexpr const std::array<const char*, 3> supportedRegPrefixes = { 30 "Base", "OpenBMC", "Task"}; 31 static constexpr const std::array<const char*, 3> supportedRetryPolicies = { 32 "TerminateAfterRetries", "SuspendRetries", "RetryForever"}; 33 34 static constexpr const uint8_t maxNoOfSubscriptions = 20; 35 36 struct EventSrvConfig 37 { 38 bool enabled; 39 uint32_t retryAttempts; 40 uint32_t retryTimeoutInterval; 41 }; 42 43 struct 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 56 EventSrvConfig configData; 57 boost::container::flat_map<std::string, EventSrvSubscription> subscriptionsMap; 58 59 inline 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 68 inline 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 } 75 class 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 171 class 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 376 class 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 518