diff options
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | redfish-core/lib/log_services.hpp | 78 | ||||
-rw-r--r-- | test/redfish-core/lib/log_services_test.cpp | 69 |
3 files changed, 131 insertions, 17 deletions
diff --git a/meson.build b/meson.build index 207d59b025..b79e849b32 100644 --- a/meson.build +++ b/meson.build @@ -454,6 +454,7 @@ srcfiles_unittest = files( 'test/redfish-core/lib/chassis_test.cpp', 'test/redfish-core/lib/sensors_test.cpp', 'test/redfish-core/lib/log_services_dump_test.cpp', + 'test/redfish-core/lib/log_services_test.cpp', 'test/redfish-core/lib/service_root_test.cpp', 'test/redfish-core/lib/thermal_subsystem_test.cpp', 'test/redfish-core/lib/power_subsystem_test.cpp', diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp index 5354f0a4c9..e634191151 100644 --- a/redfish-core/lib/log_services.hpp +++ b/redfish-core/lib/log_services.hpp @@ -31,6 +31,7 @@ #include "utils/dbus_utils.hpp" #include "utils/time_utils.hpp" +#include <systemd/sd-id128.h> #include <systemd/sd-journal.h> #include <tinyxml2.h> #include <unistd.h> @@ -171,23 +172,27 @@ inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID, const bool firstEntry = true) { int ret = 0; + static sd_id128_t prevBootID{}; static uint64_t prevTs = 0; static int index = 0; if (firstEntry) { + prevBootID = {}; prevTs = 0; } // Get the entry timestamp uint64_t curTs = 0; - ret = sd_journal_get_realtime_usec(journal, &curTs); + sd_id128_t curBootID{}; + ret = sd_journal_get_monotonic_usec(journal, &curTs, &curBootID); 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) + // If the timestamp isn't unique on the same boot, increment the index + bool sameBootIDs = sd_id128_equal(curBootID, prevBootID) != 0; + if (sameBootIDs && (curTs == prevTs)) { index++; } @@ -196,10 +201,19 @@ inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID, // Otherwise, reset it index = 0; } + + if (!sameBootIDs) + { + // Save the bootID + prevBootID = curBootID; + } // Save the timestamp prevTs = curTs; - entryID = std::to_string(curTs); + // make entryID as <bootID>_<timestamp>[_<index>] + std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; + sd_id128_to_string(curBootID, bootIDStr.data()); + entryID = std::format("{}_{}", bootIDStr.data(), curTs); if (index > 0) { entryID += "_" + std::to_string(index); @@ -246,25 +260,52 @@ static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, return true; } +// Entry is formed like "BootID_timestamp" or "BootID_timestamp_index" inline static bool getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& entryID, uint64_t& timestamp, - uint64_t& index) + const std::string& entryID, sd_id128_t& bootID, + uint64_t& timestamp, uint64_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 != std::string_view::npos) + // Convert the unique ID back to a bootID + timestamp to find the entry + std::string_view entryIDStrView(entryID); + auto underscore1Pos = entryIDStrView.find('_'); + if (underscore1Pos == std::string_view::npos) + { + // EntryID has no bootID or timestamp + messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); + return false; + } + + // EntryID has bootID + timestamp + + // Convert entryIDViewString to BootID + // NOTE: bootID string which needs to be null-terminated for + // sd_id128_from_string() + std::string bootIDStr(entryID, 0, underscore1Pos); + if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0) + { + messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); + return false; + } + + // Get the timestamp from entryID + std::string_view timestampStrView = entryIDStrView; + timestampStrView.remove_prefix(underscore1Pos + 1); + + // Check the index in timestamp + auto underscore2Pos = timestampStrView.find('_'); + if (underscore2Pos != std::string_view::npos) { // Timestamp has an index - tsStr.remove_suffix(tsStr.size() - underscorePos); - std::string_view indexStr(entryID); - indexStr.remove_prefix(underscorePos + 1); + timestampStrView.remove_suffix(timestampStrView.size() - + underscore2Pos); + std::string_view indexStr(timestampStrView); + indexStr.remove_prefix(underscore2Pos + 1); auto [ptr, ec] = std::from_chars(indexStr.begin(), indexStr.end(), index); if (ec != std::errc()) @@ -273,8 +314,10 @@ inline static bool return false; } } - // Timestamp has no index - auto [ptr, ec] = std::from_chars(tsStr.begin(), tsStr.end(), timestamp); + + // Now timestamp has no index + auto [ptr, ec] = std::from_chars(timestampStrView.begin(), + timestampStrView.end(), timestamp); if (ec != std::errc()) { messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); @@ -2665,9 +2708,10 @@ inline void requestRoutesBMCJournalLogEntry(App& app) return; } // Convert the unique ID back to a timestamp to find the entry + sd_id128_t bootID{}; uint64_t ts = 0; uint64_t index = 0; - if (!getTimestampFromID(asyncResp, entryID, ts, index)) + if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) { return; } @@ -2687,7 +2731,7 @@ inline void requestRoutesBMCJournalLogEntry(App& app) // index tracking the unique ID std::string idStr; bool firstEntry = true; - ret = sd_journal_seek_realtime_usec(journal.get(), ts); + ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); if (ret < 0) { BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", diff --git a/test/redfish-core/lib/log_services_test.cpp b/test/redfish-core/lib/log_services_test.cpp new file mode 100644 index 0000000000..4f7bedb72f --- /dev/null +++ b/test/redfish-core/lib/log_services_test.cpp @@ -0,0 +1,69 @@ +#include "app.hpp" +#include "async_resp.hpp" +#include "log_services.hpp" + +#include <systemd/sd-id128.h> + +#include <nlohmann/json.hpp> + +#include <format> +#include <string> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace redfish +{ +namespace +{ + +TEST(LogServicesBMCJouralTest, LogServicesBMCJouralGetReturnsError) +{ + auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>(); + sd_id128_t bootIDOut{}; + uint64_t timestampOut = 0; + uint64_t indexOut = 0; + uint64_t timestampIn = 1740970301UL; + std::string badBootIDStr = "78770392794344a29f81507f3ce5e"; + std::string goodBootIDStr = "78770392794344a29f81507f3ce5e78c"; + sd_id128_t goodBootID{}; + + // invalid test cases + EXPECT_FALSE(getTimestampFromID(shareAsyncResp, "", bootIDOut, timestampOut, + indexOut)); + EXPECT_FALSE(getTimestampFromID(shareAsyncResp, badBootIDStr, bootIDOut, + timestampOut, indexOut)); + EXPECT_FALSE(getTimestampFromID( + shareAsyncResp, std::format("{}_{}", badBootIDStr, timestampIn), + bootIDOut, timestampOut, indexOut)); + EXPECT_FALSE(getTimestampFromID( + shareAsyncResp, std::format("{}_{}", badBootIDStr, timestampIn), + bootIDOut, timestampOut, indexOut)); + + // obtain a goodBootID + EXPECT_GE(sd_id128_from_string(goodBootIDStr.c_str(), &goodBootID), 0); + + EXPECT_FALSE(getTimestampFromID( + shareAsyncResp, std::format("{}_{}", goodBootIDStr, "InvalidNum"), + bootIDOut, timestampOut, indexOut)); + + // Success cases + EXPECT_TRUE(getTimestampFromID( + shareAsyncResp, std::format("{}_{}", goodBootIDStr, timestampIn), + bootIDOut, timestampOut, indexOut)); + EXPECT_NE(sd_id128_equal(goodBootID, bootIDOut), 0); + EXPECT_EQ(timestampIn, timestampOut); + EXPECT_EQ(indexOut, 0); + + uint64_t indexIn = 1; + EXPECT_TRUE(getTimestampFromID( + shareAsyncResp, + std::format("{}_{}_{}", goodBootIDStr, timestampIn, indexIn), bootIDOut, + timestampOut, indexOut)); + EXPECT_NE(sd_id128_equal(goodBootID, bootIDOut), 0); + EXPECT_EQ(timestampIn, timestampOut); + EXPECT_EQ(indexOut, indexIn); +} + +} // namespace +} // namespace redfish |