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