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