blob: e4fde6cda0079cef4c1d8f6e7a63c70a9b1eadc2 [file]
#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_