blob: c70a6ca086f384eff04e71247afff497f2c15eaa [file] [log] [blame]
#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