17fb33566SCarson Labrado #pragma once 27fb33566SCarson Labrado 346a81465SCarson Labrado #include <dbus_utility.hpp> 446a81465SCarson Labrado #include <error_messages.hpp> 57fb33566SCarson Labrado #include <http_client.hpp> 646a81465SCarson Labrado #include <http_connection.hpp> 77fb33566SCarson Labrado 87fb33566SCarson Labrado namespace redfish 97fb33566SCarson Labrado { 107fb33566SCarson Labrado 1105916cefSCarson Labrado enum class Result 1205916cefSCarson Labrado { 1305916cefSCarson Labrado LocalHandle, 1405916cefSCarson Labrado NoLocalHandle 1505916cefSCarson Labrado }; 1605916cefSCarson Labrado 17*1c0bb5c6SCarson Labrado static void addPrefixToItem(nlohmann::json& item, std::string_view prefix) 18*1c0bb5c6SCarson Labrado { 19*1c0bb5c6SCarson Labrado std::string* strValue = item.get_ptr<std::string*>(); 20*1c0bb5c6SCarson Labrado if (strValue == nullptr) 21*1c0bb5c6SCarson Labrado { 22*1c0bb5c6SCarson Labrado BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; 23*1c0bb5c6SCarson Labrado return; 24*1c0bb5c6SCarson Labrado } 25*1c0bb5c6SCarson Labrado // Make sure the value is a properly formatted URI 26*1c0bb5c6SCarson Labrado auto parsed = boost::urls::parse_relative_ref(*strValue); 27*1c0bb5c6SCarson Labrado if (!parsed) 28*1c0bb5c6SCarson Labrado { 29*1c0bb5c6SCarson Labrado BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << *strValue; 30*1c0bb5c6SCarson Labrado return; 31*1c0bb5c6SCarson Labrado } 32*1c0bb5c6SCarson Labrado 33*1c0bb5c6SCarson Labrado boost::urls::url_view thisUrl = *parsed; 34*1c0bb5c6SCarson Labrado 35*1c0bb5c6SCarson Labrado // We don't need to add prefixes to these URIs since 36*1c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/ itself is not a collection 37*1c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory 38*1c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory 39*1c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 40*1c0bb5c6SCarson Labrado "UpdateService", "FirmwareInventory") || 41*1c0bb5c6SCarson Labrado crow::utility::readUrlSegments(thisUrl, "redfish", "v1", 42*1c0bb5c6SCarson Labrado "UpdateService", "SoftwareInventory")) 43*1c0bb5c6SCarson Labrado { 44*1c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Skipping UpdateService URI prefix fixing"; 45*1c0bb5c6SCarson Labrado return; 46*1c0bb5c6SCarson Labrado } 47*1c0bb5c6SCarson Labrado 48*1c0bb5c6SCarson Labrado // We also need to aggregate FirmwareInventory and 49*1c0bb5c6SCarson Labrado // SoftwareInventory so add an extra offset 50*1c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory/<id> 51*1c0bb5c6SCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory/<id> 52*1c0bb5c6SCarson Labrado std::string collectionName; 53*1c0bb5c6SCarson Labrado std::string softwareItem; 54*1c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments( 55*1c0bb5c6SCarson Labrado thisUrl, "redfish", "v1", "UpdateService", std::ref(collectionName), 56*1c0bb5c6SCarson Labrado std::ref(softwareItem), crow::utility::OrMorePaths())) 57*1c0bb5c6SCarson Labrado { 58*1c0bb5c6SCarson Labrado softwareItem.insert(0, "_"); 59*1c0bb5c6SCarson Labrado softwareItem.insert(0, prefix); 60*1c0bb5c6SCarson Labrado item = crow::utility::replaceUrlSegment(thisUrl, 4, softwareItem); 61*1c0bb5c6SCarson Labrado } 62*1c0bb5c6SCarson Labrado 63*1c0bb5c6SCarson Labrado // A collection URI that ends with "/" such as 64*1c0bb5c6SCarson Labrado // "/redfish/v1/Chassis/" will have 4 segments so we need to 65*1c0bb5c6SCarson Labrado // make sure we don't try to add a prefix to an empty segment 66*1c0bb5c6SCarson Labrado if (crow::utility::readUrlSegments( 67*1c0bb5c6SCarson Labrado thisUrl, "redfish", "v1", std::ref(collectionName), 68*1c0bb5c6SCarson Labrado std::ref(softwareItem), crow::utility::OrMorePaths())) 69*1c0bb5c6SCarson Labrado { 70*1c0bb5c6SCarson Labrado softwareItem.insert(0, "_"); 71*1c0bb5c6SCarson Labrado softwareItem.insert(0, prefix); 72*1c0bb5c6SCarson Labrado item = crow::utility::replaceUrlSegment(thisUrl, 3, softwareItem); 73*1c0bb5c6SCarson Labrado } 74*1c0bb5c6SCarson Labrado } 75*1c0bb5c6SCarson Labrado 76*1c0bb5c6SCarson Labrado // We need to attempt to update all URIs under Actions 77*1c0bb5c6SCarson Labrado static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix) 78*1c0bb5c6SCarson Labrado { 79*1c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 80*1c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 81*1c0bb5c6SCarson Labrado if (object != nullptr) 82*1c0bb5c6SCarson Labrado { 83*1c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 84*1c0bb5c6SCarson Labrado { 85*1c0bb5c6SCarson Labrado std::string* strValue = item.second.get_ptr<std::string*>(); 86*1c0bb5c6SCarson Labrado if (strValue != nullptr) 87*1c0bb5c6SCarson Labrado { 88*1c0bb5c6SCarson Labrado addPrefixToItem(item.second, prefix); 89*1c0bb5c6SCarson Labrado } 90*1c0bb5c6SCarson Labrado else 91*1c0bb5c6SCarson Labrado { 92*1c0bb5c6SCarson Labrado addPrefixesToActions(item.second, prefix); 93*1c0bb5c6SCarson Labrado } 94*1c0bb5c6SCarson Labrado } 95*1c0bb5c6SCarson Labrado } 96*1c0bb5c6SCarson Labrado } 97*1c0bb5c6SCarson Labrado 98*1c0bb5c6SCarson Labrado // Search the json for all URIs and add the supplied prefix if the URI is for 99*1c0bb5c6SCarson Labrado // and aggregated resource. 100*1c0bb5c6SCarson Labrado static void addPrefixes(nlohmann::json& json, std::string_view prefix) 101*1c0bb5c6SCarson Labrado { 102*1c0bb5c6SCarson Labrado nlohmann::json::object_t* object = 103*1c0bb5c6SCarson Labrado json.get_ptr<nlohmann::json::object_t*>(); 104*1c0bb5c6SCarson Labrado if (object != nullptr) 105*1c0bb5c6SCarson Labrado { 106*1c0bb5c6SCarson Labrado for (std::pair<const std::string, nlohmann::json>& item : *object) 107*1c0bb5c6SCarson Labrado { 108*1c0bb5c6SCarson Labrado if (item.first == "Actions") 109*1c0bb5c6SCarson Labrado { 110*1c0bb5c6SCarson Labrado addPrefixesToActions(item.second, prefix); 111*1c0bb5c6SCarson Labrado continue; 112*1c0bb5c6SCarson Labrado } 113*1c0bb5c6SCarson Labrado 114*1c0bb5c6SCarson Labrado if ((item.first == "@odata.id") || (item.first.ends_with("URI"))) 115*1c0bb5c6SCarson Labrado { 116*1c0bb5c6SCarson Labrado addPrefixToItem(item.second, prefix); 117*1c0bb5c6SCarson Labrado } 118*1c0bb5c6SCarson Labrado // Recusively parse the rest of the json 119*1c0bb5c6SCarson Labrado addPrefixes(item.second, prefix); 120*1c0bb5c6SCarson Labrado } 121*1c0bb5c6SCarson Labrado return; 122*1c0bb5c6SCarson Labrado } 123*1c0bb5c6SCarson Labrado nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); 124*1c0bb5c6SCarson Labrado if (array != nullptr) 125*1c0bb5c6SCarson Labrado { 126*1c0bb5c6SCarson Labrado for (nlohmann::json& item : *array) 127*1c0bb5c6SCarson Labrado { 128*1c0bb5c6SCarson Labrado addPrefixes(item, prefix); 129*1c0bb5c6SCarson Labrado } 130*1c0bb5c6SCarson Labrado } 131*1c0bb5c6SCarson Labrado } 132*1c0bb5c6SCarson Labrado 1337fb33566SCarson Labrado class RedfishAggregator 1347fb33566SCarson Labrado { 1357fb33566SCarson Labrado private: 136a7a80296SCarson Labrado const std::string retryPolicyName = "RedfishAggregation"; 137a7a80296SCarson Labrado const uint32_t retryAttempts = 5; 138a7a80296SCarson Labrado const uint32_t retryTimeoutInterval = 0; 13946a81465SCarson Labrado const std::string id = "Aggregator"; 140a7a80296SCarson Labrado 1417fb33566SCarson Labrado RedfishAggregator() 1427fb33566SCarson Labrado { 1437fb33566SCarson Labrado getSatelliteConfigs(constructorCallback); 144a7a80296SCarson Labrado 145a7a80296SCarson Labrado // Setup the retry policy to be used by Redfish Aggregation 146a7a80296SCarson Labrado crow::HttpClient::getInstance().setRetryConfig( 147a7a80296SCarson Labrado retryAttempts, retryTimeoutInterval, aggregationRetryHandler, 148a7a80296SCarson Labrado retryPolicyName); 1497fb33566SCarson Labrado } 1507fb33566SCarson Labrado 151a7a80296SCarson Labrado static inline boost::system::error_code 152a7a80296SCarson Labrado aggregationRetryHandler(unsigned int respCode) 153a7a80296SCarson Labrado { 154a7a80296SCarson Labrado // As a default, assume 200X is alright. 155a7a80296SCarson Labrado // We don't need to retry on a 404 156a7a80296SCarson Labrado if ((respCode < 200) || ((respCode >= 300) && (respCode != 404))) 157a7a80296SCarson Labrado { 158a7a80296SCarson Labrado return boost::system::errc::make_error_code( 159a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 160a7a80296SCarson Labrado } 161a7a80296SCarson Labrado 162a7a80296SCarson Labrado // Return 0 if the response code is valid 163a7a80296SCarson Labrado return boost::system::errc::make_error_code( 164a7a80296SCarson Labrado boost::system::errc::success); 1659fa6d147SNan Zhou } 166a7a80296SCarson Labrado 1677fb33566SCarson Labrado // Dummy callback used by the Constructor so that it can report the number 1687fb33566SCarson Labrado // of satellite configs when the class is first created 1697fb33566SCarson Labrado static void constructorCallback( 1707fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 1717fb33566SCarson Labrado { 1727fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "There were " 1737fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 1747fb33566SCarson Labrado << " satellite configs found at startup"; 1757fb33566SCarson Labrado } 1767fb33566SCarson Labrado 1777fb33566SCarson Labrado // Polls D-Bus to get all available satellite config information 1787fb33566SCarson Labrado // Expects a handler which interacts with the returned configs 1797fb33566SCarson Labrado static void getSatelliteConfigs( 1807fb33566SCarson Labrado const std::function<void( 1817fb33566SCarson Labrado const std::unordered_map<std::string, boost::urls::url>&)>& handler) 1827fb33566SCarson Labrado { 1837fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 1847fb33566SCarson Labrado crow::connections::systemBus->async_method_call( 1857fb33566SCarson Labrado [handler](const boost::system::error_code ec, 1867fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects) { 1877fb33566SCarson Labrado if (ec) 1887fb33566SCarson Labrado { 189002d39b4SEd Tanous BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 190002d39b4SEd Tanous << ec.message(); 1917fb33566SCarson Labrado return; 1927fb33566SCarson Labrado } 1937fb33566SCarson Labrado 1947fb33566SCarson Labrado // Maps a chosen alias representing a satellite BMC to a url 1957fb33566SCarson Labrado // containing the information required to create a http 1967fb33566SCarson Labrado // connection to the satellite 1977fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url> satelliteInfo; 1987fb33566SCarson Labrado 1997fb33566SCarson Labrado findSatelliteConfigs(objects, satelliteInfo); 2007fb33566SCarson Labrado 2017fb33566SCarson Labrado if (!satelliteInfo.empty()) 2027fb33566SCarson Labrado { 2037fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 2047fb33566SCarson Labrado << std::to_string(satelliteInfo.size()) 2057fb33566SCarson Labrado << " satellite BMCs"; 2067fb33566SCarson Labrado } 2077fb33566SCarson Labrado else 2087fb33566SCarson Labrado { 2097fb33566SCarson Labrado BMCWEB_LOG_DEBUG 2107fb33566SCarson Labrado << "No satellite BMCs detected. Redfish Aggregation not enabled"; 2117fb33566SCarson Labrado } 2127fb33566SCarson Labrado handler(satelliteInfo); 2137fb33566SCarson Labrado }, 2147fb33566SCarson Labrado "xyz.openbmc_project.EntityManager", "/", 2157fb33566SCarson Labrado "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2167fb33566SCarson Labrado } 2177fb33566SCarson Labrado 2187fb33566SCarson Labrado // Search D-Bus objects for satellite config objects and add their 2197fb33566SCarson Labrado // information if valid 2207fb33566SCarson Labrado static void findSatelliteConfigs( 2217fb33566SCarson Labrado const dbus::utility::ManagedObjectType& objects, 2227fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2237fb33566SCarson Labrado { 2247fb33566SCarson Labrado for (const auto& objectPath : objects) 2257fb33566SCarson Labrado { 2267fb33566SCarson Labrado for (const auto& interface : objectPath.second) 2277fb33566SCarson Labrado { 2287fb33566SCarson Labrado if (interface.first == 2297fb33566SCarson Labrado "xyz.openbmc_project.Configuration.SatelliteController") 2307fb33566SCarson Labrado { 2317fb33566SCarson Labrado BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 2327fb33566SCarson Labrado << objectPath.first.str; 2337fb33566SCarson Labrado 23405916cefSCarson Labrado if (!satelliteInfo.empty()) 23505916cefSCarson Labrado { 23605916cefSCarson Labrado BMCWEB_LOG_ERROR 23705916cefSCarson Labrado << "Redfish Aggregation only supports one satellite!"; 23805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 23905916cefSCarson Labrado satelliteInfo.clear(); 24005916cefSCarson Labrado return; 24105916cefSCarson Labrado } 24205916cefSCarson Labrado 24305916cefSCarson Labrado // For now assume there will only be one satellite config. 24405916cefSCarson Labrado // Assign it the name/prefix "5B247A" 24505916cefSCarson Labrado addSatelliteConfig("5B247A", interface.second, 24605916cefSCarson Labrado satelliteInfo); 2477fb33566SCarson Labrado } 2487fb33566SCarson Labrado } 2497fb33566SCarson Labrado } 2507fb33566SCarson Labrado } 2517fb33566SCarson Labrado 2527fb33566SCarson Labrado // Parse the properties of a satellite config object and add the 2537fb33566SCarson Labrado // configuration if the properties are valid 2547fb33566SCarson Labrado static void addSatelliteConfig( 25505916cefSCarson Labrado const std::string& name, 2567fb33566SCarson Labrado const dbus::utility::DBusPropertiesMap& properties, 2577fb33566SCarson Labrado std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 2587fb33566SCarson Labrado { 2597fb33566SCarson Labrado boost::urls::url url; 2607fb33566SCarson Labrado 2617fb33566SCarson Labrado for (const auto& prop : properties) 2627fb33566SCarson Labrado { 26305916cefSCarson Labrado if (prop.first == "Hostname") 2647fb33566SCarson Labrado { 2657fb33566SCarson Labrado const std::string* propVal = 2667fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 2677fb33566SCarson Labrado if (propVal == nullptr) 2687fb33566SCarson Labrado { 2697fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Hostname value"; 2707fb33566SCarson Labrado return; 2717fb33566SCarson Labrado } 2727fb33566SCarson Labrado url.set_host(*propVal); 2737fb33566SCarson Labrado } 2747fb33566SCarson Labrado 2757fb33566SCarson Labrado else if (prop.first == "Port") 2767fb33566SCarson Labrado { 2777fb33566SCarson Labrado const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 2787fb33566SCarson Labrado if (propVal == nullptr) 2797fb33566SCarson Labrado { 2807fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid Port value"; 2817fb33566SCarson Labrado return; 2827fb33566SCarson Labrado } 2837fb33566SCarson Labrado 2847fb33566SCarson Labrado if (*propVal > std::numeric_limits<uint16_t>::max()) 2857fb33566SCarson Labrado { 2867fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Port value out of range"; 2877fb33566SCarson Labrado return; 2887fb33566SCarson Labrado } 2897fb33566SCarson Labrado url.set_port(static_cast<uint16_t>(*propVal)); 2907fb33566SCarson Labrado } 2917fb33566SCarson Labrado 2927fb33566SCarson Labrado else if (prop.first == "AuthType") 2937fb33566SCarson Labrado { 2947fb33566SCarson Labrado const std::string* propVal = 2957fb33566SCarson Labrado std::get_if<std::string>(&prop.second); 2967fb33566SCarson Labrado if (propVal == nullptr) 2977fb33566SCarson Labrado { 2987fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Invalid AuthType value"; 2997fb33566SCarson Labrado return; 3007fb33566SCarson Labrado } 3017fb33566SCarson Labrado 3027fb33566SCarson Labrado // For now assume authentication not required to communicate 3037fb33566SCarson Labrado // with the satellite BMC 3047fb33566SCarson Labrado if (*propVal != "None") 3057fb33566SCarson Labrado { 3067fb33566SCarson Labrado BMCWEB_LOG_ERROR 3077fb33566SCarson Labrado << "Unsupported AuthType value: " << *propVal 3087fb33566SCarson Labrado << ", only \"none\" is supported"; 3097fb33566SCarson Labrado return; 3107fb33566SCarson Labrado } 3117fb33566SCarson Labrado url.set_scheme("http"); 3127fb33566SCarson Labrado } 3137fb33566SCarson Labrado } // Finished reading properties 3147fb33566SCarson Labrado 3157fb33566SCarson Labrado // Make sure all required config information was made available 3167fb33566SCarson Labrado if (url.host().empty()) 3177fb33566SCarson Labrado { 3187fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 3197fb33566SCarson Labrado return; 3207fb33566SCarson Labrado } 3217fb33566SCarson Labrado 3227fb33566SCarson Labrado if (!url.has_port()) 3237fb33566SCarson Labrado { 3247fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 3257fb33566SCarson Labrado return; 3267fb33566SCarson Labrado } 3277fb33566SCarson Labrado 3287fb33566SCarson Labrado if (!url.has_scheme()) 3297fb33566SCarson Labrado { 3307fb33566SCarson Labrado BMCWEB_LOG_ERROR << "Satellite config " << name 3317fb33566SCarson Labrado << " missing AuthType"; 3327fb33566SCarson Labrado return; 3337fb33566SCarson Labrado } 3347fb33566SCarson Labrado 3357fb33566SCarson Labrado std::string resultString; 3367fb33566SCarson Labrado auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 3377fb33566SCarson Labrado if (result.second) 3387fb33566SCarson Labrado { 3397fb33566SCarson Labrado resultString = "Added new satellite config "; 3407fb33566SCarson Labrado } 3417fb33566SCarson Labrado else 3427fb33566SCarson Labrado { 3437fb33566SCarson Labrado resultString = "Updated existing satellite config "; 3447fb33566SCarson Labrado } 3457fb33566SCarson Labrado 3467fb33566SCarson Labrado BMCWEB_LOG_DEBUG << resultString << name << " at " 3477fb33566SCarson Labrado << result.first->second.scheme() << "://" 3487fb33566SCarson Labrado << result.first->second.encoded_host_and_port(); 3497fb33566SCarson Labrado } 3507fb33566SCarson Labrado 35146a81465SCarson Labrado enum AggregationType 35246a81465SCarson Labrado { 35346a81465SCarson Labrado Collection, 35446a81465SCarson Labrado Resource, 35546a81465SCarson Labrado }; 35646a81465SCarson Labrado 35746a81465SCarson Labrado static void 35846a81465SCarson Labrado startAggregation(AggregationType isCollection, 35946a81465SCarson Labrado const crow::Request& thisReq, 36046a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 36146a81465SCarson Labrado { 36246a81465SCarson Labrado // Create a copy of thisReq so we we can still locally process the req 36346a81465SCarson Labrado std::error_code ec; 36446a81465SCarson Labrado auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); 36546a81465SCarson Labrado if (ec) 36646a81465SCarson Labrado { 36746a81465SCarson Labrado BMCWEB_LOG_ERROR << "Failed to create copy of request"; 36846a81465SCarson Labrado if (isCollection != AggregationType::Collection) 36946a81465SCarson Labrado { 37046a81465SCarson Labrado messages::internalError(asyncResp->res); 37146a81465SCarson Labrado } 37246a81465SCarson Labrado return; 37346a81465SCarson Labrado } 37446a81465SCarson Labrado 37546a81465SCarson Labrado getSatelliteConfigs(std::bind_front(aggregateAndHandle, isCollection, 37646a81465SCarson Labrado localReq, asyncResp)); 37746a81465SCarson Labrado } 37846a81465SCarson Labrado 37946a81465SCarson Labrado static void findSatelite( 38046a81465SCarson Labrado const crow::Request& req, 38146a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 38246a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, 38346a81465SCarson Labrado std::string_view memberName) 38446a81465SCarson Labrado { 38546a81465SCarson Labrado // Determine if the resource ID begins with a known prefix 38646a81465SCarson Labrado for (const auto& satellite : satelliteInfo) 38746a81465SCarson Labrado { 38846a81465SCarson Labrado std::string targetPrefix = satellite.first; 38946a81465SCarson Labrado targetPrefix += "_"; 39046a81465SCarson Labrado if (memberName.starts_with(targetPrefix)) 39146a81465SCarson Labrado { 39246a81465SCarson Labrado BMCWEB_LOG_DEBUG << "\"" << satellite.first 39346a81465SCarson Labrado << "\" is a known prefix"; 39446a81465SCarson Labrado 39546a81465SCarson Labrado // Remove the known prefix from the request's URI and 39646a81465SCarson Labrado // then forward to the associated satellite BMC 39746a81465SCarson Labrado getInstance().forwardRequest(req, asyncResp, satellite.first, 39846a81465SCarson Labrado satelliteInfo); 39946a81465SCarson Labrado return; 40046a81465SCarson Labrado } 40146a81465SCarson Labrado } 40246a81465SCarson Labrado } 40346a81465SCarson Labrado 40446a81465SCarson Labrado // Intended to handle an incoming request based on if Redfish Aggregation 40546a81465SCarson Labrado // is enabled. Forwards request to satellite BMC if it exists. 40646a81465SCarson Labrado static void aggregateAndHandle( 40746a81465SCarson Labrado AggregationType isCollection, 40846a81465SCarson Labrado const std::shared_ptr<crow::Request>& sharedReq, 40946a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 41046a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 41146a81465SCarson Labrado { 41246a81465SCarson Labrado if (sharedReq == nullptr) 41346a81465SCarson Labrado { 41446a81465SCarson Labrado return; 41546a81465SCarson Labrado } 41646a81465SCarson Labrado const crow::Request& thisReq = *sharedReq; 41746a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " 41846a81465SCarson Labrado << thisReq.target(); 41946a81465SCarson Labrado 42046a81465SCarson Labrado // We previously determined the request is for a collection. No need to 42146a81465SCarson Labrado // check again 42246a81465SCarson Labrado if (isCollection == AggregationType::Collection) 42346a81465SCarson Labrado { 42446a81465SCarson Labrado // TODO: This should instead be handled so that we can 42546a81465SCarson Labrado // aggregate the satellite resource collections 42646a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Aggregating a collection"; 42746a81465SCarson Labrado return; 42846a81465SCarson Labrado } 42946a81465SCarson Labrado 43046a81465SCarson Labrado std::string updateServiceName; 43146a81465SCarson Labrado std::string memberName; 43246a81465SCarson Labrado if (crow::utility::readUrlSegments( 43346a81465SCarson Labrado thisReq.urlView, "redfish", "v1", "UpdateService", 43446a81465SCarson Labrado std::ref(updateServiceName), std::ref(memberName), 43546a81465SCarson Labrado crow::utility::OrMorePaths())) 43646a81465SCarson Labrado { 43746a81465SCarson Labrado // Must be FirmwareInventory or SoftwareInventory 43846a81465SCarson Labrado findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 43946a81465SCarson Labrado return; 44046a81465SCarson Labrado } 44146a81465SCarson Labrado 44246a81465SCarson Labrado std::string collectionName; 44346a81465SCarson Labrado if (crow::utility::readUrlSegments( 44446a81465SCarson Labrado thisReq.urlView, "redfish", "v1", std::ref(collectionName), 44546a81465SCarson Labrado std::ref(memberName), crow::utility::OrMorePaths())) 44646a81465SCarson Labrado { 44746a81465SCarson Labrado findSatelite(thisReq, asyncResp, satelliteInfo, memberName); 44846a81465SCarson Labrado } 44946a81465SCarson Labrado } 45046a81465SCarson Labrado 45146a81465SCarson Labrado // Attempt to forward a request to the satellite BMC associated with the 45246a81465SCarson Labrado // prefix. 45346a81465SCarson Labrado void forwardRequest( 45446a81465SCarson Labrado const crow::Request& thisReq, 45546a81465SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 45646a81465SCarson Labrado const std::string& prefix, 45746a81465SCarson Labrado const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 45846a81465SCarson Labrado { 45946a81465SCarson Labrado const auto& sat = satelliteInfo.find(prefix); 46046a81465SCarson Labrado if (sat == satelliteInfo.end()) 46146a81465SCarson Labrado { 46246a81465SCarson Labrado // Realistically this shouldn't get called since we perform an 46346a81465SCarson Labrado // earlier check to make sure the prefix exists 46446a81465SCarson Labrado BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix 46546a81465SCarson Labrado << "\""; 46646a81465SCarson Labrado return; 46746a81465SCarson Labrado } 46846a81465SCarson Labrado 46946a81465SCarson Labrado // We need to strip the prefix from the request's path 47046a81465SCarson Labrado std::string targetURI(thisReq.target()); 47146a81465SCarson Labrado size_t pos = targetURI.find(prefix + "_"); 47246a81465SCarson Labrado if (pos == std::string::npos) 47346a81465SCarson Labrado { 47446a81465SCarson Labrado // If this fails then something went wrong 47546a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix 47646a81465SCarson Labrado << "_\" from request URI"; 47746a81465SCarson Labrado messages::internalError(asyncResp->res); 47846a81465SCarson Labrado return; 47946a81465SCarson Labrado } 48046a81465SCarson Labrado targetURI.erase(pos, prefix.size() + 1); 48146a81465SCarson Labrado 48246a81465SCarson Labrado std::function<void(crow::Response&)> cb = 483*1c0bb5c6SCarson Labrado std::bind_front(processResponse, prefix, asyncResp); 48446a81465SCarson Labrado 48546a81465SCarson Labrado std::string data = thisReq.req.body(); 48646a81465SCarson Labrado crow::HttpClient::getInstance().sendDataWithCallback( 48746a81465SCarson Labrado data, id, std::string(sat->second.host()), 48846a81465SCarson Labrado sat->second.port_number(), targetURI, thisReq.fields, 48946a81465SCarson Labrado thisReq.method(), retryPolicyName, cb); 49046a81465SCarson Labrado } 49146a81465SCarson Labrado 49246a81465SCarson Labrado // Processes the response returned by a satellite BMC and loads its 49346a81465SCarson Labrado // contents into asyncResp 49446a81465SCarson Labrado static void 495*1c0bb5c6SCarson Labrado processResponse(std::string_view prefix, 496*1c0bb5c6SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 49746a81465SCarson Labrado crow::Response& resp) 49846a81465SCarson Labrado { 49946a81465SCarson Labrado // No processing needed if the request wasn't successful 50046a81465SCarson Labrado if (resp.resultInt() != 200) 50146a81465SCarson Labrado { 50246a81465SCarson Labrado BMCWEB_LOG_DEBUG << "No need to parse satellite response"; 50346a81465SCarson Labrado asyncResp->res.stringResponse = std::move(resp.stringResponse); 50446a81465SCarson Labrado return; 50546a81465SCarson Labrado } 50646a81465SCarson Labrado 50746a81465SCarson Labrado // The resp will not have a json component 50846a81465SCarson Labrado // We need to create a json from resp's stringResponse 50946a81465SCarson Labrado if (resp.getHeaderValue("Content-Type") == "application/json") 51046a81465SCarson Labrado { 51146a81465SCarson Labrado nlohmann::json jsonVal = 51246a81465SCarson Labrado nlohmann::json::parse(resp.body(), nullptr, false); 51346a81465SCarson Labrado if (jsonVal.is_discarded()) 51446a81465SCarson Labrado { 51546a81465SCarson Labrado BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; 51646a81465SCarson Labrado messages::operationFailed(asyncResp->res); 51746a81465SCarson Labrado return; 51846a81465SCarson Labrado } 51946a81465SCarson Labrado 52046a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; 52146a81465SCarson Labrado 52246a81465SCarson Labrado // TODO: For collections we want to add the satellite responses to 52346a81465SCarson Labrado // our response rather than just straight overwriting them if our 52446a81465SCarson Labrado // local handling was successful (i.e. would return a 200). 52546a81465SCarson Labrado 526*1c0bb5c6SCarson Labrado addPrefixes(jsonVal, prefix); 527*1c0bb5c6SCarson Labrado 528*1c0bb5c6SCarson Labrado BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; 529*1c0bb5c6SCarson Labrado 53046a81465SCarson Labrado asyncResp->res.stringResponse.emplace( 53146a81465SCarson Labrado boost::beast::http::response< 53246a81465SCarson Labrado boost::beast::http::string_body>{}); 53346a81465SCarson Labrado asyncResp->res.result(resp.result()); 53446a81465SCarson Labrado asyncResp->res.jsonValue = std::move(jsonVal); 53546a81465SCarson Labrado 53646a81465SCarson Labrado BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; 53746a81465SCarson Labrado } 53846a81465SCarson Labrado else 53946a81465SCarson Labrado { 54046a81465SCarson Labrado if (!resp.body().empty()) 54146a81465SCarson Labrado { 54246a81465SCarson Labrado // We received a 200 response without the correct Content-Type 54346a81465SCarson Labrado // so return an Operation Failed error 54446a81465SCarson Labrado BMCWEB_LOG_ERROR 54546a81465SCarson Labrado << "Satellite response must be of type \"application/json\""; 54646a81465SCarson Labrado messages::operationFailed(asyncResp->res); 54746a81465SCarson Labrado } 54846a81465SCarson Labrado } 54946a81465SCarson Labrado } 55046a81465SCarson Labrado 5517fb33566SCarson Labrado public: 5527fb33566SCarson Labrado RedfishAggregator(const RedfishAggregator&) = delete; 5537fb33566SCarson Labrado RedfishAggregator& operator=(const RedfishAggregator&) = delete; 5547fb33566SCarson Labrado RedfishAggregator(RedfishAggregator&&) = delete; 5557fb33566SCarson Labrado RedfishAggregator& operator=(RedfishAggregator&&) = delete; 5567fb33566SCarson Labrado ~RedfishAggregator() = default; 5577fb33566SCarson Labrado 5587fb33566SCarson Labrado static RedfishAggregator& getInstance() 5597fb33566SCarson Labrado { 5607fb33566SCarson Labrado static RedfishAggregator handler; 5617fb33566SCarson Labrado return handler; 5627fb33566SCarson Labrado } 56305916cefSCarson Labrado 56405916cefSCarson Labrado // Entry point to Redfish Aggregation 56505916cefSCarson Labrado // Returns Result stating whether or not we still need to locally handle the 56605916cefSCarson Labrado // request 56705916cefSCarson Labrado static Result 56805916cefSCarson Labrado beginAggregation(const crow::Request& thisReq, 56905916cefSCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 57005916cefSCarson Labrado { 57105916cefSCarson Labrado using crow::utility::OrMorePaths; 57205916cefSCarson Labrado using crow::utility::readUrlSegments; 57305916cefSCarson Labrado const boost::urls::url_view& url = thisReq.urlView; 57405916cefSCarson Labrado // UpdateService is the only top level resource that is not a Collection 57505916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 57605916cefSCarson Labrado { 57705916cefSCarson Labrado return Result::LocalHandle; 57805916cefSCarson Labrado } 57946a81465SCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService", 58046a81465SCarson Labrado "SoftwareInventory") || 58146a81465SCarson Labrado readUrlSegments(url, "redfish", "v1", "UpdateService", 58246a81465SCarson Labrado "FirmwareInventory")) 58346a81465SCarson Labrado { 58446a81465SCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 58546a81465SCarson Labrado return Result::LocalHandle; 58646a81465SCarson Labrado } 58705916cefSCarson Labrado 58805916cefSCarson Labrado // Is the request for a resource collection?: 58905916cefSCarson Labrado // /redfish/v1/<resource> 59005916cefSCarson Labrado // e.g. /redfish/v1/Chassis 59105916cefSCarson Labrado std::string collectionName; 59205916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 59305916cefSCarson Labrado { 59446a81465SCarson Labrado startAggregation(AggregationType::Collection, thisReq, asyncResp); 59505916cefSCarson Labrado return Result::LocalHandle; 59605916cefSCarson Labrado } 59705916cefSCarson Labrado 59805916cefSCarson Labrado // We know that the ID of an aggregated resource will begin with 59905916cefSCarson Labrado // "5B247A". For the most part the URI will begin like this: 60005916cefSCarson Labrado // /redfish/v1/<resource>/<resource ID> 60105916cefSCarson Labrado // Note, FirmwareInventory and SoftwareInventory are "special" because 60205916cefSCarson Labrado // they are two levels deep, but still need aggregated 60305916cefSCarson Labrado // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 60405916cefSCarson Labrado // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 60505916cefSCarson Labrado std::string memberName; 60605916cefSCarson Labrado if (readUrlSegments(url, "redfish", "v1", "UpdateService", 60705916cefSCarson Labrado "SoftwareInventory", std::ref(memberName), 60805916cefSCarson Labrado OrMorePaths()) || 60905916cefSCarson Labrado readUrlSegments(url, "redfish", "v1", "UpdateService", 61005916cefSCarson Labrado "FirmwareInventory", std::ref(memberName), 61105916cefSCarson Labrado OrMorePaths()) || 61205916cefSCarson Labrado readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 61305916cefSCarson Labrado std::ref(memberName), OrMorePaths())) 61405916cefSCarson Labrado { 61505916cefSCarson Labrado if (memberName.starts_with("5B247A")) 61605916cefSCarson Labrado { 61705916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Need to forward a request"; 61805916cefSCarson Labrado 61946a81465SCarson Labrado // Extract the prefix from the request's URI, retrieve the 62046a81465SCarson Labrado // associated satellite config information, and then forward the 62146a81465SCarson Labrado // request to that satellite. 62246a81465SCarson Labrado startAggregation(AggregationType::Resource, thisReq, asyncResp); 62305916cefSCarson Labrado return Result::NoLocalHandle; 62405916cefSCarson Labrado } 62505916cefSCarson Labrado return Result::LocalHandle; 62605916cefSCarson Labrado } 62705916cefSCarson Labrado 62805916cefSCarson Labrado BMCWEB_LOG_DEBUG << "Aggregation not required"; 62905916cefSCarson Labrado return Result::LocalHandle; 63005916cefSCarson Labrado } 6317fb33566SCarson Labrado }; 6327fb33566SCarson Labrado 6337fb33566SCarson Labrado } // namespace redfish 634