| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_NETWORK_ADAPTER_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_NETWORK_ADAPTER_H_ | |
| /* | |
| // Copyright 2024 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| */ | |
| #include <algorithm> | |
| #include <array> | |
| #include <charconv> | |
| #include <cmath> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <filesystem> // NOLINT | |
| #include <fstream> | |
| #include <functional> | |
| #include <limits> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "bmcweb_config.h" | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "human_sort.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "chassis_utils.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "location_utils.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "bmcweb_authorizer_singleton.h" | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| namespace redfish { | |
| constexpr char const* networkAdapterInventoryIntf = | |
| "xyz.openbmc_project.Inventory.Item.NetworkAdapter"; | |
| constexpr char const* portInventoryIntf = | |
| "xyz.openbmc_project.Inventory.Item.Port"; | |
| constexpr char const* inventoryPrefix = "/xyz/openbmc_project/inventory"; | |
| // D-Bus endpoint representation: <object_path, service_name> | |
| using DBusEndpoint = std::pair<std::string, std::string>; | |
| struct PortProviderEndpoints { | |
| DBusEndpoint pldm; // Flat path: authoritative for base link telemetry | |
| DBusEndpoint | |
| nsm; // Hierarchical path: authoritative for OEM metrics & location | |
| }; | |
| inline PortProviderEndpoints findPortProviderEndpoints( | |
| const dbus::utility::MapperGetSubTreeResponse& subtree, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| std::string suffixHierarchical = absl::StrCat( | |
| "/", chassisId, "/NetworkAdapters/", networkAdapterId, "/Ports/", portId); | |
| std::string suffixFlat = | |
| absl::StrCat("/", chassisId, "/", networkAdapterId, "/", portId); | |
| PortProviderEndpoints endpoints; | |
| for (const auto& [path, conn] : subtree) { | |
| if (conn.empty()) { | |
| continue; | |
| } | |
| if (path.ends_with(suffixHierarchical)) { | |
| endpoints.nsm = {path, conn[0].first}; | |
| } else if (path.ends_with(suffixFlat)) { | |
| endpoints.pldm = {path, conn[0].first}; | |
| } | |
| } | |
| return endpoints; | |
| } | |
| inline DBusEndpoint findMatchingPortPath( | |
| const dbus::utility::MapperGetSubTreeResponse& subtree, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| PortProviderEndpoints ep = | |
| findPortProviderEndpoints(subtree, chassisId, networkAdapterId, portId); | |
| if (!ep.nsm.first.empty()) { | |
| return ep.nsm; | |
| } | |
| return ep.pldm; | |
| } | |
| #ifdef G3_UNIT_TEST_BUILD | |
| constexpr const char* netSysfsPath = | |
| "third_party/openbmc_build_scripts/additional/sys/class/net"; | |
| #else | |
| #ifdef UNIT_TEST_BUILD | |
| constexpr const char* netSysfsPath = sysfsNetBasePathUT; | |
| #else | |
| constexpr const char* netSysfsPath = "/sys/class/net/"; | |
| #endif | |
| #endif | |
| // Helper function to read a file and return its contents as a string | |
| static bool readOneLineFile(const std::filesystem::path& filePath, | |
| std::string& content) { | |
| try { | |
| std::ifstream ifs(filePath); | |
| if (ifs.is_open() && std::getline(ifs, content)) { | |
| ifs.close(); | |
| return true; | |
| } | |
| } catch (...) { | |
| } | |
| return false; | |
| } | |
| // Helper function for convert a string to uint64 type | |
| // if string is invalid, return default val instead | |
| static uint64_t stringToUint64(const std::string_view str, | |
| uint64_t defVal = 0) { | |
| uint64_t value = defVal; | |
| std::from_chars(str.data(), str.data() + str.size(), value); | |
| return value; | |
| } | |
| // Helper function for convert a hex string to uint64 type | |
| // if string is invalid, return default val instead | |
| static uint64_t hexStringToUint64(const std::string_view str, | |
| uint64_t defVal = 0) { | |
| uint64_t value = defVal; | |
| // Handle the "0x" or "0X" prefix for hex string | |
| if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { | |
| std::from_chars(str.data() + 2, str.data() + str.size(), value, 16); | |
| } | |
| return value; | |
| } | |
| static std::vector<std::string> sysfsGetEtherDevList() { | |
| std::vector<std::string> ethList; | |
| // Loop through the directory looking for redfish log files | |
| for (const std::filesystem::directory_entry& dirEnt : | |
| std::filesystem::directory_iterator(netSysfsPath)) { | |
| std::string type; | |
| // ignore non-ethernet device | |
| if (readOneLineFile(dirEnt.path() / "type", type) && | |
| stringToUint64(type) == 1) { | |
| ethList.push_back(dirEnt.path().filename()); | |
| } | |
| } | |
| return ethList; | |
| } | |
| static std::vector<std::string> sysfsGetEthAddrList( | |
| const std::string& portName) { | |
| std::vector<std::string> addrList; | |
| std::filesystem::path macPath = | |
| std::filesystem::path(netSysfsPath) / portName / "address"; | |
| std::string mac; | |
| try { | |
| std::ifstream ifs(macPath); | |
| if (ifs.is_open()) { | |
| while (std::getline(ifs, mac)) { | |
| addrList.push_back(std::move(mac)); | |
| } | |
| } | |
| } catch (...) { | |
| } | |
| return addrList; | |
| } | |
| // gsys uses ethtool ETHTOOL_GLINK, which checks IFF_UP and carriers. | |
| // LinkStatus follows the same metrics with LinkState | |
| static std::string sysfsGetLinkState(const std::string& portName) { | |
| std::filesystem::path carrierPath = | |
| std::filesystem::path(netSysfsPath) / portName / "carrier"; | |
| std::filesystem::path ifflagPath = | |
| std::filesystem::path(netSysfsPath) / portName / "flags"; | |
| std::string flags; | |
| std::string carrier; | |
| if (readOneLineFile(ifflagPath, flags) && | |
| static_cast<bool>(hexStringToUint64(flags) & IFF_UP) && | |
| readOneLineFile(carrierPath, carrier) && stringToUint64(carrier) == 1) { | |
| return "Enabled"; | |
| } | |
| return "Disabled"; | |
| } | |
| static double sysfsGetSpeedGbps(const std::string& portName) { | |
| std::filesystem::path speedPath = | |
| std::filesystem::path(netSysfsPath) / portName / "speed"; | |
| std::string speed; | |
| if (readOneLineFile(speedPath, speed)) { | |
| return static_cast<double>(stringToUint64(speed)) / 1000; | |
| } | |
| return 0; | |
| } | |
| static nlohmann::json sysfsGetNetworkPortMetrics(const std::string& portName) { | |
| nlohmann::json metrics; | |
| std::filesystem::path statPath = | |
| std::filesystem::path(netSysfsPath) / portName / "statistics"; | |
| std::string carrierChanges; | |
| std::string rxCrcErrors; | |
| std::string rxErrors; | |
| std::string rxDropped; | |
| std::string rxPackets; | |
| if (readOneLineFile( | |
| std::filesystem::path(netSysfsPath) / portName / "carrier_changes", | |
| carrierChanges)) { | |
| metrics["CarrierChanges"] = | |
| stringToUint64(carrierChanges, std::numeric_limits<uint64_t>::max()); | |
| } | |
| if (readOneLineFile(statPath / "rx_crc_errors", rxCrcErrors)) { | |
| metrics["RXCRCErrors"] = | |
| stringToUint64(rxCrcErrors, std::numeric_limits<uint64_t>::max()); | |
| } | |
| if (readOneLineFile(statPath / "rx_errors", rxErrors)) { | |
| metrics["RXErrors"] = | |
| stringToUint64(rxErrors, std::numeric_limits<uint64_t>::max()); | |
| } | |
| if (readOneLineFile(statPath / "rx_dropped", rxDropped)) { | |
| metrics["ReceivedPacketsDropped"] = | |
| stringToUint64(rxDropped, std::numeric_limits<uint64_t>::max()); | |
| } | |
| if (readOneLineFile(statPath / "rx_packets", rxPackets)) { | |
| metrics["ReceivedPackets"] = stringToUint64(rxPackets, 0); | |
| } | |
| return metrics; | |
| } | |
| template <typename Func, typename... Args> | |
| auto handleChassisNetworkAdapterCommon( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string chassisId, Func funcSysfs, Func funcDBus, Args... args) | |
| -> decltype(funcSysfs(asyncResp, chassisId, args...), | |
| funcDBus(asyncResp, chassisId, args...)) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.EntityManager", | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId + "/BmcNet", | |
| "xyz.openbmc_project.Configuration.BmcNet", "Name", context, | |
| [asyncResp, chassisId, funcSysfs, funcDBus, args...]( | |
| const boost::system::error_code& ec2, const std::string& bmcNetName) { | |
| if (ec2 || bmcNetName != "BmcNet") { | |
| // non-bmc chassis | |
| funcDBus(asyncResp, chassisId, args...); | |
| } else { | |
| // bmc chassis | |
| funcSysfs(asyncResp, chassisId, args...); | |
| } | |
| }); | |
| } | |
| inline void handleChassisNetworkAdapterCollectionGetSysfs( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| /** | |
| * Functions triggers appropriate requests | |
| */ | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#NetworkAdapterCollection.NetworkAdapterCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters"); | |
| asyncResp->res.jsonValue["Name"] = | |
| "Network adapter Collection for chassis " + chassisId; | |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | |
| members = nlohmann::json::array(); | |
| nlohmann::json::object_t member; | |
| member["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", "0"); | |
| members.push_back(std::move(member)); | |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | |
| } | |
| // Chassis NetworkAdapters, this URL will show all the NetworkAdapterCollection | |
| // information | |
| inline void handleChassisNetworkAdapterCollectionGetDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| // mapper call lambda | |
| constexpr std::array<std::string_view, 2> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| inventoryPrefix, 0, interfaces, requestContext, | |
| [asyncResp, chassisId, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| if (ec == boost::system::errc::host_unreachable) { | |
| messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "Failed to get chassis subtree:" << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const auto& [path, connectionNames] : subtree) { | |
| sdbusplus::message::object_path objPath(path); | |
| if (objPath.filename() != chassisId) { | |
| continue; | |
| } | |
| if (connectionNames.empty()) { | |
| BMCWEB_LOG_DEBUG << "Got 0 Connection names"; | |
| continue; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#NetworkAdapterCollection.NetworkAdapterCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters"); | |
| asyncResp->res.jsonValue["Name"] = "NetworkAdapter Collection"; | |
| // Get /network_adapter association | |
| dbus_utils::getAssociationEndPoints( | |
| path + "/network_adapter", requestContext, | |
| [asyncResp, chassisId]( | |
| const boost::system::error_code& ec1, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | |
| members = nlohmann::json::array(); | |
| if (ec1) { | |
| // "/network_adapter" association is optional for chassis | |
| BMCWEB_LOG_DEBUG | |
| << "Cannot get chassis NetworkAdapter association:" | |
| << ec1.what(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| return; | |
| } | |
| std::vector<std::string> leafNames; | |
| leafNames.reserve(resp.size()); | |
| for (const auto& nic : resp) { | |
| sdbusplus::message::object_path nicPath(nic); | |
| leafNames.push_back(nicPath.filename()); | |
| } | |
| std::sort(leafNames.begin(), leafNames.end(), | |
| AlphanumLess<std::string>()); | |
| leafNames.erase(std::unique(leafNames.begin(), leafNames.end()), | |
| leafNames.end()); | |
| for (const auto& leafName : leafNames) { | |
| nlohmann::json::object_t member; | |
| member["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| leafName); | |
| members.push_back(std::move(member)); | |
| } | |
| asyncResp->res.jsonValue["Members@odata.count"] = | |
| members.size(); | |
| }); // end association lambda | |
| return; // No need to check other ObjectPaths | |
| } // end Iterate over all retrieved ObjectPaths | |
| // chassisId is not found | |
| messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); | |
| }); | |
| } | |
| // Chassis NetworkAdapters, this URL will show all the NetworkAdapterCollection | |
| // information | |
| inline void handleChassisNetworkAdapterCollectionGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| handleChassisNetworkAdapterCommon( | |
| app, req, asyncResp, chassisId, | |
| handleChassisNetworkAdapterCollectionGetSysfs, | |
| handleChassisNetworkAdapterCollectionGetDBus); | |
| } | |
| inline void requestRoutesChassisNetworkAdapterCollection(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/NetworkAdapters/") | |
| .privileges(redfish::privileges::getNetworkAdapterCollection) | |
| .methods(boost::beast::http::verb::get)(std::bind_front( | |
| handleChassisNetworkAdapterCollectionGet, std::ref(app))); | |
| } | |
| inline void buildNetworkAdapterDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& service, const std::string& networkAdapterPath) { | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "NetworkAdapters", networkAdapterId); | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#NetworkAdapter.v1_9_0.NetworkAdapter"; | |
| asyncResp->res.jsonValue["Name"] = networkAdapterId; | |
| asyncResp->res.jsonValue["Id"] = networkAdapterId; | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| service, networkAdapterPath, networkAdapterInventoryIntf, "StatusHealth", | |
| requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const std::string& statusHealth) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Get StatusHealth property failed:" << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (statusHealth.empty()) { | |
| BMCWEB_LOG_ERROR << "Empty StatusHealth property"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["Status"]["Health"] = statusHealth; | |
| }); | |
| dbus_utils::getAssociationEndPoints( | |
| networkAdapterPath + "/all_ports", requestContext, | |
| [asyncResp, chassisId, networkAdapterId]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (ec || resp.empty()) { | |
| return; // no ports = no failures | |
| } | |
| nlohmann::json reference; | |
| reference["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports"); | |
| asyncResp->res.jsonValue["Ports"] = std::move(reference); | |
| }); | |
| } | |
| inline void handleChassisNetworkAdapterGetSysfs( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| // currently we only define one network adapter id and put all the bmc ports | |
| // under it. | |
| asyncResp->res.jsonValue["@odata.type"] = "#NetworkAdapter.NetworkAdapter"; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "NetworkAdapters", networkAdapterId); | |
| asyncResp->res.jsonValue["Name"] = "Network adapter"; | |
| asyncResp->res.jsonValue["Ports"]["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports"); | |
| } | |
| inline void handleChassisNetworkAdapterGetDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| networkAdapterInventoryIntf}; | |
| // mapper call chassis | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| inventoryPrefix, 0, interfaces, requestContext, | |
| [asyncResp, chassisId, networkAdapterId, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Get NetworkAdapter inventory failed:" | |
| << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| std::string suffixHierarchical = | |
| absl::StrCat("/", chassisId, "/NetworkAdapters/", networkAdapterId); | |
| std::string suffixFlat = | |
| absl::StrCat("/", chassisId, "/", networkAdapterId); | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const auto& [objectPath, serviceMap] : subtree) { | |
| if (!objectPath.ends_with(suffixHierarchical) && | |
| !objectPath.ends_with(suffixFlat)) { | |
| continue; | |
| } | |
| if (serviceMap.empty()) { | |
| BMCWEB_LOG_DEBUG << "Got 0 Connection names"; | |
| continue; | |
| } | |
| const std::string& serviceName = serviceMap[0].first; | |
| buildNetworkAdapterDBus(asyncResp, chassisId, networkAdapterId, | |
| serviceName, objectPath); | |
| return; | |
| } | |
| // chassisId is not found | |
| messages::resourceNotFound(asyncResp->res, "Chassis/NetworkAdapter", | |
| networkAdapterId); | |
| }); | |
| } | |
| inline void handleChassisNetworkAdapterGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| handleChassisNetworkAdapterCommon( | |
| app, req, asyncResp, chassisId, handleChassisNetworkAdapterGetSysfs, | |
| handleChassisNetworkAdapterGetDBus, networkAdapterId); | |
| } | |
| inline void requestRoutesChassisNetworkAdapter(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/NetworkAdapters/<str>/") | |
| .privileges(redfish::privileges::getNetworkAdapter) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleChassisNetworkAdapterGet, std::ref(app))); | |
| } | |
| inline void handleNetworkPortCollectionGetSysfs( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| /** | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| // currently we only define one network adapter and put all the ports under | |
| // it. | |
| asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports"); | |
| asyncResp->res.jsonValue["Name"] = "Network ports"; | |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | |
| members = nlohmann::json::array(); | |
| for (auto& i : sysfsGetEtherDevList()) { | |
| nlohmann::json::object_t member; | |
| member["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", i); | |
| members.push_back(std::move(member)); | |
| } | |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | |
| } | |
| inline void buildNetworkPortCollectionDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::vector<std::string>& networkAdapterPaths) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports"); | |
| asyncResp->res.jsonValue["Name"] = "Network Port Collection"; | |
| if (networkAdapterPaths.empty()) { | |
| asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Members@odata.count"] = 0; | |
| return; | |
| } | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| auto allPorts = std::make_shared<std::vector<std::string>>(); | |
| auto remaining = std::make_shared<size_t>(networkAdapterPaths.size()); | |
| for (const auto& adapterPath : networkAdapterPaths) { | |
| dbus_utils::getAssociationEndPoints( | |
| adapterPath + "/all_ports", requestContext, | |
| [asyncResp, chassisId, networkAdapterId, allPorts, remaining]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (!ec) { | |
| allPorts->insert(allPorts->end(), resp.begin(), resp.end()); | |
| } else { | |
| BMCWEB_LOG_DEBUG << "Cannot find chassis /all_ports association:" | |
| << ec.what(); | |
| } | |
| --(*remaining); | |
| if (*remaining != 0) { | |
| return; | |
| } | |
| nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | |
| members = nlohmann::json::array(); | |
| std::vector<std::string> leafNames; | |
| leafNames.reserve(allPorts->size()); | |
| for (const auto& port : *allPorts) { | |
| sdbusplus::message::object_path portPath(port); | |
| leafNames.push_back(portPath.filename()); | |
| } | |
| std::sort(leafNames.begin(), leafNames.end(), | |
| AlphanumLess<std::string>()); | |
| leafNames.erase(std::unique(leafNames.begin(), leafNames.end()), | |
| leafNames.end()); | |
| for (const auto& leafName : leafNames) { | |
| nlohmann::json::object_t member; | |
| member["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", leafName); | |
| members.push_back(std::move(member)); | |
| } | |
| asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | |
| }); // end association lambda | |
| } | |
| } | |
| inline void buildNetworkPortCollectionDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& networkAdapterPath) { | |
| buildNetworkPortCollectionDBus(asyncResp, chassisId, networkAdapterId, | |
| std::vector<std::string>{networkAdapterPath}); | |
| } | |
| inline void handleNetworkPortCollectionGetDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| networkAdapterInventoryIntf}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| inventoryPrefix, 0, interfaces, requestContext, | |
| [asyncResp, chassisId, networkAdapterId, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| if (ec == boost::system::errc::host_unreachable) { | |
| messages::resourceNotFound(asyncResp->res, | |
| "#NetworkAdapter.v1_9_0.NetworkAdapter", | |
| networkAdapterId); | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "Failed to get " << networkAdapterInventoryIntf | |
| << " due to :" << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| std::string suffixHierarchical = | |
| absl::StrCat("/", chassisId, "/NetworkAdapters/", networkAdapterId); | |
| std::string suffixFlat = | |
| absl::StrCat("/", chassisId, "/", networkAdapterId); | |
| std::vector<std::string> matchingAdapterPaths; | |
| matchingAdapterPaths.reserve(subtree.size()); | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const auto& [path, connectionNames] : subtree) { | |
| if (connectionNames.empty()) { | |
| BMCWEB_LOG_DEBUG << "Got 0 Connection names"; | |
| continue; | |
| } | |
| if (path.ends_with(suffixHierarchical) || | |
| path.ends_with(suffixFlat)) { | |
| matchingAdapterPaths.push_back(path); | |
| } | |
| } // end Iterate over all retrieved ObjectPaths | |
| if (matchingAdapterPaths.empty()) { | |
| messages::resourceNotFound(asyncResp->res, | |
| "#NetworkAdapter.v1_9_0.NetworkAdapter", | |
| networkAdapterId); | |
| return; | |
| } | |
| buildNetworkPortCollectionDBus(asyncResp, chassisId, networkAdapterId, | |
| matchingAdapterPaths); | |
| }); | |
| } | |
| inline void handleNetworkPortCollectionGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId) { | |
| handleChassisNetworkAdapterCommon( | |
| app, req, asyncResp, chassisId, handleNetworkPortCollectionGetSysfs, | |
| handleNetworkPortCollectionGetDBus, networkAdapterId); | |
| } | |
| inline void requestRoutesNetworkPortCollection(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/NetworkAdapters/<str>/Ports/") | |
| .privileges(redfish::privileges::getPortCollection) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleNetworkPortCollectionGet, std::ref(app))); | |
| } | |
| inline void handleNetworkPortGetSysfs( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#Port.Port"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", portId); | |
| asyncResp->res.jsonValue["Id"] = portId; | |
| asyncResp->res.jsonValue["Metrics"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "NetworkAdapters", networkAdapterId, "Ports", | |
| portId, "Metrics"); | |
| if (std::string(bmcDirectPathIntf).find(portId) != std::string::npos && | |
| ::milotic::authz::BmcWebAuthorizerSingleton::GetInstance() | |
| .HasBmcDirectEthernet()) { | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.EntityManager", | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId + "/BmcNet", | |
| "xyz.openbmc_project.Configuration.BmcNet", "LocationCode", | |
| requestContext, | |
| [asyncResp, chassisId](const boost::system::error_code& ec, | |
| const std::string& locationCode) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Fail to get LocationCode for front cable: " | |
| << ec.what(); | |
| return; | |
| } | |
| // Get Chassis location code | |
| managedStore::ManagedObjectStoreContext requestContext2(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.EntityManager", | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId, | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", | |
| "LocationCode", requestContext2, | |
| [asyncResp, locationCode, chassisId]( | |
| const boost::system::error_code& ec2, | |
| const std::string& chassisLocationCode) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR << "Fail to get LocationCode for chassis: " | |
| << ec2.what(); | |
| return; | |
| } | |
| asyncResp->res | |
| .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| locationCode; | |
| asyncResp->res | |
| .jsonValue["Location"]["PartLocation"]["LocationType"] = | |
| "Slot"; | |
| asyncResp->res.jsonValue["Location"]["PartLocationContext"] = | |
| chassisLocationCode; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["ConnectionPath"] = | |
| "DIRECT"; | |
| asyncResp->res | |
| .jsonValue["Location"]["Oem"]["Google"]["Devpath"] = | |
| "/phys/" + chassisLocationCode + "/" + locationCode; | |
| // recursively find the full oem devpath and PartLocationContext | |
| redfish::location_util::getLocationContextWithOemDevpath( | |
| asyncResp, "/Location"_json_pointer, | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId + | |
| "/contained_by"); | |
| }); | |
| }); | |
| } else if (std::string(bmcSidePathIntf).find(portId) != std::string::npos) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.Configuration.Port"}; | |
| managedStore::ManagedObjectStoreContext requestContext3(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/xyz/openbmc_project/inventory/system/cable/", 0, interfaces, | |
| requestContext3, | |
| [asyncResp, portId]( | |
| const boost::system::error_code& ec3, | |
| const dbus::utility::MapperGetSubTreePathsResponse& cableList) { | |
| if (ec3) { | |
| BMCWEB_LOG_ERROR << "Fail to get subtree for cables: " | |
| << ec3.what(); | |
| return; | |
| } | |
| for (const auto& pathStr : cableList) { | |
| if (pathStr.size() - portId.size() > 0 && | |
| pathStr.substr(pathStr.size() - portId.size()) == portId) { | |
| // get the cable location code | |
| managedStore::ManagedObjectStoreContext requestContext4( | |
| asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.EntityManager", | |
| pathStr.substr(0, pathStr.size() - portId.size() - 1), | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", | |
| "LocationCode", requestContext4, | |
| [asyncResp](const boost::system::error_code& ec4, | |
| const std::string& cableLocationCode) { | |
| if (ec4) { | |
| BMCWEB_LOG_ERROR << "Fail to get LocationCode for cable: " | |
| << ec4.what(); | |
| return; | |
| } | |
| asyncResp->res | |
| .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| cableLocationCode; | |
| asyncResp->res | |
| .jsonValue["Location"]["Oem"]["Google"]["Devpath"] = | |
| "/phys/" + cableLocationCode; | |
| asyncResp->res | |
| .jsonValue["Oem"]["Google"]["ConnectionPath"] = "SIDE"; | |
| }); | |
| break; | |
| } | |
| } | |
| }); | |
| } else { | |
| // usb sideband using ifalias | |
| std::string ifalias; | |
| if (readOneLineFile( | |
| std::filesystem::path(netSysfsPath) / portId / "ifalias", | |
| ifalias)) { | |
| if (ifalias.size() >= 5 && | |
| ifalias.compare(ifalias.size() - 5, 5, "-side") == 0) { | |
| managedStore::ManagedObjectStoreContext requestContext5(asyncResp); | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| "xyz.openbmc_project.EntityManager", | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId + "/" + | |
| portId, | |
| "xyz.openbmc_project.Configuration.BmcNet_Side", requestContext5, | |
| [asyncResp]( | |
| const boost::system::error_code& ec5, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec5) { | |
| BMCWEB_LOG_ERROR << "GetAllProperties failed: " << ec5.what(); | |
| return; | |
| } | |
| const std::string* locationCode = nullptr; | |
| const std::string* partLocationContext = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), propertiesList, | |
| "LocationCode", locationCode, "PartLocationContext", | |
| partLocationContext); | |
| if (!success) { | |
| BMCWEB_LOG_ERROR << "Failed to unpack BmcNet_Side properties"; | |
| return; | |
| } | |
| if (locationCode != nullptr) { | |
| asyncResp->res | |
| .jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| *locationCode; | |
| } | |
| if (partLocationContext != nullptr) { | |
| asyncResp->res.jsonValue["Location"]["PartLocationContext"] = | |
| *partLocationContext; | |
| } | |
| asyncResp->res.jsonValue["Oem"]["Google"]["ConnectionPath"] = | |
| "SIDEBAND"; | |
| }); | |
| } | |
| } | |
| } | |
| std::vector<std::string> ethList = sysfsGetEthAddrList(portId); | |
| asyncResp->res.jsonValue["Ethernet"]["AssociatedMACAddresses"] = | |
| std::move(ethList); | |
| asyncResp->res.jsonValue["LinkState"] = sysfsGetLinkState(portId); | |
| if (asyncResp->res.jsonValue["LinkState"] == "Enabled") { | |
| asyncResp->res.jsonValue["LinkStatus"] = "LinkUp"; | |
| } else { | |
| asyncResp->res.jsonValue["LinkStatus"] = "LinkDown"; | |
| } | |
| asyncResp->res.jsonValue["CurrentSpeedGbps"] = sysfsGetSpeedGbps(portId); | |
| asyncResp->res.jsonValue["Name"] = portId; | |
| } | |
| inline void setPortLocationFields( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| std::string_view serviceLabel, std::string_view locationType, | |
| std::string_view parentLocationCode) { | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| serviceLabel; | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] = | |
| locationType; | |
| asyncResp->res.jsonValue["Location"]["PartLocationContext"] = | |
| parentLocationCode; | |
| if (locationType == "Embedded") { | |
| asyncResp->res.jsonValue["Location"]["Oem"]["Google"]["Devpath"] = | |
| absl::StrCat("/phys/", parentLocationCode, ":device:", serviceLabel); | |
| } else { | |
| asyncResp->res.jsonValue["Location"]["Oem"]["Google"]["Devpath"] = | |
| absl::StrCat("/phys/", parentLocationCode, "/", serviceLabel); | |
| } | |
| } | |
| inline void populateNetworkPortLocationDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& portId, | |
| const std::string& serviceName, const std::string& networkPortPath) { | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| auto onServiceLabelResolved = [asyncResp, chassisId, | |
| requestContext](std::string serviceLabel) { | |
| chassis_utils::getValidChassisPath( | |
| asyncResp, chassisId, | |
| [asyncResp, chassisId, serviceLabel, | |
| requestContext](const std::optional<std::string>& chassisPath) { | |
| if (!chassisPath || chassisPath->empty()) { | |
| BMCWEB_LOG_DEBUG << "Chassis path not found for " << chassisId; | |
| return; | |
| } | |
| constexpr std::array<std::string_view, 2> interfaces = { | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", | |
| "xyz.openbmc_project.Inventory.Decorator.Location"}; | |
| dbus_utils::getDbusObject( | |
| *chassisPath, interfaces, requestContext, | |
| [asyncResp, chassisPath = *chassisPath, chassisId, serviceLabel, | |
| requestContext](const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetObject& object) { | |
| if (ec || object.empty()) { | |
| BMCWEB_LOG_DEBUG << "Failed to get D-Bus object for chassis: " | |
| << ec.message(); | |
| return; | |
| } | |
| std::string locationCodeService; | |
| std::string locationTypeService; | |
| for (const auto& [svc, ifaces] : object) { | |
| for (const auto& iface : ifaces) { | |
| if (iface == | |
| "xyz.openbmc_project.Inventory.Decorator." | |
| "LocationCode") { | |
| locationCodeService = svc; | |
| } | |
| if (iface == | |
| "xyz.openbmc_project.Inventory.Decorator.Location") { | |
| locationTypeService = svc; | |
| } | |
| } | |
| } | |
| if (locationCodeService.empty()) { | |
| locationCodeService = object.begin()->first; | |
| } | |
| if (locationTypeService.empty()) { | |
| locationTypeService = locationCodeService; | |
| } | |
| dbus_utils::getProperty<std::string>( | |
| locationCodeService, chassisPath, | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", | |
| "LocationCode", requestContext, | |
| [asyncResp, chassisPath, chassisId, serviceLabel, | |
| locationTypeService, requestContext]( | |
| const boost::system::error_code& ecLoc, | |
| const std::string& chassisLocationCodeProp) { | |
| std::string chassisLocationCode = | |
| (!ecLoc && !chassisLocationCodeProp.empty()) | |
| ? chassisLocationCodeProp | |
| : chassisId; | |
| dbus_utils::getProperty<std::string>( | |
| locationTypeService, chassisPath, | |
| "xyz.openbmc_project.Inventory.Decorator.Location", | |
| "LocationType", requestContext, | |
| [asyncResp, chassisPath, serviceLabel, | |
| chassisLocationCode]( | |
| const boost::system::error_code& /*ecType*/, | |
| const std::string& chassisLocationTypeProp) { | |
| std::string locationType = "Embedded"; | |
| if (absl::StrContains(chassisLocationTypeProp, | |
| "Slot")) { | |
| locationType = "Slot"; | |
| } else if (absl::StrContains( | |
| chassisLocationTypeProp, | |
| "Embedded")) { | |
| locationType = "Embedded"; | |
| } | |
| chassis_utils::getAssociatedChassisSubtree( | |
| asyncResp, chassisPath + "/contained_by", | |
| [asyncResp, serviceLabel, locationType, | |
| chassisLocationCode]( | |
| const std::shared_ptr<bmcweb::AsyncResp>& | |
| asyncResp2, | |
| const dbus::utility:: | |
| MapperGetSubTreeResponse& subtree) { | |
| if (asyncResp2->res.result() != | |
| boost::beast::http::status::ok || | |
| asyncResp2->res.jsonValue.contains( | |
| "error")) { | |
| return; | |
| } | |
| if (subtree.empty()) { | |
| setPortLocationFields( | |
| asyncResp2, serviceLabel, locationType, | |
| chassisLocationCode); | |
| return; | |
| } | |
| const std::string& carrierPath = | |
| subtree.begin()->first; | |
| const dbus::utility::MapperServiceMap& | |
| serviceMap = subtree.begin()->second; | |
| if (serviceMap.empty()) { | |
| return; | |
| } | |
| const std::string& carrierService = | |
| serviceMap.begin()->first; | |
| managedStore::ManagedObjectStoreContext ctx( | |
| asyncResp2); | |
| dbus_utils::getProperty<std::string>( | |
| carrierService, carrierPath, | |
| "xyz.openbmc_project.Inventory.Decorator." | |
| "LocationCode", | |
| "LocationCode", ctx, | |
| [asyncResp2, carrierPath, serviceLabel, | |
| locationType]( | |
| const boost::system::error_code& ec2, | |
| const std::string& carrierLocCode) { | |
| if (asyncResp2->res.result() != | |
| boost::beast::http::status:: | |
| ok || | |
| asyncResp2->res.jsonValue.contains( | |
| "error")) { | |
| return; | |
| } | |
| std::string carrierLocationCode = | |
| (!ec2 && !carrierLocCode.empty()) | |
| ? carrierLocCode | |
| : sdbusplus::message:: | |
| object_path(carrierPath) | |
| .filename(); | |
| setPortLocationFields( | |
| asyncResp2, serviceLabel, | |
| locationType, carrierLocationCode); | |
| redfish::location_util:: | |
| getLocationContextWithOemDevpath( | |
| asyncResp2, | |
| "/Location"_json_pointer, | |
| carrierPath + "/contained_by", | |
| carrierLocationCode); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }; | |
| // 1. Check if the port D-Bus object publishes its own LocationCode decorator | |
| // (e.g. from Entity-Manager PortLocationCodeMap). | |
| dbus_utils::getProperty<std::string>( | |
| serviceName, networkPortPath, | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", | |
| requestContext, | |
| [onServiceLabelResolved, portId]( | |
| const boost::system::error_code& ecPortLoc, | |
| const std::string& portLocationCode) { | |
| if (!ecPortLoc && !portLocationCode.empty()) { | |
| onServiceLabelResolved(portLocationCode); | |
| } else { | |
| onServiceLabelResolved(portId); | |
| } | |
| }); | |
| } | |
| inline void buildNetworkPortDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId, const DBusEndpoint& pldmEndpoint, | |
| const DBusEndpoint& nsmEndpoint) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#Port.v1_9_0.Port"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", portId); | |
| asyncResp->res.jsonValue["Name"] = portId; | |
| asyncResp->res.jsonValue["Id"] = portId; | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| asyncResp->res.jsonValue["Status"]["Health"] = "OK"; | |
| asyncResp->res.jsonValue["Metrics"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "NetworkAdapters", networkAdapterId, "Ports", | |
| portId, "Metrics"); | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| if (pldmEndpoint.first.empty() && nsmEndpoint.first.empty()) { | |
| messages::resourceNotFound(asyncResp->res, "#Port.v1_9_0.Port", portId); | |
| return; | |
| } | |
| const DBusEndpoint& decoratorEndpoint = | |
| !nsmEndpoint.first.empty() ? nsmEndpoint : pldmEndpoint; | |
| if (!decoratorEndpoint.first.empty()) { | |
| populateNetworkPortLocationDBus(asyncResp, chassisId, portId, | |
| decoratorEndpoint.second, | |
| decoratorEndpoint.first); | |
| } | |
| size_t totalProviders = 0; | |
| if (!pldmEndpoint.first.empty() && !pldmEndpoint.second.empty()) { | |
| ++totalProviders; | |
| } | |
| if (!nsmEndpoint.first.empty() && !nsmEndpoint.second.empty()) { | |
| ++totalProviders; | |
| } | |
| if (totalProviders == 0) { | |
| BMCWEB_LOG_ERROR << "No valid provider service names found for port " | |
| << portId; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| auto pendingProviders = std::make_shared<size_t>(totalProviders); | |
| auto onProviderDone = [asyncResp, pendingProviders]() { | |
| if (*pendingProviders == 0) { | |
| return; | |
| } | |
| --(*pendingProviders); | |
| if (*pendingProviders == 0) { | |
| if (asyncResp->res.result() != boost::beast::http::status::ok || | |
| asyncResp->res.jsonValue.contains("error")) { | |
| return; | |
| } | |
| // Only return 500 error if required link telemetry is not available | |
| // from either PLDM or NSM. | |
| if (!asyncResp->res.jsonValue.contains("LinkState") || | |
| !asyncResp->res.jsonValue.contains("LinkStatus") || | |
| !asyncResp->res.jsonValue.contains("CurrentSpeedGbps")) { | |
| BMCWEB_LOG_ERROR << "Required port telemetry properties missing from " | |
| "both PLDM and NSM"; | |
| messages::internalError(asyncResp->res); | |
| } | |
| } | |
| }; | |
| // 1. PLDM: Authoritative source for link telemetry. Blindly writes to the | |
| // returned resource structure. | |
| if (!pldmEndpoint.first.empty() && !pldmEndpoint.second.empty()) { | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| pldmEndpoint.second, pldmEndpoint.first, portInventoryIntf, | |
| requestContext, | |
| [asyncResp, pldmEndpoint, onProviderDone]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG | |
| << "getAllProperties on path " << pldmEndpoint.first | |
| << " iface " << portInventoryIntf << " service " | |
| << pldmEndpoint.second << " failed with code " << ec.what(); | |
| onProviderDone(); | |
| return; | |
| } | |
| const std::string* linkState = nullptr; | |
| const std::string* linkStatus = nullptr; | |
| const double* currentSpeedGbps = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "LinkState", | |
| linkState, "LinkStatus", linkStatus, "CurrentSpeedGbps", | |
| currentSpeedGbps); | |
| if (!success) { | |
| BMCWEB_LOG_DEBUG << "Failed to unpack " << portInventoryIntf; | |
| onProviderDone(); | |
| return; | |
| } | |
| if (asyncResp->res.result() != boost::beast::http::status::ok || | |
| asyncResp->res.jsonValue.contains("error")) { | |
| onProviderDone(); | |
| return; | |
| } | |
| if (linkState != nullptr && !linkState->empty()) { | |
| asyncResp->res.jsonValue["LinkState"] = *linkState; | |
| } | |
| if (linkStatus != nullptr && !linkStatus->empty()) { | |
| asyncResp->res.jsonValue["LinkStatus"] = *linkStatus; | |
| } | |
| if (currentSpeedGbps != nullptr && std::isfinite(*currentSpeedGbps) && | |
| *currentSpeedGbps > 0.0) { | |
| asyncResp->res.jsonValue["CurrentSpeedGbps"] = *currentSpeedGbps; | |
| } else if (linkStatus != nullptr && !linkStatus->empty()) { | |
| asyncResp->res.jsonValue["CurrentSpeedGbps"] = 0.0; | |
| } | |
| onProviderDone(); | |
| }); | |
| } | |
| // 2. NSM: Extended telemetry source. Conditionally writes only if there is | |
| // nothing already populated. | |
| if (!nsmEndpoint.first.empty() && !nsmEndpoint.second.empty()) { | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| nsmEndpoint.second, nsmEndpoint.first, portInventoryIntf, | |
| requestContext, | |
| [asyncResp, nsmEndpoint, onProviderDone]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "getAllProperties on path " << nsmEndpoint.first | |
| << " iface " << portInventoryIntf << " service " | |
| << nsmEndpoint.second << " failed with code " | |
| << ec.what(); | |
| onProviderDone(); | |
| return; | |
| } | |
| const std::string* linkState = nullptr; | |
| const std::string* linkStatus = nullptr; | |
| const double* currentSpeedGbps = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), propertiesList, "LinkState", | |
| linkState, "LinkStatus", linkStatus, "CurrentSpeedGbps", | |
| currentSpeedGbps); | |
| if (!success) { | |
| onProviderDone(); | |
| return; | |
| } | |
| if (asyncResp->res.result() != boost::beast::http::status::ok || | |
| asyncResp->res.jsonValue.contains("error")) { | |
| onProviderDone(); | |
| return; | |
| } | |
| if (linkState != nullptr && !linkState->empty() && | |
| !asyncResp->res.jsonValue.contains("LinkState")) { | |
| asyncResp->res.jsonValue["LinkState"] = *linkState; | |
| } | |
| if (linkStatus != nullptr && !linkStatus->empty() && | |
| !asyncResp->res.jsonValue.contains("LinkStatus")) { | |
| asyncResp->res.jsonValue["LinkStatus"] = *linkStatus; | |
| } | |
| if (currentSpeedGbps != nullptr && std::isfinite(*currentSpeedGbps) && | |
| *currentSpeedGbps > 0.0 && | |
| !asyncResp->res.jsonValue.contains("CurrentSpeedGbps")) { | |
| asyncResp->res.jsonValue["CurrentSpeedGbps"] = *currentSpeedGbps; | |
| } else if (linkStatus != nullptr && !linkStatus->empty() && | |
| !asyncResp->res.jsonValue.contains("CurrentSpeedGbps")) { | |
| asyncResp->res.jsonValue["CurrentSpeedGbps"] = 0.0; | |
| } | |
| onProviderDone(); | |
| }); | |
| } | |
| } | |
| inline void handleNetworkPortGetDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| constexpr std::array<std::string_view, 1> interfaces = {portInventoryIntf}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| inventoryPrefix, 0, interfaces, requestContext, | |
| [asyncResp, chassisId, networkAdapterId, portId, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| if (ec == boost::system::errc::host_unreachable) { | |
| messages::resourceNotFound(asyncResp->res, "#Port.v1_9_0.Port", | |
| portId); | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "Failed to get " << portInventoryIntf | |
| << " due to:" << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| PortProviderEndpoints portEndpoints = findPortProviderEndpoints( | |
| subtree, chassisId, networkAdapterId, portId); | |
| if (!portEndpoints.pldm.first.empty() || | |
| !portEndpoints.nsm.first.empty()) { | |
| buildNetworkPortDBus(asyncResp, chassisId, networkAdapterId, portId, | |
| portEndpoints.pldm, portEndpoints.nsm); | |
| return; | |
| } | |
| messages::resourceNotFound(asyncResp->res, "#Port.v1_9_0.Port", portId); | |
| }); | |
| } | |
| inline void handleNetworkPortGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| handleChassisNetworkAdapterCommon( | |
| app, req, asyncResp, chassisId, handleNetworkPortGetSysfs, | |
| handleNetworkPortGetDBus, networkAdapterId, portId); | |
| } | |
| inline void requestRoutesNetworkPort(App& app) { | |
| BMCWEB_ROUTE(app, | |
| "/redfish/v1/Chassis/<str>/NetworkAdapters/<str>/Ports/<str>/") | |
| .privileges(redfish::privileges::getPort) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleNetworkPortGet, std::ref(app))); | |
| } | |
| inline void handleNetworkPortMetricGetSysfs( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#PortMetrics.PortMetrics"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", portId, "Metrics"); | |
| asyncResp->res.jsonValue["Name"] = "Network port metrics"; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["Networking"] = | |
| sysfsGetNetworkPortMetrics(portId); | |
| } | |
| inline void buildNetworkPortMetricDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId, const std::string& serviceName, | |
| const std::string& networkPortPath) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#PortMetrics.v1_3_0.PortMetrics"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters", | |
| networkAdapterId, "Ports", portId, "Metrics"); | |
| asyncResp->res.jsonValue["Name"] = portId + " Metrics"; | |
| asyncResp->res.jsonValue["Id"] = "Metrics"; | |
| asyncResp->res.jsonValue["CorrectableBitErrors"] = 0; | |
| asyncResp->res.jsonValue["SymbolErrors"] = 0; | |
| asyncResp->res.jsonValue["RXBroadcastPackets"] = 0; | |
| asyncResp->res.jsonValue["RXMulticastPackets"] = 0; | |
| asyncResp->res.jsonValue["RXUnicastPackets"] = 0; | |
| asyncResp->res.jsonValue["TXBroadcastPackets"] = 0; | |
| asyncResp->res.jsonValue["TXMulticastPackets"] = 0; | |
| asyncResp->res.jsonValue["TXUnicastPackets"] = 0; | |
| asyncResp->res | |
| .jsonValue["Oem"]["Google"]["TransceiverTelemetry"]["FECCorrectedCount"] = | |
| 0; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| ["FECUncorrectableCount"] = 0; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| ["PerLaneFECCounters"] = nlohmann::json::array(); | |
| asyncResp->res | |
| .jsonValue["Oem"]["Google"]["TransceiverTelemetry"]["FECHistogramBins"] = | |
| nlohmann::json::array(); | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| // Query PortECC metrics | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, "xyz.openbmc_project.Metrics.PortECC", | |
| requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| uint64_t numVal = 0; | |
| bool hasNum = false; | |
| std::visit( | |
| [&numVal, &hasNum](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| numVal = static_cast<uint64_t>(v); | |
| hasNum = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasNum) { | |
| if (propName == "CorrectedBits") { | |
| asyncResp->res.jsonValue["CorrectableBitErrors"] = numVal; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| ["FECCorrectedCount"] = numVal; | |
| } else if (propName == "SymbolErrorRXBytes") { | |
| asyncResp->res.jsonValue["SymbolErrors"] = numVal; | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| ["FECUncorrectableCount"] = numVal; | |
| } | |
| } | |
| if (propName == "RawErrorsPerLane") { | |
| std::visit( | |
| [asyncResp](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_same_v<T, std::vector<uint32_t>> || | |
| std::is_same_v<T, std::vector<uint64_t>> || | |
| std::is_same_v<T, std::vector<int32_t>> || | |
| std::is_same_v<T, std::vector<int64_t>>) { | |
| std::vector<uint64_t> uCounters; | |
| uCounters.reserve(v.size()); | |
| for (auto elem : v) { | |
| uCounters.push_back(static_cast<uint64_t>(elem)); | |
| } | |
| asyncResp->res | |
| .jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| ["PerLaneFECCounters"] = uCounters; | |
| } | |
| }, | |
| propValue); | |
| } | |
| } | |
| }); | |
| // Query PortPacketCounters | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, | |
| "xyz.openbmc_project.Metrics.PortPacketCounters", requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| uint64_t numVal = 0; | |
| bool hasNum = false; | |
| std::visit( | |
| [&numVal, &hasNum](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| numVal = static_cast<uint64_t>(v); | |
| hasNum = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasNum) { | |
| if (propName == "RXBroadcastPkts") { | |
| asyncResp->res.jsonValue["RXBroadcastPackets"] = numVal; | |
| } else if (propName == "RXMulticastPkts") { | |
| asyncResp->res.jsonValue["RXMulticastPackets"] = numVal; | |
| } else if (propName == "RXUnicastPkts") { | |
| asyncResp->res.jsonValue["RXUnicastPackets"] = numVal; | |
| } else if (propName == "TXBroadcastPkts") { | |
| asyncResp->res.jsonValue["TXBroadcastPackets"] = numVal; | |
| } else if (propName == "TXMulticastPkts") { | |
| asyncResp->res.jsonValue["TXMulticastPackets"] = numVal; | |
| } else if (propName == "TXUnicastPkts") { | |
| asyncResp->res.jsonValue["TXUnicastPackets"] = numVal; | |
| } | |
| } | |
| } | |
| }); | |
| // Query EthPort metrics | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, "xyz.openbmc_project.Metrics.EthPort", | |
| requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| uint64_t numVal = 0; | |
| bool hasNum = false; | |
| std::visit( | |
| [&numVal, &hasNum](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| numVal = static_cast<uint64_t>(v); | |
| hasNum = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasNum) { | |
| if (propName == "RXFCSErrors") { | |
| asyncResp->res.jsonValue["RXFCSErrors"] = numVal; | |
| } else if (propName == "RXAlignmentErrors") { | |
| asyncResp->res.jsonValue["RXAlignmentErrors"] = numVal; | |
| } else if (propName == "RXFalseCarrierDetections") { | |
| asyncResp->res.jsonValue["RXFalseCarrierDetections"] = numVal; | |
| } else if (propName == "RXRuntPkts") { | |
| asyncResp->res.jsonValue["RXRuntPackets"] = numVal; | |
| } else if (propName == "RXJabberPkts") { | |
| asyncResp->res.jsonValue["RXJabberPackets"] = numVal; | |
| } else if (propName == "RXXOFFFrames") { | |
| asyncResp->res.jsonValue["PauseXOFFFramesReceived"] = numVal; | |
| } else if (propName == "RXXONFrames") { | |
| asyncResp->res.jsonValue["PauseXONFramesReceived"] = numVal; | |
| } else if (propName == "TXXOFFFrames") { | |
| asyncResp->res.jsonValue["PauseXOFFFramesTransmitted"] = numVal; | |
| } else if (propName == "TXXONFrames") { | |
| asyncResp->res.jsonValue["PauseXONFramesTransmitted"] = numVal; | |
| } else if (propName == "TXSingleCollisionFrames") { | |
| asyncResp->res.jsonValue["SingleCollisionTransmitFrames"] = | |
| numVal; | |
| } else if (propName == "TXMultipleCollisionFrames") { | |
| asyncResp->res.jsonValue["MultipleCollisionTransmitFrames"] = | |
| numVal; | |
| } else if (propName == "TXLateCollisionFrames") { | |
| asyncResp->res.jsonValue["LateCollisionFrames"] = numVal; | |
| } else if (propName == "TXExcessCollisionFrames") { | |
| asyncResp->res.jsonValue["ExcessiveCollisionFrames"] = numVal; | |
| } | |
| } | |
| } | |
| }); | |
| // Query IBPort metrics | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, "xyz.openbmc_project.Metrics.IBPort", | |
| requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| if (propName == "BitErrorRate" || propName == "EffectiveBER" || | |
| propName == "TotalRawBER") { | |
| double dVal = 0.0; | |
| bool hasD = false; | |
| std::visit( | |
| [&dVal, &hasD](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_floating_point_v<T> || | |
| std::is_arithmetic_v<T>) { | |
| dVal = static_cast<double>(v); | |
| hasD = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasD) { | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TransceiverTelemetry"] | |
| [propName] = dVal; | |
| } | |
| } else if (propName == "LinkDownCount") { | |
| uint64_t numVal = 0; | |
| bool hasNum = false; | |
| std::visit( | |
| [&numVal, &hasNum](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| numVal = static_cast<uint64_t>(v); | |
| hasNum = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasNum) { | |
| asyncResp->res.jsonValue["LinkDownCount"] = numVal; | |
| } | |
| } | |
| } | |
| }); | |
| // Query PortMetricsOem2 (RXBytes, TXBytes) | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, | |
| "xyz.openbmc_project.Metrics.PortMetricsOem2", requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| uint64_t numVal = 0; | |
| bool hasNum = false; | |
| std::visit( | |
| [&numVal, &hasNum](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| numVal = static_cast<uint64_t>(v); | |
| hasNum = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasNum) { | |
| if (propName == "RXBytes") { | |
| asyncResp->res.jsonValue["RXBytes"] = numVal; | |
| } else if (propName == "TXBytes") { | |
| asyncResp->res.jsonValue["TXBytes"] = numVal; | |
| } | |
| } | |
| } | |
| }); | |
| // Query PCIeECC | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| serviceName, networkPortPath, "xyz.openbmc_project.PCIe.PCIeECC", | |
| requestContext, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| if (ec) { | |
| return; | |
| } | |
| nlohmann::json& pcieObj = | |
| asyncResp->res.jsonValue["Oem"]["Google"]["PCIeErrors"]; | |
| for (const auto& [propName, propValue] : propertiesList) { | |
| uint64_t val = 0; | |
| bool hasVal = false; | |
| std::visit( | |
| [&val, &hasVal](const auto& v) { | |
| using T = std::decay_t<decltype(v)>; | |
| if constexpr (std::is_arithmetic_v<T>) { | |
| val = static_cast<uint64_t>(v); | |
| hasVal = true; | |
| } | |
| }, | |
| propValue); | |
| if (hasVal) { | |
| pcieObj[propName] = val; | |
| } | |
| } | |
| }); | |
| } | |
| inline void handleNetworkPortMetricGetDBus( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| constexpr std::array<std::string_view, 1> interfaces = {portInventoryIntf}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| inventoryPrefix, 0, interfaces, requestContext, | |
| [asyncResp, chassisId, networkAdapterId, portId, requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| if (ec == boost::system::errc::host_unreachable) { | |
| messages::resourceNotFound( | |
| asyncResp->res, "#PortMetrics.v1_3_0.PortMetrics", portId); | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "Failed to get " << portInventoryIntf | |
| << " due to:" << ec.what(); | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| auto [matchingPath, matchingService] = | |
| findMatchingPortPath(subtree, chassisId, networkAdapterId, portId); | |
| if (!matchingPath.empty()) { | |
| buildNetworkPortMetricDBus(asyncResp, chassisId, networkAdapterId, | |
| portId, matchingService, matchingPath); | |
| return; | |
| } | |
| messages::resourceNotFound(asyncResp->res, | |
| "#PortMetrics.v1_3_0.PortMetrics", portId); | |
| }); | |
| } | |
| inline void handleNetworkPortMetricGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& networkAdapterId, | |
| const std::string& portId) { | |
| handleChassisNetworkAdapterCommon( | |
| app, req, asyncResp, chassisId, handleNetworkPortMetricGetSysfs, | |
| handleNetworkPortMetricGetDBus, networkAdapterId, portId); | |
| } | |
| inline void requestRoutesNetworkPortMetrics(App& app) { | |
| BMCWEB_ROUTE( | |
| app, | |
| "/redfish/v1/Chassis/<str>/NetworkAdapters/<str>/Ports/<str>/Metrics") | |
| .privileges(redfish::privileges::getPortMetrics) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleNetworkPortMetricGet, std::ref(app))); | |
| } | |
| } // namespace redfish | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_NETWORK_ADAPTER_H_ |