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