Implement Fabric PortCollection and Port schemas
This implements 2 schemas for FabricAdapters [1][2].
The implementation uses `GetAssociatedSubTreePathsById` &
`GetAssociatedSubTreeById`.
- https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/69999
The association is defined via
- https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/62881.
The backend port examples are also committed via
- https://gerrit.openbmc.org/c/openbmc/openpower-vpd-parser/+/66540
- https://gerrit.openbmc.org/c/openbmc/openpower-vpd-parser/+/70888
- https://gerrit.openbmc.org/c/openbmc/openbmc/+/66541
The current submission only implements the basic properties of Port
(e.g. Id, Name etc) as a foundation of the future additional
properties.
- Location
- LocationIndicatorActive
- Status
One example of Ports is this cable card for the i/o expansion drawers
and modeling the 2 ports on the cable card [3]. These ports have an
identify led, a location code, and a status.
Tested:
- Redfish Validator passes
- perform GET methods like these:
```
curl -k -X GET https://${bmc}/redfish/v1/Systems/system/FabricAdapters/disk_backplane0
{
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0",
"@odata.type": "#FabricAdapter.v1_4_0.FabricAdapter",
"Id": "disk_backplane0",
...
"Ports": {
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports"
},
...
}
```
```
curl -k -X GET https://${bmc}/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports
{
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports",
"@odata.type": "#PortCollection.PortCollection",
"Members": [
{
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports/dp0_connector4"
},
{
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports/dp0_connector5"
}
],
"Members@odata.count": 2,
"Name": "Port Collection"
}
```
```
curl -k -X GET https://${bmc}:18080/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports/dp0_connector4
{
"@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0/Ports/dp0_connector4",
"@odata.type": "#Port.v1_7_0.Port",
"Id": "dp0_connector4",
"Name": "dp0_connector4"
}%
```
Also try the invalid port like
```
curl -k -X GET https://${bmc}:18080/redfish/v1/Systems/system/FabricAdapters/io_module1/Ports/INVALID
{
"error": {
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The requested resource of type Port named 'INVALID' was not found.",
"MessageArgs": [
"Port",
"INVALID"
],
"MessageId": "Base.1.16.0.ResourceNotFound",
"MessageSeverity": "Critical",
"Resolution": "Provide a valid resource identifier and resubmit the request."
}
],
"code": "Base.1.16.0.ResourceNotFound",
"message": "The requested resource of type Port named 'INVALID' was not found."
}
}%
```
[1] https://redfish.dmtf.org/schemas/v1/PortCollection_v1.xml
[2] https://redfish.dmtf.org/schemas/v1/Port_v1.xml
[3] https://www.ibm.com/docs/en/power10?topic=details-pcie4-cable-adapter-fc-ej24-ccin-6b92
Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I8c64c16764e85c0716e264263708b18f897a2c0c
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
diff --git a/docs/Redfish.md b/docs/Redfish.md
index 20ed111..0575962 100644
--- a/docs/Redfish.md
+++ b/docs/Redfish.md
@@ -849,10 +849,24 @@
- LocationIndicatorActive
- Model
- PartNumber
+- Ports
- SerialNumber
- SparePartNumber
- Status
+### /redfish/v1/Systems/system/FabricAdapters/{FabricAdapterId}/Ports/
+
+#### PortCollection
+
+- Members
+- `Members@odata.count`
+
+### /redfish/v1/Systems/system/FabricAdapters/{FabricAdapterId}/Ports/{PortId}/
+
+#### Port
+
+- no properties
+
### /redfish/v1/Systems/system/LogServices/
#### LogServiceCollection
diff --git a/redfish-core/lib/fabric_adapters.hpp b/redfish-core/lib/fabric_adapters.hpp
index 52213c7..532da5f 100644
--- a/redfish-core/lib/fabric_adapters.hpp
+++ b/redfish-core/lib/fabric_adapters.hpp
@@ -191,6 +191,10 @@
asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
+ asyncResp->res.jsonValue["Ports"]["@odata.id"] =
+ boost::urls::format("/redfish/v1/Systems/{}/FabricAdapters/{}/Ports",
+ systemName, adapterId);
+
getFabricAdapterLocation(asyncResp, serviceName, fabricAdapterPath);
getFabricAdapterAsset(asyncResp, serviceName, fabricAdapterPath);
getFabricAdapterState(asyncResp, serviceName, fabricAdapterPath);
diff --git a/redfish-core/lib/fabric_ports.hpp b/redfish-core/lib/fabric_ports.hpp
new file mode 100644
index 0000000..24d5e51
--- /dev/null
+++ b/redfish-core/lib/fabric_ports.hpp
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+
+#include "bmcweb_config.h"
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "dbus_utility.hpp"
+#include "error_messages.hpp"
+#include "http_request.hpp"
+#include "human_sort.hpp"
+#include "logging.hpp"
+#include "query.hpp"
+#include "registries/privilege_registry.hpp"
+
+#include <boost/beast/http/field.hpp>
+#include <boost/beast/http/verb.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/url/format.hpp>
+
+#include <algorithm>
+#include <array>
+#include <functional>
+#include <memory>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace redfish
+{
+static constexpr std::array<std::string_view, 1> fabricInterfaces{
+ "xyz.openbmc_project.Inventory.Item.FabricAdapter"};
+static constexpr std::array<std::string_view, 1> portInterfaces{
+ "xyz.openbmc_project.Inventory.Connector.Port"};
+
+inline void getFabricPortProperties(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& systemName, const std::string& adapterId,
+ const std::string& portId, const std::string& portPath)
+{
+ if (portPath.empty())
+ {
+ BMCWEB_LOG_WARNING("Port not found");
+ messages::resourceNotFound(asyncResp->res, "Port", portId);
+ return;
+ }
+
+ asyncResp->res.addHeader(
+ boost::beast::http::field::link,
+ "</redfish/v1/JsonSchemas/Port/Port.json>; rel=describedby");
+
+ asyncResp->res.jsonValue["@odata.type"] = "#Port.v1_11_0.Port";
+ asyncResp->res.jsonValue["@odata.id"] =
+ boost::urls::format("/redfish/v1/Systems/{}/FabricAdapters/{}/Ports/{}",
+ systemName, adapterId, portId);
+ asyncResp->res.jsonValue["Id"] = portId;
+ asyncResp->res.jsonValue["Name"] = "Fabric Port";
+}
+
+inline void afterGetValidFabricPortPath(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& portId,
+ std::function<void(const std::string&)>& callback,
+ const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreePathsResponse& portSubTreePaths)
+{
+ if (ec)
+ {
+ if (ec.value() != boost::system::errc::io_error)
+ {
+ BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ // Port not found
+ callback(std::string());
+ return;
+ }
+ const auto& it =
+ std::ranges::find_if(portSubTreePaths, [portId](const auto& portPath) {
+ return portId ==
+ sdbusplus::message::object_path(portPath).filename();
+ });
+ if (it == portSubTreePaths.end())
+ {
+ // Port not found
+ callback(std::string());
+ return;
+ }
+
+ const std::string& portPath = *it;
+ dbus::utility::getDbusObject(
+ portPath, portInterfaces,
+ [asyncResp, portPath, callback{std::move(callback)}](
+ const boost::system::error_code& ec1,
+ const dbus::utility::MapperGetObject& object) {
+ if (ec1 || object.empty())
+ {
+ BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
+ ec1.value());
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ callback(portPath);
+ });
+}
+
+inline void getValidFabricPortPath(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& adapterId, const std::string& portId,
+ std::function<void(const std::string&)>&& callback)
+{
+ dbus::utility::getAssociatedSubTreePathsById(
+ adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
+ "connecting", portInterfaces,
+ std::bind_front(afterGetValidFabricPortPath, asyncResp, portId,
+ std::move(callback)));
+}
+
+inline void 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)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+ {
+ // Option currently returns no systems. TBD
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+ if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+ {
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+
+ getValidFabricPortPath(
+ asyncResp, adapterId, portId,
+ [asyncResp, portId](const std::string& portPath) {
+ if (portPath.empty())
+ {
+ BMCWEB_LOG_WARNING("Port not found");
+ messages::resourceNotFound(asyncResp->res, "Port", portId);
+ return;
+ }
+ asyncResp->res.addHeader(
+ boost::beast::http::field::link,
+ "</redfish/v1/JsonSchemas/Port/Port.json>; rel=describedby");
+ });
+}
+
+inline void 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)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+ {
+ // Option currently returns no systems. TBD
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+ if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+ {
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+ getValidFabricPortPath(asyncResp, adapterId, portId,
+ std::bind_front(getFabricPortProperties, asyncResp,
+ systemName, adapterId, portId));
+}
+
+inline void afterHandleFabricPortCollectionHead(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& adapterId, const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreePathsResponse& /* portSubTreePaths */)
+{
+ if (ec)
+ {
+ if (ec.value() != boost::system::errc::io_error)
+ {
+ BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ BMCWEB_LOG_WARNING("Adapter not found");
+ messages::resourceNotFound(asyncResp->res, "Adapter", adapterId);
+ return;
+ }
+ asyncResp->res.addHeader(
+ boost::beast::http::field::link,
+ "</redfish/v1/JsonSchemas/PortCollection/PortCollection.json>; rel=describedby");
+}
+
+inline void handleFabricPortCollectionHead(
+ crow::App& app, const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& systemName, const std::string& adapterId)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+ {
+ // Option currently returns no systems. TBD
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+ if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+ {
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+
+ dbus::utility::getAssociatedSubTreePathsById(
+ adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
+ "connecting", portInterfaces,
+ std::bind_front(afterHandleFabricPortCollectionHead, asyncResp,
+ adapterId));
+}
+
+inline void 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)
+{
+ if (ec)
+ {
+ if (ec.value() != boost::system::errc::io_error)
+ {
+ BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ BMCWEB_LOG_WARNING("Adapter not found");
+ messages::resourceNotFound(asyncResp->res, "Adapter", adapterId);
+ return;
+ }
+ asyncResp->res.addHeader(
+ boost::beast::http::field::link,
+ "</redfish/v1/JsonSchemas/PortCollection/PortCollection.json>; rel=describedby");
+
+ asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection";
+ asyncResp->res.jsonValue["Name"] = "Port Collection";
+ asyncResp->res.jsonValue["@odata.id"] =
+ boost::urls::format("/redfish/v1/Systems/{}/FabricAdapters/{}/Ports",
+ systemName, adapterId);
+ asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
+
+ std::vector<std::string> portIdNames;
+ for (const std::string& portPath : portSubTreePaths)
+ {
+ std::string portId =
+ sdbusplus::message::object_path(portPath).filename();
+ if (!portId.empty())
+ {
+ portIdNames.emplace_back(std::move(portId));
+ }
+ }
+
+ std::ranges::sort(portIdNames, AlphanumLess<std::string>());
+
+ nlohmann::json& members = asyncResp->res.jsonValue["Members"];
+ for (const std::string& portId : portIdNames)
+ {
+ nlohmann::json item;
+ item["@odata.id"] = boost::urls::format(
+ "/redfish/v1/Systems/{}/FabricAdapters/{}/Ports/{}", systemName,
+ adapterId, portId);
+ members.emplace_back(std::move(item));
+ }
+ asyncResp->res.jsonValue["Members@odata.count"] = members.size();
+}
+
+inline void handleFabricPortCollectionGet(
+ App& app, const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& systemName, const std::string& adapterId)
+{
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
+ {
+ // Option currently returns no systems. TBD
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+ if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
+ {
+ messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+ systemName);
+ return;
+ }
+
+ dbus::utility::getAssociatedSubTreePathsById(
+ adapterId, "/xyz/openbmc_project/inventory", fabricInterfaces,
+ "connecting", portInterfaces,
+ std::bind_front(doHandleFabricPortCollectionGet, asyncResp, systemName,
+ adapterId));
+}
+inline void requestRoutesFabricPort(App& app)
+{
+ BMCWEB_ROUTE(app,
+ "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/<str>/")
+ .privileges(redfish::privileges::headPort)
+ .methods(boost::beast::http::verb::head)(
+ std::bind_front(handleFabricPortHead, std::ref(app)));
+
+ BMCWEB_ROUTE(app,
+ "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/<str>/")
+ .privileges(redfish::privileges::getPort)
+ .methods(boost::beast::http::verb::get)(
+ std::bind_front(handleFabricPortGet, std::ref(app)));
+
+ BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/")
+ .privileges(redfish::privileges::headPortCollection)
+ .methods(boost::beast::http::verb::head)(
+ std::bind_front(handleFabricPortCollectionHead, std::ref(app)));
+
+ BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/Ports/")
+ .privileges(redfish::privileges::getPortCollection)
+ .methods(boost::beast::http::verb::get)(
+ std::bind_front(handleFabricPortCollectionGet, std::ref(app)));
+}
+
+} // namespace redfish
diff --git a/redfish-core/src/redfish.cpp b/redfish-core/src/redfish.cpp
index 9162bff..c797251 100644
--- a/redfish-core/src/redfish.cpp
+++ b/redfish-core/src/redfish.cpp
@@ -16,6 +16,7 @@
#include "event_service.hpp"
#include "eventservice_sse.hpp"
#include "fabric_adapters.hpp"
+#include "fabric_ports.hpp"
#include "fan.hpp"
#include "hypervisor_system.hpp"
#include "log_services.hpp"
@@ -214,6 +215,7 @@
requestRoutesEventDestination(app);
requestRoutesFabricAdapters(app);
requestRoutesFabricAdapterCollection(app);
+ requestRoutesFabricPort(app);
requestRoutesSubmitTestEvent(app);
if constexpr (BMCWEB_HYPERVISOR_COMPUTER_SYSTEM)