| #include "api.hpp" |
| #include "resource.hpp" |
| |
| #include <fmt/format.h> |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdlib> |
| #include <filesystem> |
| #include <fstream> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| namespace boot_time_monitor |
| { |
| |
| namespace btm = boot_time_monitor; |
| |
| namespace fs = std::filesystem; |
| namespace |
| { |
| |
| class ApiTest : public ::testing::Test |
| { |
| public: |
| // Wall time (second param) equals zero means BMC will get wall time by |
| // itself. |
| const std::vector<std::tuple<std::string_view, int64_t, int64_t>> inCP = { |
| {"reboot_start", 1000, 0}, |
| {"Shutdown1", 0 /*will be 2000*/, 1000}, |
| {"thisIs_2_shutdown", 0 /*will be 3000*/, 50}, |
| {"000S0", 0 /*will be 4000*/, 0}, |
| {"_PowerBack_", 0 /*will be 5000*/, 100}, |
| {"start1", 0 /*will be 6000*/, 500}, |
| {"start2", 0 /*will be 7000*/, 100}, |
| {"reboot_end", 8000, 0}, |
| }; |
| |
| const std::vector<std::tuple<std::string_view, int64_t>> inDur = { |
| {"nic1", 0}, |
| {"A_nic", 1000}, |
| {"_nextNIC", 4}, |
| {"99NNN", 99}, |
| }; |
| |
| ApiTest() |
| { |
| // Use a temporary directory for test files |
| // Prefer TMPDIR environment variable if set, otherwise use /tmp |
| const char* tmp_env = std::getenv("TMPDIR"); |
| std::string base_tmp = (tmp_env != nullptr) ? tmp_env : "/tmp"; |
| testDir = base_tmp + "/api_test_temp_" + std::to_string(getpid()) + "/"; |
| |
| // Clean up the temporary directory and its contents |
| // Check if the directory exists before removing |
| if (fs::exists(testDir)) |
| { |
| fs::remove_all(testDir); |
| } |
| else |
| { |
| fs::create_directories(testDir); // Ensure the directory exists |
| } |
| |
| // Use a temporary directory for test files |
| btm::resource::BTMonitorDir = |
| testDir; // Set the global monitor dir for this test scope |
| } |
| |
| protected: |
| std::string testDir; |
| }; |
| |
| /* |
| * Verifies that setting multiple checkpoints, including those that generate |
| * an additional ":BEGIN" entry due to a non-zero duration, results in the |
| * correct total count when retrieved via GetNodeCheckpointList immediately |
| * after setting. |
| */ |
| TEST_F(ApiTest, checkpoint_set) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host0NodeConfig("host0", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host0NodeConfig); |
| |
| // Calculate expected lines: checkpoints with duration != 0 add two lines |
| std::size_t expectedLines = 0; |
| for (const auto& cp : inCP) |
| { |
| expectedLines += (std::get<2>(cp) != 0) ? 2 : 1; |
| } |
| |
| // Set checkpoints for host0 |
| for (const auto& cp : inCP) |
| { |
| api->SetNodeCheckpoint(host0NodeConfig, std::get<0>(cp), |
| std::get<1>(cp), std::get<2>(cp)); |
| } |
| |
| EXPECT_EQ(api->GetNodeCheckpointList(host0NodeConfig).size(), |
| expectedLines); |
| } |
| |
| /* |
| * Verifies that calling SetNodeCheckpoint with a non-zero duration specifically |
| * creates both the original checkpoint entry and the associated ":BEGIN" entry. |
| */ |
| TEST_F(ApiTest, checkpoint_set_with_duration) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host1NodeConfig("host1", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host1NodeConfig); |
| |
| // Test on host1 |
| const std::string_view testName = "StageWithDuration"; |
| const int64_t testDuration = 500; |
| const std::string expectedBeginName = std::string(testName) + |
| btm::resource::kBeginStageSuffix; |
| |
| api->SetNodeCheckpoint(host1NodeConfig, testName, 0, testDuration); |
| |
| auto checkpoints = api->GetNodeCheckpointList(host1NodeConfig); |
| EXPECT_EQ(checkpoints.size(), 2); |
| |
| // Check if both the original and the :BEGIN checkpoints exist |
| bool foundOriginal = false; |
| bool foundBegin = false; |
| for (const auto& cp : checkpoints) |
| { |
| if (std::get<0>(cp) == testName) |
| { |
| foundOriginal = true; |
| } |
| else if (std::get<0>(cp) == expectedBeginName) |
| { |
| foundBegin = true; |
| } |
| } |
| EXPECT_TRUE(foundOriginal); |
| EXPECT_TRUE(foundBegin); |
| } |
| |
| /* |
| * Verifies that setting multiple durations via SetNodeDuration results in the |
| * correct total count when retrieved via GetNodeAdditionalDurations |
| * immediately after setting. |
| */ |
| TEST_F(ApiTest, duration_set) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host2NodeConfig("host2", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host2NodeConfig); |
| |
| // Test on host2 |
| // Set durations for host2 |
| for (const auto& dur : inDur) |
| { |
| api->SetNodeDuration(host2NodeConfig, std::get<0>(dur), |
| std::get<1>(dur)); |
| } |
| EXPECT_EQ(api->GetNodeAdditionalDurations(host2NodeConfig).size(), |
| inDur.size()); |
| } |
| |
| /* |
| * Verifies the state transitions of the IsNodeRebooting flag. |
| * It should be false initially, true after a checkpoint is set, false after |
| * NotifyNodeComplete is called, and true again after another checkpoint is set. |
| */ |
| TEST_F(ApiTest, rebootcomplete_behavior) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host3NodeConfig("host3", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host3NodeConfig); |
| |
| // Test on host3 |
| // Initially, no files exist or are empty, so not rebooting |
| EXPECT_FALSE(api->IsNodeRebooting(host3NodeConfig)); |
| |
| // Setting a checkpoint starts the "reboot" process for the node |
| api->SetNodeCheckpoint(host3NodeConfig, "FirstCheckpoint", 0, 0); |
| EXPECT_TRUE(api->IsNodeRebooting(host3NodeConfig)); |
| |
| // Marking complete finishes the reboot process |
| api->NotifyNodeComplete(host3NodeConfig); |
| EXPECT_FALSE(api->IsNodeRebooting(host3NodeConfig)); |
| |
| // Setting another checkpoint starts a new reboot cycle |
| api->SetNodeCheckpoint(host3NodeConfig, "SecondCheckpoint", 0, 0); |
| EXPECT_TRUE(api->IsNodeRebooting(host3NodeConfig)); |
| } |
| |
| /* |
| * Verifies that GetNodeCheckpointList retrieves the list of checkpoints |
| * belonging to the *current* boot cycle (i.e., since the last |
| * NotifyNodeComplete). Checkpoints from the previous completed cycle are not |
| * included. |
| */ |
| TEST_F(ApiTest, checkpoint_get) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host4NodeConfig("host4", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host4NodeConfig); |
| |
| // Test on host4 |
| // Set the initial checkpoints |
| for (const auto& cp : inCP) |
| { |
| api->SetNodeCheckpoint(host4NodeConfig, std::get<0>(cp), |
| std::get<1>(cp), std::get<2>(cp)); |
| } |
| // Calculate expected lines for the initial set |
| std::size_t expectedLines = |
| api->GetNodeCheckpointList(host4NodeConfig).size(); |
| |
| // Mark the initial set as complete |
| api->NotifyNodeComplete(host4NodeConfig); |
| // GetCheckpointList should return the *completed* list, also add one to the |
| // "RebootEnd" checkpoint |
| EXPECT_EQ(api->GetNodeCheckpointList(host4NodeConfig).size(), |
| expectedLines + 1); |
| |
| // Set a new checkpoint (part of the *next* boot cycle) |
| api->SetNodeCheckpoint(host4NodeConfig, "NewCycleCheckpoint", 0, 0); |
| |
| // GetCheckpointList should return the checkpoint amount after previous |
| // RebootComplete |
| EXPECT_EQ(api->GetNodeCheckpointList(host4NodeConfig).size(), 1); |
| } |
| |
| /* |
| * Verifies that when SetNodeCheckpoint is called with wallTime=0, the system |
| * automatically assigns non-zero values for both the wall time (epoch) and |
| * monotonic time in the recorded checkpoint data. |
| */ |
| TEST_F(ApiTest, checkpoint_monotory) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host5NodeConfig("host5", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host5NodeConfig); |
| |
| // Test on host5 |
| api->SetNodeCheckpoint(host5NodeConfig, "AutoTimestamp", 0, 0); |
| |
| auto checkpoints = api->GetNodeCheckpointList(host5NodeConfig); |
| ASSERT_EQ(checkpoints.size(), 1); |
| |
| // Wall time (index 1) and monotonic time (index 2) should be non-zero |
| EXPECT_NE(std::get<1>(checkpoints[0]), 0); |
| EXPECT_NE(std::get<2>(checkpoints[0]), 0); |
| } |
| |
| /* |
| * Verifies that calls to SetPSUCheckpoint and SetPSUDuration are broadcast |
| * and correctly recorded for *all* registered nodes. |
| */ |
| TEST_F(ApiTest, powercycle_behavior) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| std::vector<btm::NodeConfig> ncs; |
| for (int i = 6; i < 8; i++) |
| { |
| ncs.emplace_back("host6", std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(ncs.back()); |
| } |
| |
| // This test inherently checks all nodes |
| const std::string_view psuCpName = "PSU_PowerOn"; |
| const std::string_view psuDurName = "PSU_Stabilize"; |
| |
| api->SetPSUCheckpoint(psuCpName, 0, 0); |
| api->SetPSUDuration(psuDurName, 1234); |
| |
| for (const auto& nc : ncs) // Iterate through all created nodes |
| { |
| auto checkpoints = api->GetNodeCheckpointList(nc); |
| EXPECT_EQ(checkpoints.size(), 1); |
| EXPECT_EQ(std::get<0>(checkpoints.back()), psuCpName); |
| |
| auto durations = api->GetNodeAdditionalDurations(nc); |
| EXPECT_EQ(durations.size(), 1); |
| EXPECT_EQ(std::get<0>(durations.back()), psuDurName); |
| EXPECT_EQ(std::get<1>(durations.back()), 1234); |
| } |
| } |
| |
| /* |
| * Verifies that the NotifyNodeComplete(RebootComplete) should be individule, |
| * not boardcast to all of the nodes. |
| */ |
| TEST_F(ApiTest, notify_complete_is_individual) |
| { |
| std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>(); |
| btm::NodeConfig host9NodeConfig("host9", |
| std::string(btm::kBootTimeTagHost)); |
| btm::NodeConfig host10NodeConfig("host10", |
| std::string(btm::kBootTimeTagHost)); |
| api->RegisterNode(host9NodeConfig); |
| api->RegisterNode(host10NodeConfig); |
| |
| // Set checkpoints on two different nodes to mark them as rebooting |
| api->SetNodeCheckpoint(host9NodeConfig, "Node0_BootStart", 0, 0); |
| api->SetNodeCheckpoint(host10NodeConfig, "Node1_BootStart", 0, 0); |
| |
| EXPECT_TRUE(api->IsNodeRebooting(host9NodeConfig)); |
| EXPECT_TRUE(api->IsNodeRebooting(host10NodeConfig)); |
| |
| // Notify only the first node as complete |
| api->NotifyNodeComplete(host9NodeConfig); |
| |
| // Verify the first node is no longer rebooting, but the second one still is |
| EXPECT_FALSE(api->IsNodeRebooting(host9NodeConfig)); |
| EXPECT_TRUE(api->IsNodeRebooting(host10NodeConfig)); |
| } |
| } // namespace |
| } // namespace boot_time_monitor |