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 // Create a copy of thisReq so we we can still locally process the req 366 std::error_code ec; 367 auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 368 if (ec) 369 { 370 BMCWEB_LOG_ERROR << "Failed to create copy of request"; 371 if (isCollection != AggregationType::Collection) 372 { 373 messages::internalError(asyncResp->res); 374 } 375 return; 376 } 377 378 getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 379 localReq, asyncResp)); 380 } 381 382 static void findSatelite( 383 const crow::Request& req, 384 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 385 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 386 std::string_view memberName) 387 { 388 // Determine if the resource ID begins with a known prefix 389 for (const auto& satellite : satelliteInfo) 390 { 391 std::string targetPrefix = satellite.first; 392 targetPrefix += "_"; 393 if (memberName.starts_with(targetPrefix)) 394 { 395 BMCWEB_LOG_DEBUG << "\"" << satellite.first 396 << "\" is a known prefix"; 397 398 // Remove the known prefix from the request's URI and 399 // then forward to the associated satellite BMC 400 getInstance().forwardRequest(req, asyncResp, satellite.first, 401 satelliteInfo); 402 return; 403 } 404 } 405 } 406 407 // Intended to handle an incoming request based on if Redfish Aggregation 408 // is enabled. Forwards request to satellite BMC if it exists. 409 static void aggregateAndHandle( 410 AggregationType isCollection, 411 const std::shared_ptr<crow::Request>& sharedReq, 412 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 413 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 414 { 415 if (sharedReq == nullptr) 416 { 417 return; 418 } 419 const crow::Request& thisReq = *sharedReq; 420 BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 421 << thisReq.target(); 422 423 // We previously determined the request is for a collection. No need to 424 // check again 425 if (isCollection == AggregationType::Collection) 426 { 427 BMCWEB_LOG_DEBUG << "Aggregating a collection"; 428 // We need to use a specific response handler and send the 429 // request to all known satellites 430 getInstance().forwardCollectionRequests(thisReq, asyncResp, 431 satelliteInfo); 432 return; 433 } 434 435 std::string updateServiceName; 436 std::string memberName; 437 if (crow::utility::readUrlSegments( 438 thisReq.urlView, "redfish", "v1", "UpdateService", 439 std::ref(updateServiceName), std::ref(memberName), 440 crow::utility::OrMorePaths())) 441 { 442 // Must be FirmwareInventory or SoftwareInventory 443 findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 444 return; 445 } 446 447 std::string collectionName; 448 if (crow::utility::readUrlSegments( 449 thisReq.urlView, "redfish", "v1", std::ref(collectionName), 450 std::ref(memberName), crow::utility::OrMorePaths())) 451 { 452 findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 453 } 454 } 455 456 // Attempt to forward a request to the satellite BMC associated with the 457 // prefix. 458 void forwardRequest( 459 const crow::Request& thisReq, 460 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 461 const std::string& prefix, 462 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 463 { 464 const auto& sat = satelliteInfo.find(prefix); 465 if (sat == satelliteInfo.end()) 466 { 467 // Realistically this shouldn't get called since we perform an 468 // earlier check to make sure the prefix exists 469 BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 470 << "\""; 471 return; 472 } 473 474 // We need to strip the prefix from the request's path 475 std::string targetURI(thisReq.target()); 476 size_t pos = targetURI.find(prefix + "_"); 477 if (pos == std::string::npos) 478 { 479 // If this fails then something went wrong 480 BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 481 << "_\" from request URI"; 482 messages::internalError(asyncResp->res); 483 return; 484 } 485 targetURI.erase(pos, prefix.size() + 1); 486 487 std::function<void(crow::Response&)> cb = 488 std::bind_front(processResponse, prefix, asyncResp); 489 490 std::string data = thisReq.req.body(); 491 crow::HttpClient::getInstance().sendDataWithCallback( 492 data, id, std::string(sat->second.host()), 493 sat->second.port_number(), targetURI, false /*useSSL*/, 494 thisReq.fields, thisReq.method(), retryPolicyName, cb); 495 } 496 497 // Forward a request for a collection URI to each known satellite BMC 498 void forwardCollectionRequests( 499 const crow::Request& thisReq, 500 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 501 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 502 { 503 for (const auto& sat : satelliteInfo) 504 { 505 std::function<void(crow::Response&)> cb = std::bind_front( 506 processCollectionResponse, sat.first, asyncResp); 507 508 std::string targetURI(thisReq.target()); 509 std::string data = thisReq.req.body(); 510 crow::HttpClient::getInstance().sendDataWithCallback( 511 data, id, std::string(sat.second.host()), 512 sat.second.port_number(), targetURI, false /*useSSL*/, 513 thisReq.fields, thisReq.method(), retryPolicyName, cb); 514 } 515 } 516 517 // Processes the response returned by a satellite BMC and loads its 518 // contents into asyncResp 519 static void 520 processResponse(std::string_view prefix, 521 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 522 crow::Response& resp) 523 { 524 // No processing needed if the request wasn't successful 525 if (resp.resultInt() != 200) 526 { 527 BMCWEB_LOG_DEBUG << "No need to parse satellite response"; 528 asyncResp->res.stringResponse = std::move(resp.stringResponse); 529 return; 530 } 531 532 // The resp will not have a json component 533 // We need to create a json from resp's stringResponse 534 if (resp.getHeaderValue("Content-Type") == "application/json") 535 { 536 nlohmann::json jsonVal = 537 nlohmann::json::parse(resp.body(), nullptr, false); 538 if (jsonVal.is_discarded()) 539 { 540 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 541 messages::operationFailed(asyncResp->res); 542 return; 543 } 544 545 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 546 547 // TODO: For collections we want to add the satellite responses to 548 // our response rather than just straight overwriting them if our 549 // local handling was successful (i.e. would return a 200). 550 addPrefixes(jsonVal, prefix); 551 552 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 553 554 asyncResp->res.stringResponse.emplace( 555 boost::beast::http::response< 556 boost::beast::http::string_body>{}); 557 asyncResp->res.result(resp.result()); 558 asyncResp->res.jsonValue = std::move(jsonVal); 559 560 BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 561 } 562 else 563 { 564 if (!resp.body().empty()) 565 { 566 // We received a 200 response without the correct Content-Type 567 // so return an Operation Failed error 568 BMCWEB_LOG_ERROR 569 << "Satellite response must be of type \"application/json\""; 570 messages::operationFailed(asyncResp->res); 571 } 572 } 573 } 574 575 // Processes the collection response returned by a satellite BMC and merges 576 // its "@odata.id" values 577 static void processCollectionResponse( 578 const std::string& prefix, 579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 580 crow::Response& resp) 581 { 582 if (resp.resultInt() != 200) 583 { 584 BMCWEB_LOG_DEBUG 585 << "Collection resource does not exist in satellite BMC \"" 586 << prefix << "\""; 587 // Return the error if we haven't had any successes 588 if (asyncResp->res.resultInt() != 200) 589 { 590 asyncResp->res.stringResponse = std::move(resp.stringResponse); 591 } 592 return; 593 } 594 595 // The resp will not have a json component 596 // We need to create a json from resp's stringResponse 597 if (resp.getHeaderValue("Content-Type") == "application/json") 598 { 599 nlohmann::json jsonVal = 600 nlohmann::json::parse(resp.body(), nullptr, false); 601 if (jsonVal.is_discarded()) 602 { 603 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 604 605 // Notify the user if doing so won't overwrite a valid response 606 if ((asyncResp->res.resultInt() != 200) && 607 (asyncResp->res.resultInt() != 502)) 608 { 609 messages::operationFailed(asyncResp->res); 610 } 611 return; 612 } 613 614 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 615 616 // Now we need to add the prefix to the URIs contained in the 617 // response. 618 addPrefixes(jsonVal, prefix); 619 620 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 621 622 // If this resource collection does not exist on the aggregating bmc 623 // and has not already been added from processing the response from 624 // a different satellite then we need to completely overwrite 625 // asyncResp 626 if (asyncResp->res.resultInt() != 200) 627 { 628 // We only want to aggregate collections that contain a 629 // "Members" array 630 if ((!jsonVal.contains("Members")) && 631 (!jsonVal["Members"].is_array())) 632 { 633 BMCWEB_LOG_DEBUG 634 << "Skipping aggregating unsupported resource"; 635 return; 636 } 637 638 BMCWEB_LOG_DEBUG 639 << "Collection does not exist, overwriting asyncResp"; 640 asyncResp->res.stringResponse.emplace( 641 boost::beast::http::response< 642 boost::beast::http::string_body>{}); 643 asyncResp->res.result(resp.result()); 644 asyncResp->res.jsonValue = std::move(jsonVal); 645 646 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 647 } 648 else 649 { 650 // We only want to aggregate collections that contain a 651 // "Members" array 652 if ((!asyncResp->res.jsonValue.contains("Members")) && 653 (!asyncResp->res.jsonValue["Members"].is_array())) 654 655 { 656 BMCWEB_LOG_DEBUG 657 << "Skipping aggregating unsupported resource"; 658 return; 659 } 660 661 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 662 << prefix << "\" to collection"; 663 664 // TODO: This is a potential race condition with multiple 665 // satellites and the aggregating bmc attempting to write to 666 // update this array. May need to cascade calls to the next 667 // satellite at the end of this function. 668 // This is presumably not a concern when there is only a single 669 // satellite since the aggregating bmc should have completed 670 // before the response is received from the satellite. 671 672 auto& members = asyncResp->res.jsonValue["Members"]; 673 auto& satMembers = jsonVal["Members"]; 674 for (auto& satMem : satMembers) 675 { 676 members.push_back(std::move(satMem)); 677 } 678 asyncResp->res.jsonValue["Members@odata.count"] = 679 members.size(); 680 681 // TODO: Do we need to sort() after updating the array? 682 } 683 } 684 else 685 { 686 BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 687 << "\""; 688 // We received as response that was not a json 689 // Notify the user only if we did not receive any valid responses, 690 // if the resource collection does not already exist on the 691 // aggregating BMC, and if we did not already set this warning due 692 // to a failure from a different satellite 693 if ((asyncResp->res.resultInt() != 200) && 694 (asyncResp->res.resultInt() != 502)) 695 { 696 messages::operationFailed(asyncResp->res); 697 } 698 } 699 } // End processCollectionResponse() 700 701 public: 702 RedfishAggregator(const RedfishAggregator&) = delete; 703 RedfishAggregator& operator=(const RedfishAggregator&) = delete; 704 RedfishAggregator(RedfishAggregator&&) = delete; 705 RedfishAggregator& operator=(RedfishAggregator&&) = delete; 706 ~RedfishAggregator() = default; 707 708 static RedfishAggregator& getInstance() 709 { 710 static RedfishAggregator handler; 711 return handler; 712 } 713 714 // Entry point to Redfish Aggregation 715 // Returns Result stating whether or not we still need to locally handle the 716 // request 717 static Result 718 beginAggregation(const crow::Request& thisReq, 719 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 720 { 721 using crow::utility::OrMorePaths; 722 using crow::utility::readUrlSegments; 723 const boost::urls::url_view& url = thisReq.urlView; 724 // UpdateService is the only top level resource that is not a Collection 725 if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 726 { 727 return Result::LocalHandle; 728 } 729 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 730 "SoftwareInventory") || 731 readUrlSegments(url, "redfish", "v1", "UpdateService", 732 "FirmwareInventory")) 733 { 734 startAggregation(AggregationType::Collection, thisReq, asyncResp); 735 return Result::LocalHandle; 736 } 737 738 // Is the request for a resource collection?: 739 // /redfish/v1/<resource> 740 // e.g. /redfish/v1/Chassis 741 std::string collectionName; 742 if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 743 { 744 startAggregation(AggregationType::Collection, thisReq, asyncResp); 745 return Result::LocalHandle; 746 } 747 748 // We know that the ID of an aggregated resource will begin with 749 // "5B247A". For the most part the URI will begin like this: 750 // /redfish/v1/<resource>/<resource ID> 751 // Note, FirmwareInventory and SoftwareInventory are "special" because 752 // they are two levels deep, but still need aggregated 753 // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 754 // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 755 std::string memberName; 756 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 757 "SoftwareInventory", std::ref(memberName), 758 OrMorePaths()) || 759 readUrlSegments(url, "redfish", "v1", "UpdateService", 760 "FirmwareInventory", std::ref(memberName), 761 OrMorePaths()) || 762 readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 763 std::ref(memberName), OrMorePaths())) 764 { 765 if (memberName.starts_with("5B247A")) 766 { 767 BMCWEB_LOG_DEBUG << "Need to forward a request"; 768 769 // Extract the prefix from the request's URI, retrieve the 770 // associated satellite config information, and then forward the 771 // request to that satellite. 772 startAggregation(AggregationType::Resource, thisReq, asyncResp); 773 return Result::NoLocalHandle; 774 } 775 return Result::LocalHandle; 776 } 777 778 BMCWEB_LOG_DEBUG << "Aggregation not required"; 779 return Result::LocalHandle; 780 } 781 }; 782 783 } // namespace redfish 784