xref: /openbmc/bmcweb/redfish-core/lib/assembly.hpp (revision 5ecf7b4e1bb6988dd3403fa80b723afbe2be30ae)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "dbus_singleton.hpp"
8 #include "dbus_utility.hpp"
9 #include "error_messages.hpp"
10 #include "generated/enums/resource.hpp"
11 #include "http_request.hpp"
12 #include "http_response.hpp"
13 #include "logging.hpp"
14 #include "query.hpp"
15 #include "registries/privilege_registry.hpp"
16 #include "utils/assembly_utils.hpp"
17 #include "utils/asset_utils.hpp"
18 #include "utils/chassis_utils.hpp"
19 #include "utils/dbus_utils.hpp"
20 
21 #include <boost/beast/http/verb.hpp>
22 #include <boost/system/error_code.hpp>
23 #include <boost/url/format.hpp>
24 #include <nlohmann/json.hpp>
25 #include <sdbusplus/asio/property.hpp>
26 #include <sdbusplus/unpack_properties.hpp>
27 
28 #include <cstddef>
29 #include <functional>
30 #include <memory>
31 #include <ranges>
32 #include <string>
33 #include <string_view>
34 #include <vector>
35 
36 namespace redfish
37 {
38 
39 /**
40  * @brief Get Location code for the given assembly.
41  * @param[in] asyncResp - Shared pointer for asynchronous calls.
42  * @param[in] serviceName - Service in which the assembly is hosted.
43  * @param[in] assembly - Assembly object.
44  * @param[in] assemblyJsonPtr - json-keyname on the assembly list output.
45  * @return None.
46  */
47 inline void getAssemblyLocationCode(
48     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
49     const std::string& serviceName, const std::string& assembly,
50     const nlohmann::json::json_pointer& assemblyJsonPtr)
51 {
52     sdbusplus::asio::getProperty<std::string>(
53         *crow::connections::systemBus, serviceName, assembly,
54         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
55         [asyncResp, assembly, assemblyJsonPtr](
56             const boost::system::error_code& ec, const std::string& value) {
57             if (ec)
58             {
59                 if (ec.value() != EBADR)
60                 {
61                     BMCWEB_LOG_ERROR("DBUS response error: {} for assembly {}",
62                                      ec.value(), assembly);
63                     messages::internalError(asyncResp->res);
64                 }
65                 return;
66             }
67 
68             asyncResp->res.jsonValue[assemblyJsonPtr]["Location"]
69                                     ["PartLocation"]["ServiceLabel"] = value;
70         });
71 }
72 
73 inline void getAssemblyState(
74     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
75     const auto& serviceName, const auto& assembly,
76     const nlohmann::json::json_pointer& assemblyJsonPtr)
77 {
78     asyncResp->res.jsonValue[assemblyJsonPtr]["Status"]["State"] =
79         resource::State::Enabled;
80 
81     dbus::utility::getProperty<bool>(
82         serviceName, assembly, "xyz.openbmc_project.Inventory.Item", "Present",
83         [asyncResp, assemblyJsonPtr,
84          assembly](const boost::system::error_code& ec, const bool value) {
85             if (ec)
86             {
87                 if (ec.value() != EBADR)
88                 {
89                     BMCWEB_LOG_ERROR("DBUS response error: {}", ec.value());
90                     messages::internalError(asyncResp->res);
91                 }
92                 return;
93             }
94 
95             if (!value)
96             {
97                 asyncResp->res.jsonValue[assemblyJsonPtr]["Status"]["State"] =
98                     resource::State::Absent;
99             }
100         });
101 }
102 
103 void getAssemblyHealth(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
104                        const auto& serviceName, const auto& assembly,
105                        const nlohmann::json::json_pointer& assemblyJsonPtr)
106 {
107     asyncResp->res.jsonValue[assemblyJsonPtr]["Status"]["Health"] =
108         resource::Health::OK;
109 
110     dbus::utility::getProperty<bool>(
111         serviceName, assembly,
112         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
113         [asyncResp, assemblyJsonPtr](const boost::system::error_code& ec,
114                                      bool functional) {
115             if (ec)
116             {
117                 if (ec.value() != EBADR)
118                 {
119                     BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
120                     messages::internalError(asyncResp->res);
121                 }
122                 return;
123             }
124 
125             if (!functional)
126             {
127                 asyncResp->res.jsonValue[assemblyJsonPtr]["Status"]["Health"] =
128                     resource::Health::Critical;
129             }
130         });
131 }
132 
133 inline void afterGetDbusObject(
134     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
135     const std::string& assembly,
136     const nlohmann::json::json_pointer& assemblyJsonPtr,
137     const boost::system::error_code& ec,
138     const dbus::utility::MapperGetObject& object)
139 {
140     if (ec)
141     {
142         BMCWEB_LOG_ERROR("DBUS response error : {} for assembly {}", ec.value(),
143                          assembly);
144         messages::internalError(asyncResp->res);
145         return;
146     }
147 
148     for (const auto& [serviceName, interfaceList] : object)
149     {
150         for (const auto& interface : interfaceList)
151         {
152             if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
153             {
154                 asset_utils::getAssetInfo(asyncResp, serviceName, assembly,
155                                           assemblyJsonPtr, true, false);
156             }
157             else if (interface ==
158                      "xyz.openbmc_project.Inventory.Decorator.LocationCode")
159             {
160                 getAssemblyLocationCode(asyncResp, serviceName, assembly,
161                                         assemblyJsonPtr);
162             }
163             else if (interface == "xyz.openbmc_project.Inventory.Item")
164             {
165                 getAssemblyState(asyncResp, serviceName, assembly,
166                                  assemblyJsonPtr);
167             }
168             else if (interface ==
169                      "xyz.openbmc_project.State.Decorator.OperationalStatus")
170             {
171                 getAssemblyHealth(asyncResp, serviceName, assembly,
172                                   assemblyJsonPtr);
173             }
174         }
175     }
176 }
177 
178 /**
179  * @brief Get properties for the assemblies associated to given chassis
180  * @param[in] asyncResp - Shared pointer for asynchronous calls.
181  * @param[in] chassisId - Chassis the assemblies are associated with.
182  * @param[in] assemblies - list of all the assemblies associated with the
183  * chassis.
184  * @return None.
185  */
186 inline void getAssemblyProperties(
187     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
188     const std::string& chassisId, const std::vector<std::string>& assemblies)
189 {
190     BMCWEB_LOG_DEBUG("Get properties for assembly associated");
191 
192     std::size_t assemblyIndex = 0;
193     for (const std::string& assembly : assemblies)
194     {
195         nlohmann::json::object_t item;
196         item["@odata.type"] = "#Assembly.v1_5_1.AssemblyData";
197         item["@odata.id"] = boost::urls::format(
198             "/redfish/v1/Chassis/{}/Assembly#/Assemblies/{}", chassisId,
199             std::to_string(assemblyIndex));
200         item["MemberId"] = std::to_string(assemblyIndex);
201         item["Name"] = sdbusplus::message::object_path(assembly).filename();
202 
203         asyncResp->res.jsonValue["Assemblies"].emplace_back(item);
204 
205         nlohmann::json::json_pointer assemblyJsonPtr(
206             "/Assemblies/" + std::to_string(assemblyIndex));
207 
208         dbus::utility::getDbusObject(
209             assembly, assemblyInterfaces,
210             std::bind_front(afterGetDbusObject, asyncResp, assembly,
211                             assemblyJsonPtr));
212 
213         nlohmann::json& assemblyArray = asyncResp->res.jsonValue["Assemblies"];
214         asyncResp->res.jsonValue["Assemblies@odata.count"] =
215             assemblyArray.size();
216 
217         assemblyIndex++;
218     }
219 }
220 
221 inline void afterHandleChassisAssemblyGet(
222     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
223     const std::string& chassisID, const boost::system::error_code& ec,
224     const std::vector<std::string>& assemblyList)
225 {
226     if (ec)
227     {
228         BMCWEB_LOG_WARNING("Chassis {} not found", chassisID);
229         messages::resourceNotFound(asyncResp->res, "Chassis", chassisID);
230         return;
231     }
232 
233     asyncResp->res.addHeader(
234         boost::beast::http::field::link,
235         "</redfish/v1/JsonSchemas/Assembly/Assembly.json>; rel=describedby");
236 
237     asyncResp->res.jsonValue["@odata.type"] = "#Assembly.v1_5_1.Assembly";
238     asyncResp->res.jsonValue["@odata.id"] =
239         boost::urls::format("/redfish/v1/Chassis/{}/Assembly", chassisID);
240     asyncResp->res.jsonValue["Name"] = "Assembly Collection";
241     asyncResp->res.jsonValue["Id"] = "Assembly";
242 
243     asyncResp->res.jsonValue["Assemblies"] = nlohmann::json::array();
244     asyncResp->res.jsonValue["Assemblies@odata.count"] = 0;
245 
246     if (!assemblyList.empty())
247     {
248         getAssemblyProperties(asyncResp, chassisID, assemblyList);
249     }
250 }
251 
252 /**
253  * @param[in] asyncResp - Shared pointer for asynchronous calls.
254  * @param[in] chassisID - Chassis to which the assemblies are
255  * associated.
256  *
257  * @return None.
258  */
259 inline void handleChassisAssemblyGet(
260     App& app, const crow::Request& req,
261     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262     const std::string& chassisID)
263 {
264     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
265     {
266         return;
267     }
268 
269     BMCWEB_LOG_DEBUG("Get chassis Assembly");
270     assembly_utils::getChassisAssembly(
271         asyncResp, chassisID,
272         std::bind_front(afterHandleChassisAssemblyGet, asyncResp, chassisID));
273 }
274 
275 inline void handleChassisAssemblyHead(
276     crow::App& app, const crow::Request& req,
277     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
278     const std::string& chassisID)
279 {
280     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
281     {
282         return;
283     }
284 
285     assembly_utils::getChassisAssembly(
286         asyncResp, chassisID,
287         [asyncResp,
288          chassisID](const boost::system::error_code& ec,
289                     const std::vector<std::string>& /*assemblyList*/) {
290             if (ec)
291             {
292                 BMCWEB_LOG_WARNING("Chassis {} not found", chassisID);
293                 messages::resourceNotFound(asyncResp->res, "Chassis",
294                                            chassisID);
295                 return;
296             }
297             asyncResp->res.addHeader(
298                 boost::beast::http::field::link,
299                 "</redfish/v1/JsonSchemas/Assembly.json>; rel=describedby");
300         });
301 }
302 
303 inline void requestRoutesAssembly(App& app)
304 {
305     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Assembly/")
306         .privileges(redfish::privileges::headAssembly)
307         .methods(boost::beast::http::verb::head)(
308             std::bind_front(handleChassisAssemblyHead, std::ref(app)));
309 
310     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Assembly/")
311         .privileges(redfish::privileges::getAssembly)
312         .methods(boost::beast::http::verb::get)(
313             std::bind_front(handleChassisAssemblyGet, std::ref(app)));
314 }
315 
316 } // namespace redfish
317