xref: /openbmc/bmcweb/redfish-core/lib/fabric_ports.hpp (revision ae16a89913b06505551738bc0d33e3a1c188c12b)
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 "app.hpp"
8 #include "async_resp.hpp"
9 #include "dbus_singleton.hpp"
10 #include "dbus_utility.hpp"
11 #include "error_messages.hpp"
12 #include "generated/enums/resource.hpp"
13 #include "http_request.hpp"
14 #include "human_sort.hpp"
15 #include "logging.hpp"
16 #include "query.hpp"
17 #include "registries/privilege_registry.hpp"
18 
19 #include <asm-generic/errno.h>
20 
21 #include <boost/beast/http/field.hpp>
22 #include <boost/beast/http/verb.hpp>
23 #include <boost/system/error_code.hpp>
24 #include <boost/url/format.hpp>
25 #include <sdbusplus/asio/property.hpp>
26 
27 #include <algorithm>
28 #include <array>
29 #include <functional>
30 #include <memory>
31 #include <ranges>
32 #include <string>
33 #include <string_view>
34 #include <utility>
35 #include <vector>
36 
37 namespace redfish
38 {
39 static constexpr std::array<std::string_view, 1> fabricInterfaces{
40     "xyz.openbmc_project.Inventory.Item.FabricAdapter"};
41 static constexpr std::array<std::string_view, 1> portInterfaces{
42     "xyz.openbmc_project.Inventory.Connector.Port"};
43 
44 inline void afterGetFabricPortLocation(
45     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
46     const boost::system::error_code& ec, const std::string& value)
47 {
48     if (ec)
49     {
50         if (ec.value() != EBADR)
51         {
52             BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
53             messages::internalError(asyncResp->res);
54         }
55         return;
56     }
57     asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
58         value;
59 }
60 
61 inline void getFabricPortLocation(
62     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
63     const std::string& portPath, const std::string& serviceName)
64 {
65     dbus::utility::getProperty<std::string>(
66         serviceName, portPath,
67         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
68         std::bind_front(afterGetFabricPortLocation, asyncResp));
69 }
70 
71 inline void afterGetFabricPortState(
72     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
73     const boost::system::error_code& ec, bool present)
74 {
75     if (ec)
76     {
77         if (ec.value() != EBADR)
78         {
79             BMCWEB_LOG_ERROR("DBUS response error for State, ec {}",
80                              ec.value());
81             messages::internalError(asyncResp->res);
82         }
83         return;
84     }
85     if (!present)
86     {
87         asyncResp->res.jsonValue["Status"]["State"] = resource::State::Absent;
88     }
89 }
90 
91 inline void getFabricPortState(
92     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
93     const std::string& portPath, const std::string& serviceName)
94 {
95     asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
96     dbus::utility::getProperty<bool>(
97         serviceName, portPath, "xyz.openbmc_project.Inventory.Item", "Present",
98         std::bind_front(afterGetFabricPortState, asyncResp));
99 }
100 
101 inline void afterGetFabricPortHealth(
102     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
103     const boost::system::error_code& ec, bool functional)
104 {
105     if (ec)
106     {
107         if (ec.value() != EBADR)
108         {
109             BMCWEB_LOG_ERROR("DBUS response error for Health, ec {}",
110                              ec.value());
111             messages::internalError(asyncResp->res);
112         }
113         return;
114     }
115 
116     if (!functional)
117     {
118         asyncResp->res.jsonValue["Status"]["Health"] =
119             resource::Health::Critical;
120     }
121 }
122 
123 inline void getFabricPortHealth(
124     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
125     const std::string& portPath, const std::string& serviceName)
126 {
127     asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
128     dbus::utility::getProperty<bool>(
129         serviceName, portPath,
130         "xyz.openbmc_project.State.Decorator.OperationalStatus", "Functional",
131         std::bind_front(afterGetFabricPortHealth, asyncResp));
132 }
133 
134 inline void getFabricPortProperties(
135     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
136     const std::string& systemName, const std::string& adapterId,
137     const std::string& portId, const std::string& portPath,
138     const std::string& serviceName)
139 {
140     if (portPath.empty())
141     {
142         BMCWEB_LOG_WARNING("Port not found");
143         messages::resourceNotFound(asyncResp->res, "Port", portId);
144         return;
145     }
146 
147     asyncResp->res.addHeader(
148         boost::beast::http::field::link,
149         "</redfish/v1/JsonSchemas/Port/Port.json>; rel=describedby");
150 
151     asyncResp->res.jsonValue["@odata.type"] = "#Port.v1_11_0.Port";
152     asyncResp->res.jsonValue["@odata.id"] =
153         boost::urls::format("/redfish/v1/Systems/{}/FabricAdapters/{}/Ports/{}",
154                             systemName, adapterId, portId);
155     asyncResp->res.jsonValue["Id"] = portId;
156     asyncResp->res.jsonValue["Name"] = "Fabric Port";
157 
158     getFabricPortLocation(asyncResp, portPath, serviceName);
159     getFabricPortState(asyncResp, portPath, serviceName);
160     getFabricPortHealth(asyncResp, portPath, serviceName);
161 }
162 
163 inline void afterGetValidFabricPortPath(
164     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
165     const std::string& portId,
166     std::function<void(const std::string& portPath,
167                        const std::string& portServiceName)>& callback,
168     const boost::system::error_code& ec,
169     const dbus::utility::MapperGetSubTreePathsResponse& portSubTreePaths)
170 {
171     if (ec)
172     {
173         if (ec.value() != boost::system::errc::io_error)
174         {
175             BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
176             messages::internalError(asyncResp->res);
177             return;
178         }
179         // Port not found
180         callback(std::string(), std::string());
181         return;
182     }
183     const auto& it =
184         std::ranges::find_if(portSubTreePaths, [portId](const auto& portPath) {
185             return portId ==
186                    sdbusplus::message::object_path(portPath).filename();
187         });
188     if (it == portSubTreePaths.end())
189     {
190         // Port not found
191         callback(std::string(), std::string());
192         return;
193     }
194 
195     const std::string& portPath = *it;
196     dbus::utility::getDbusObject(
197         portPath, portInterfaces,
198         [asyncResp, portPath, callback{std::move(callback)}](
199             const boost::system::error_code& ec1,
200             const dbus::utility::MapperGetObject& object) {
201             if (ec1 || object.empty())
202             {
203                 BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
204                                  ec1.value());
205                 messages::internalError(asyncResp->res);
206                 return;
207             }
208             callback(portPath, object.begin()->first);
209         });
210 }
211 
212 inline void getValidFabricPortPath(
213     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
214     const std::string& adapterId, const std::string& portId,
215     std::function<void(const std::string& portPath,
216                        const std::string& portServiceName)>&& callback)
217 {
218     dbus::utility::getAssociatedSubTreePathsById(
219         adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
220         "connecting", portInterfaces,
221         std::bind_front(afterGetValidFabricPortPath, asyncResp, portId,
222                         std::move(callback)));
223 }
224 
225 inline void handleFabricPortHead(
226     crow::App& app, const crow::Request& req,
227     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
228     const std::string& systemName, const std::string& adapterId,
229     const std::string& portId)
230 {
231     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
232     {
233         return;
234     }
235     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
236     {
237         // Option currently returns no systems.  TBD
238         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
239                                    systemName);
240         return;
241     }
242     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
243     {
244         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
245                                    systemName);
246         return;
247     }
248 
249     getValidFabricPortPath(
250         asyncResp, adapterId, portId,
251         [asyncResp, portId](const std::string& portPath, const std::string&) {
252             if (portPath.empty())
253             {
254                 BMCWEB_LOG_WARNING("Port not found");
255                 messages::resourceNotFound(asyncResp->res, "Port", portId);
256                 return;
257             }
258             asyncResp->res.addHeader(
259                 boost::beast::http::field::link,
260                 "</redfish/v1/JsonSchemas/Port/Port.json>; rel=describedby");
261         });
262 }
263 
264 inline void handleFabricPortGet(
265     App& app, const crow::Request& req,
266     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
267     const std::string& systemName, const std::string& adapterId,
268     const std::string& portId)
269 {
270     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
271     {
272         return;
273     }
274     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
275     {
276         // Option currently returns no systems.  TBD
277         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
278                                    systemName);
279         return;
280     }
281     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
282     {
283         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
284                                    systemName);
285         return;
286     }
287     getValidFabricPortPath(asyncResp, adapterId, portId,
288                            std::bind_front(getFabricPortProperties, asyncResp,
289                                            systemName, adapterId, portId));
290 }
291 
292 inline void afterHandleFabricPortCollectionHead(
293     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
294     const std::string& adapterId, const boost::system::error_code& ec,
295     const dbus::utility::MapperGetSubTreePathsResponse& /* portSubTreePaths */)
296 {
297     if (ec)
298     {
299         if (ec.value() != boost::system::errc::io_error)
300         {
301             BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
302             messages::internalError(asyncResp->res);
303             return;
304         }
305         BMCWEB_LOG_WARNING("Adapter not found");
306         messages::resourceNotFound(asyncResp->res, "Adapter", adapterId);
307         return;
308     }
309     asyncResp->res.addHeader(
310         boost::beast::http::field::link,
311         "</redfish/v1/JsonSchemas/PortCollection/PortCollection.json>; rel=describedby");
312 }
313 
314 inline void handleFabricPortCollectionHead(
315     crow::App& app, const crow::Request& req,
316     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
317     const std::string& systemName, const std::string& adapterId)
318 {
319     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
320     {
321         return;
322     }
323     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
324     {
325         // Option currently returns no systems.  TBD
326         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
327                                    systemName);
328         return;
329     }
330     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
331     {
332         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
333                                    systemName);
334         return;
335     }
336 
337     dbus::utility::getAssociatedSubTreePathsById(
338         adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
339         "connecting", portInterfaces,
340         std::bind_front(afterHandleFabricPortCollectionHead, asyncResp,
341                         adapterId));
342 }
343 
344 inline void doHandleFabricPortCollectionGet(
345     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
346     const std::string& systemName, const std::string& adapterId,
347     const boost::system::error_code& ec,
348     const dbus::utility::MapperGetSubTreePathsResponse& portSubTreePaths)
349 {
350     if (ec)
351     {
352         if (ec.value() != boost::system::errc::io_error)
353         {
354             BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
355             messages::internalError(asyncResp->res);
356             return;
357         }
358         BMCWEB_LOG_WARNING("Adapter not found");
359         messages::resourceNotFound(asyncResp->res, "Adapter", adapterId);
360         return;
361     }
362     asyncResp->res.addHeader(
363         boost::beast::http::field::link,
364         "</redfish/v1/JsonSchemas/PortCollection/PortCollection.json>; rel=describedby");
365 
366     asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection";
367     asyncResp->res.jsonValue["Name"] = "Port Collection";
368     asyncResp->res.jsonValue["@odata.id"] =
369         boost::urls::format("/redfish/v1/Systems/{}/FabricAdapters/{}/Ports",
370                             systemName, adapterId);
371     asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
372 
373     std::vector<std::string> portIdNames;
374     for (const std::string& portPath : portSubTreePaths)
375     {
376         std::string portId =
377             sdbusplus::message::object_path(portPath).filename();
378         if (!portId.empty())
379         {
380             portIdNames.emplace_back(std::move(portId));
381         }
382     }
383 
384     std::ranges::sort(portIdNames, AlphanumLess<std::string>());
385 
386     nlohmann::json& members = asyncResp->res.jsonValue["Members"];
387     for (const std::string& portId : portIdNames)
388     {
389         nlohmann::json item;
390         item["@odata.id"] = boost::urls::format(
391             "/redfish/v1/Systems/{}/FabricAdapters/{}/Ports/{}", systemName,
392             adapterId, portId);
393         members.emplace_back(std::move(item));
394     }
395     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
396 }
397 
398 inline void handleFabricPortCollectionGet(
399     App& app, const crow::Request& req,
400     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
401     const std::string& systemName, const std::string& adapterId)
402 {
403     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
404     {
405         return;
406     }
407     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
408     {
409         // Option currently returns no systems.  TBD
410         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
411                                    systemName);
412         return;
413     }
414     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
415     {
416         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
417                                    systemName);
418         return;
419     }
420 
421     dbus::utility::getAssociatedSubTreePathsById(
422         adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
423         "connecting", portInterfaces,
424         std::bind_front(doHandleFabricPortCollectionGet, asyncResp, systemName,
425                         adapterId));
426 }
427 inline void requestRoutesFabricPort(App& app)
428 {
429     BMCWEB_ROUTE(app,
430                  "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/<str>/")
431         .privileges(redfish::privileges::headPort)
432         .methods(boost::beast::http::verb::head)(
433             std::bind_front(handleFabricPortHead, std::ref(app)));
434 
435     BMCWEB_ROUTE(app,
436                  "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/<str>/")
437         .privileges(redfish::privileges::getPort)
438         .methods(boost::beast::http::verb::get)(
439             std::bind_front(handleFabricPortGet, std::ref(app)));
440 
441     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/")
442         .privileges(redfish::privileges::headPortCollection)
443         .methods(boost::beast::http::verb::head)(
444             std::bind_front(handleFabricPortCollectionHead, std::ref(app)));
445 
446     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/")
447         .privileges(redfish::privileges::getPortCollection)
448         .methods(boost::beast::http::verb::get)(
449             std::bind_front(handleFabricPortCollectionGet, std::ref(app)));
450 }
451 
452 } // namespace redfish
453