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.result(resp.result()); 608 asyncResp->res.jsonValue = std::move(jsonVal); 609 610 BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 611 } 612 else 613 { 614 if (!resp.body().empty()) 615 { 616 // We received a 200 response without the correct Content-Type 617 // so return an Operation Failed error 618 BMCWEB_LOG_ERROR 619 << "Satellite response must be of type \"application/json\""; 620 messages::operationFailed(asyncResp->res); 621 } 622 } 623 } 624 625 // Processes the collection response returned by a satellite BMC and merges 626 // its "@odata.id" values 627 static void processCollectionResponse( 628 const std::string& prefix, 629 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 630 crow::Response& resp) 631 { 632 if (resp.resultInt() != 200) 633 { 634 BMCWEB_LOG_DEBUG 635 << "Collection resource does not exist in satellite BMC \"" 636 << prefix << "\""; 637 // Return the error if we haven't had any successes 638 if (asyncResp->res.resultInt() != 200) 639 { 640 asyncResp->res.stringResponse = std::move(resp.stringResponse); 641 } 642 return; 643 } 644 645 // The resp will not have a json component 646 // We need to create a json from resp's stringResponse 647 if (resp.getHeaderValue("Content-Type") == "application/json") 648 { 649 nlohmann::json jsonVal = 650 nlohmann::json::parse(resp.body(), nullptr, false); 651 if (jsonVal.is_discarded()) 652 { 653 BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 654 655 // Notify the user if doing so won't overwrite a valid response 656 if ((asyncResp->res.resultInt() != 200) && 657 (asyncResp->res.resultInt() != 502)) 658 { 659 messages::operationFailed(asyncResp->res); 660 } 661 return; 662 } 663 664 BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 665 666 // Now we need to add the prefix to the URIs contained in the 667 // response. 668 addPrefixes(jsonVal, prefix); 669 670 BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 671 672 // If this resource collection does not exist on the aggregating bmc 673 // and has not already been added from processing the response from 674 // a different satellite then we need to completely overwrite 675 // asyncResp 676 if (asyncResp->res.resultInt() != 200) 677 { 678 // We only want to aggregate collections that contain a 679 // "Members" array 680 if ((!jsonVal.contains("Members")) && 681 (!jsonVal["Members"].is_array())) 682 { 683 BMCWEB_LOG_DEBUG 684 << "Skipping aggregating unsupported resource"; 685 return; 686 } 687 688 BMCWEB_LOG_DEBUG 689 << "Collection does not exist, overwriting asyncResp"; 690 asyncResp->res.result(resp.result()); 691 asyncResp->res.jsonValue = std::move(jsonVal); 692 693 BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; 694 } 695 else 696 { 697 // We only want to aggregate collections that contain a 698 // "Members" array 699 if ((!asyncResp->res.jsonValue.contains("Members")) && 700 (!asyncResp->res.jsonValue["Members"].is_array())) 701 702 { 703 BMCWEB_LOG_DEBUG 704 << "Skipping aggregating unsupported resource"; 705 return; 706 } 707 708 BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" 709 << prefix << "\" to collection"; 710 711 // TODO: This is a potential race condition with multiple 712 // satellites and the aggregating bmc attempting to write to 713 // update this array. May need to cascade calls to the next 714 // satellite at the end of this function. 715 // This is presumably not a concern when there is only a single 716 // satellite since the aggregating bmc should have completed 717 // before the response is received from the satellite. 718 719 auto& members = asyncResp->res.jsonValue["Members"]; 720 auto& satMembers = jsonVal["Members"]; 721 for (auto& satMem : satMembers) 722 { 723 members.push_back(std::move(satMem)); 724 } 725 asyncResp->res.jsonValue["Members@odata.count"] = 726 members.size(); 727 728 // TODO: Do we need to sort() after updating the array? 729 } 730 } 731 else 732 { 733 BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix 734 << "\""; 735 // We received as response that was not a json 736 // Notify the user only if we did not receive any valid responses, 737 // if the resource collection does not already exist on the 738 // aggregating BMC, and if we did not already set this warning due 739 // to a failure from a different satellite 740 if ((asyncResp->res.resultInt() != 200) && 741 (asyncResp->res.resultInt() != 502)) 742 { 743 messages::operationFailed(asyncResp->res); 744 } 745 } 746 } // End processCollectionResponse() 747 748 public: 749 RedfishAggregator(const RedfishAggregator&) = delete; 750 RedfishAggregator& operator=(const RedfishAggregator&) = delete; 751 RedfishAggregator(RedfishAggregator&&) = delete; 752 RedfishAggregator& operator=(RedfishAggregator&&) = delete; 753 ~RedfishAggregator() = default; 754 755 static RedfishAggregator& getInstance() 756 { 757 static RedfishAggregator handler; 758 return handler; 759 } 760 761 // Entry point to Redfish Aggregation 762 // Returns Result stating whether or not we still need to locally handle the 763 // request 764 static Result 765 beginAggregation(const crow::Request& thisReq, 766 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 767 { 768 using crow::utility::OrMorePaths; 769 using crow::utility::readUrlSegments; 770 const boost::urls::url_view& url = thisReq.urlView; 771 // UpdateService is the only top level resource that is not a Collection 772 if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 773 { 774 return Result::LocalHandle; 775 } 776 777 // We don't need to aggregate JsonSchemas due to potential issues such 778 // as version mismatches between aggregator and satellite BMCs. For 779 // now assume that the aggregator has all the schemas and versions that 780 // the aggregated server has. 781 if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", 782 crow::utility::OrMorePaths())) 783 { 784 return Result::LocalHandle; 785 } 786 787 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 788 "SoftwareInventory") || 789 readUrlSegments(url, "redfish", "v1", "UpdateService", 790 "FirmwareInventory")) 791 { 792 startAggregation(AggregationType::Collection, thisReq, asyncResp); 793 return Result::LocalHandle; 794 } 795 796 // Is the request for a resource collection?: 797 // /redfish/v1/<resource> 798 // e.g. /redfish/v1/Chassis 799 std::string collectionName; 800 if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 801 { 802 startAggregation(AggregationType::Collection, thisReq, asyncResp); 803 return Result::LocalHandle; 804 } 805 806 // We know that the ID of an aggregated resource will begin with 807 // "5B247A". For the most part the URI will begin like this: 808 // /redfish/v1/<resource>/<resource ID> 809 // Note, FirmwareInventory and SoftwareInventory are "special" because 810 // they are two levels deep, but still need aggregated 811 // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 812 // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 813 std::string memberName; 814 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 815 "SoftwareInventory", std::ref(memberName), 816 OrMorePaths()) || 817 readUrlSegments(url, "redfish", "v1", "UpdateService", 818 "FirmwareInventory", std::ref(memberName), 819 OrMorePaths()) || 820 readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 821 std::ref(memberName), OrMorePaths())) 822 { 823 if (memberName.starts_with("5B247A")) 824 { 825 BMCWEB_LOG_DEBUG << "Need to forward a request"; 826 827 // Extract the prefix from the request's URI, retrieve the 828 // associated satellite config information, and then forward the 829 // request to that satellite. 830 startAggregation(AggregationType::Resource, thisReq, asyncResp); 831 return Result::NoLocalHandle; 832 } 833 return Result::LocalHandle; 834 } 835 836 BMCWEB_LOG_DEBUG << "Aggregation not required"; 837 return Result::LocalHandle; 838 } 839 }; 840 841 } // namespace redfish 842