diff options
-rw-r--r-- | redfish-core/include/redfish.hpp | 1 | ||||
-rw-r--r-- | redfish-core/lib/log_services.hpp | 214 |
2 files changed, 164 insertions, 51 deletions
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index b39ea0b..d537e9a 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -105,6 +105,7 @@ class RedfishService nodes.emplace_back(std::make_unique<CrashdumpService>(app)); nodes.emplace_back(std::make_unique<CrashdumpEntryCollection>(app)); nodes.emplace_back(std::make_unique<CrashdumpEntry>(app)); + nodes.emplace_back(std::make_unique<CrashdumpFile>(app)); nodes.emplace_back(std::make_unique<OnDemandCrashdump>(app)); #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI nodes.emplace_back(std::make_unique<SendRawPECI>(app)); diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp index f1bfda6..5248fb3 100644 --- a/redfish-core/lib/log_services.hpp +++ b/redfish-core/lib/log_services.hpp @@ -1519,6 +1519,104 @@ class CrashdumpService : public Node } }; +std::string getLogCreatedTime(const std::string &crashdump) +{ + nlohmann::json crashdumpJson = + nlohmann::json::parse(crashdump, nullptr, false); + if (crashdumpJson.is_discarded()) + { + return std::string(); + } + + nlohmann::json::const_iterator cdIt = crashdumpJson.find("crash_data"); + if (cdIt == crashdumpJson.end()) + { + return std::string(); + } + + nlohmann::json::const_iterator siIt = cdIt->find("METADATA"); + if (siIt == cdIt->end()) + { + return std::string(); + } + + nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); + if (tsIt == siIt->end()) + { + return std::string(); + } + + const std::string *logTime = tsIt->get_ptr<const std::string *>(); + if (logTime == nullptr) + { + return std::string(); + } + + std::string redfishDateTime = *logTime; + if (redfishDateTime.length() > 2) + { + redfishDateTime.insert(redfishDateTime.end() - 2, ':'); + } + + return redfishDateTime; +} + +std::string getLogFileName(const std::string &logTime) +{ + // Set the crashdump file name to "crashdump_<logTime>.json" using the + // created time without the timezone info + std::string fileTime = logTime; + size_t plusPos = fileTime.rfind('+'); + if (plusPos != std::string::npos) + { + fileTime.erase(plusPos); + } + return "crashdump_" + fileTime + ".json"; +} + +static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, + const std::string &logID, + nlohmann::json &logEntryJson) +{ + auto getStoredLogCallback = [asyncResp, logID, &logEntryJson]( + const boost::system::error_code ec, + const std::variant<std::string> &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<std::string>(&resp); + if (log == nullptr) + { + messages::internalError(asyncResp->res); + return; + } + std::string logTime = getLogCreatedTime(*log); + std::string fileName = getLogFileName(logTime); + + logEntryJson = { + {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, + {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, + {"@odata.id", + "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + + logID}, + {"Name", "CPU Crashdump"}, + {"Id", logID}, + {"EntryType", "Oem"}, + {"OemRecordFormat", "Crashdump URI"}, + {"Message", + "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + + logID + "/" + fileName}, + {"Created", std::move(logTime)}}; + }; + crow::connections::systemBus->async_method_call( + std::move(getStoredLogCallback), CrashdumpObject, + CrashdumpPath + std::string("/") + logID, + "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, "Log"); +} + class CrashdumpEntryCollection : public Node { public: @@ -1564,28 +1662,40 @@ class CrashdumpEntryCollection : public Node asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; asyncResp->res.jsonValue["@odata.context"] = - "/redfish/v1/" - "$metadata#LogEntryCollection.LogEntryCollection"; + "/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(); + std::vector<std::string> logIDs; + // Get the list of log entries and build up an empty array big + // enough to hold them for (const std::string &objpath : resp) { - // Don't list the on-demand log + // Ignore the on-demand log if (objpath.compare(CrashdumpOnDemandPath) == 0) { continue; } + + // Get the log ID std::size_t lastPos = objpath.rfind("/"); - if (lastPos != std::string::npos) + if (lastPos == std::string::npos) { - logEntryArray.push_back( - {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" - "Crashdump/Entries/" + - objpath.substr(lastPos + 1)}}); + continue; } + logIDs.emplace_back(objpath.substr(lastPos + 1)); + + // Add a space for the log entry to the array + logEntryArray.push_back({}); + } + // Now go through and set up async calls to fill in the entries + size_t index = 0; + for (const std::string &logID : logIDs) + { + // Add the log entry to the array + logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); } asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size(); @@ -1599,38 +1709,46 @@ class CrashdumpEntryCollection : public Node } }; -std::string getLogCreatedTime(const nlohmann::json &Crashdump) +class CrashdumpEntry : public Node { - nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data"); - if (cdIt != Crashdump.end()) + public: + CrashdumpEntry(CrowApp &app) : + Node(app, + "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", + 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<std::string> ¶ms) override { - nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); - if (siIt != cdIt->end()) + std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); + if (params.size() != 1) { - nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); - if (tsIt != siIt->end()) - { - const std::string *logTime = - tsIt->get_ptr<const std::string *>(); - if (logTime != nullptr) - { - return *logTime; - } - } + messages::internalError(asyncResp->res); + return; } + const std::string &logID = params[0]; + logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); } - BMCWEB_LOG_DEBUG << "failed to find log timestamp"; - - return std::string(); -} +}; -class CrashdumpEntry : public Node +class CrashdumpFile : public Node { public: - CrashdumpEntry(CrowApp &app) : + CrashdumpFile(CrowApp &app) : Node(app, - "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", - std::string()) + "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" + "<str>/", + std::string(), std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, @@ -1646,13 +1764,15 @@ class CrashdumpEntry : public Node const std::vector<std::string> ¶ms) override { std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); - if (params.size() != 1) + if (params.size() != 2) { messages::internalError(asyncResp->res); return; } - const int logId = std::atoi(params[0].c_str()); - auto getStoredLogCallback = [asyncResp, logId]( + const std::string &logID = params[0]; + const std::string &fileName = params[1]; + + auto getStoredLogCallback = [asyncResp, logID, fileName]( const boost::system::error_code ec, const std::variant<std::string> &resp) { if (ec) @@ -1667,29 +1787,21 @@ class CrashdumpEntry : public Node messages::internalError(asyncResp->res); return; } - nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); - if (j.is_discarded()) + + // Verify the file name parameter is correct + if (fileName != getLogFileName(getLogCreatedTime(*log))) { - messages::internalError(asyncResp->res); + messages::resourceMissingAtURI(asyncResp->res, fileName); 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)}}; + + // Configure this to be a file download when accessed from a browser + asyncResp->res.addHeader("Content-Disposition", "attachment"); + asyncResp->res.body() = *log; }; crow::connections::systemBus->async_method_call( std::move(getStoredLogCallback), CrashdumpObject, - CrashdumpPath + std::string("/") + std::to_string(logId), + CrashdumpPath + std::string("/") + logID, "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, "Log"); } |