Implementation of FabricAdapter schema in bmcweb

This commit implements FabricAdapter and FabricAdapter collection
schema. This code assumes all FabricAdapters are under
/redfish/v1/Systems/system like we do for Memory and Processors.

The schema can be used to publish inventory properties for FRUs
which can be modelled as Fabric adapters.

As a current use case, this schema is required to link ports on
fabric adapters back to the system.

A FabricAdapter represents the physical fabric adapter capable of
connecting to an interconnect fabric.
Examples include but are not limited to Ethernet, NVMe over Fabrics,
Gen-Z, and SAS fabric adapters.

Tested: Manually tested on the system, Run Redfish validator. Found no
error.

{
  "@odata.id": "/redfish/v1/Systems/system/FabricAdapters",
  "@odata.type": "#FabricAdapterCollection.FabricAdapterCollection",
  "Members": [
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane0"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/disk_backplane1"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card0"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card3"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card4"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card8"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card10"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card11"
    }
  ],
  "Members@odata.count": 8,
  "Name": "Fabric Adapter Collection"
}

{
  "@odata.id": "/redfish/v1/Systems/system/FabricAdapters/pcie_card11",
  "@odata.type": "#FabricAdapter.v1_0_0.FabricAdapter",
  "Id": "pcie_card11",
  "Name": "Fabric Adapter"
}

Signed-off-by: sunny srivastava <sunnsr25@in.ibm.com>
Change-Id: I4d3bc31a6f0036c262c0e30481d0da4aaf59b5ab
Signed-off-by: Lakshmi Yadlapati <lakshmiy@us.ibm.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index e98a04c..cf38510 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -23,6 +23,7 @@
 #include "environment_metrics.hpp"
 #include "ethernet.hpp"
 #include "event_service.hpp"
+#include "fabric_adapters.hpp"
 #include "hypervisor_system.hpp"
 #include "log_services.hpp"
 #include "manager_diagnostic_data.hpp"
@@ -213,6 +214,8 @@
         requestRoutesEventService(app);
         requestRoutesEventDestinationCollection(app);
         requestRoutesEventDestination(app);
+        requestRoutesFabricAdapters(app);
+        requestRoutesFabricAdapterCollection(app);
         requestRoutesSubmitTestEvent(app);
 
         hypervisor::requestRoutesHypervisorSystems(app);
