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
29 namespace systems_utils
30 {
31
handleSystemCollectionMembers(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & objects)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 */
getSystemCollectionMembers(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)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
getManagedHostProperty(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & systemPath,std::function<void (const uint64_t computerSystemIndex)> callback)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
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)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 */
getComputerSystemIndex(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,std::function<void (const uint64_t computerSystemIndex)> && callback)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
getHostStateObjectPath(const uint64_t computerSystemIndex)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
getHostStateServiceName(const uint64_t computerSystemIndex)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
getChassisStateObjectPath(const uint64_t computerSystemIndex)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
getChassisStateServiceName(const uint64_t computerSystemIndex)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
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 /**
316 * @brief Match computerSystemIndex with index contained by an object path
317 * i.e 1 in /xyz/openbmc/project/control/host1/policy/TPMEnable
318 *
319 * @param[i] asyncResp Shared pointer for generating response
320 * @param[i] computerSystemIndex The index to match against
321 * @param[i] subtree Mapper response object
322 * @param[o] objectPath Buffer for matched object path
323 * @param[o] service Buffer for service of matched object
324 * path
325 *
326 * @return true if match found, else false
327 */
indexMatchingSubTreeMapObjectPath(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const uint64_t computerSystemIndex,const dbus::utility::MapperGetSubTreeResponse & subtree,std::string & objectPath,std::string & service)328 inline bool indexMatchingSubTreeMapObjectPath(
329 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
330 const uint64_t computerSystemIndex,
331 const dbus::utility::MapperGetSubTreeResponse& subtree,
332 std::string& objectPath, std::string& service)
333 {
334 const std::string host = std::format("host{}", computerSystemIndex);
335
336 for (const auto& obj : subtree)
337 {
338 std::string tmp = host;
339 const sdbusplus::message::object_path path{obj.first};
340 const std::string serv = obj.second.begin()->first;
341
342 if (path.str.empty() || obj.second.size() != 1)
343 {
344 BMCWEB_LOG_DEBUG("Error finding index in object path");
345 messages::internalError(asyncResp->res);
346 return false;
347 }
348
349 objectPath = path;
350 service = serv;
351
352 if (path.filename() == host)
353 {
354 return true;
355 }
356
357 tmp.insert(0, 1, '/');
358 tmp.append("/");
359 if (path.str.find(host) != std::string::npos)
360 {
361 return true;
362 }
363 }
364
365 return false;
366 }
367 } // namespace systems_utils
368 } // namespace redfish
369