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