diff options
Diffstat (limited to 'meta-ibs/meta-common/recipes-phosphor/interfaces/bmcweb/0010-Support-setting-IPMI-and-SSH-port-via-redfish.patch')
-rw-r--r-- | meta-ibs/meta-common/recipes-phosphor/interfaces/bmcweb/0010-Support-setting-IPMI-and-SSH-port-via-redfish.patch | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/meta-ibs/meta-common/recipes-phosphor/interfaces/bmcweb/0010-Support-setting-IPMI-and-SSH-port-via-redfish.patch b/meta-ibs/meta-common/recipes-phosphor/interfaces/bmcweb/0010-Support-setting-IPMI-and-SSH-port-via-redfish.patch new file mode 100644 index 0000000000..015be55b61 --- /dev/null +++ b/meta-ibs/meta-common/recipes-phosphor/interfaces/bmcweb/0010-Support-setting-IPMI-and-SSH-port-via-redfish.patch @@ -0,0 +1,701 @@ +From 6af17d9d37ed4ef06012d22fa91e477633cfb882 Mon Sep 17 00:00:00 2001 +From: Nikita Kosenkov <NKosenkov@IBS.RU> +Date: Fri, 16 Sep 2022 10:54:15 +0300 +Subject: [PATCH] Support setting IPMI and SSH port via redfish + +--- + meson.build | 1 + + redfish-core/include/utils/service_utils.hpp | 270 +++++++++++++++++++ + redfish-core/lib/network_protocol.hpp | 146 +++------- + redfish-core/lib/redfish_util.hpp | 128 --------- + redfish-core/ut/service_utils_test.cpp | 33 +++ + 5 files changed, 342 insertions(+), 236 deletions(-) + create mode 100644 redfish-core/include/utils/service_utils.hpp + create mode 100644 redfish-core/ut/service_utils_test.cpp + +diff --git a/meson.build b/meson.build +index c1d253fb..7e01c9df 100644 +--- a/meson.build ++++ b/meson.build +@@ -426,6 +426,7 @@ srcfiles_unittest = [ + 'redfish-core/ut/lock_test.cpp', + 'redfish-core/ut/privileges_test.cpp', + 'redfish-core/ut/registries_test.cpp', ++ 'redfish-core/ut/service_utils_test.cpp', + 'redfish-core/ut/stl_utils_test.cpp', + 'redfish-core/ut/time_utils_test.cpp', + 'src/crow_getroutes_test.cpp', +diff --git a/redfish-core/include/utils/service_utils.hpp b/redfish-core/include/utils/service_utils.hpp +new file mode 100644 +index 00000000..0ae93732 +--- /dev/null ++++ b/redfish-core/include/utils/service_utils.hpp +@@ -0,0 +1,270 @@ ++#pragma once ++ ++#include "app.hpp" ++#include "dbus_utility.hpp" ++#include "error_messages.hpp" ++ ++#include <nlohmann/json.hpp> ++#include <sdbusplus/asio/property.hpp> ++#include <sdbusplus/message/types.hpp> ++ ++#include <string> ++#include <string_view> ++ ++namespace redfish ++{ ++namespace service_util ++{ ++namespace details ++{ ++ ++bool matchService(const sdbusplus::message::object_path& objPath, ++ const std::string_view serviceName) ++{ ++ // For service named as <unitName>@<instanceName>, only compare the unitName ++ // part. Object path is automatically decoded as it is encoded by sdbusplus. ++ std::string fullUnitName = objPath.filename(); ++ size_t pos = fullUnitName.rfind('@'); ++ return std::string_view(fullUnitName).substr(0, pos) == serviceName; ++} ++ ++enum class FindError ++{ ++ Ok, ++ NotFound, ++ DBusError, ++}; ++ ++template <typename Callback> ++inline void findMatchedServices(const std::string& serviceName, ++ Callback&& callback) ++{ ++ crow::connections::systemBus->async_method_call( ++ [serviceName, ++ callback](const boost::system::error_code ec, ++ const dbus::utility::ManagedObjectType& objects) { ++ if (ec) ++ { ++ callback(FindError::DBusError, nullptr); ++ return; ++ } ++ ++ bool serviceFound = false; ++ for (const auto& object : objects) ++ { ++ if (!matchService(object.first, serviceName)) ++ { ++ continue; ++ } ++ ++ serviceFound = true; ++ // The return value indicates whether to break the loop or not, ++ // used for get property ++ if (callback(FindError::Ok, &object)) ++ { ++ return; ++ } ++ } ++ ++ if (!serviceFound) ++ { ++ callback(FindError::NotFound, nullptr); ++ } ++ }, ++ "xyz.openbmc_project.Control.Service.Manager", ++ "/xyz/openbmc_project/control/service", ++ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); ++} ++ ++template <typename T> ++inline T ++ getPropertyFromInterface(const dbus::utility::DBusInteracesMap& interfaces, ++ const std::string& interfaceName, ++ const std::string& propertyName) ++{ ++ for (const auto& [interface, properties] : interfaces) ++ { ++ if (interface != interfaceName) ++ { ++ continue; ++ } ++ ++ for (const auto& [key, val] : properties) ++ { ++ if (key != propertyName) ++ { ++ continue; ++ } ++ ++ const auto* value = std::get_if<T>(&val); ++ if (value != nullptr) ++ { ++ return *value; ++ } ++ return T{}; ++ } ++ } ++ return T{}; ++} ++ ++void setEnabledAndRunning( ++ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const dbus::utility::ManagedObjectType::value_type* object, bool enabled) ++{ ++ auto errorCallback = [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }; ++ sdbusplus::asio::setProperty( ++ *crow::connections::systemBus, ++ "xyz.openbmc_project.Control.Service.Manager", object->first, ++ "xyz.openbmc_project.Control.Service.Attributes", "Running", enabled, ++ errorCallback); ++ sdbusplus::asio::setProperty( ++ *crow::connections::systemBus, ++ "xyz.openbmc_project.Control.Service.Manager", object->first, ++ "xyz.openbmc_project.Control.Service.Attributes", "Enabled", enabled, ++ errorCallback); ++} ++} // namespace details ++ ++void getEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& serviceName, ++ const nlohmann::json::json_pointer& valueJsonPtr) ++{ ++ details::findMatchedServices( ++ serviceName, ++ [asyncResp, valueJsonPtr]( ++ const details::FindError error, ++ const dbus::utility::ManagedObjectType::value_type* object) -> int { ++ if (error == details::FindError::DBusError) ++ { ++ messages::internalError(asyncResp->res); ++ return 0; ++ } ++ if (error == details::FindError::NotFound) ++ { ++ // Do nothing ++ return 0; ++ } ++ ++ bool enabled = details::getPropertyFromInterface<bool>( ++ object->second, ++ "xyz.openbmc_project.Control.Service.Attributes", "Running"); ++ asyncResp->res.jsonValue[valueJsonPtr] = enabled; ++ ++ // If one of the service instance is running, show it as Enabled ++ // in redfish. ++ if (enabled) ++ { ++ return 1; ++ } ++ return 0; ++ }); ++} ++ ++void getPortNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& serviceName, ++ const nlohmann::json::json_pointer& valueJsonPtr) ++{ ++ details::findMatchedServices( ++ serviceName, ++ [asyncResp, valueJsonPtr]( ++ const details::FindError error, ++ const dbus::utility::ManagedObjectType::value_type* object) -> int { ++ if (error == details::FindError::DBusError) ++ { ++ messages::internalError(asyncResp->res); ++ return 0; ++ } ++ if (error == details::FindError::NotFound) ++ { ++ // Do nothing ++ return 0; ++ } ++ ++ uint16_t port = details::getPropertyFromInterface<uint16_t>( ++ object->second, ++ "xyz.openbmc_project.Control.Service.SocketAttributes", "Port"); ++ asyncResp->res.jsonValue[valueJsonPtr] = port; ++ ++ // For service with multiple instances, return the port of first ++ // valid instance found as redfish only support one port value, they ++ // should be same ++ if (port != 0) ++ { ++ return 1; ++ } ++ return 0; ++ }); ++} ++ ++void setEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& propertyName, const std::string& serviceName, ++ const bool enabled) ++{ ++ details::findMatchedServices( ++ serviceName, ++ [asyncResp, propertyName, enabled]( ++ const details::FindError error, ++ const dbus::utility::ManagedObjectType::value_type* object) -> int { ++ if (error == details::FindError::DBusError) ++ { ++ messages::internalError(asyncResp->res); ++ return 0; ++ } ++ if (error == details::FindError::NotFound) ++ { ++ // The Redfish property will not be populated in if service is ++ // not found, return PropertyUnknown for PATCH request ++ messages::propertyUnknown(asyncResp->res, propertyName); ++ return 0; ++ } ++ ++ details::setEnabledAndRunning(asyncResp, object, enabled); ++ return 0; ++ }); ++} ++ ++void setPortNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, ++ const std::string& propertyName, ++ const std::string& serviceName, const uint16_t portNumber) ++{ ++ details::findMatchedServices( ++ serviceName, ++ [asyncResp, propertyName, portNumber]( ++ const details::FindError error, ++ const dbus::utility::ManagedObjectType::value_type* object) -> int { ++ if (error == details::FindError::DBusError) ++ { ++ messages::internalError(asyncResp->res); ++ return 0; ++ } ++ if (error == details::FindError::NotFound) ++ { ++ // The Redfish property will not be populated in if service is ++ // not found, return PropertyUnknown for PATCH request ++ messages::propertyUnknown(asyncResp->res, propertyName); ++ return 0; ++ } ++ ++ sdbusplus::asio::setProperty( ++ *crow::connections::systemBus, ++ "xyz.openbmc_project.Control.Service.Manager", object->first, ++ "xyz.openbmc_project.Control.Service.SocketAttributes", "Port", ++ portNumber, [asyncResp](const boost::system::error_code ec) { ++ if (ec) ++ { ++ messages::internalError(asyncResp->res); ++ return; ++ } ++ }); ++ return 0; ++ }); ++} ++ ++} // namespace service_util ++} // namespace redfish +diff --git a/redfish-core/lib/network_protocol.hpp b/redfish-core/lib/network_protocol.hpp +index f99cfd36..8b7597ff 100644 +--- a/redfish-core/lib/network_protocol.hpp ++++ b/redfish-core/lib/network_protocol.hpp +@@ -25,6 +25,7 @@ + #include <registries/privilege_registry.hpp> + #include <sdbusplus/asio/property.hpp> + #include <utils/json_utils.hpp> ++#include <utils/service_utils.hpp> + #include <utils/stl_utils.hpp> + + #include <optional> +@@ -44,6 +45,22 @@ static constexpr std::array<std::pair<const char*, const char*>, 3> + {"HTTPS", httpsServiceName}, + {"IPMI", ipmiServiceName}}}; + ++inline void ++ getServiceStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) ++{ ++ for (const auto& [protocolName, serviceName] : protocolToService) ++ { ++ service_util::getEnabled( ++ asyncResp, serviceName, ++ nlohmann::json::json_pointer(std::string("/") + protocolName + ++ "/ProtocolEnabled")); ++ service_util::getPortNumber( ++ asyncResp, serviceName, ++ nlohmann::json::json_pointer(std::string("/") + protocolName + ++ "/Port")); ++ } ++} ++ + inline void extractNTPServersAndDomainNamesData( + const dbus::utility::ManagedObjectType& dbusData, + std::vector<std::string>& ntpData, std::vector<std::string>& dnData) +@@ -136,6 +153,7 @@ inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + asyncResp->res.jsonValue["HostName"] = hostName; + + getNTPProtocolEnabled(asyncResp); ++ getServiceStatus(asyncResp); + + getEthernetIfaceData( + [hostName, asyncResp](const bool& success, +@@ -172,45 +190,7 @@ inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] = + "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; + } +- +- for (const auto& protocol : protocolToService) +- { +- const std::string& protocolName = protocol.first; +- const std::string& serviceName = protocol.second; +- getPortStatusAndPath( +- serviceName, +- [asyncResp, protocolName](const boost::system::error_code ec, +- const std::string& socketPath, +- bool isProtocolEnabled) { +- // If the service is not installed, that is not an error +- if (ec == boost::system::errc::no_such_process) +- { +- asyncResp->res.jsonValue[protocolName]["Port"] = +- nlohmann::detail::value_t::null; +- asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = +- false; +- return; +- } +- if (ec) +- { +- messages::internalError(asyncResp->res); +- return; +- } +- asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = +- isProtocolEnabled; +- getPortNumber(socketPath, [asyncResp, protocolName]( +- const boost::system::error_code ec2, +- int portNumber) { +- if (ec2) +- { +- messages::internalError(asyncResp->res); +- return; +- } +- asyncResp->res.jsonValue[protocolName]["Port"] = portNumber; +- }); +- }); +- } +-} // namespace redfish ++} + + inline void handleNTPProtocolEnabled( + const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) +@@ -363,61 +343,6 @@ inline void + "xyz.openbmc_project.Network.EthernetInterface"}); + } + +-inline void +- handleProtocolEnabled(const bool protocolEnabled, +- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, +- const std::string& netBasePath) +-{ +- crow::connections::systemBus->async_method_call( +- [protocolEnabled, asyncResp, +- netBasePath](const boost::system::error_code ec, +- const dbus::utility::MapperGetSubTreeResponse& subtree) { +- if (ec) +- { +- messages::internalError(asyncResp->res); +- return; +- } +- +- for (const auto& entry : subtree) +- { +- if (boost::algorithm::starts_with(entry.first, netBasePath)) +- { +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec2) { +- if (ec2) +- { +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- entry.second.begin()->first, entry.first, +- "org.freedesktop.DBus.Properties", "Set", +- "xyz.openbmc_project.Control.Service.Attributes", "Running", +- dbus::utility::DbusVariantType{protocolEnabled}); +- +- crow::connections::systemBus->async_method_call( +- [asyncResp](const boost::system::error_code ec2) { +- if (ec2) +- { +- messages::internalError(asyncResp->res); +- return; +- } +- }, +- entry.second.begin()->first, entry.first, +- "org.freedesktop.DBus.Properties", "Set", +- "xyz.openbmc_project.Control.Service.Attributes", "Enabled", +- dbus::utility::DbusVariantType{protocolEnabled}); +- } +- } +- }, +- "xyz.openbmc_project.ObjectMapper", +- "/xyz/openbmc_project/object_mapper", +- "xyz.openbmc_project.ObjectMapper", "GetSubTree", +- "/xyz/openbmc_project/control/service", 0, +- std::array<const char*, 1>{ +- "xyz.openbmc_project.Control.Service.Attributes"}); +-} +- + inline std::string getHostName() + { + std::string hostName; +@@ -457,14 +382,6 @@ inline void + }); + } + +-inline std::string encodeServiceObjectPath(const std::string& serviceName) +-{ +- sdbusplus::message::object_path objPath( +- "/xyz/openbmc_project/control/service"); +- objPath /= serviceName; +- return objPath.str; +-} +- + inline void requestRoutesNetworkProtocol(App& app) + { + BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") +@@ -480,7 +397,9 @@ inline void requestRoutesNetworkProtocol(App& app) + std::optional<std::vector<nlohmann::json>> ntpServerObjects; + std::optional<bool> ntpEnabled; + std::optional<bool> ipmiEnabled; ++ std::optional<uint16_t> ipmiPort; + std::optional<bool> sshEnabled; ++ std::optional<uint16_t> sshPort; + + // clang-format off + if (!json_util::readJsonPatch( +@@ -489,7 +408,9 @@ inline void requestRoutesNetworkProtocol(App& app) + "NTP/NTPServers", ntpServerObjects, + "NTP/ProtocolEnabled", ntpEnabled, + "IPMI/ProtocolEnabled", ipmiEnabled, +- "SSH/ProtocolEnabled", sshEnabled)) ++ "IPMI/Port", ipmiPort, ++ "SSH/ProtocolEnabled", sshEnabled, ++ "SSH/Port", sshPort)) + { + return; + } +@@ -525,15 +446,24 @@ inline void requestRoutesNetworkProtocol(App& app) + + if (ipmiEnabled) + { +- handleProtocolEnabled( +- *ipmiEnabled, asyncResp, +- encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); ++ service_util::setEnabled(asyncResp, "IPMI/ProtocolEnabled", ++ ipmiServiceName, *ipmiEnabled); ++ } ++ if (ipmiPort) ++ { ++ service_util::setPortNumber(asyncResp, "IPMI/Port", ipmiServiceName, ++ *ipmiPort); + } + + if (sshEnabled) + { +- handleProtocolEnabled(*sshEnabled, asyncResp, +- encodeServiceObjectPath(sshServiceName)); ++ service_util::setEnabled(asyncResp, "SSH/ProtocolEnabled", ++ sshServiceName, *sshEnabled); ++ } ++ if (sshPort) ++ { ++ service_util::setPortNumber(asyncResp, "SSH/Port", sshServiceName, ++ *sshPort); + } + }); + +diff --git a/redfish-core/lib/redfish_util.hpp b/redfish-core/lib/redfish_util.hpp +index 04d6ceb3..9b604ce9 100644 +--- a/redfish-core/lib/redfish_util.hpp ++++ b/redfish-core/lib/redfish_util.hpp +@@ -93,133 +93,5 @@ void getMainChassisId(std::shared_ptr<bmcweb::AsyncResp> asyncResp, + "xyz.openbmc_project.Inventory.Item.Chassis"}); + } + +-template <typename CallbackFunc> +-void getPortStatusAndPath(const std::string& serviceName, +- CallbackFunc&& callback) +-{ +- crow::connections::systemBus->async_method_call( +- [serviceName, callback{std::forward<CallbackFunc>(callback)}]( +- const boost::system::error_code ec, +- const std::vector<UnitStruct>& r) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << ec; +- // return error code +- callback(ec, "", false); +- return; +- } +- +- for (const UnitStruct& unit : r) +- { +- // Only traverse through <xyz>.socket units +- const std::string& unitName = std::get<NET_PROTO_UNIT_NAME>(unit); +- +- // find "." into unitsName +- size_t lastCharPos = unitName.rfind('.'); +- if (lastCharPos == std::string::npos) +- { +- continue; +- } +- +- // is unitsName end with ".socket" +- std::string unitNameEnd = unitName.substr(lastCharPos); +- if (unitNameEnd != ".socket") +- { +- continue; +- } +- +- // find "@" into unitsName +- if (size_t atCharPos = unitName.rfind('@'); +- atCharPos != std::string::npos) +- { +- lastCharPos = atCharPos; +- } +- +- // unitsName without "@eth(x).socket", only <xyz> +- // unitsName without ".socket", only <xyz> +- std::string unitNameStr = unitName.substr(0, lastCharPos); +- +- // We are interested in services, which starts with +- // mapped service name +- if (unitNameStr != serviceName) +- { +- continue; +- } +- +- const std::string& socketPath = +- std::get<NET_PROTO_UNIT_OBJ_PATH>(unit); +- const std::string& unitState = +- std::get<NET_PROTO_UNIT_SUB_STATE>(unit); +- +- bool isProtocolEnabled = +- ((unitState == "running") || (unitState == "listening")); +- // We found service, return from inner loop. +- callback(ec, socketPath, isProtocolEnabled); +- return; +- } +- +- // no service foudn, throw error +- boost::system::error_code ec1 = boost::system::errc::make_error_code( +- boost::system::errc::no_such_process); +- // return error code +- callback(ec1, "", false); +- BMCWEB_LOG_ERROR << ec1; +- }, +- "org.freedesktop.systemd1", "/org/freedesktop/systemd1", +- "org.freedesktop.systemd1.Manager", "ListUnits"); +-} +- +-template <typename CallbackFunc> +-void getPortNumber(const std::string& socketPath, CallbackFunc&& callback) +-{ +- sdbusplus::asio::getProperty< +- std::vector<std::tuple<std::string, std::string>>>( +- *crow::connections::systemBus, "org.freedesktop.systemd1", socketPath, +- "org.freedesktop.systemd1.Socket", "Listen", +- [callback{std::forward<CallbackFunc>(callback)}]( +- const boost::system::error_code ec, +- const std::vector<std::tuple<std::string, std::string>>& resp) { +- if (ec) +- { +- BMCWEB_LOG_ERROR << ec; +- callback(ec, 0); +- return; +- } +- if (resp.empty()) +- { +- // Network Protocol Listen Response Elements is empty +- boost::system::error_code ec1 = +- boost::system::errc::make_error_code( +- boost::system::errc::bad_message); +- // return error code +- callback(ec1, 0); +- BMCWEB_LOG_ERROR << ec1; +- return; +- } +- const std::string& listenStream = +- std::get<NET_PROTO_LISTEN_STREAM>(resp[0]); +- const char* pa = &listenStream[listenStream.rfind(':') + 1]; +- int port{0}; +- if (auto [p, ec2] = std::from_chars(pa, nullptr, port); +- ec2 != std::errc()) +- { +- // there is only two possibility invalid_argument and +- // result_out_of_range +- boost::system::error_code ec3 = +- boost::system::errc::make_error_code( +- boost::system::errc::invalid_argument); +- if (ec2 == std::errc::result_out_of_range) +- { +- ec3 = boost::system::errc::make_error_code( +- boost::system::errc::result_out_of_range); +- } +- // return error code +- callback(ec3, 0); +- BMCWEB_LOG_ERROR << ec3; +- } +- callback(ec, port); +- }); +-} +- + } // namespace redfish + #endif +diff --git a/redfish-core/ut/service_utils_test.cpp b/redfish-core/ut/service_utils_test.cpp +new file mode 100644 +index 00000000..01e5dcc9 +--- /dev/null ++++ b/redfish-core/ut/service_utils_test.cpp +@@ -0,0 +1,33 @@ ++#include "utils/service_utils.hpp" ++ ++#include <gmock/gmock.h> ++ ++namespace redfish ++{ ++namespace service_util ++{ ++ ++TEST(ServiceUtilsTest, MatchServiceGood) ++{ ++ using details::matchService; ++ sdbusplus::message::object_path basePath("/service/base/path"); ++ ++ EXPECT_TRUE(matchService(basePath / "serviceUnit", "serviceUnit")); ++ EXPECT_TRUE(matchService(basePath / "service-unit", "service-unit")); ++ EXPECT_TRUE(matchService(basePath / "serviceUnit@test", "serviceUnit")); ++ EXPECT_TRUE(matchService(basePath / "service-unit@test", "service-unit")); ++} ++ ++TEST(ServiceUtilsTest, MatchServiceBad) ++{ ++ using details::matchService; ++ sdbusplus::message::object_path basePath("/service/base/path"); ++ ++ EXPECT_FALSE(matchService(basePath / "serviceUnit", "service-unit")); ++ EXPECT_FALSE(matchService(basePath / "serviceUnitTest", "serviceUnit")); ++ EXPECT_FALSE(matchService(basePath / "serviceUnit@test", "service-unit")); ++ EXPECT_FALSE(matchService(basePath / "service-unit-test", "service-unit")); ++} ++ ++} // namespace service_util ++} // namespace redfish +-- +2.35.1 + |