From 76889ebb1cdc9d5cc2e29c065d1ab0c906b88f47 Mon Sep 17 00:00:00 2001 From: roly Date: Mon, 9 Dec 2024 14:30:51 +0800 Subject: [PATCH] Add Managers SEL log service --- include/luxshare/sel.hpp | 434 ++++++++++++++++++++++++++++++ include/luxshare/utils.hpp | 38 +++ meson.build | 1 + meson_options.txt | 8 + redfish-core/lib/log_services.hpp | 6 + src/webserver_main.cpp | 6 + 6 files changed, 493 insertions(+) create mode 100755 include/luxshare/sel.hpp create mode 100755 include/luxshare/utils.hpp diff --git a/include/luxshare/sel.hpp b/include/luxshare/sel.hpp new file mode 100755 index 00000000..cbaa1cd5 --- /dev/null +++ b/include/luxshare/sel.hpp @@ -0,0 +1,434 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace crow +{ +namespace luxshare_sel_api +{ + +enum class SELStatus +{ + DEASSERTED, + ASSERTED, + UNKNOWN +}; + + + +inline std::string translateSeverityDbusToRedfish(const std::string& s) +{ + if ((s == "xyz.openbmc_project.Logging.Entry.Level.Critical") || + (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") || + (s == "xyz.openbmc_project.Logging.Entry.Level.Error")) + { + return "Critical"; + } + if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") || + (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") || + (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")) + { + return "OK"; + } + if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") + { + return "Warning"; + } + if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") + { + return "NonRecoverable"; + } + return ""; +} + +inline void requestRoutesSelClear(App& app) +{ + BMCWEB_ROUTE( + app, + "/redfish/v1/Managers/bmc/LogServices/SEL/Actions/LogService.ClearLog/") + .privileges(redfish::privileges::privilegeSetConfigureComponents) + .methods(boost::beast::http::verb::post)( + [](const crow::Request& req, + const std::shared_ptr& asyncResp) { + if (!redfish::luxshare::checkPostRequestBodyEmpty(req)) + { + redfish::messages::actionParameterNotSupported( + asyncResp->res, req.body(), "ClearLog"); + return; + } + using IpmiDbusRspType = std::tuple>; + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code &ec, IpmiDbusRspType response1) { + // Handle the response of the first method call + if (ec) { + BMCWEB_LOG_ERROR("Error calling execute method: {}", ec.message()); + redfish::messages::internalError(asyncResp->res); + return; + } + + const uint8_t& cc1 = std::get<3>(response1); + if (cc1 != 0) { + BMCWEB_LOG_ERROR("Failed to reserve sel: cc: {}", cc1); + redfish::messages::internalError(asyncResp->res); + return; + } + + // Extract reservation ID from the response + std::vector reservationId = std::get<4>(response1); + std::vector commandData = {0x43, 0x4C, 0x52, 0xAA}; + commandData.insert(commandData.begin(), reservationId.begin(), reservationId.end()); + + // Execute "Clear SEL" IPMI command + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code &ec1,IpmiDbusRspType response2) { + // Handle the response of the clear SEL command + if (ec1) { + BMCWEB_LOG_ERROR("Error calling execute method: {}", ec1.message()); + redfish::messages::internalError(asyncResp->res); + return; + } + + const uint8_t& cc2 = std::get<3>(response2); + if (cc2 != 0) { + BMCWEB_LOG_ERROR("Failed to clear sel: cc: {}", cc2); + redfish::messages::internalError(asyncResp->res); + return; + } + + // Success logic for clearing SEL can go here + BMCWEB_LOG_INFO("Successfully cleared SEL."); + }, + "xyz.openbmc_project.Ipmi.Host", + "/xyz/openbmc_project/Ipmi", + "xyz.openbmc_project.Ipmi.Server", "execute", + uint8_t{0x0a}, uint8_t{0x00}, uint8_t{0x47}, // Command to clear SEL + commandData, + std::map>{} + ); + }, + "xyz.openbmc_project.Ipmi.Host", + "/xyz/openbmc_project/Ipmi", + "xyz.openbmc_project.Ipmi.Server", "execute", + uint8_t{0x0a}, uint8_t{0x00}, uint8_t{0x42}, // Command to reserve SEL + std::vector{}, + std::map>{}); + }); +} + +inline void requestRoutesSelEntries(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/SEL/Entries/") + .privileges(redfish::privileges::getLogService) + .methods(boost::beast::http::verb::get)( + [&app](const crow::Request& req, + const std::shared_ptr& asyncResp) { + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + // Get sensor info from xyz.openbmc_project.Ipmi.Host + auto respHandler = + [asyncResp]( + const boost::system::error_code& ec, + const std::unordered_map>& + sensorInfo) { + if (ec) + { + BMCWEB_LOG_ERROR("Error getting SensorInfo property: {}", + ec.message()); + redfish::messages::internalError(asyncResp->res); + return; + } + // Get all sel log from xyz.openbmc_project.Logging service. + sdbusplus::message::object_path path( + "/xyz/openbmc_project/logging"); + dbus::utility::getManagedObjects( + "xyz.openbmc_project.Logging", path, + [asyncResp, + sensorInfo](const boost::system::error_code& errorCode, + const dbus::utility::ManagedObjectType& resp) { + if (errorCode) + { + BMCWEB_LOG_ERROR("Logging GetManagedObjects error: {}", + errorCode.message()); + redfish::messages::internalError(asyncResp->res); + return; + } + + nlohmann::json& entriesArray = + asyncResp->res.jsonValue["Members"]; + entriesArray = nlohmann::json::array(); + std::map idToTimestamp; + for (const auto& objectPath : resp) + { + const uint32_t* id = nullptr; + const uint64_t* timestamp = nullptr; + const std::string* severity = nullptr; + const std::string* message = nullptr; + const std::vector* additionalData = nullptr; + nlohmann::json sensorName; + nlohmann::json sensorValue; + nlohmann::json sensorNumber; + nlohmann::json sensorType; + nlohmann::json eventType; + std::string sensorPath; + SELStatus selStatus = SELStatus::UNKNOWN; + + for (const auto& interfaceMap : objectPath.second) + { + if (interfaceMap.first == + "xyz.openbmc_project.Logging.Entry") + { + for (const auto& propertyMap : interfaceMap.second) + { + if (propertyMap.first == "Id") + { + id = std::get_if( + &propertyMap.second); + } + else if (propertyMap.first == "Timestamp") + { + timestamp = std::get_if( + &propertyMap.second); + } + else if (propertyMap.first == "Severity") + { + severity = std::get_if( + &propertyMap.second); + } + else if (propertyMap.first == "Message") + { + message = std::get_if( + &propertyMap.second); + if (*message == + "xyz.openbmc_project.Common.Error." + "InternalFailure") + { + // Skip this entry + break; + } + } + else if (propertyMap.first == "AdditionalData") + { + additionalData = + std::get_if>( + &propertyMap.second); + } + } + } + } + + if (id == nullptr || message == nullptr || + timestamp == nullptr || severity == nullptr || + additionalData == nullptr) + { + continue; + } + + idToTimestamp[*id] = *timestamp; + + entriesArray.push_back({}); + nlohmann::json& thisEntry = entriesArray.back(); + thisEntry["@odata.id"] = boost::urls::format( + "/redfish/v1/Managers/bmc/LogServices/SEL/Entries/{}", + std::to_string(*id)); + thisEntry["Id"] = *id; + thisEntry["Message"] = *message; + + // Get the Created time from the timestamp. + thisEntry["Created"] = + redfish::time_utils::getDateTimeUintMs(*timestamp); + thisEntry["Severity"] = + translateSeverityDbusToRedfish(*severity); + + for (const auto& data : *additionalData) + { + if (data.starts_with("SENSOR_PATH=")) + { + sensorPath = + data.substr(data.find_first_of('=') + 1); + if (!sensorPath.empty()) + { + // Get the sensor name from the last level of + // the path. + sensorName = + data.substr(data.find_last_of('/') + 1); + } + } + else if (data.starts_with("EVENT_DIR=")) + { + selStatus = static_cast(std::stoi( + data.substr(data.find_first_of('=') + 1))); + } + else if (data.starts_with("READING=")) + { + sensorValue = + data.substr(data.find_first_of('=') + 1); + } + else if (data.starts_with("SENSOR_DATA=")) + { + // The second character is the status of the + // discrete sensor. Convert to string format + // starting with 0x. + auto sensorValueStr = + data.substr(data.find_first_of('=') + 2, 1); + + try + { + std::stringstream output; + output << "0x" << std::hex << std::setw(2) + << std::setfill('0') + << static_cast(std::stoi( + sensorValueStr, nullptr, 16)); + + sensorValue = output.str(); + } + catch (const std::exception&) + { + sensorValue = nlohmann::json::value_t::null; + } + } + } + + if (selStatus == SELStatus::UNKNOWN) + { + // Match SEL status in message property. + std::string messageStr = *message; + if (messageStr.find("deassert") != std::string::npos) + { + selStatus = SELStatus::DEASSERTED; + } + else + { + selStatus = SELStatus::ASSERTED; + } + } + + if (sensorValue == nlohmann::json::value_t::null) + { + // Get SEL Reading in message properties. + std::string messageErased(*message); + std::string::size_type posString = 0; + posString = messageErased.find("Reading "); + if (posString == std::string::npos) + { + posString = messageErased.find("Reading="); + } + + if (posString != std::string::npos) + { + // Offset the length of 'Reading ' or 'Reading=' to + // obtain the substring before the space. + messageErased = messageErased.substr(posString + 8); + auto posSpace = messageErased.find_last_of(' '); + if (posSpace != std::string::npos) + { + sensorValue = messageErased.substr(0, posSpace); + } + } + } + + auto sensorEntryInfo = sensorInfo.find(sensorPath); + if (sensorEntryInfo != sensorInfo.end()) + { + std::stringstream output; + // Sensor number subscript in the vector is 6. + output << "0x" << std::hex << std::setw(2) + << std::setfill('0') + << static_cast( + sensorEntryInfo->second[6]); + sensorNumber = output.str(); + // Sensor type subscript in the vector is 0. + output.str(""); + output << "0x" << std::hex << std::setw(2) + << std::setfill('0') + << static_cast( + sensorEntryInfo->second[0]); + sensorType = output.str(); + // Event type subscript in the vector is 1. + output.str(""); + output << "0x" << std::hex << std::setw(2) + << std::setfill('0') + << static_cast( + sensorEntryInfo->second[1]); + eventType = output.str(); + } + + thisEntry["SensorType"] = sensorType; + thisEntry["SensorName"] = sensorName; + thisEntry["SensorNumber"] = sensorNumber; + thisEntry["SensorValue"] = sensorValue; + thisEntry["EventType"] = eventType; + if (selStatus == SELStatus::DEASSERTED) + { + thisEntry["Status"] = "Deasserted"; + } + else + { + thisEntry["Status"] = "Asserted"; + } + } + + std::sort(entriesArray.begin(), entriesArray.end(), + [&idToTimestamp](const nlohmann::json& left, + const nlohmann::json& right) { + return (idToTimestamp[left["Id"].get()] >= + idToTimestamp[right["Id"].get()]); + }); + asyncResp->res.jsonValue["Members@odata.count"] = + entriesArray.size(); + }); + }; + + sdbusplus::asio::getProperty< + std::unordered_map>>( + *crow::connections::systemBus, "xyz.openbmc_project.Ipmi.Host", + "/xyz/openbmc_project/Ipmi/SensorInfo", + "xyz.openbmc_project.IPMI.SensorInfo", "SensorInfo", + std::move(respHandler)); + }); +} + +inline void requestRoutesSel(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/SEL/") + .privileges(redfish::privileges::getLogService) + .methods(boost::beast::http::verb::get)( + [&app](const crow::Request& req, + const std::shared_ptr& asyncResp) { + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + asyncResp->res.jsonValue["@odata.type"] = + "#LogService.v1_1_0.LogService"; + asyncResp->res.jsonValue["@odata.id"] = + "/redfish/v1/Managers/bmc/LogServices/SEL"; + asyncResp->res.jsonValue["Name"] = "Event Log Service"; + asyncResp->res.jsonValue["Description"] = "System Event Log Service"; + asyncResp->res.jsonValue["Id"] = "SEL"; + std::pair redfishDateTimeOffset = + redfish::time_utils::getDateTimeOffsetNow(); + asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; + asyncResp->res.jsonValue["DateTimeLocalOffset"] = + redfishDateTimeOffset.second; + + asyncResp->res.jsonValue["Entries"]["@odata.id"] = + "/redfish/v1/Managers/bmc/LogServices/SEL/Entries"; + }); +} + +inline void requestRoutes(App& app) +{ + requestRoutesSelClear(app); + requestRoutesSelEntries(app); + requestRoutesSel(app); +} + +} // namespace luxshare_sel_api +} // namespace crow diff --git a/include/luxshare/utils.hpp b/include/luxshare/utils.hpp new file mode 100755 index 00000000..d1d7b97a --- /dev/null +++ b/include/luxshare/utils.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include + + +namespace redfish +{ +namespace luxshare +{ + +inline bool checkPostRequestBodyEmpty(const crow::Request& req) +{ + if (req.body().empty()) + { + return true; + } + + nlohmann::json jsonRequest = nlohmann::json::parse(req.body(), nullptr, + false); + if (jsonRequest.is_discarded()) + { + BMCWEB_LOG_DEBUG("Failed to parse json in request"); + return false; + } + + nlohmann::json::object_t* object = + jsonRequest.get_ptr(); + if (object == nullptr || (!object->empty())) + { + BMCWEB_LOG_DEBUG("Json value is not object or empty"); + return false; + } + + return true; +} + +} // namespace luxshare +} // namespace redfish diff --git a/meson.build b/meson.build index fcc0d7c4..3afb44e1 100644 --- a/meson.build +++ b/meson.build @@ -94,6 +94,7 @@ feature_map = { 'experimental-redfish-multi-computer-system' : '-DBMCWEB_ENABLE_MULTI_COMPUTERSYSTEM', 'vm-websocket' : '-DBMCWEB_ENABLE_VM_WEBSOCKET', 'xtoken-auth' : '-DBMCWEB_ENABLE_XTOKEN_AUTHENTICATION', + 'luxshare-api' : '-DBMCWEB_ENABLE_LUXSHARE_API', #'vm-nbdproxy' : '-DBMCWEB_ENABLE_VM_NBDPROXY', } diff --git a/meson_options.txt b/meson_options.txt index 017c16bd..562ac55b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -200,6 +200,14 @@ option( /google/v1/''' ) +# Luxshare redfish option +option( + 'luxshare-api', + type: 'feature', + value : 'enabled', + description: 'Enable Luxshare readfish feature' +) + option( 'http-body-limit', type: 'integer', diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp index 9cb1fe05..51fe2313 100644 --- a/redfish-core/lib/log_services.hpp +++ b/redfish-core/lib/log_services.hpp @@ -2365,6 +2365,12 @@ inline void handleBMCLogServicesCollectionGet( nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; logServiceArray = nlohmann::json::array(); +#ifdef BMCWEB_ENABLE_LUXSHARE_API + nlohmann::json::object_t eventLog; + eventLog["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/SEL"; + logServiceArray.emplace_back(std::move(eventLog)); +#endif + #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL nlohmann::json::object_t journal; journal["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal"; diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp index 67e2aaef..0008ad52 100644 --- a/src/webserver_main.cpp +++ b/src/webserver_main.cpp @@ -28,6 +28,8 @@ #include #include +#include + #include #include #include @@ -116,6 +118,10 @@ static int run() crow::google_api::requestRoutes(app); #endif +#ifdef BMCWEB_ENABLE_LUXSHARE_API + crow::luxshare_sel_api::requestRoutes(app); +#endif + if (bmcwebInsecureDisableXssPrevention != 0) { cors_preflight::requestRoutes(app); -- 2.25.1