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 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 236 } 237 238 // Search D-Bus objects for satellite config objects and add their 239 // information if valid 240 static void findSatelliteConfigs( 241 const dbus::utility::ManagedObjectType& objects, 242 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 243 { 244 for (const auto& objectPath : objects) 245 { 246 for (const auto& interface : objectPath.second) 247 { 248 if (interface.first == 249 "xyz.openbmc_project.Configuration.SatelliteController") 250 { 251 BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 252 << objectPath.first.str; 253 254 if (!satelliteInfo.empty()) 255 { 256 BMCWEB_LOG_ERROR 257 << "Redfish Aggregation only supports one satellite!"; 258 BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 259 satelliteInfo.clear(); 260 return; 261 } 262 263 // For now assume there will only be one satellite config. 264 // Assign it the name/prefix "5B247A" 265 addSatelliteConfig("5B247A", interface.second, 266 satelliteInfo); 267 } 268 } 269 } 270 } 271 272 // Parse the properties of a satellite config object and add the 273 // configuration if the properties are valid 274 static void addSatelliteConfig( 275 const std::string& name, 276 const dbus::utility::DBusPropertiesMap& properties, 277 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 278 { 279 boost::urls::url url; 280 281 for (const auto& prop : properties) 282 { 283 if (prop.first == "Hostname") 284 { 285 const std::string* propVal = 286 std::get_if<std::string>(&prop.second); 287 if (propVal == nullptr) 288 { 289 BMCWEB_LOG_ERROR << "Invalid Hostname value"; 290 return; 291 } 292 url.set_host(*propVal); 293 } 294 295 else if (prop.first == "Port") 296 { 297 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 298 if (propVal == nullptr) 299 { 300 BMCWEB_LOG_ERROR << "Invalid Port value"; 301 return; 302 } 303 304 if (*propVal > std::numeric_limits<uint16_t>::max()) 305 { 306 BMCWEB_LOG_ERROR << "Port value out of range"; 307 return; 308 } 309 url.set_port(static_cast<uint16_t>(*propVal)); 310 } 311 312 else if (prop.first == "AuthType") 313 { 314 const std::string* propVal = 315 std::get_if<std::string>(&prop.second); 316 if (propVal == nullptr) 317 { 318 BMCWEB_LOG_ERROR << "Invalid AuthType value"; 319 return; 320 } 321 322 // For now assume authentication not required to communicate 323 // with the satellite BMC 324 if (*propVal != "None") 325 { 326 BMCWEB_LOG_ERROR 327 << "Unsupported AuthType value: " << *propVal 328 << ", only \"none\" is supported"; 329 return; 330 } 331 url.set_scheme("http"); 332 } 333 } // Finished reading properties 334 335 // Make sure all required config information was made available 336 if (url.host().empty()) 337 { 338 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 339 return; 340 } 341 342 if (!url.has_port()) 343 { 344 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 345 return; 346 } 347 348 if (!url.has_scheme()) 349 { 350 BMCWEB_LOG_ERROR << "Satellite config " << name 351 << " missing AuthType"; 352 return; 353 } 354 355 std::string resultString; 356 auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 357 if (result.second) 358 { 359 resultString = "Added new satellite config "; 360 } 361 else 362 { 363 resultString = "Updated existing satellite config "; 364 } 365 366 BMCWEB_LOG_DEBUG << resultString << name << " at " 367 << result.first->second.scheme() << "://" 368 << result.first->second.encoded_host_and_port(); 369 } 370 371 enum AggregationType 372 { 373 Collection, 374 Resource, 375 }; 376 377 static void 378 startAggregation(AggregationType isCollection, 379 const crow::Request& thisReq, 380 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 381 { 382 if ((isCollection == AggregationType::Collection) && 383 (thisReq.method() != boost::beast::http::verb::get)) 384 { 385 BMCWEB_LOG_DEBUG 386 << "Only aggregate GET requests to top level collections"; 387 return; 388 } 389 390 // Create a copy of thisReq so we we can still locally process the req 391 std::error_code ec; 392 auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 393 if (ec) 394 { 395 BMCWEB_LOG_ERROR << "Failed to create copy of request"; 396 if (isCollection != AggregationType::Collection) 397 { 398 messages::internalError(asyncResp->res); 399 } 400 return; 401 } 402 403 getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 404 localReq, asyncResp)); 405 } 406 407 static void findSatellite( 408 const crow::Request& req, 409 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 410 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 411 std::string_view memberName) 412 { 413 // Determine if the resource ID begins with a known prefix 414 for (const auto& satellite : satelliteInfo) 415 { 416 std::string targetPrefix = satellite.first; 417 targetPrefix += "_"; 418 if (memberName.starts_with(targetPrefix)) 419 { 420 BMCWEB_LOG_DEBUG << "\"" << satellite.first 421 << "\" is a known prefix"; 422 423 // Remove the known prefix from the request's URI and 424 // then forward to the associated satellite BMC 425 getInstance().forwardRequest(req, asyncResp, satellite.first, 426 satelliteInfo); 427 return; 428 } 429 } 430 431 // We didn't recognize the prefix and need to return a 404 432 boost::urls::string_value name = req.urlView.segments().back(); 433 std::string_view nameStr(name.data(), name.size()); 434 messages::resourceNotFound(asyncResp->res, "", nameStr); 435 } 436 437 // Intended to handle an incoming request based on if Redfish Aggregation 438 // is enabled. Forwards request to satellite BMC if it exists. 439 static void aggregateAndHandle( 440 AggregationType isCollection, 441 const std::shared_ptr<crow::Request>& sharedReq, 442 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 443 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 444 { 445 if (sharedReq == nullptr) 446 { 447 return; 448 } 449 450 // No satellite configs means we don't need to keep attempting to 451 // aggregate 452 if (satelliteInfo.empty()) 453 { 454 // For collections we'll also handle the request locally so we 455 // don't need to write an error code 456 if (isCollection == AggregationType::Resource) 457 { 458 boost::urls::string_value name = 459 sharedReq->urlView.segments().back(); 460 std::string_view nameStr(name.data(), name.size()); 461 messages::resourceNotFound(asyncResp->res, "", nameStr); 462 } 463 return; 464 } 465 466 const crow::Request& thisReq = *sharedReq; 467 BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 468 << thisReq.target(); 469 470 // We previously determined the request is for a collection. No need to 471 // check again 472 if (isCollection == AggregationType::Collection) 473 { 474 BMCWEB_LOG_DEBUG << "Aggregating a collection"; 475 // We need to use a specific response handler and send the 476 // request to all known satellites 477 getInstance().forwardCollectionRequests(thisReq, asyncResp, 478 satelliteInfo); 479 return; 480 } 481 482 std::string updateServiceName; 483 std::string memberName; 484 if (crow::utility::readUrlSegments( 485 thisReq.urlView, "redfish", "v1", "UpdateService", 486 std::ref(updateServiceName), std::ref(memberName), 487 crow::utility::OrMorePaths())) 488 { 489 // Must be FirmwareInventory or SoftwareInventory 490 findSatellite(thisReq, asyncResp, satelliteInfo, memberName); 491 return; 492 } 493 494 std::string collectionName; 495 if (crow::utility::readUrlSegments( 496 thisReq.urlView, "redfish", "v1", std::ref(collectionName), 497 std::ref(memberName), crow::utility::OrMorePaths())) 498 { 499 findSatellite(thisReq, asyncResp, satelliteInfo, memberName); 500 return; 501 } 502 503 // We shouldn't reach this point since we should've hit one of the 504 // previous exits 505 messages::internalError(asyncResp->res); 506 } 507 508 // Attempt to forward a request to the satellite BMC associated with the 509 // prefix. 510 void forwardRequest( 511 const crow::Request& thisReq, 512 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 513 const std::string& prefix, 514 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 515 { 516 const auto& sat = satelliteInfo.find(prefix); 517 if (sat == satelliteInfo.end()) 518 { 519 // Realistically this shouldn't get called since we perform an 520 // earlier check to make sure the prefix exists 521 BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 522 << "\""; 523 return; 524 } 525 526 // We need to strip the prefix from the request's path 527 std::string targetURI(thisReq.target()); 528 size_t pos = targetURI.find(prefix + "_"); 529 if (pos == std::string::npos) 530 { 531 // If this fails then something went wrong 532 BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 533 << "_\" from request URI"; 534 messages::internalError(asyncResp->res); 535 return; 536 } 537 targetURI.erase(pos, prefix.size() + 1); 538 539 std::function<void(crow::Response&)> cb = 540 std::bind_front(processResponse, prefix, asyncResp); 541 542 std::string data = thisReq.req.body(); 543 crow::HttpClient::getInstance().sendDataWithCallback( 544 data, id, std::string(sat->second.host()), 545 sat->second.port_number(), targetURI, false /*useSSL*/, 546 thisReq.fields, thisReq.method(), retryPolicyName, cb); 547 } 548 549 // Forward a request for a collection URI to each known satellite BMC 550 void forwardCollectionRequests( 551 const crow::Request& thisReq, 552 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 553 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 554 { 555 for (const auto& sat : satelliteInfo) 556 { 557 std::function<void(crow::Response&)> cb = std::bind_front( 558 processCollectionResponse, sat.first, asyncResp); 559 560 std::string targetURI(thisReq.target()); 561 std::string data = thisReq.req.body(); 562 crow::HttpClient::getInstance().sendDataWithCallback( 563 data, id, std::string(sat.second.host()), 564 sat.second.port_number(), targetURI, false /*useSSL*/, 565 thisReq.fields, thisReq.method(), retryPolicyName, cb); 566 } 567 } 568 569 // Processes the response returned by a satellite BMC and loads its 570 // contents into asyncResp 571 static void 572 processResponse(std::string_view prefix, 573 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 574 crow::Response& resp) 575 { 576 // No processing needed if the request wasn't successful 577 if (resp.resultInt() != 200) 578 { 579 BMCWEB_LOG_DEBUG << "No need to parse satellite response"; 580 asyncResp->res.stringResponse = std::move(resp.stringResponse); 581 return; 582 } 583 584 // The resp will not have a json component 585 // We need to create a json from resp's stringResponse 586 if (resp.getHeaderValue("Content-Type") == "application/json") 587 { 588 nlohmann::json jsonVal = 589 nlohmann::json::parse(resp.body(), nullptr, false); 590 if (jsonVal.is_discarded()) 591 { 592 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 593 messages::operationFailed(asyncResp->res); 594 return; 595 } 596 597 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 598 599 // TODO: For collections we want to add the satellite responses to 600 // our response rather than just straight overwriting them if our 601 // local handling was successful (i.e. would return a 200). 602 addPrefixes(jsonVal, prefix); 603 604 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 605 606 asyncResp->res.stringResponse.emplace( 607 boost::beast::http::response< 608 boost::beast::http::string_body>{}); 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.stringResponse.emplace( 693 boost::beast::http::response< 694 boost::beast::http::string_body>{}); 695 asyncResp->res.result(resp.result()); 696 asyncResp->res.jsonValue = std::move(jsonVal); 697 698 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 699 } 700 else 701 { 702 // We only want to aggregate collections that contain a 703 // "Members" array 704 if ((!asyncResp->res.jsonValue.contains("Members")) && 705 (!asyncResp->res.jsonValue["Members"].is_array())) 706 707 { 708 BMCWEB_LOG_DEBUG 709 << "Skipping aggregating unsupported resource"; 710 return; 711 } 712 713 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 714 << prefix << "\" to collection"; 715 716 // TODO: This is a potential race condition with multiple 717 // satellites and the aggregating bmc attempting to write to 718 // update this array. May need to cascade calls to the next 719 // satellite at the end of this function. 720 // This is presumably not a concern when there is only a single 721 // satellite since the aggregating bmc should have completed 722 // before the response is received from the satellite. 723 724 auto& members = asyncResp->res.jsonValue["Members"]; 725 auto& satMembers = jsonVal["Members"]; 726 for (auto& satMem : satMembers) 727 { 728 members.push_back(std::move(satMem)); 729 } 730 asyncResp->res.jsonValue["Members@odata.count"] = 731 members.size(); 732 733 // TODO: Do we need to sort() after updating the array? 734 } 735 } 736 else 737 { 738 BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 739 << "\""; 740 // We received as response that was not a json 741 // Notify the user only if we did not receive any valid responses, 742 // if the resource collection does not already exist on the 743 // aggregating BMC, and if we did not already set this warning due 744 // to a failure from a different satellite 745 if ((asyncResp->res.resultInt() != 200) && 746 (asyncResp->res.resultInt() != 502)) 747 { 748 messages::operationFailed(asyncResp->res); 749 } 750 } 751 } // End processCollectionResponse() 752 753 public: 754 RedfishAggregator(const RedfishAggregator&) = delete; 755 RedfishAggregator& operator=(const RedfishAggregator&) = delete; 756 RedfishAggregator(RedfishAggregator&&) = delete; 757 RedfishAggregator& operator=(RedfishAggregator&&) = delete; 758 ~RedfishAggregator() = default; 759 760 static RedfishAggregator& getInstance() 761 { 762 static RedfishAggregator handler; 763 return handler; 764 } 765 766 // Entry point to Redfish Aggregation 767 // Returns Result stating whether or not we still need to locally handle the 768 // request 769 static Result 770 beginAggregation(const crow::Request& thisReq, 771 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 772 { 773 using crow::utility::OrMorePaths; 774 using crow::utility::readUrlSegments; 775 const boost::urls::url_view& url = thisReq.urlView; 776 // UpdateService is the only top level resource that is not a Collection 777 if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 778 { 779 return Result::LocalHandle; 780 } 781 782 // We don't need to aggregate JsonSchemas due to potential issues such 783 // as version mismatches between aggregator and satellite BMCs. For 784 // now assume that the aggregator has all the schemas and versions that 785 // the aggregated server has. 786 if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 787 crow::utility::OrMorePaths())) 788 { 789 return Result::LocalHandle; 790 } 791 792 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 793 "SoftwareInventory") || 794 readUrlSegments(url, "redfish", "v1", "UpdateService", 795 "FirmwareInventory")) 796 { 797 startAggregation(AggregationType::Collection, thisReq, asyncResp); 798 return Result::LocalHandle; 799 } 800 801 // Is the request for a resource collection?: 802 // /redfish/v1/<resource> 803 // e.g. /redfish/v1/Chassis 804 std::string collectionName; 805 if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 806 { 807 startAggregation(AggregationType::Collection, thisReq, asyncResp); 808 return Result::LocalHandle; 809 } 810 811 // We know that the ID of an aggregated resource will begin with 812 // "5B247A". For the most part the URI will begin like this: 813 // /redfish/v1/<resource>/<resource ID> 814 // Note, FirmwareInventory and SoftwareInventory are "special" because 815 // they are two levels deep, but still need aggregated 816 // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 817 // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 818 std::string memberName; 819 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 820 "SoftwareInventory", std::ref(memberName), 821 OrMorePaths()) || 822 readUrlSegments(url, "redfish", "v1", "UpdateService", 823 "FirmwareInventory", std::ref(memberName), 824 OrMorePaths()) || 825 readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 826 std::ref(memberName), OrMorePaths())) 827 { 828 if (memberName.starts_with("5B247A")) 829 { 830 BMCWEB_LOG_DEBUG << "Need to forward a request"; 831 832 // Extract the prefix from the request's URI, retrieve the 833 // associated satellite config information, and then forward the 834 // request to that satellite. 835 startAggregation(AggregationType::Resource, thisReq, asyncResp); 836 return Result::NoLocalHandle; 837 } 838 return Result::LocalHandle; 839 } 840 841 BMCWEB_LOG_DEBUG << "Aggregation not required"; 842 return Result::LocalHandle; 843 } 844 }; 845 846 } // namespace redfish 847