1 #pragma once 2 3 #include <dbus_utility.hpp> 4 #include <error_messages.hpp> 5 #include <http_client.hpp> 6 #include <http_connection.hpp> 7 8 namespace redfish 9 { 10 11 enum class Result 12 { 13 LocalHandle, 14 NoLocalHandle 15 }; 16 17 static void addPrefixToItem(nlohmann::json& item, std::string_view prefix) 18 { 19 std::string* strValue = item.get_ptr<std::string*>(); 20 if (strValue == nullptr) 21 { 22 BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 23 return; 24 } 25 // Make sure the value is a properly formatted URI 26 auto parsed = boost::urls::parse_relative_ref(*strValue); 27 if (!parsed) 28 { 29 BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue; 30 return; 31 } 32 33 boost::urls::url_view thisUrl = *parsed; 34 35 // We don't need to add prefixes to these URIs since 36 // /redfish/v1/UpdateService/ itself is not a collection 37 // /redfish/v1/UpdateService/FirmwareInventory 38 // /redfish/v1/UpdateService/SoftwareInventory 39 if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 40 "UpdateService", "FirmwareInventory") || 41 crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 42 "UpdateService", "SoftwareInventory")) 43 { 44 BMCWEB_LOG_DEBUG << "Skipping UpdateService URI prefix fixing"; 45 return; 46 } 47 48 // We also need to aggregate FirmwareInventory and 49 // SoftwareInventory so add an extra offset 50 // /redfish/v1/UpdateService/FirmwareInventory/<id> 51 // /redfish/v1/UpdateService/SoftwareInventory/<id> 52 std::string collectionName; 53 std::string softwareItem; 54 if (crow::utility::readUrlSegments( 55 thisUrl, "redfish", "v1", "UpdateService", std::ref(collectionName), 56 std::ref(softwareItem), crow::utility::OrMorePaths())) 57 { 58 softwareItem.insert(0, "_"); 59 softwareItem.insert(0, prefix); 60 item = crow::utility::replaceUrlSegment(thisUrl, 4, softwareItem); 61 } 62 63 // A collection URI that ends with "/" such as 64 // "/redfish/v1/Chassis/" will have 4 segments so we need to 65 // make sure we don't try to add a prefix to an empty segment 66 if (crow::utility::readUrlSegments( 67 thisUrl, "redfish", "v1", std::ref(collectionName), 68 std::ref(softwareItem), crow::utility::OrMorePaths())) 69 { 70 softwareItem.insert(0, "_"); 71 softwareItem.insert(0, prefix); 72 item = crow::utility::replaceUrlSegment(thisUrl, 3, softwareItem); 73 } 74 } 75 76 // We need to attempt to update all URIs under Actions 77 static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix) 78 { 79 nlohmann::json::object_t* object = 80 json.get_ptr<nlohmann::json::object_t*>(); 81 if (object != nullptr) 82 { 83 for (std::pair<const std::string, nlohmann::json>& item : *object) 84 { 85 std::string* strValue = item.second.get_ptr<std::string*>(); 86 if (strValue != nullptr) 87 { 88 addPrefixToItem(item.second, prefix); 89 } 90 else 91 { 92 addPrefixesToActions(item.second, prefix); 93 } 94 } 95 } 96 } 97 98 // Search the json for all URIs and add the supplied prefix if the URI is for 99 // and aggregated resource. 100 static void addPrefixes(nlohmann::json& json, std::string_view prefix) 101 { 102 nlohmann::json::object_t* object = 103 json.get_ptr<nlohmann::json::object_t*>(); 104 if (object != nullptr) 105 { 106 for (std::pair<const std::string, nlohmann::json>& item : *object) 107 { 108 if (item.first == "Actions") 109 { 110 addPrefixesToActions(item.second, prefix); 111 continue; 112 } 113 114 if ((item.first == "@odata.id") || (item.first.ends_with("URI"))) 115 { 116 addPrefixToItem(item.second, prefix); 117 } 118 // Recusively parse the rest of the json 119 addPrefixes(item.second, prefix); 120 } 121 return; 122 } 123 nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 124 if (array != nullptr) 125 { 126 for (nlohmann::json& item : *array) 127 { 128 addPrefixes(item, prefix); 129 } 130 } 131 } 132 133 class RedfishAggregator 134 { 135 private: 136 const std::string retryPolicyName = "RedfishAggregation"; 137 const std::string retryPolicyAction = "TerminateAfterRetries"; 138 const uint32_t retryAttempts = 1; 139 const uint32_t retryTimeoutInterval = 0; 140 const std::string id = "Aggregator"; 141 142 RedfishAggregator() 143 { 144 getSatelliteConfigs(constructorCallback); 145 146 // Setup the retry policy to be used by Redfish Aggregation 147 crow::HttpClient::getInstance().setRetryConfig( 148 retryAttempts, retryTimeoutInterval, aggregationRetryHandler, 149 retryPolicyName); 150 crow::HttpClient::getInstance().setRetryPolicy(retryPolicyAction, 151 retryPolicyName); 152 } 153 154 static inline boost::system::error_code 155 aggregationRetryHandler(unsigned int respCode) 156 { 157 // As a default, assume 200X is alright. 158 // We don't need to retry on a 404 159 if ((respCode < 200) || ((respCode >= 300) && (respCode != 404))) 160 { 161 return boost::system::errc::make_error_code( 162 boost::system::errc::result_out_of_range); 163 } 164 165 // Return 0 if the response code is valid 166 return boost::system::errc::make_error_code( 167 boost::system::errc::success); 168 } 169 170 // Dummy callback used by the Constructor so that it can report the number 171 // of satellite configs when the class is first created 172 static void constructorCallback( 173 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 174 { 175 BMCWEB_LOG_DEBUG << "There were " 176 << std::to_string(satelliteInfo.size()) 177 << " satellite configs found at startup"; 178 } 179 180 // Polls D-Bus to get all available satellite config information 181 // Expects a handler which interacts with the returned configs 182 static void getSatelliteConfigs( 183 const std::function<void( 184 const std::unordered_map<std::string, boost::urls::url>&)>& handler) 185 { 186 BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 187 crow::connections::systemBus->async_method_call( 188 [handler](const boost::system::error_code ec, 189 const dbus::utility::ManagedObjectType& objects) { 190 if (ec) 191 { 192 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 193 << ec.message(); 194 return; 195 } 196 197 // Maps a chosen alias representing a satellite BMC to a url 198 // containing the information required to create a http 199 // connection to the satellite 200 std::unordered_map<std::string, boost::urls::url> satelliteInfo; 201 202 findSatelliteConfigs(objects, satelliteInfo); 203 204 if (!satelliteInfo.empty()) 205 { 206 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 207 << std::to_string(satelliteInfo.size()) 208 << " satellite BMCs"; 209 } 210 else 211 { 212 BMCWEB_LOG_DEBUG 213 << "No satellite BMCs detected. Redfish Aggregation not enabled"; 214 } 215 handler(satelliteInfo); 216 }, 217 "xyz.openbmc_project.EntityManager", "/", 218 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 219 } 220 221 // Search D-Bus objects for satellite config objects and add their 222 // information if valid 223 static void findSatelliteConfigs( 224 const dbus::utility::ManagedObjectType& objects, 225 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 226 { 227 for (const auto& objectPath : objects) 228 { 229 for (const auto& interface : objectPath.second) 230 { 231 if (interface.first == 232 "xyz.openbmc_project.Configuration.SatelliteController") 233 { 234 BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 235 << objectPath.first.str; 236 237 if (!satelliteInfo.empty()) 238 { 239 BMCWEB_LOG_ERROR 240 << "Redfish Aggregation only supports one satellite!"; 241 BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 242 satelliteInfo.clear(); 243 return; 244 } 245 246 // For now assume there will only be one satellite config. 247 // Assign it the name/prefix "5B247A" 248 addSatelliteConfig("5B247A", interface.second, 249 satelliteInfo); 250 } 251 } 252 } 253 } 254 255 // Parse the properties of a satellite config object and add the 256 // configuration if the properties are valid 257 static void addSatelliteConfig( 258 const std::string& name, 259 const dbus::utility::DBusPropertiesMap& properties, 260 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 261 { 262 boost::urls::url url; 263 264 for (const auto& prop : properties) 265 { 266 if (prop.first == "Hostname") 267 { 268 const std::string* propVal = 269 std::get_if<std::string>(&prop.second); 270 if (propVal == nullptr) 271 { 272 BMCWEB_LOG_ERROR << "Invalid Hostname value"; 273 return; 274 } 275 url.set_host(*propVal); 276 } 277 278 else if (prop.first == "Port") 279 { 280 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 281 if (propVal == nullptr) 282 { 283 BMCWEB_LOG_ERROR << "Invalid Port value"; 284 return; 285 } 286 287 if (*propVal > std::numeric_limits<uint16_t>::max()) 288 { 289 BMCWEB_LOG_ERROR << "Port value out of range"; 290 return; 291 } 292 url.set_port(static_cast<uint16_t>(*propVal)); 293 } 294 295 else if (prop.first == "AuthType") 296 { 297 const std::string* propVal = 298 std::get_if<std::string>(&prop.second); 299 if (propVal == nullptr) 300 { 301 BMCWEB_LOG_ERROR << "Invalid AuthType value"; 302 return; 303 } 304 305 // For now assume authentication not required to communicate 306 // with the satellite BMC 307 if (*propVal != "None") 308 { 309 BMCWEB_LOG_ERROR 310 << "Unsupported AuthType value: " << *propVal 311 << ", only \"none\" is supported"; 312 return; 313 } 314 url.set_scheme("http"); 315 } 316 } // Finished reading properties 317 318 // Make sure all required config information was made available 319 if (url.host().empty()) 320 { 321 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 322 return; 323 } 324 325 if (!url.has_port()) 326 { 327 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 328 return; 329 } 330 331 if (!url.has_scheme()) 332 { 333 BMCWEB_LOG_ERROR << "Satellite config " << name 334 << " missing AuthType"; 335 return; 336 } 337 338 std::string resultString; 339 auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 340 if (result.second) 341 { 342 resultString = "Added new satellite config "; 343 } 344 else 345 { 346 resultString = "Updated existing satellite config "; 347 } 348 349 BMCWEB_LOG_DEBUG << resultString << name << " at " 350 << result.first->second.scheme() << "://" 351 << result.first->second.encoded_host_and_port(); 352 } 353 354 enum AggregationType 355 { 356 Collection, 357 Resource, 358 }; 359 360 static void 361 startAggregation(AggregationType isCollection, 362 const crow::Request& thisReq, 363 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 364 { 365 if ((isCollection == AggregationType::Collection) && 366 (thisReq.method() != boost::beast::http::verb::get)) 367 { 368 BMCWEB_LOG_DEBUG 369 << "Only aggregate GET requests to top level collections"; 370 return; 371 } 372 373 // Create a copy of thisReq so we we can still locally process the req 374 std::error_code ec; 375 auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 376 if (ec) 377 { 378 BMCWEB_LOG_ERROR << "Failed to create copy of request"; 379 if (isCollection != AggregationType::Collection) 380 { 381 messages::internalError(asyncResp->res); 382 } 383 return; 384 } 385 386 getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 387 localReq, asyncResp)); 388 } 389 390 static void findSatellite( 391 const crow::Request& req, 392 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 393 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 394 std::string_view memberName) 395 { 396 // Determine if the resource ID begins with a known prefix 397 for (const auto& satellite : satelliteInfo) 398 { 399 std::string targetPrefix = satellite.first; 400 targetPrefix += "_"; 401 if (memberName.starts_with(targetPrefix)) 402 { 403 BMCWEB_LOG_DEBUG << "\"" << satellite.first 404 << "\" is a known prefix"; 405 406 // Remove the known prefix from the request's URI and 407 // then forward to the associated satellite BMC 408 getInstance().forwardRequest(req, asyncResp, satellite.first, 409 satelliteInfo); 410 return; 411 } 412 } 413 414 // We didn't recognize the prefix and need to return a 404 415 boost::urls::string_value name = req.urlView.segments().back(); 416 std::string_view nameStr(name.data(), name.size()); 417 messages::resourceNotFound(asyncResp->res, "", nameStr); 418 } 419 420 // Intended to handle an incoming request based on if Redfish Aggregation 421 // is enabled. Forwards request to satellite BMC if it exists. 422 static void aggregateAndHandle( 423 AggregationType isCollection, 424 const std::shared_ptr<crow::Request>& sharedReq, 425 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 426 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 427 { 428 if (sharedReq == nullptr) 429 { 430 return; 431 } 432 433 // No satellite configs means we don't need to keep attempting to 434 // aggregate 435 if (satelliteInfo.empty()) 436 { 437 // For collections we'll also handle the request locally so we 438 // don't need to write an error code 439 if (isCollection == AggregationType::Resource) 440 { 441 boost::urls::string_value name = 442 sharedReq->urlView.segments().back(); 443 std::string_view nameStr(name.data(), name.size()); 444 messages::resourceNotFound(asyncResp->res, "", nameStr); 445 } 446 return; 447 } 448 449 const crow::Request& thisReq = *sharedReq; 450 BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 451 << thisReq.target(); 452 453 // We previously determined the request is for a collection. No need to 454 // check again 455 if (isCollection == AggregationType::Collection) 456 { 457 BMCWEB_LOG_DEBUG << "Aggregating a collection"; 458 // We need to use a specific response handler and send the 459 // request to all known satellites 460 getInstance().forwardCollectionRequests(thisReq, asyncResp, 461 satelliteInfo); 462 return; 463 } 464 465 std::string updateServiceName; 466 std::string memberName; 467 if (crow::utility::readUrlSegments( 468 thisReq.urlView, "redfish", "v1", "UpdateService", 469 std::ref(updateServiceName), std::ref(memberName), 470 crow::utility::OrMorePaths())) 471 { 472 // Must be FirmwareInventory or SoftwareInventory 473 findSatellite(thisReq, asyncResp, satelliteInfo, memberName); 474 return; 475 } 476 477 std::string collectionName; 478 if (crow::utility::readUrlSegments( 479 thisReq.urlView, "redfish", "v1", std::ref(collectionName), 480 std::ref(memberName), crow::utility::OrMorePaths())) 481 { 482 findSatellite(thisReq, asyncResp, satelliteInfo, memberName); 483 return; 484 } 485 486 // We shouldn't reach this point since we should've hit one of the 487 // previous exits 488 messages::internalError(asyncResp->res); 489 } 490 491 // Attempt to forward a request to the satellite BMC associated with the 492 // prefix. 493 void forwardRequest( 494 const crow::Request& thisReq, 495 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 496 const std::string& prefix, 497 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 498 { 499 const auto& sat = satelliteInfo.find(prefix); 500 if (sat == satelliteInfo.end()) 501 { 502 // Realistically this shouldn't get called since we perform an 503 // earlier check to make sure the prefix exists 504 BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 505 << "\""; 506 return; 507 } 508 509 // We need to strip the prefix from the request's path 510 std::string targetURI(thisReq.target()); 511 size_t pos = targetURI.find(prefix + "_"); 512 if (pos == std::string::npos) 513 { 514 // If this fails then something went wrong 515 BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 516 << "_\" from request URI"; 517 messages::internalError(asyncResp->res); 518 return; 519 } 520 targetURI.erase(pos, prefix.size() + 1); 521 522 std::function<void(crow::Response&)> cb = 523 std::bind_front(processResponse, prefix, asyncResp); 524 525 std::string data = thisReq.req.body(); 526 crow::HttpClient::getInstance().sendDataWithCallback( 527 data, id, std::string(sat->second.host()), 528 sat->second.port_number(), targetURI, false /*useSSL*/, 529 thisReq.fields, thisReq.method(), retryPolicyName, cb); 530 } 531 532 // Forward a request for a collection URI to each known satellite BMC 533 void forwardCollectionRequests( 534 const crow::Request& thisReq, 535 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 536 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 537 { 538 for (const auto& sat : satelliteInfo) 539 { 540 std::function<void(crow::Response&)> cb = std::bind_front( 541 processCollectionResponse, sat.first, asyncResp); 542 543 std::string targetURI(thisReq.target()); 544 std::string data = thisReq.req.body(); 545 crow::HttpClient::getInstance().sendDataWithCallback( 546 data, id, std::string(sat.second.host()), 547 sat.second.port_number(), targetURI, false /*useSSL*/, 548 thisReq.fields, thisReq.method(), retryPolicyName, cb); 549 } 550 } 551 552 // Processes the response returned by a satellite BMC and loads its 553 // contents into asyncResp 554 static void 555 processResponse(std::string_view prefix, 556 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 557 crow::Response& resp) 558 { 559 // No processing needed if the request wasn't successful 560 if (resp.resultInt() != 200) 561 { 562 BMCWEB_LOG_DEBUG << "No need to parse satellite response"; 563 asyncResp->res.stringResponse = std::move(resp.stringResponse); 564 return; 565 } 566 567 // The resp will not have a json component 568 // We need to create a json from resp's stringResponse 569 if (resp.getHeaderValue("Content-Type") == "application/json") 570 { 571 nlohmann::json jsonVal = 572 nlohmann::json::parse(resp.body(), nullptr, false); 573 if (jsonVal.is_discarded()) 574 { 575 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 576 messages::operationFailed(asyncResp->res); 577 return; 578 } 579 580 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 581 582 // TODO: For collections we want to add the satellite responses to 583 // our response rather than just straight overwriting them if our 584 // local handling was successful (i.e. would return a 200). 585 addPrefixes(jsonVal, prefix); 586 587 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 588 589 asyncResp->res.stringResponse.emplace( 590 boost::beast::http::response< 591 boost::beast::http::string_body>{}); 592 asyncResp->res.result(resp.result()); 593 asyncResp->res.jsonValue = std::move(jsonVal); 594 595 BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 596 } 597 else 598 { 599 if (!resp.body().empty()) 600 { 601 // We received a 200 response without the correct Content-Type 602 // so return an Operation Failed error 603 BMCWEB_LOG_ERROR 604 << "Satellite response must be of type \"application/json\""; 605 messages::operationFailed(asyncResp->res); 606 } 607 } 608 } 609 610 // Processes the collection response returned by a satellite BMC and merges 611 // its "@odata.id" values 612 static void processCollectionResponse( 613 const std::string& prefix, 614 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 615 crow::Response& resp) 616 { 617 if (resp.resultInt() != 200) 618 { 619 BMCWEB_LOG_DEBUG 620 << "Collection resource does not exist in satellite BMC \"" 621 << prefix << "\""; 622 // Return the error if we haven't had any successes 623 if (asyncResp->res.resultInt() != 200) 624 { 625 asyncResp->res.stringResponse = std::move(resp.stringResponse); 626 } 627 return; 628 } 629 630 // The resp will not have a json component 631 // We need to create a json from resp's stringResponse 632 if (resp.getHeaderValue("Content-Type") == "application/json") 633 { 634 nlohmann::json jsonVal = 635 nlohmann::json::parse(resp.body(), nullptr, false); 636 if (jsonVal.is_discarded()) 637 { 638 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 639 640 // Notify the user if doing so won't overwrite a valid response 641 if ((asyncResp->res.resultInt() != 200) && 642 (asyncResp->res.resultInt() != 502)) 643 { 644 messages::operationFailed(asyncResp->res); 645 } 646 return; 647 } 648 649 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 650 651 // Now we need to add the prefix to the URIs contained in the 652 // response. 653 addPrefixes(jsonVal, prefix); 654 655 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 656 657 // If this resource collection does not exist on the aggregating bmc 658 // and has not already been added from processing the response from 659 // a different satellite then we need to completely overwrite 660 // asyncResp 661 if (asyncResp->res.resultInt() != 200) 662 { 663 // We only want to aggregate collections that contain a 664 // "Members" array 665 if ((!jsonVal.contains("Members")) && 666 (!jsonVal["Members"].is_array())) 667 { 668 BMCWEB_LOG_DEBUG 669 << "Skipping aggregating unsupported resource"; 670 return; 671 } 672 673 BMCWEB_LOG_DEBUG 674 << "Collection does not exist, overwriting asyncResp"; 675 asyncResp->res.stringResponse.emplace( 676 boost::beast::http::response< 677 boost::beast::http::string_body>{}); 678 asyncResp->res.result(resp.result()); 679 asyncResp->res.jsonValue = std::move(jsonVal); 680 681 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 682 } 683 else 684 { 685 // We only want to aggregate collections that contain a 686 // "Members" array 687 if ((!asyncResp->res.jsonValue.contains("Members")) && 688 (!asyncResp->res.jsonValue["Members"].is_array())) 689 690 { 691 BMCWEB_LOG_DEBUG 692 << "Skipping aggregating unsupported resource"; 693 return; 694 } 695 696 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 697 << prefix << "\" to collection"; 698 699 // TODO: This is a potential race condition with multiple 700 // satellites and the aggregating bmc attempting to write to 701 // update this array. May need to cascade calls to the next 702 // satellite at the end of this function. 703 // This is presumably not a concern when there is only a single 704 // satellite since the aggregating bmc should have completed 705 // before the response is received from the satellite. 706 707 auto& members = asyncResp->res.jsonValue["Members"]; 708 auto& satMembers = jsonVal["Members"]; 709 for (auto& satMem : satMembers) 710 { 711 members.push_back(std::move(satMem)); 712 } 713 asyncResp->res.jsonValue["Members@odata.count"] = 714 members.size(); 715 716 // TODO: Do we need to sort() after updating the array? 717 } 718 } 719 else 720 { 721 BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 722 << "\""; 723 // We received as response that was not a json 724 // Notify the user only if we did not receive any valid responses, 725 // if the resource collection does not already exist on the 726 // aggregating BMC, and if we did not already set this warning due 727 // to a failure from a different satellite 728 if ((asyncResp->res.resultInt() != 200) && 729 (asyncResp->res.resultInt() != 502)) 730 { 731 messages::operationFailed(asyncResp->res); 732 } 733 } 734 } // End processCollectionResponse() 735 736 public: 737 RedfishAggregator(const RedfishAggregator&) = delete; 738 RedfishAggregator& operator=(const RedfishAggregator&) = delete; 739 RedfishAggregator(RedfishAggregator&&) = delete; 740 RedfishAggregator& operator=(RedfishAggregator&&) = delete; 741 ~RedfishAggregator() = default; 742 743 static RedfishAggregator& getInstance() 744 { 745 static RedfishAggregator handler; 746 return handler; 747 } 748 749 // Entry point to Redfish Aggregation 750 // Returns Result stating whether or not we still need to locally handle the 751 // request 752 static Result 753 beginAggregation(const crow::Request& thisReq, 754 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 755 { 756 using crow::utility::OrMorePaths; 757 using crow::utility::readUrlSegments; 758 const boost::urls::url_view& url = thisReq.urlView; 759 // UpdateService is the only top level resource that is not a Collection 760 if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 761 { 762 return Result::LocalHandle; 763 } 764 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 765 "SoftwareInventory") || 766 readUrlSegments(url, "redfish", "v1", "UpdateService", 767 "FirmwareInventory")) 768 { 769 startAggregation(AggregationType::Collection, thisReq, asyncResp); 770 return Result::LocalHandle; 771 } 772 773 // Is the request for a resource collection?: 774 // /redfish/v1/<resource> 775 // e.g. /redfish/v1/Chassis 776 std::string collectionName; 777 if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 778 { 779 startAggregation(AggregationType::Collection, thisReq, asyncResp); 780 return Result::LocalHandle; 781 } 782 783 // We know that the ID of an aggregated resource will begin with 784 // "5B247A". For the most part the URI will begin like this: 785 // /redfish/v1/<resource>/<resource ID> 786 // Note, FirmwareInventory and SoftwareInventory are "special" because 787 // they are two levels deep, but still need aggregated 788 // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 789 // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 790 std::string memberName; 791 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 792 "SoftwareInventory", std::ref(memberName), 793 OrMorePaths()) || 794 readUrlSegments(url, "redfish", "v1", "UpdateService", 795 "FirmwareInventory", std::ref(memberName), 796 OrMorePaths()) || 797 readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 798 std::ref(memberName), OrMorePaths())) 799 { 800 if (memberName.starts_with("5B247A")) 801 { 802 BMCWEB_LOG_DEBUG << "Need to forward a request"; 803 804 // Extract the prefix from the request's URI, retrieve the 805 // associated satellite config information, and then forward the 806 // request to that satellite. 807 startAggregation(AggregationType::Resource, thisReq, asyncResp); 808 return Result::NoLocalHandle; 809 } 810 return Result::LocalHandle; 811 } 812 813 BMCWEB_LOG_DEBUG << "Aggregation not required"; 814 return Result::LocalHandle; 815 } 816 }; 817 818 } // namespace redfish 819