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