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