1 #pragma once 2 3 #include <http_client.hpp> 4 5 namespace redfish 6 { 7 8 enum class Result 9 { 10 LocalHandle, 11 NoLocalHandle 12 }; 13 14 // Checks if the provided path is related to one of the resource collections 15 // under UpdateService 16 static inline bool 17 isUpdateServiceCollection(const std::vector<std::string>& segments) 18 { 19 if (segments.size() < 4) 20 { 21 return false; 22 } 23 24 return (segments[2] == "UpdateService") && 25 ((segments[3] == "FirmwareInventory") || 26 (segments[3] == "SoftwareInventory")); 27 } 28 29 class RedfishAggregator 30 { 31 private: 32 const std::string retryPolicyName = "RedfishAggregation"; 33 const uint32_t retryAttempts = 5; 34 const uint32_t retryTimeoutInterval = 0; 35 36 RedfishAggregator() 37 { 38 getSatelliteConfigs(constructorCallback); 39 40 // Setup the retry policy to be used by Redfish Aggregation 41 crow::HttpClient::getInstance().setRetryConfig( 42 retryAttempts, retryTimeoutInterval, aggregationRetryHandler, 43 retryPolicyName); 44 } 45 46 static inline boost::system::error_code 47 aggregationRetryHandler(unsigned int respCode) 48 { 49 // As a default, assume 200X is alright. 50 // We don't need to retry on a 404 51 if ((respCode < 200) || ((respCode >= 300) && (respCode != 404))) 52 { 53 return boost::system::errc::make_error_code( 54 boost::system::errc::result_out_of_range); 55 } 56 57 // Return 0 if the response code is valid 58 return boost::system::errc::make_error_code( 59 boost::system::errc::success); 60 } 61 62 // Dummy callback used by the Constructor so that it can report the number 63 // of satellite configs when the class is first created 64 static void constructorCallback( 65 const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 66 { 67 BMCWEB_LOG_DEBUG << "There were " 68 << std::to_string(satelliteInfo.size()) 69 << " satellite configs found at startup"; 70 } 71 72 // Polls D-Bus to get all available satellite config information 73 // Expects a handler which interacts with the returned configs 74 static void getSatelliteConfigs( 75 const std::function<void( 76 const std::unordered_map<std::string, boost::urls::url>&)>& handler) 77 { 78 BMCWEB_LOG_DEBUG << "Gathering satellite configs"; 79 crow::connections::systemBus->async_method_call( 80 [handler](const boost::system::error_code ec, 81 const dbus::utility::ManagedObjectType& objects) { 82 if (ec) 83 { 84 BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " 85 << ec.message(); 86 return; 87 } 88 89 // Maps a chosen alias representing a satellite BMC to a url 90 // containing the information required to create a http 91 // connection to the satellite 92 std::unordered_map<std::string, boost::urls::url> satelliteInfo; 93 94 findSatelliteConfigs(objects, satelliteInfo); 95 96 if (!satelliteInfo.empty()) 97 { 98 BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " 99 << std::to_string(satelliteInfo.size()) 100 << " satellite BMCs"; 101 } 102 else 103 { 104 BMCWEB_LOG_DEBUG 105 << "No satellite BMCs detected. Redfish Aggregation not enabled"; 106 } 107 handler(satelliteInfo); 108 }, 109 "xyz.openbmc_project.EntityManager", "/", 110 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 111 } 112 113 // Search D-Bus objects for satellite config objects and add their 114 // information if valid 115 static void findSatelliteConfigs( 116 const dbus::utility::ManagedObjectType& objects, 117 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 118 { 119 for (const auto& objectPath : objects) 120 { 121 for (const auto& interface : objectPath.second) 122 { 123 if (interface.first == 124 "xyz.openbmc_project.Configuration.SatelliteController") 125 { 126 BMCWEB_LOG_DEBUG << "Found Satellite Controller at " 127 << objectPath.first.str; 128 129 if (!satelliteInfo.empty()) 130 { 131 BMCWEB_LOG_ERROR 132 << "Redfish Aggregation only supports one satellite!"; 133 BMCWEB_LOG_DEBUG << "Clearing all satellite data"; 134 satelliteInfo.clear(); 135 return; 136 } 137 138 // For now assume there will only be one satellite config. 139 // Assign it the name/prefix "5B247A" 140 addSatelliteConfig("5B247A", interface.second, 141 satelliteInfo); 142 } 143 } 144 } 145 } 146 147 // Parse the properties of a satellite config object and add the 148 // configuration if the properties are valid 149 static void addSatelliteConfig( 150 const std::string& name, 151 const dbus::utility::DBusPropertiesMap& properties, 152 std::unordered_map<std::string, boost::urls::url>& satelliteInfo) 153 { 154 boost::urls::url url; 155 156 for (const auto& prop : properties) 157 { 158 if (prop.first == "Hostname") 159 { 160 const std::string* propVal = 161 std::get_if<std::string>(&prop.second); 162 if (propVal == nullptr) 163 { 164 BMCWEB_LOG_ERROR << "Invalid Hostname value"; 165 return; 166 } 167 url.set_host(*propVal); 168 } 169 170 else if (prop.first == "Port") 171 { 172 const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); 173 if (propVal == nullptr) 174 { 175 BMCWEB_LOG_ERROR << "Invalid Port value"; 176 return; 177 } 178 179 if (*propVal > std::numeric_limits<uint16_t>::max()) 180 { 181 BMCWEB_LOG_ERROR << "Port value out of range"; 182 return; 183 } 184 url.set_port(static_cast<uint16_t>(*propVal)); 185 } 186 187 else if (prop.first == "AuthType") 188 { 189 const std::string* propVal = 190 std::get_if<std::string>(&prop.second); 191 if (propVal == nullptr) 192 { 193 BMCWEB_LOG_ERROR << "Invalid AuthType value"; 194 return; 195 } 196 197 // For now assume authentication not required to communicate 198 // with the satellite BMC 199 if (*propVal != "None") 200 { 201 BMCWEB_LOG_ERROR 202 << "Unsupported AuthType value: " << *propVal 203 << ", only \"none\" is supported"; 204 return; 205 } 206 url.set_scheme("http"); 207 } 208 } // Finished reading properties 209 210 // Make sure all required config information was made available 211 if (url.host().empty()) 212 { 213 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; 214 return; 215 } 216 217 if (!url.has_port()) 218 { 219 BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; 220 return; 221 } 222 223 if (!url.has_scheme()) 224 { 225 BMCWEB_LOG_ERROR << "Satellite config " << name 226 << " missing AuthType"; 227 return; 228 } 229 230 std::string resultString; 231 auto result = satelliteInfo.insert_or_assign(name, std::move(url)); 232 if (result.second) 233 { 234 resultString = "Added new satellite config "; 235 } 236 else 237 { 238 resultString = "Updated existing satellite config "; 239 } 240 241 BMCWEB_LOG_DEBUG << resultString << name << " at " 242 << result.first->second.scheme() << "://" 243 << result.first->second.encoded_host_and_port(); 244 } 245 246 public: 247 RedfishAggregator(const RedfishAggregator&) = delete; 248 RedfishAggregator& operator=(const RedfishAggregator&) = delete; 249 RedfishAggregator(RedfishAggregator&&) = delete; 250 RedfishAggregator& operator=(RedfishAggregator&&) = delete; 251 ~RedfishAggregator() = default; 252 253 static RedfishAggregator& getInstance() 254 { 255 static RedfishAggregator handler; 256 return handler; 257 } 258 259 // Entry point to Redfish Aggregation 260 // Returns Result stating whether or not we still need to locally handle the 261 // request 262 static Result 263 beginAggregation(const crow::Request& thisReq, 264 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 265 { 266 using crow::utility::OrMorePaths; 267 using crow::utility::readUrlSegments; 268 const boost::urls::url_view& url = thisReq.urlView; 269 // UpdateService is the only top level resource that is not a Collection 270 if (readUrlSegments(url, "redfish", "v1", "UpdateService")) 271 { 272 return Result::LocalHandle; 273 } 274 275 // Is the request for a resource collection?: 276 // /redfish/v1/<resource> 277 // e.g. /redfish/v1/Chassis 278 std::string collectionName; 279 if (readUrlSegments(url, "redfish", "v1", std::ref(collectionName))) 280 { 281 return Result::LocalHandle; 282 } 283 284 // We know that the ID of an aggregated resource will begin with 285 // "5B247A". For the most part the URI will begin like this: 286 // /redfish/v1/<resource>/<resource ID> 287 // Note, FirmwareInventory and SoftwareInventory are "special" because 288 // they are two levels deep, but still need aggregated 289 // /redfish/v1/UpdateService/FirmwareInventory/<FirmwareInventory ID> 290 // /redfish/v1/UpdateService/SoftwareInventory/<SoftwareInventory ID> 291 std::string memberName; 292 if (readUrlSegments(url, "redfish", "v1", "UpdateService", 293 "SoftwareInventory", std::ref(memberName), 294 OrMorePaths()) || 295 readUrlSegments(url, "redfish", "v1", "UpdateService", 296 "FirmwareInventory", std::ref(memberName), 297 OrMorePaths()) || 298 readUrlSegments(url, "redfish", "v1", std::ref(collectionName), 299 std::ref(memberName), OrMorePaths())) 300 { 301 if (memberName.starts_with("5B247A")) 302 { 303 BMCWEB_LOG_DEBUG << "Need to forward a request"; 304 305 // TODO: Extract the prefix from the request's URI, retrieve 306 // the associated satellite config information, and then 307 // forward the request to that satellite. 308 redfish::messages::internalError(asyncResp->res); 309 return Result::NoLocalHandle; 310 } 311 return Result::LocalHandle; 312 } 313 314 BMCWEB_LOG_DEBUG << "Aggregation not required"; 315 return Result::LocalHandle; 316 } 317 }; 318 319 } // namespace redfish 320