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