xref: /openbmc/bmcweb/features/redfish/include/utils/systems_utils.hpp (revision 5e7c1f31a0b00c1da2d8bc8b346e43035061f5e8)
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