/* // Copyright (c) 2018 Intel Corporation // // 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. */ #pragma once #include "node.hpp" #include "registries.hpp" #include "registries/base_message_registry.hpp" #include "registries/openbmc_message_registry.hpp" #include #include #include #include #include #include #include namespace redfish { constexpr char const *CrashdumpObject = "com.intel.crashdump"; constexpr char const *CrashdumpPath = "/com/intel/crashdump"; constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; constexpr char const *CrashdumpInterface = "com.intel.crashdump"; constexpr char const *CrashdumpOnDemandInterface = "com.intel.crashdump.OnDemand"; constexpr char const *CrashdumpRawPECIInterface = "com.intel.crashdump.SendRawPeci"; namespace message_registries { static const Message *getMessageFromRegistry( const std::string &messageKey, const boost::beast::span registry) { boost::beast::span::const_iterator messageIt = std::find_if(registry.cbegin(), registry.cend(), [&messageKey](const MessageEntry &messageEntry) { return !std::strcmp(messageEntry.first, messageKey.c_str()); }); if (messageIt != registry.cend()) { return &messageIt->second; } return nullptr; } static const Message *getMessage(const std::string_view &messageID) { // Redfish MessageIds are in the form // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find // the right Message std::vector fields; fields.reserve(4); boost::split(fields, messageID, boost::is_any_of(".")); std::string ®istryName = fields[0]; std::string &messageKey = fields[3]; // Find the right registry and check it for the MessageKey if (std::string(base::header.registryPrefix) == registryName) { return getMessageFromRegistry( messageKey, boost::beast::span(base::registry)); } if (std::string(openbmc::header.registryPrefix) == registryName) { return getMessageFromRegistry( messageKey, boost::beast::span(openbmc::registry)); } return nullptr; } } // namespace message_registries namespace fs = std::filesystem; using GetManagedPropertyType = boost::container::flat_map< std::string, sdbusplus::message::variant>; using GetManagedObjectsType = boost::container::flat_map< sdbusplus::message::object_path, boost::container::flat_map>; inline std::string translateSeverityDbusToRedfish(const std::string &s) { if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") { return "Critical"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") { return "Critical"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") { return "OK"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") { return "Critical"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") { return "Critical"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") { return "OK"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") { return "OK"; } else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") { return "Warning"; } return ""; } static int getJournalMetadata(sd_journal *journal, const std::string_view &field, std::string_view &contents) { const char *data = nullptr; size_t length = 0; int ret = 0; // Get the metadata from the requested field of the journal entry ret = sd_journal_get_data(journal, field.data(), (const void **)&data, &length); if (ret < 0) { return ret; } contents = std::string_view(data, length); // Only use the content after the "=" character. contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); return ret; } static int getJournalMetadata(sd_journal *journal, const std::string_view &field, const int &base, int &contents) { int ret = 0; std::string_view metadata; // Get the metadata from the requested field of the journal entry ret = getJournalMetadata(journal, field, metadata); if (ret < 0) { return ret; } contents = strtol(metadata.data(), nullptr, base); return ret; } static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) { int ret = 0; uint64_t timestamp = 0; ret = sd_journal_get_realtime_usec(journal, ×tamp); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " << strerror(-ret); return false; } time_t t = static_cast(timestamp / 1000 / 1000); // Convert from us to s struct tm *loctime = localtime(&t); char entryTime[64] = {}; if (NULL != loctime) { strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); } // Insert the ':' into the timezone std::string_view t1(entryTime); std::string_view t2(entryTime); if (t1.size() > 2 && t2.size() > 2) { t1.remove_suffix(2); t2.remove_prefix(t2.size() - 2); } entryTimestamp = std::string(t1) + ":" + std::string(t2); return true; } static bool getSkipParam(crow::Response &res, const crow::Request &req, long &skip) { char *skipParam = req.urlParams.get("$skip"); if (skipParam != nullptr) { char *ptr = nullptr; skip = std::strtol(skipParam, &ptr, 10); if (*skipParam == '\0' || *ptr != '\0') { messages::queryParameterValueTypeError(res, std::string(skipParam), "$skip"); return false; } if (skip < 0) { messages::queryParameterOutOfRange(res, std::to_string(skip), "$skip", "greater than 0"); return false; } } return true; } static constexpr const long maxEntriesPerPage = 1000; static bool getTopParam(crow::Response &res, const crow::Request &req, long &top) { char *topParam = req.urlParams.get("$top"); if (topParam != nullptr) { char *ptr = nullptr; top = std::strtol(topParam, &ptr, 10); if (*topParam == '\0' || *ptr != '\0') { messages::queryParameterValueTypeError(res, std::string(topParam), "$top"); return false; } if (top < 1 || top > maxEntriesPerPage) { messages::queryParameterOutOfRange( res, std::to_string(top), "$top", "1-" + std::to_string(maxEntriesPerPage)); return false; } } return true; } static bool getUniqueEntryID(sd_journal *journal, std::string &entryID) { int ret = 0; static uint64_t prevTs = 0; static int index = 0; // Get the entry timestamp uint64_t curTs = 0; ret = sd_journal_get_realtime_usec(journal, &curTs); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " << strerror(-ret); return false; } // If the timestamp isn't unique, increment the index if (curTs == prevTs) { index++; } else { // Otherwise, reset it index = 0; } // Save the timestamp prevTs = curTs; entryID = std::to_string(curTs); if (index > 0) { entryID += "_" + std::to_string(index); } return true; } static bool getTimestampFromID(crow::Response &res, const std::string &entryID, uint64_t ×tamp, uint16_t &index) { if (entryID.empty()) { return false; } // Convert the unique ID back to a timestamp to find the entry std::string_view tsStr(entryID); auto underscorePos = tsStr.find("_"); if (underscorePos != tsStr.npos) { // Timestamp has an index tsStr.remove_suffix(tsStr.size() - underscorePos); std::string_view indexStr(entryID); indexStr.remove_prefix(underscorePos + 1); std::size_t pos; try { index = std::stoul(std::string(indexStr), &pos); } catch (std::invalid_argument) { messages::resourceMissingAtURI(res, entryID); return false; } catch (std::out_of_range) { messages::resourceMissingAtURI(res, entryID); return false; } if (pos != indexStr.size()) { messages::resourceMissingAtURI(res, entryID); return false; } } // Timestamp has no index std::size_t pos; try { timestamp = std::stoull(std::string(tsStr), &pos); } catch (std::invalid_argument) { messages::resourceMissingAtURI(res, entryID); return false; } catch (std::out_of_range) { messages::resourceMissingAtURI(res, entryID); return false; } if (pos != tsStr.size()) { messages::resourceMissingAtURI(res, entryID); return false; } return true; } class SystemLogServiceCollection : public Node { public: template SystemLogServiceCollection(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: /** * Functions triggers appropriate requests on DBus */ void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); // Collections don't include the static data added by SubRoute because // it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices"; asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; asyncResp->res.jsonValue["Description"] = "Collection of LogServices for this Computer System"; nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; logServiceArray = nlohmann::json::array(); logServiceArray.push_back( {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG logServiceArray.push_back( {{ "@odata.id", "/redfish/v1/Systems/system/LogServices/Crashdump" }}); #endif asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); } }; class EventLogService : public Node { public: template EventLogService(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/EventLog"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogService.LogService"; asyncResp->res.jsonValue["Name"] = "Event Log Service"; asyncResp->res.jsonValue["Description"] = "System Event Log Service"; asyncResp->res.jsonValue["Id"] = "Event Log"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; asyncResp->res.jsonValue["Entries"] = { {"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; } }; static int fillEventLogEntryJson(const std::string &bmcLogEntryID, const std::string_view &messageID, sd_journal *journal, nlohmann::json &bmcLogEntryJson) { // Get the Message from the MessageRegistry const message_registries::Message *message = message_registries::getMessage(messageID); std::string msg; std::string severity; if (message != nullptr) { msg = message->message; severity = message->severity; } // Get the MessageArgs from the journal entry by finding all of the // REDFISH_MESSAGE_ARG_x fields const void *data; size_t length; std::vector messageArgs; SD_JOURNAL_FOREACH_DATA(journal, data, length) { std::string_view field(static_cast(data), length); if (boost::starts_with(field, "REDFISH_MESSAGE_ARG_")) { // Get the Arg number from the field name field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); if (field.empty()) { continue; } int argNum = std::strtoul(field.data(), nullptr, 10); if (argNum == 0) { continue; } // Get the Arg value after the "=" character. field.remove_prefix(std::min(field.find("=") + 1, field.size())); // Make sure we have enough space in messageArgs if (argNum > messageArgs.size()) { messageArgs.resize(argNum); } messageArgs[argNum - 1] = std::string(field); } } // Fill the MessageArgs into the Message for (size_t i = 0; i < messageArgs.size(); i++) { std::string argStr = "%" + std::to_string(i + 1); size_t argPos = msg.find(argStr); if (argPos != std::string::npos) { msg.replace(argPos, argStr.length(), messageArgs[i]); } } // Get the Created time from the timestamp std::string entryTimeStr; if (!getEntryTimestamp(journal, entryTimeStr)) { return 1; } // Fill in the log entry with the gathered data bmcLogEntryJson = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + bmcLogEntryID}, {"Name", "System Event Log Entry"}, {"Id", bmcLogEntryID}, {"Message", msg}, {"MessageId", messageID}, {"MessageArgs", std::move(messageArgs)}, {"EntryType", "Event"}, {"Severity", severity}, {"Created", std::move(entryTimeStr)}}; return 0; } class EventLogEntryCollection : public Node { public: template EventLogEntryCollection(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); long skip = 0; long top = maxEntriesPerPage; // Show max entries by default if (!getSkipParam(asyncResp->res, req, skip)) { return; } if (!getTopParam(asyncResp->res, req, top)) { return; } // Collections don't include the static data added by SubRoute because // it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; asyncResp->res.jsonValue["Description"] = "Collection of System Event Log Entries"; #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; logEntryArray = nlohmann::json::array(); // Go through the journal and create a unique ID for each entry sd_journal *journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; uint64_t entryCount = 0; SD_JOURNAL_FOREACH(journal.get()) { // Look for only journal entries that contain a REDFISH_MESSAGE_ID // field std::string_view messageID; ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); if (ret < 0) { continue; } entryCount++; // Handle paging using skip (number of entries to skip from the // start) and top (number of entries to display) if (entryCount <= skip || entryCount > skip + top) { continue; } std::string idStr; if (!getUniqueEntryID(journal.get(), idStr)) { continue; } logEntryArray.push_back({}); nlohmann::json &bmcLogEntry = logEntryArray.back(); if (fillEventLogEntryJson(idStr, messageID, journal.get(), bmcLogEntry) != 0) { messages::internalError(asyncResp->res); return; } } asyncResp->res.jsonValue["Members@odata.count"] = entryCount; if (skip + top < entryCount) { asyncResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" + std::to_string(skip + top); } #else // DBus implementation of EventLog/Entries // Make call to Logging Service to find all log entry objects crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, GetManagedObjectsType &resp) { if (ec) { // TODO Handle for specific error code BMCWEB_LOG_ERROR << "getLogEntriesIfaceData resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } nlohmann::json &entriesArray = asyncResp->res.jsonValue["Members"]; entriesArray = nlohmann::json::array(); for (auto &objectPath : resp) { for (auto &interfaceMap : objectPath.second) { if (interfaceMap.first != "xyz.openbmc_project.Logging.Entry") { BMCWEB_LOG_DEBUG << "Bailing early on " << interfaceMap.first; continue; } entriesArray.push_back({}); nlohmann::json &thisEntry = entriesArray.back(); uint32_t *id; std::time_t timestamp; std::string *severity, *message; bool *resolved; for (auto &propertyMap : interfaceMap.second) { if (propertyMap.first == "Id") { id = sdbusplus::message::variant_ns::get_if< uint32_t>(&propertyMap.second); if (id == nullptr) { messages::propertyMissing(asyncResp->res, "Id"); } } else if (propertyMap.first == "Timestamp") { const uint64_t *millisTimeStamp = std::get_if(&propertyMap.second); if (millisTimeStamp == nullptr) { messages::propertyMissing(asyncResp->res, "Timestamp"); } // Retrieve Created property with format: // yyyy-mm-ddThh:mm:ss std::chrono::milliseconds chronoTimeStamp( *millisTimeStamp); timestamp = std::chrono::duration_cast< std::chrono::seconds>(chronoTimeStamp) .count(); } else if (propertyMap.first == "Severity") { severity = std::get_if( &propertyMap.second); if (severity == nullptr) { messages::propertyMissing(asyncResp->res, "Severity"); } } else if (propertyMap.first == "Message") { message = std::get_if( &propertyMap.second); if (message == nullptr) { messages::propertyMissing(asyncResp->res, "Message"); } } } thisEntry = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/" "$metadata#LogEntry.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog/" "Entries/" + std::to_string(*id)}, {"Name", "System DBus Event Log Entry"}, {"Id", std::to_string(*id)}, {"Message", *message}, {"EntryType", "Event"}, {"Severity", translateSeverityDbusToRedfish(*severity)}, {"Created", crow::utility::getDateTime(timestamp)}}; } } std::sort(entriesArray.begin(), entriesArray.end(), [](const nlohmann::json &left, const nlohmann::json &right) { return (left["Id"] <= right["Id"]); }); asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); }, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); #endif } }; class EventLogEntry : public Node { public: EventLogEntry(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//", std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } const std::string &entryID = params[0]; #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES // Convert the unique ID back to a timestamp to find the entry uint64_t ts = 0; uint16_t index = 0; if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) { return; } sd_journal *journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; // Go to the timestamp in the log and move to the entry at the index ret = sd_journal_seek_realtime_usec(journal.get(), ts); for (int i = 0; i <= index; i++) { sd_journal_next(journal.get()); } // Confirm that the entry ID matches what was requested std::string idStr; if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) { messages::resourceMissingAtURI(asyncResp->res, entryID); return; } // only use journal entries that contain a REDFISH_MESSAGE_ID field std::string_view messageID; ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); if (ret < 0) { messages::resourceNotFound(asyncResp->res, "LogEntry", "system"); return; } if (fillEventLogEntryJson(entryID, messageID, journal.get(), asyncResp->res.jsonValue) != 0) { messages::internalError(asyncResp->res); return; } #else // DBus implementation of EventLog/Entries // Make call to Logging Service to find all log entry objects crow::connections::systemBus->async_method_call( [asyncResp, entryID](const boost::system::error_code ec, GetManagedPropertyType &resp) { if (ec) { BMCWEB_LOG_ERROR << "EventLogEntry (DBus) resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } uint32_t *id; std::time_t timestamp; std::string *severity, *message; bool *resolved; for (auto &propertyMap : resp) { if (propertyMap.first == "Id") { id = std::get_if(&propertyMap.second); if (id == nullptr) { messages::propertyMissing(asyncResp->res, "Id"); } } else if (propertyMap.first == "Timestamp") { const uint64_t *millisTimeStamp = std::get_if(&propertyMap.second); if (millisTimeStamp == nullptr) { messages::propertyMissing(asyncResp->res, "Timestamp"); } // Retrieve Created property with format: // yyyy-mm-ddThh:mm:ss std::chrono::milliseconds chronoTimeStamp( *millisTimeStamp); timestamp = std::chrono::duration_cast( chronoTimeStamp) .count(); } else if (propertyMap.first == "Severity") { severity = std::get_if(&propertyMap.second); if (severity == nullptr) { messages::propertyMissing(asyncResp->res, "Severity"); } } else if (propertyMap.first == "Message") { message = std::get_if(&propertyMap.second); if (message == nullptr) { messages::propertyMissing(asyncResp->res, "Message"); } } } asyncResp->res.jsonValue = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/" "$metadata#LogEntry.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog/" "Entries/" + std::to_string(*id)}, {"Name", "System DBus Event Log Entry"}, {"Id", std::to_string(*id)}, {"Message", *message}, {"EntryType", "Event"}, {"Severity", translateSeverityDbusToRedfish(*severity)}, {"Created", crow::utility::getDateTime(timestamp)}}; }, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging/entry/" + entryID, "org.freedesktop.DBus.Properties", "GetAll", "xyz.openbmc_project.Logging.Entry"); #endif } }; class BMCLogServiceCollection : public Node { public: template BMCLogServiceCollection(CrowApp &app) : Node(app, "/redfish/v1/Managers/bmc/LogServices/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: /** * Functions triggers appropriate requests on DBus */ void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); // Collections don't include the static data added by SubRoute because // it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; asyncResp->res.jsonValue["Description"] = "Collection of LogServices for this Manager"; nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; logServiceArray = nlohmann::json::array(); #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL logServiceArray.push_back( {{ "@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal" }}); #endif asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); } }; class BMCJournalLogService : public Node { public: template BMCJournalLogService(CrowApp &app) : Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogService.LogService"; asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; asyncResp->res.jsonValue["Id"] = "BMC Journal"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; asyncResp->res.jsonValue["Entries"] = { {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}}; } }; static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, sd_journal *journal, nlohmann::json &bmcJournalLogEntryJson) { // Get the Log Entry contents int ret = 0; std::string_view msg; ret = getJournalMetadata(journal, "MESSAGE", msg); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); return 1; } // Get the severity from the PRIORITY field int severity = 8; // Default to an invalid priority ret = getJournalMetadata(journal, "PRIORITY", 10, severity); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); return 1; } // Get the Created time from the timestamp std::string entryTimeStr; if (!getEntryTimestamp(journal, entryTimeStr)) { return 1; } // Fill in the log entry with the gathered data bmcJournalLogEntryJson = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + bmcJournalLogEntryID}, {"Name", "BMC Journal Entry"}, {"Id", bmcJournalLogEntryID}, {"Message", msg}, {"EntryType", "Oem"}, {"Severity", severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, {"OemRecordFormat", "Intel BMC Journal Entry"}, {"Created", std::move(entryTimeStr)}}; return 0; } class BMCJournalLogEntryCollection : public Node { public: template BMCJournalLogEntryCollection(CrowApp &app) : Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); static constexpr const long maxEntriesPerPage = 1000; long skip = 0; long top = maxEntriesPerPage; // Show max entries by default if (!getSkipParam(asyncResp->res, req, skip)) { return; } if (!getTopParam(asyncResp->res, req, top)) { return; } // Collections don't include the static data added by SubRoute because // it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; asyncResp->res.jsonValue["Description"] = "Collection of BMC Journal Entries"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; logEntryArray = nlohmann::json::array(); // Go through the journal and use the timestamp to create a unique ID // for each entry sd_journal *journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; uint64_t entryCount = 0; SD_JOURNAL_FOREACH(journal.get()) { entryCount++; // Handle paging using skip (number of entries to skip from the // start) and top (number of entries to display) if (entryCount <= skip || entryCount > skip + top) { continue; } std::string idStr; if (!getUniqueEntryID(journal.get(), idStr)) { continue; } logEntryArray.push_back({}); nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); if (fillBMCJournalLogEntryJson(idStr, journal.get(), bmcJournalLogEntry) != 0) { messages::internalError(asyncResp->res); return; } } asyncResp->res.jsonValue["Members@odata.count"] = entryCount; if (skip + top < entryCount) { asyncResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + std::to_string(skip + top); } } }; class BMCJournalLogEntry : public Node { public: BMCJournalLogEntry(CrowApp &app) : Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries//", std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } const std::string &entryID = params[0]; // Convert the unique ID back to a timestamp to find the entry uint64_t ts = 0; uint16_t index = 0; if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) { return; } sd_journal *journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; // Go to the timestamp in the log and move to the entry at the index ret = sd_journal_seek_realtime_usec(journal.get(), ts); for (int i = 0; i <= index; i++) { sd_journal_next(journal.get()); } // Confirm that the entry ID matches what was requested std::string idStr; if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) { messages::resourceMissingAtURI(asyncResp->res, entryID); return; } if (fillBMCJournalLogEntryJson(entryID, journal.get(), asyncResp->res.jsonValue) != 0) { messages::internalError(asyncResp->res); return; } } }; class CrashdumpService : public Node { public: template CrashdumpService(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: /** * Functions triggers appropriate requests on DBus */ void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); // Copy over the static data to include the entries added by SubRoute asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#LogService.LogService"; asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service"; asyncResp->res.jsonValue["Description"] = "Crashdump Service"; asyncResp->res.jsonValue["Id"] = "Crashdump"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; asyncResp->res.jsonValue["Entries"] = { {"@odata.id", "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; asyncResp->res.jsonValue["Actions"] = { {"Oem", {{"#Crashdump.OnDemand", {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" "Actions/Oem/Crashdump.OnDemand"}}}}}}; #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI asyncResp->res.jsonValue["Actions"]["Oem"].push_back( {"#Crashdump.SendRawPeci", { { "target", "/redfish/v1/Systems/system/LogServices/Crashdump/" "Actions/Oem/Crashdump.SendRawPeci" } }}); #endif } }; class CrashdumpEntryCollection : public Node { public: template CrashdumpEntryCollection(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: /** * Functions triggers appropriate requests on DBus */ void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); // Collections don't include the static data added by SubRoute because // it has a duplicate entry for members auto getLogEntriesCallback = [asyncResp]( const boost::system::error_code ec, const std::vector &resp) { if (ec) { if (ec.value() != boost::system::errc::no_such_file_or_directory) { BMCWEB_LOG_DEBUG << "failed to get entries ec: " << ec.message(); messages::internalError(asyncResp->res); return; } } asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; asyncResp->res.jsonValue["@odata.context"] = "/redfish/v1/" "$metadata#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; asyncResp->res.jsonValue["Description"] = "Collection of Crashdump Entries"; nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; logEntryArray = nlohmann::json::array(); for (const std::string &objpath : resp) { // Don't list the on-demand log if (objpath.compare(CrashdumpOnDemandPath) == 0) { continue; } std::size_t lastPos = objpath.rfind("/"); if (lastPos != std::string::npos) { logEntryArray.push_back( {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" "Crashdump/Entries/" + objpath.substr(lastPos + 1)}}); } } asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size(); }; crow::connections::systemBus->async_method_call( std::move(getLogEntriesCallback), "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, std::array{CrashdumpInterface}); } }; std::string getLogCreatedTime(const nlohmann::json &Crashdump) { nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data"); if (cdIt != Crashdump.end()) { nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); if (siIt != cdIt->end()) { nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); if (tsIt != siIt->end()) { const std::string *logTime = tsIt->get_ptr(); if (logTime != nullptr) { return *logTime; } } } } BMCWEB_LOG_DEBUG << "failed to find log timestamp"; return std::string(); } class CrashdumpEntry : public Node { public: CrashdumpEntry(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries//", std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } const uint8_t logId = std::atoi(params[0].c_str()); auto getStoredLogCallback = [asyncResp, logId]( const boost::system::error_code ec, const std::variant &resp) { if (ec) { BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); messages::internalError(asyncResp->res); return; } const std::string *log = std::get_if(&resp); if (log == nullptr) { messages::internalError(asyncResp->res); return; } nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); if (j.is_discarded()) { messages::internalError(asyncResp->res); return; } std::string t = getLogCreatedTime(j); asyncResp->res.jsonValue = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + std::to_string(logId)}, {"Name", "CPU Crashdump"}, {"Id", logId}, {"EntryType", "Oem"}, {"OemRecordFormat", "Intel Crashdump"}, {"Oem", {{"Intel", std::move(j)}}}, {"Created", std::move(t)}}; }; crow::connections::systemBus->async_method_call( std::move(getStoredLogCallback), CrashdumpObject, CrashdumpPath + std::string("/") + std::to_string(logId), "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, "Log"); } }; class OnDemandCrashdump : public Node { public: OnDemandCrashdump(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" "Crashdump.OnDemand/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: void doPost(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); static std::unique_ptr onDemandLogMatcher; // Only allow one OnDemand Log request at a time if (onDemandLogMatcher != nullptr) { asyncResp->res.addHeader("Retry-After", "30"); messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); return; } // Make this static so it survives outside this method static boost::asio::deadline_timer timeout(*req.ioService); timeout.expires_from_now(boost::posix_time::seconds(30)); timeout.async_wait([asyncResp](const boost::system::error_code &ec) { onDemandLogMatcher = nullptr; if (ec) { // operation_aborted is expected if timer is canceled before // completion. if (ec != boost::asio::error::operation_aborted) { BMCWEB_LOG_ERROR << "Async_wait failed " << ec; } return; } BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log"; messages::internalError(asyncResp->res); }); auto onDemandLogMatcherCallback = [asyncResp]( sdbusplus::message::message &m) { BMCWEB_LOG_DEBUG << "OnDemand log available match fired"; boost::system::error_code ec; timeout.cancel(ec); if (ec) { BMCWEB_LOG_ERROR << "error canceling timer " << ec; } sdbusplus::message::object_path objPath; boost::container::flat_map< std::string, boost::container::flat_map< std::string, std::variant>> interfacesAdded; m.read(objPath, interfacesAdded); const std::string *log = std::get_if( &interfacesAdded[CrashdumpInterface]["Log"]); if (log == nullptr) { messages::internalError(asyncResp->res); // Careful with onDemandLogMatcher. It is a unique_ptr to the // match object inside which this lambda is executing. Once it // is set to nullptr, the match object will be destroyed and the // lambda will lose its context, including res, so it needs to // be the last thing done. onDemandLogMatcher = nullptr; return; } nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); if (j.is_discarded()) { messages::internalError(asyncResp->res); // Careful with onDemandLogMatcher. It is a unique_ptr to the // match object inside which this lambda is executing. Once it // is set to nullptr, the match object will be destroyed and the // lambda will lose its context, including res, so it needs to // be the last thing done. onDemandLogMatcher = nullptr; return; } std::string t = getLogCreatedTime(j); asyncResp->res.jsonValue = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, {"Name", "CPU Crashdump"}, {"EntryType", "Oem"}, {"OemRecordFormat", "Intel Crashdump"}, {"Oem", {{"Intel", std::move(j)}}}, {"Created", std::move(t)}}; // Careful with onDemandLogMatcher. It is a unique_ptr to the // match object inside which this lambda is executing. Once it is // set to nullptr, the match object will be destroyed and the lambda // will lose its context, including res, so it needs to be the last // thing done. onDemandLogMatcher = nullptr; }; onDemandLogMatcher = std::make_unique( *crow::connections::systemBus, sdbusplus::bus::match::rules::interfacesAdded() + sdbusplus::bus::match::rules::argNpath(0, CrashdumpOnDemandPath), std::move(onDemandLogMatcherCallback)); auto generateonDemandLogCallback = [asyncResp](const boost::system::error_code ec, const std::string &resp) { if (ec) { if (ec.value() == boost::system::errc::operation_not_supported) { messages::resourceInStandby(asyncResp->res); } else { messages::internalError(asyncResp->res); } boost::system::error_code timeoutec; timeout.cancel(timeoutec); if (timeoutec) { BMCWEB_LOG_ERROR << "error canceling timer " << timeoutec; } onDemandLogMatcher = nullptr; return; } }; crow::connections::systemBus->async_method_call( std::move(generateonDemandLogCallback), CrashdumpObject, CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog"); } }; class SendRawPECI : public Node { public: SendRawPECI(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" "Crashdump.SendRawPeci/") { entityPrivileges = { {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; } private: void doPost(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { std::shared_ptr asyncResp = std::make_shared(res); uint8_t clientAddress = 0; uint8_t readLength = 0; std::vector peciCommand; if (!json_util::readJson(req, res, "ClientAddress", clientAddress, "ReadLength", readLength, "PECICommand", peciCommand)) { return; } // Callback to return the Raw PECI response auto sendRawPECICallback = [asyncResp](const boost::system::error_code ec, const std::vector &resp) { if (ec) { BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " << ec.message(); messages::internalError(asyncResp->res); return; } asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, {"PECIResponse", resp}}; }; // Call the SendRawPECI command with the provided data crow::connections::systemBus->async_method_call( std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath, CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength, peciCommand); } }; /** * DBusLogServiceActionsClear class supports POST method for ClearLog action. */ class DBusLogServiceActionsClear : public Node { public: DBusLogServiceActionsClear(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" "LogService.Reset") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, {boost::beast::http::verb::put, {{"ConfigureManager"}}}, {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; } private: /** * Function handles POST method request. * The Clear Log actions does not require any parameter.The action deletes * all entries found in the Entries collection for this Log Service. */ void doPost(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { BMCWEB_LOG_DEBUG << "Do delete all entries."; auto asyncResp = std::make_shared(res); // Process response from Logging service. auto resp_handler = [asyncResp](const boost::system::error_code ec) { BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; if (ec) { // TODO Handle for specific error code BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } asyncResp->res.result(boost::beast::http::status::no_content); }; // Make call to Logging service to request Clear Log crow::connections::systemBus->async_method_call( resp_handler, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); } }; } // namespace redfish