diff --git a/redfish-core/lib/fabric_adapters.hpp b/redfish-core/lib/fabric_adapters.hpp
new file mode 100644
index 0000000..aacb056
--- /dev/null
+++ b/redfish-core/lib/fabric_adapters.hpp
@@ -0,0 +1,208 @@
+#pragma once
+
+#include "app.hpp"
+#include "dbus_utility.hpp"
+#include "utils/collection.hpp"
+#include "utils/json_utils.hpp"
+
+#include <boost/system/error_code.hpp>
+
+#include <array>
+#include <functional>
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace redfish
+{
+
+inline void handleAdapterError(const boost::system::error_code& ec,
+                               crow::Response& res,
+                               const std::string& adapterId)
+{
+
+    if (ec.value() == boost::system::errc::io_error)
+    {
+        messages::resourceNotFound(res, "#FabricAdapter.v1_0_0.FabricAdapter",
+                                   adapterId);
+        return;
+    }
+
+    BMCWEB_LOG_ERROR << "DBus method call failed with error " << ec.value();
+    messages::internalError(res);
+}
+
+inline void doAdapterGet(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                         const std::string& systemName,
+                         const std::string& adapterId)
+{
+    aResp->res.addHeader(
+        boost::beast::http::field::link,
+        "</redfish/v1/JsonSchemas/FabricAdapter/FabricAdapter.json>; rel=describedby");
+    aResp->res.jsonValue["@odata.type"] = "#FabricAdapter.v1_0_0.FabricAdapter";
+    aResp->res.jsonValue["Name"] = "Fabric Adapter";
+    aResp->res.jsonValue["Id"] = adapterId;
+    aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+        "redfish", "v1", "Systems", systemName, "FabricAdapters", adapterId);
+}
+
+inline bool checkFabricAdapterId(const std::string& adapterPath,
+                                 const std::string& adapterId)
+{
+    std::string fabricAdapterName =
+        sdbusplus::message::object_path(adapterPath).filename();
+
+    return !(fabricAdapterName.empty() || fabricAdapterName != adapterId);
+}
+
+inline void getValidFabricAdapterPath(
+    const std::string& adapterId, const std::string& systemName,
+    const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+    std::function<void(const std::string& fabricAdapterPath,
+                       const std::string& serviceName)>&& callback)
+{
+    if (systemName != "system")
+    {
+        messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
+        return;
+    }
+    constexpr std::array<std::string_view, 1> interfaces{
+        "xyz.openbmc_project.Inventory.Item.FabricAdapter"};
+
+    dbus::utility::getSubTree(
+        "/xyz/openbmc_project/inventory", 0, interfaces,
+        [adapterId, aResp,
+         callback](const boost::system::error_code& ec,
+                   const dbus::utility::MapperGetSubTreeResponse& subtree) {
+        if (ec)
+        {
+            handleAdapterError(ec, aResp->res, adapterId);
+            return;
+        }
+        for (const auto& [adapterPath, serviceMap] : subtree)
+        {
+            if (checkFabricAdapterId(adapterPath, adapterId))
+            {
+                callback(adapterPath, serviceMap.begin()->first);
+                return;
+            }
+        }
+        BMCWEB_LOG_WARNING << "Adapter not found";
+        messages::resourceNotFound(aResp->res, "FabricAdapter", adapterId);
+        });
+}
+
+inline void
+    handleFabricAdapterGet(App& app, const crow::Request& req,
+                           const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                           const std::string& systemName,
+                           const std::string& adapterId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, aResp))
+    {
+        return;
+    }
+
+    getValidFabricAdapterPath(
+        adapterId, systemName, aResp,
+        [aResp, systemName, adapterId](const std::string&, const std::string&) {
+        doAdapterGet(aResp, systemName, adapterId);
+        });
+}
+
+inline void handleFabricAdapterCollectionGet(
+    crow::App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+    const std::string& systemName)
+{
+    if (!redfish::setUpRedfishRoute(app, req, aResp))
+    {
+        return;
+    }
+    if (systemName != "system")
+    {
+        messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
+        return;
+    }
+
+    aResp->res.addHeader(
+        boost::beast::http::field::link,
+        "</redfish/v1/JsonSchemas/FabricAdapterCollection/FabricAdapterCollection.json>; rel=describedby");
+    aResp->res.jsonValue["@odata.type"] =
+        "#FabricAdapterCollection.FabricAdapterCollection";
+    aResp->res.jsonValue["Name"] = "Fabric Adapter Collection";
+    aResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+        "redfish", "v1", "Systems", systemName, "FabricAdapters");
+
+    constexpr std::array<std::string_view, 1> interfaces{
+        "xyz.openbmc_project.Inventory.Item.FabricAdapter"};
+    collection_util::getCollectionMembers(
+        aResp, boost::urls::url("/redfish/v1/Systems/system/FabricAdapters"),
+        interfaces);
+}
+
+inline void handleFabricAdapterCollectionHead(
+    crow::App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+    const std::string& systemName)
+{
+    if (!redfish::setUpRedfishRoute(app, req, aResp))
+    {
+        return;
+    }
+    if (systemName != "system")
+    {
+        messages::resourceNotFound(aResp->res, "ComputerSystem", systemName);
+        return;
+    }
+    aResp->res.addHeader(
+        boost::beast::http::field::link,
+        "</redfish/v1/JsonSchemas/FabricAdapterCollection/FabricAdapterCollection.json>; rel=describedby");
+}
+
+inline void
+    handleFabricAdapterHead(crow::App& app, const crow::Request& req,
+                            const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                            const std::string& systemName,
+                            const std::string& adapterId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, aResp))
+    {
+        return;
+    }
+
+    getValidFabricAdapterPath(
+        adapterId, systemName, aResp,
+        [aResp, systemName, adapterId](const std::string&, const std::string&) {
+        aResp->res.addHeader(
+            boost::beast::http::field::link,
+            "</redfish/v1/JsonSchemas/FabricAdapter/FabricAdapter.json>; rel=describedby");
+        });
+}
+
+inline void requestRoutesFabricAdapterCollection(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/")
+        .privileges(redfish::privileges::getFabricAdapterCollection)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handleFabricAdapterCollectionGet, std::ref(app)));
+
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/")
+        .privileges(redfish::privileges::headFabricAdapterCollection)
+        .methods(boost::beast::http::verb::head)(
+            std::bind_front(handleFabricAdapterCollectionHead, std::ref(app)));
+}
+
+inline void requestRoutesFabricAdapters(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/")
+        .privileges(redfish::privileges::getFabricAdapter)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(handleFabricAdapterGet, std::ref(app)));
+
+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/FabricAdapters/<str>/")
+        .privileges(redfish::privileges::headFabricAdapter)
+        .methods(boost::beast::http::verb::head)(
+            std::bind_front(handleFabricAdapterHead, std::ref(app)));
+}
+} // namespace redfish
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 864a8d9..d6f4fba 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -2930,6 +2930,8 @@
             "/redfish/v1/Systems/system/Memory";
         asyncResp->res.jsonValue["Storage"]["@odata.id"] =
             "/redfish/v1/Systems/system/Storage";
+        asyncResp->res.jsonValue["FabricAdapters"]["@odata.id"] =
+            "/redfish/v1/Systems/system/FabricAdapters";
 
         asyncResp->res.jsonValue["Actions"]["#ComputerSystem.Reset"]["target"] =
             "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset";