1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "bmcweb_config.h" 6 7 #include "async_resp.hpp" 8 #include "dbus_singleton.hpp" 9 #include "dbus_utility.hpp" 10 #include "error_messages.hpp" 11 #include "human_sort.hpp" 12 #include "logging.hpp" 13 #include "utility.hpp" 14 15 #include <boost/url/format.hpp> 16 #include <boost/url/url.hpp> 17 #include <sdbusplus/message/native_types.hpp> 18 19 #include <algorithm> 20 #include <array> 21 #include <cstdint> 22 #include <functional> 23 #include <memory> 24 #include <string> 25 #include <string_view> 26 #include <utility> 27 #include <vector> 28 29 namespace redfish 30 { 31 32 inline void handleSystemCollectionMembers( 33 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 34 const boost::system::error_code& ec, 35 const dbus::utility::MapperGetSubTreePathsResponse& objects) 36 { 37 if (ec == boost::system::errc::io_error) 38 { 39 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 40 asyncResp->res.jsonValue["Members@odata.count"] = 0; 41 return; 42 } 43 44 if (ec) 45 { 46 BMCWEB_LOG_ERROR("DBUS response error {}", ec.value()); 47 messages::internalError(asyncResp->res); 48 return; 49 } 50 51 nlohmann::json& membersArray = asyncResp->res.jsonValue["Members"]; 52 membersArray = nlohmann::json::array(); 53 54 // consider an empty result as single-host, since single-host systems 55 // do not populate the ManagedHost dbus interface 56 if (objects.empty()) 57 { 58 asyncResp->res.jsonValue["Members@odata.count"] = 1; 59 nlohmann::json::object_t system; 60 system["@odata.id"] = boost::urls::format( 61 "/redfish/v1/Systems/{}", BMCWEB_REDFISH_SYSTEM_URI_NAME); 62 membersArray.emplace_back(std::move(system)); 63 64 if constexpr (BMCWEB_HYPERVISOR_COMPUTER_SYSTEM) 65 { 66 BMCWEB_LOG_DEBUG("Hypervisor is available"); 67 asyncResp->res.jsonValue["Members@odata.count"] = 2; 68 69 nlohmann::json::object_t hypervisor; 70 hypervisor["@odata.id"] = "/redfish/v1/Systems/hypervisor"; 71 membersArray.emplace_back(std::move(hypervisor)); 72 } 73 74 return; 75 } 76 77 std::vector<std::string> pathNames; 78 for (const auto& object : objects) 79 { 80 sdbusplus::message::object_path path(object); 81 std::string leaf = path.filename(); 82 if (leaf.empty()) 83 { 84 continue; 85 } 86 pathNames.emplace_back(leaf); 87 } 88 std::ranges::sort(pathNames, AlphanumLess<std::string>()); 89 90 for (const std::string& systemName : pathNames) 91 { 92 nlohmann::json::object_t member; 93 member["@odata.id"] = 94 boost::urls::format("/redfish/v1/Systems/{}", systemName); 95 membersArray.emplace_back(std::move(member)); 96 } 97 asyncResp->res.jsonValue["Members@odata.count"] = membersArray.size(); 98 } 99 100 /** 101 * @brief Populate the system collection members from a GetSubTreePaths search 102 * of the inventory based of the ManagedHost dbus interface 103 * 104 * @param[i] asyncResp Async response object 105 * 106 * @return None 107 */ 108 inline void getSystemCollectionMembers( 109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 110 { 111 constexpr std::array<std::string_view, 1> interfaces{ 112 "xyz.openbmc_project.Inventory.Decorator.ManagedHost", 113 }; 114 115 BMCWEB_LOG_DEBUG("Get system collection members for /redfish/v1/Systems"); 116 117 dbus::utility::getSubTreePaths( 118 "/xyz/openbmc_project/inventory", 0, interfaces, 119 std::bind_front(handleSystemCollectionMembers, asyncResp)); 120 } 121 122 inline void getManagedHostProperty( 123 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 124 const std::string& systemName, const std::string& systemPath, 125 std::function<void(const uint64_t computerSystemIndex)> callback) 126 { 127 dbus::utility::getProperty<uint64_t>( 128 "xyz.openbmc_project.EntityManager", systemPath, 129 "xyz.openbmc_project.Inventory.Decorator.ManagedHost", "HostIndex", 130 [asyncResp, systemName, systemPath, callback = std::move(callback)]( 131 const boost::system::error_code& ec, const uint64_t hostIndex) { 132 if (ec) 133 { 134 BMCWEB_LOG_WARNING("DBUS response error {}", ec); 135 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 136 systemName); 137 return; 138 } 139 BMCWEB_LOG_DEBUG("Got index {} for path {}", hostIndex, systemPath); 140 callback(hostIndex); 141 }); 142 } 143 144 inline void afterGetComputerSystemSubTreePaths( 145 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 146 const std::string& systemName, 147 std::function<void(const uint64_t computerSystemIndex)>& callback, 148 const boost::system::error_code& ec, 149 const dbus::utility::MapperGetSubTreePathsResponse& objects) 150 { 151 sdbusplus::message::object_path systemPath; 152 if (ec) 153 { 154 if (ec.value() == boost::system::errc::io_error) 155 { 156 BMCWEB_LOG_WARNING("EIO - System not found"); 157 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 158 systemName); 159 return; 160 } 161 162 BMCWEB_LOG_ERROR("DBus method call failed with error {}", ec.value()); 163 messages::internalError(asyncResp->res); 164 return; 165 } 166 167 const auto& found = std::ranges::find_if( 168 objects, [systemName](const sdbusplus::message::object_path& path) { 169 return path.filename() == systemName; 170 }); 171 172 if (found == objects.end()) 173 { 174 BMCWEB_LOG_WARNING("Failed to match systemName: {}", systemName); 175 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 176 systemName); 177 return; 178 } 179 180 systemPath = *found; 181 182 getManagedHostProperty(asyncResp, systemName, systemPath, 183 std::move(callback)); 184 } 185 186 /** 187 * @brief Retrieve the index associated with the requested system based on the 188 * ManagedHost interface 189 * 190 * @param[i] asyncResp Async response object 191 * @param[i] systemName The requested system 192 * @param[i] callback Callback to call once the index has been found 193 * 194 * @return None 195 */ 196 inline void getComputerSystemIndex( 197 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 198 const std::string& systemName, 199 std::function<void(const uint64_t computerSystemIndex)>&& callback) 200 { 201 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 202 { 203 constexpr std::array<std::string_view, 1> interfaces{ 204 "xyz.openbmc_project.Inventory.Decorator.ManagedHost"}; 205 dbus::utility::getSubTreePaths( 206 "/xyz/openbmc_project/inventory", 0, interfaces, 207 std::bind_front(afterGetComputerSystemSubTreePaths, asyncResp, 208 systemName, std::move(callback))); 209 } 210 else 211 { 212 // on single-host, fallback to index 0 213 BMCWEB_LOG_DEBUG( 214 "Single-host detected, fallback to computerSystemIndex 0"); 215 callback(0); 216 } 217 } 218 219 inline sdbusplus::message::object_path getHostStateObjectPath( 220 const uint64_t computerSystemIndex) 221 { 222 const sdbusplus::message::object_path hostStatePath( 223 "/xyz/openbmc_project/state/host" + 224 std::to_string(computerSystemIndex)); 225 226 return hostStatePath; 227 } 228 229 inline std::string getHostStateServiceName(const uint64_t computerSystemIndex) 230 { 231 std::string hostStateService = "xyz.openbmc_project.State.Host"; 232 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 233 { 234 hostStateService += std::to_string(computerSystemIndex); 235 } 236 237 return hostStateService; 238 } 239 240 inline sdbusplus::message::object_path getChassisStateObjectPath( 241 const uint64_t computerSystemIndex) 242 { 243 const sdbusplus::message::object_path chassisStatePath( 244 "/xyz/openbmc_project/state/chassis" + 245 std::to_string(computerSystemIndex)); 246 247 return chassisStatePath; 248 } 249 250 inline std::string getChassisStateServiceName( 251 const uint64_t computerSystemIndex) 252 { 253 std::string chassisStateService = "xyz.openbmc_project.State.Chassis"; 254 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 255 { 256 chassisStateService += std::to_string(computerSystemIndex); 257 } 258 259 return chassisStateService; 260 } 261 262 namespace systems_utils 263 { 264 265 inline void afterGetValidSystemsPath( 266 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 267 const std::string& systemId, 268 const std::function<void(const std::optional<std::string>&)>& callback, 269 const boost::system::error_code& ec, 270 const dbus::utility::MapperGetSubTreePathsResponse& systemsPaths) 271 { 272 if (ec) 273 { 274 if (ec == boost::system::errc::io_error) 275 { 276 BMCWEB_LOG_DEBUG("No systems found"); 277 callback(std::nullopt); 278 return; 279 } 280 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.value()); 281 messages::internalError(asyncResp->res); 282 return; 283 } 284 285 for (const std::string& system : systemsPaths) 286 { 287 sdbusplus::message::object_path path(system); 288 if (path.filename() == systemId) 289 { 290 callback(path); 291 return; 292 } 293 } 294 BMCWEB_LOG_DEBUG("No system named {} found", systemId); 295 callback(std::nullopt); 296 } 297 298 inline void getValidSystemsPath( 299 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 300 const std::string& systemId, 301 std::function<void(const std::optional<std::string>&)>&& callback) 302 { 303 BMCWEB_LOG_DEBUG("Get path for {}", systemId); 304 305 constexpr std::array<std::string_view, 2> interfaces = { 306 "xyz.openbmc_project.Inventory.Decorator.ManagedHost", 307 "xyz.openbmc_project.Inventory.Item.System"}; 308 dbus::utility::getSubTreePaths( 309 "/xyz/openbmc_project/inventory", 0, interfaces, 310 [asyncResp, systemId, callback{std::move(callback)}]( 311 const boost::system::error_code& ec, 312 const dbus::utility::MapperGetSubTreePathsResponse& systemsPaths) { 313 afterGetValidSystemsPath(asyncResp, systemId, callback, ec, 314 systemsPaths); 315 }); 316 } 317 318 } // namespace systems_utils 319 320 /** 321 * @brief Match computerSystemIndex with index contained by an object path 322 * i.e 1 in /xyz/openbmc/project/control/host1/policy/TPMEnable 323 * 324 * @param[i] asyncResp Shared pointer for generating response 325 * @param[i] computerSystemIndex The index to match against 326 * @param[i] subtree Mapper response object 327 * @param[o] objectPath Buffer for matched object path 328 * @param[o] service Buffer for service of matched object 329 * path 330 * 331 * @return true if match found, else false 332 */ 333 inline bool indexMatchingSubTreeMapObjectPath( 334 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 335 const uint64_t computerSystemIndex, 336 const dbus::utility::MapperGetSubTreeResponse& subtree, 337 std::string& objectPath, std::string& service) 338 { 339 const std::string host = std::format("host{}", computerSystemIndex); 340 341 for (const auto& obj : subtree) 342 { 343 std::string tmp = host; 344 const sdbusplus::message::object_path path{obj.first}; 345 const std::string serv = obj.second.begin()->first; 346 347 if (path.str.empty() || obj.second.size() != 1) 348 { 349 BMCWEB_LOG_DEBUG("Error finding index in object path"); 350 messages::internalError(asyncResp->res); 351 return false; 352 } 353 354 objectPath = path; 355 service = serv; 356 357 if (path.filename() == host) 358 { 359 return true; 360 } 361 362 tmp.insert(0, 1, '/'); 363 tmp.append("/"); 364 if (path.str.find(host) != std::string::npos) 365 { 366 return true; 367 } 368 } 369 370 return false; 371 } 372 } // namespace redfish 373