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