xref: /openbmc/bmcweb/features/redfish/lib/switch_port.hpp (revision 3132dacead062fa63c070fc7f404f8cf93df43a0)
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_utility.hpp"
8 #include "error_messages.hpp"
9 #include "fabric.hpp"
10 #include "generated/enums/port.hpp"
11 #include "generated/enums/resource.hpp"
12 #include "http_request.hpp"
13 #include "logging.hpp"
14 #include "query.hpp"
15 #include "registries/privilege_registry.hpp"
16 
17 #include <boost/beast/http/verb.hpp>
18 #include <boost/system/error_code.hpp>
19 #include <nlohmann/json.hpp>
20 #include <sdbusplus/message/native_types.hpp>
21 #include <sdbusplus/unpack_properties.hpp>
22 
23 #include <array>
24 #include <cstddef>
25 #include <format>
26 #include <functional>
27 #include <memory>
28 #include <optional>
29 #include <string>
30 #include <string_view>
31 #include <utility>
32 
33 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory";
34 static constexpr std::array<std::string_view, 1> portInterface = {
35     "xyz.openbmc_project.Inventory.Connector.Port"};
36 
37 namespace redfish
38 {
39 inline port::PortType dBusSensorPortTypeToRedfish(const std::string& portType)
40 {
41     if (portType ==
42         "xyz.openbmc_project.Inventory.Connector.Port.PortType.DownstreamPort")
43     {
44         return port::PortType::DownstreamPort;
45     }
46 
47     if (portType ==
48         "xyz.openbmc_project.Inventory.Connector.Port.PortType.UpstreamPort")
49     {
50         return port::PortType::UpstreamPort;
51     }
52 
53     return port::PortType::Invalid;
54 }
55 
56 inline std::string dBusSensorPortProtocolToRedfish(
57     const std::string& portProtocol)
58 {
59     if (portProtocol ==
60         "xyz.openbmc_project.Inventory.Connector.Port.PortProtocol.PCIe")
61     {
62         return "PCIe";
63     }
64 
65     return "Unknown";
66 }
67 
68 inline void afterGetFabricSwitchPortInfo(
69     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
70     const boost::system::error_code& ec,
71     const dbus::utility::DBusPropertiesMap& properties)
72 {
73     if (ec)
74     {
75         BMCWEB_LOG_ERROR("DBus response error on GetAllProperties {}",
76                          ec.value());
77         messages::internalError(asyncResp->res);
78         return;
79     }
80 
81     std::optional<uint64_t> speed;
82     std::optional<size_t> width;
83     std::optional<std::string> portType;
84     std::optional<std::string> portProtocol;
85 
86     const bool success = sdbusplus::unpackPropertiesNoThrow(
87         dbus_utils::UnpackErrorPrinter(), properties, "Speed", speed, "Width",
88         width, "PortType", portType, "PortProtocol", portProtocol);
89 
90     if (!success)
91     {
92         messages::internalError(asyncResp->res);
93         return;
94     }
95 
96     if (speed.has_value())
97     {
98         if (*speed != 0 && *speed != std::numeric_limits<uint64_t>::max())
99         {
100             // Convert from bits per second to Gigabits per second (Gbps)
101             static constexpr int gbpsToBps = 1 << 30;
102             const double convertedSpeed =
103                 static_cast<double>(*speed) / gbpsToBps;
104 
105             asyncResp->res.jsonValue["CurrentSpeedGbps"] = convertedSpeed;
106         }
107     }
108     if (width.has_value())
109     {
110         if (*width != 0 && *width != std::numeric_limits<size_t>::max())
111         {
112             asyncResp->res.jsonValue["ActiveWidth"] = *width;
113         }
114     }
115     if (portType.has_value())
116     {
117         const port::PortType portTypeEnum =
118             dBusSensorPortTypeToRedfish(*portType);
119         if (portTypeEnum != port::PortType::Invalid)
120         {
121             asyncResp->res.jsonValue["PortType"] = portTypeEnum;
122         }
123     }
124     if (portProtocol.has_value())
125     {
126         const std::string portProtocolStr =
127             dBusSensorPortProtocolToRedfish(*portProtocol);
128         if (portProtocolStr != "Unknown")
129         {
130             asyncResp->res.jsonValue["PortProtocol"] = portProtocolStr;
131         }
132     }
133 }
134 
135 inline void handleFabricSwitchPortPathPortMetricsGet(
136     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
137     const std::string& fabricId, const std::string& switchId,
138     const std::string& portId, [[maybe_unused]] const std::string& portPath,
139     [[maybe_unused]] const std::string& serviceName)
140 {
141     asyncResp->res.jsonValue["@odata.type"] = "#PortMetrics.v1_3_0.PortMetrics";
142     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
143         "/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}/Metrics", fabricId,
144         switchId, portId);
145     asyncResp->res.jsonValue["Id"] = "Metrics";
146     asyncResp->res.jsonValue["Name"] =
147         std::format("{} {} Port Metrics", switchId, portId);
148 }
149 
150 inline void handleFabricSwitchPortPathPortGet(
151     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
152     const std::string& fabricId, const std::string& switchId,
153     const std::string& portId, const std::string& portPath,
154     const std::string& serviceName)
155 {
156     asyncResp->res.jsonValue["@odata.type"] = "#Port.v1_4_0.Port";
157     asyncResp->res.jsonValue["@odata.id"] =
158         boost::urls::format("/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}",
159                             fabricId, switchId, portId);
160     asyncResp->res.jsonValue["Id"] = portId;
161     asyncResp->res.jsonValue["Name"] =
162         std::format("{} {} Port", switchId, portId);
163 
164     nlohmann::json& status = asyncResp->res.jsonValue["Status"];
165     status["Health"] = resource::Health::OK;
166     status["State"] = resource::State::Enabled;
167 
168     asyncResp->res.jsonValue["Metrics"]["@odata.id"] = boost::urls::format(
169         "/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}/Metrics", fabricId,
170         switchId, portId);
171 
172     dbus::utility::getAllProperties(
173         serviceName, portPath, std::string{portInterface[0]},
174         std::bind_front(afterGetFabricSwitchPortInfo, asyncResp));
175 }
176 
177 inline void afterHandleFabricSwitchPortPaths(
178     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
179     const std::string& portId,
180     const std::function<void(const std::string& portPath,
181                              const std::string& serviceName)>& callback,
182     const boost::system::error_code& ec,
183     const dbus::utility::MapperGetSubTreeResponse& object)
184 {
185     if (ec)
186     {
187         BMCWEB_LOG_ERROR("DBus response error on GetAssociatedSubTreeById {}",
188                          ec);
189         messages::internalError(asyncResp->res);
190         return;
191     }
192 
193     std::string portPath;
194     std::string serviceName;
195     for (const auto& [path, service] : object)
196     {
197         std::string portName = sdbusplus::message::object_path(path).filename();
198         if (portName == portId)
199         {
200             portPath = path;
201             if (service.size() != 1)
202             {
203                 messages::internalError(asyncResp->res);
204                 return;
205             }
206             serviceName = service.begin()->first;
207             break;
208         }
209     }
210 
211     if (portPath.empty())
212     {
213         messages::resourceNotFound(asyncResp->res, "Port", portId);
214         return;
215     }
216 
217     callback(portPath, serviceName);
218 }
219 
220 inline void getAssociatedPortPath(
221     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
222     const std::string& portId,
223     std::function<void(const std::string& portPath,
224                        const std::string& serviceName)>&& callback,
225     const std::string& switchPath)
226 {
227     std::string associationPath = switchPath + "/connecting";
228     dbus::utility::getAssociatedSubTree(
229         associationPath, sdbusplus::message::object_path{inventoryPath}, 0,
230         portInterface,
231         std::bind_front(afterHandleFabricSwitchPortPaths, asyncResp, portId,
232                         std::move(callback)));
233 }
234 
235 inline void handleFabricSwitchPathPortCollection(
236     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
237     const std::string& fabricId, const std::string& switchId,
238     const boost::system::error_code& ec,
239     const dbus::utility::MapperGetSubTreePathsResponse& object)
240 {
241     if (ec)
242     {
243         BMCWEB_LOG_ERROR("DBus response error on GetSubTreePaths {}", ec);
244         messages::internalError(asyncResp->res);
245         return;
246     }
247 
248     asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection";
249     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
250         "/redfish/v1/Fabrics/{}/Switches/{}/Ports", fabricId, switchId);
251     asyncResp->res.jsonValue["Name"] = switchId + " Port Collection";
252 
253     asyncResp->res.jsonValue["Members@odata.count"] = object.size();
254 
255     nlohmann::json::array_t members;
256     for (const std::string& path : object)
257     {
258         std::string name = sdbusplus::message::object_path(path).filename();
259         nlohmann::json::object_t member;
260         member["@odata.id"] =
261             boost::urls::format("/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}",
262                                 fabricId, switchId, name);
263         members.emplace_back(std::move(member));
264     }
265 
266     asyncResp->res.jsonValue["Members"] = std::move(members);
267 }
268 
269 inline void getFabricSwitchPortPaths(
270     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271     const std::string& fabricId, const std::string& switchId,
272     const std::string& switchPath)
273 {
274     std::string associationPath = switchPath + "/connecting";
275     dbus::utility::getAssociatedSubTreePaths(
276         associationPath, sdbusplus::message::object_path{inventoryPath}, 0,
277         portInterface,
278         std::bind_front(handleFabricSwitchPathPortCollection, asyncResp,
279                         fabricId, switchId));
280 }
281 
282 inline void handleFabricSwitchPortPathsSwitchCollection(
283     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
284     const std::string& fabricId, const boost::system::error_code& ec,
285     const dbus::utility::MapperGetSubTreePathsResponse& object)
286 {
287     if (ec)
288     {
289         BMCWEB_LOG_ERROR("DBus response error on GetSubTreePaths {}", ec);
290         messages::internalError(asyncResp->res);
291         return;
292     }
293 
294     asyncResp->res.jsonValue["@odata.id"] =
295         boost::urls::format("/redfish/v1/Fabrics/{}/Switches", fabricId);
296     asyncResp->res.jsonValue["@odata.type"] =
297         "#SwitchCollection.SwitchCollection";
298     asyncResp->res.jsonValue["Name"] = fabricId + " Switch Collection";
299 
300     asyncResp->res.jsonValue["Members@odata.count"] = object.size();
301 
302     nlohmann::json::array_t members;
303     for (const std::string& path : object)
304     {
305         nlohmann::json::object_t member;
306         std::string name = sdbusplus::message::object_path(path).filename();
307         member["@odata.id"] = boost::urls::format(
308             "/redfish/v1/Fabrics/{}/Switches/{}", fabricId, name);
309         members.emplace_back(std::move(member));
310     }
311 
312     asyncResp->res.jsonValue["Members"] = std::move(members);
313 }
314 
315 inline void handleFabricSwitchPortMetricsGet(
316     App& app, const crow::Request& req,
317     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
318     const std::string& fabricId, const std::string& switchId,
319     const std::string& portId)
320 {
321     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
322     {
323         return;
324     }
325 
326     getFabricSwitchPath(
327         asyncResp, fabricId, switchId,
328         std::bind_front(
329             getAssociatedPortPath, asyncResp, portId,
330             std::bind_front(handleFabricSwitchPortPathPortMetricsGet, asyncResp,
331                             fabricId, switchId, portId)));
332 }
333 
334 inline void handleFabricSwitchPortGet(
335     App& app, const crow::Request& req,
336     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
337     const std::string& fabricId, const std::string& switchId,
338     const std::string& portId)
339 {
340     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
341     {
342         return;
343     }
344 
345     getFabricSwitchPath(
346         asyncResp, fabricId, switchId,
347         std::bind_front(
348             getAssociatedPortPath, asyncResp, portId,
349             std::bind_front(handleFabricSwitchPortPathPortGet, asyncResp,
350                             fabricId, switchId, portId)));
351 }
352 
353 inline void handleFabricSwitchPortsCollectionGet(
354     App& app, const crow::Request& req,
355     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
356     const std::string& fabricId, const std::string& switchId)
357 {
358     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
359     {
360         return;
361     }
362 
363     getFabricSwitchPath(asyncResp, fabricId, switchId,
364                         std::bind_front(getFabricSwitchPortPaths, asyncResp,
365                                         fabricId, switchId));
366 }
367 
368 inline void requestRoutesFabricSwitchPort(App& app)
369 {
370     BMCWEB_ROUTE(app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/")
371         .privileges(redfish::privileges::getPortCollection)
372         .methods(boost::beast::http::verb::get)(std::bind_front(
373             handleFabricSwitchPortsCollectionGet, std::ref(app)));
374 
375     BMCWEB_ROUTE(app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/<str>/")
376         .privileges(redfish::privileges::getPort)
377         .methods(boost::beast::http::verb::get)(
378             std::bind_front(handleFabricSwitchPortGet, std::ref(app)));
379 
380     BMCWEB_ROUTE(
381         app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/<str>/Metrics/")
382         .privileges(redfish::privileges::getPortMetrics)
383         .methods(boost::beast::http::verb::get)(
384             std::bind_front(handleFabricSwitchPortMetricsGet, std::ref(app)));
385 }
386 } // namespace redfish
387