blob: 9db30d43e63c08adebbe2e47c0e9144342875145 [file] [log] [blame]
#include "absl/cleanup/cleanup.h"
#include "bmcweb_config.h"
#include "boottime_api/boottime_api.h"
#include "app.hpp"
#include "async_resp.hpp"
#include "http_response.hpp"
#include "managers.hpp"
#include "nlohmann/json.hpp"
#include "snapshot_fixture.hpp"
#include "systems.hpp"
#include "utils/boot_time_utils.hpp"
#include <memory>
#include <vector>
#include <gmock/gmock.h> // IWYU pragma: keep
#include <gtest/gtest.h> // IWYU pragma: keep
// IWYU pragma: no_include <gtest/gtest-message.h>
// IWYU pragma: no_include <gtest/gtest-test-part.h>
// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
// IWYU pragma: no_include <gmock/gmock-matchers.h>
// IWYU pragma: no_include <gtest/gtest-matchers.h>
namespace redfish
{
namespace
{
using ::dbus::utility::DbusVariantType;
using ::managedStore::KeyType;
using ::managedStore::ManagedType;
using ::managedStore::SimulateFailedAsyncPostDbusCallThreadSafeAction;
using ::managedStore::
SimulateFailedAsyncPostDbusCallThreadSafeWithEmptyValueAction;
using ::managedStore::
SimulateSuccessfulAsyncPostDbusCallThreadSafeWithValueAction;
using ::managedStore::ValueType;
using redfish::boot_time_utils::CheckpointTuple;
using redfish::boot_time_utils::DurationTuple;
using ::testing::_;
using ::testing::An;
class MockAPI : public boot_time_monitor::api::IBoottimeApi
{
public:
MOCK_METHOD(absl::Status, SetNodeCheckpoint,
(const boot_time_monitor::NodeConfig& nodeConfig,
std::string_view checkpointName, int64_t wallTime,
int64_t selfMeasuredDuration),
(override));
MOCK_METHOD(absl::Status, SetNodesCheckpointByTag,
(std::string_view tag, std::string_view checkpointName,
int64_t wallTime, int64_t selfMeasuredDuration),
(override));
MOCK_METHOD(absl::Status, SetNodeDuration,
(const boot_time_monitor::NodeConfig& nodeConfig,
std::string_view durationName, int64_t duration),
(override));
MOCK_METHOD(absl::Status, SetNodesDurationByTag,
(std::string_view tag, std::string_view durationName,
int64_t duration),
(override));
MOCK_METHOD(absl::Status, SetPSUCheckpoint,
(std::string_view checkpointName, int64_t wallTime,
int64_t selfMeasuredDuration),
(override));
MOCK_METHOD(absl::Status, SetPSUDuration,
(std::string_view durationName, int64_t duration), (override));
MOCK_METHOD(absl::Status, NotifyNodeComplete,
(const boot_time_monitor::NodeConfig& nodeConfig), (override));
MOCK_METHOD(absl::Status, NotifyNodesCompleteByTag, (std::string_view tag),
(override));
MOCK_METHOD(std::vector<CheckpointTuple>, GetNodeCheckpointList,
(const boot_time_monitor::NodeConfig& nodeConfig), (override));
MOCK_METHOD(std::vector<DurationTuple>, GetNodeAdditionalDurations,
(const boot_time_monitor::NodeConfig& nodeConfig), (override));
MOCK_METHOD(bool, IsNodeRebooting,
(const boot_time_monitor::NodeConfig& nodeConfig), (override));
};
void setCheckpointsInBootTime(const std::string& nodeName,
const std::vector<CheckpointTuple> checkpoints,
const std::shared_ptr<MockAPI>& api)
{
boot_time_monitor::NodeConfig nc{.node_name = nodeName, .tag = std::string(boot_time_monitor::kBootTimeTagHost)};
EXPECT_CALL(*api, GetNodeCheckpointList(nc))
.Times(testing::AtMost(1))
.WillOnce(testing::Return(std::vector<CheckpointTuple>(checkpoints)));
}
void setDurationsInBootTime(const std::string& nodeName,
const std::vector<DurationTuple>& durations,
const std::shared_ptr<MockAPI>& api)
{
boot_time_monitor::NodeConfig nc{.node_name = nodeName, .tag = std::string(boot_time_monitor::kBootTimeTagHost)};
EXPECT_CALL(*api, GetNodeAdditionalDurations(nc))
.Times(testing::AtMost(1))
.WillOnce(testing::Return(std::vector<DurationTuple>(durations)));
}
void setIsRebootingInBootTime(const std::string& nodeName,
const bool isRebooting,
const std::shared_ptr<MockAPI>& api)
{
boot_time_monitor::NodeConfig nc{.node_name = nodeName, .tag = std::string(boot_time_monitor::kBootTimeTagHost)};
EXPECT_CALL(*api, IsNodeRebooting(nc))
.Times(testing::AtMost(1))
.WillOnce(testing::Return(isRebooting));
}
struct BootTimeTestCase
{
std::string_view name;
std::vector<CheckpointTuple> hostCheckpoints;
std::vector<CheckpointTuple> bmcCheckpoints;
std::vector<DurationTuple> hostDurations;
std::vector<DurationTuple> bmcDurations;
bool hostRebooting;
bool bmcRebooting;
std::map<std::string, double> expectedBreakdown;
std::map<std::string, double> expectedDurations;
std::vector<std::tuple<std::string, std::string, int64_t>>
expectedRebootFlow;
std::tuple<std::string, bool> expectedStat;
double expectedTotal;
};
void BootTimeTestResponseCheck(
BootTimeTestCase testCase,
std::shared_ptr<bmcweb::AsyncResp>& share_async_resp_)
{
EXPECT_EQ(share_async_resp_->res.jsonValue["@odata.type"],
"#GoogleBootTime.v1_0_0.GoogleBootTime");
EXPECT_EQ(share_async_resp_->res.jsonValue["Id"], "BootTimeData");
EXPECT_EQ(share_async_resp_->res.jsonValue["Name"], "Boot time data");
// Breakdown
EXPECT_TRUE(share_async_resp_->res.jsonValue.contains("Breakdown"));
std::map<std::string, double> expectedBreakdown(testCase.expectedBreakdown);
for (auto item : share_async_resp_->res.jsonValue["Breakdown"])
{
EXPECT_TRUE(item.contains("Stage"));
EXPECT_TRUE(item.contains("Duration"));
auto expected = expectedBreakdown.find(item["Stage"]);
if (expected == expectedBreakdown.end())
{
FAIL() << "Could not find " << item["Stage"]
<< " in the expected Breakdown." << '\n';
continue;
}
EXPECT_NEAR(expected->second, item["Duration"], 1e-1);
expectedBreakdown.erase(expected);
}
EXPECT_EQ(expectedBreakdown.size(), 0)
<< "Not all of the expected breakdowns are found in the response";
// Duration
EXPECT_TRUE(share_async_resp_->res.jsonValue.contains("Durations"));
std::map<std::string, double> expectedDurations(testCase.expectedDurations);
for (auto item : share_async_resp_->res.jsonValue["Durations"])
{
EXPECT_TRUE(item.contains("Name"));
EXPECT_TRUE(item.contains("Duration"));
auto expected = expectedDurations.find(item["Name"]);
if (expected == expectedDurations.end())
{
FAIL() << "Could not find " << item["Name"]
<< " in the expected durations." << '\n';
continue;
}
EXPECT_NEAR(expected->second, item["Duration"], 1e-1);
expectedDurations.erase(expected);
}
EXPECT_EQ(expectedDurations.size(), 0)
<< "Not all of the expected durations are found in the response";
// RebootFlow
EXPECT_TRUE(share_async_resp_->res.jsonValue.contains("RebootFlow"));
std::vector<std::tuple<std::string, std::string, int64_t>>
expectedRebootFlow(testCase.expectedRebootFlow);
size_t index = 0;
for (auto item : share_async_resp_->res.jsonValue["RebootFlow"])
{
EXPECT_TRUE(item.contains("Stage"));
EXPECT_TRUE(item.contains("StartTime"));
EXPECT_TRUE(item.contains("MonotonicStartTime"));
if (index > expectedRebootFlow.size())
{
FAIL() << "More reboot flow than expected.";
break;
}
auto [stage, startTime, monoStartTime] = expectedRebootFlow[index++];
EXPECT_EQ(stage, static_cast<std::string>(item["Stage"]));
EXPECT_EQ(startTime, static_cast<std::string>(item["StartTime"]));
EXPECT_EQ(monoStartTime, item["MonotonicStartTime"]);
}
EXPECT_EQ(expectedRebootFlow.size(), index)
<< "Not all of the expected RebootFlow are found in the response";
// Stat
EXPECT_TRUE(share_async_resp_->res.jsonValue.contains("Stat"));
auto stat = share_async_resp_->res.jsonValue["Stat"];
EXPECT_TRUE(stat.contains("PowerSource"));
EXPECT_TRUE(stat.contains("IsRebooting"));
auto [powerSource, rebooting] = testCase.expectedStat;
EXPECT_EQ(stat["PowerSource"], powerSource);
EXPECT_EQ(stat["IsRebooting"], rebooting);
// Total
EXPECT_EQ(testCase.expectedTotal,
share_async_resp_->res.jsonValue["Total"]);
}
TEST_F(SnapshotFixture, SystemBootTime)
{
std::shared_ptr<MockAPI> api = std::make_shared<MockAPI>();
std::vector<struct BootTimeTestCase> testCases = {
BootTimeTestCase{
.name = "happy path",
.hostCheckpoints =
{CheckpointTuple({"Shutdown:BEGIN", 1731001381770, 794420}),
CheckpointTuple({"Shutdown", 1731001430000, 842650}),
CheckpointTuple({"HostState:Off", 1731001439805, 852210}),
CheckpointTuple({"OSStatus:Inactive", 1731001439808, 852210}),
CheckpointTuple({"TrayPowerCycle", 1731001558516, 970920}),
CheckpointTuple({"HostState:Off", 1731001625214, 67710}),
CheckpointTuple({"OSStatus:Inactive", 1731001625249, 67750}),
CheckpointTuple({"HostState:Running", 1731001725571, 168070}),
CheckpointTuple({"OSStatus:Standby", 1731001838682, 281180}),
CheckpointTuple({"Loader", 179000, 460860}),
CheckpointTuple({"Userspace:BEGIN", 1731002185380, 517730}),
CheckpointTuple({"Userspace", 1731002295000, 627350})},
.bmcCheckpoints =
{},
.hostDurations = {DurationTuple({"NerfKernel", 143000}),
DurationTuple({"NerfUser", 36000}),
DurationTuple({"NerfDHCP", 34000}),
DurationTuple({"TimeOffset", 17650}),
DurationTuple({"Kernel", 36870})
},
.bmcDurations = {},
.hostRebooting = false,
.bmcRebooting = false,
.expectedBreakdown = {{"Firmware", 113.11},
{"Kernel", 36.87},
{"Loader", 179.68},
{"Off", 259.8},
{"OffToPowerCycle", 118.71},
{"Shutdown", 48.23},
{"TransitioningToKernel", 20},
{"TransitioningToOff", 9.56},
{"Userspace", 109.62}},
.expectedDurations = {{"NerfKernel", 143},
{"NerfUser", 36},
{"NerfDHCP", 34},
{"TimeOffset", 17.65},
{"Kernel", 36.87}},
.expectedRebootFlow =
{{"Shutdown:BEGIN", "2024-11-07T17:43:01.770+00:00", 794420},
{"Shutdown", "2024-11-07T17:43:50.000+00:00", 842650},
{"HostState:Off", "2024-11-07T17:43:59.560+00:00", 852210},
{"OSStatus:Inactive", "2024-11-07T17:43:59.560+00:00", 852210},
{"TrayPowerCycle", "2024-11-07T17:45:58.270+00:00", 970920},
{"HostState:Off", "2024-11-07T17:45:58.370+00:00", 67710},
{"OSStatus:Inactive", "2024-11-07T17:45:58.410+00:00", 67750},
{"HostState:Running", "2024-11-07T17:47:38.730+00:00", 168070},
{"OSStatus:Standby", "2024-11-07T17:49:31.840+00:00", 281180},
{"Loader", "2024-11-07T17:52:31.520+00:00", 460860},
{"Userspace:BEGIN", "2024-11-07T17:53:28.390+00:00", 517730},
{"Userspace", "2024-11-07T17:55:18.010+00:00", 627350}},
.expectedStat = {"AC", false},
.expectedTotal = 895.58},
BootTimeTestCase{.name = "empty",
.hostCheckpoints = {},
.bmcCheckpoints = {},
.hostDurations = {},
.bmcDurations = {},
.hostRebooting = false,
.bmcRebooting = false,
.expectedBreakdown = {},
.expectedDurations = {},
.expectedRebootFlow = {},
.expectedStat = {"Unknown", false},
.expectedTotal = 0.0},
};
for (auto testCase : testCases)
{
boot_time_monitor::NodeConfig nc{.node_name = "host0", .tag = std::string(boot_time_monitor::kBootTimeTagHost)};
setCheckpointsInBootTime("host0", testCase.hostCheckpoints, api);
setDurationsInBootTime("host0", testCase.hostDurations, api);
setIsRebootingInBootTime("host0", testCase.hostRebooting, api);
populateHostBootTimeData(share_async_resp_, "system", api, nc);
RunIoUntilDone();
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::ok);
EXPECT_EQ(share_async_resp_->res.jsonValue["@odata.id"],
"/redfish/v1/Systems/system/Oem/Google/BootTime");
BootTimeTestResponseCheck(testCase, share_async_resp_);
std::cout << nlohmann::json(share_async_resp_->res.jsonValue).dump()
<< '\n'
<< '\n';
}
}
TEST_F(SnapshotFixture, BmcBootTime)
{
std::shared_ptr<MockAPI> api = std::make_shared<MockAPI>();
std::vector<struct BootTimeTestCase> testCases = {
BootTimeTestCase{
.name = "happy path",
.hostCheckpoints = {},
.bmcCheckpoints =
{CheckpointTuple({"RebootStart", 1731086409343, 948870}),
CheckpointTuple({"TrayPowerCycle", 1731086409499, 949020}),
CheckpointTuple({"HostReleased", 1731086574782, 166280}),
CheckpointTuple({"UserspaceEnd", 1731086967609, 448680})},
.hostDurations = {},
.bmcDurations = {DurationTuple({"BootToHost", 166230}),
DurationTuple({"Firmware", 0}),
DurationTuple({"Loader", 0}),
DurationTuple({"Kernel", 8231}),
DurationTuple({"InitRD", 0}),
DurationTuple({"Userspace", 438132}),
DurationTuple({"FirmwarePlusLoader", 44330})},
.hostRebooting = false,
.bmcRebooting = false,
.expectedBreakdown = {{"BootToHost", 166.3}, {"Shutdown", 0.15}},
.expectedDurations = {{"BootToHost", 166.3},
{"Firmware", 0},
{"Loader", 0},
{"Kernel", 8.231},
{"InitRD", 0},
{"Userspace", 438.1},
{"FirmwarePlusLoader", 44.3}},
.expectedRebootFlow =
{{"RebootStart", "2024-11-08T17:20:09.343+00:00", 948870},
{"TrayPowerCycle", "2024-11-08T17:20:09.493+00:00", 949020},
{"HostReleased", "2024-11-08T17:20:09.593+00:00", 166280},
{"UserspaceEnd", "2024-11-08T17:24:51.993+00:00", 448680}},
.expectedStat = {"AC", false},
.expectedTotal = 558.266},
BootTimeTestCase{.name = "empty",
.hostCheckpoints = {},
.bmcCheckpoints = {},
.hostDurations = {},
.bmcDurations = {},
.hostRebooting = false,
.bmcRebooting = false,
.expectedBreakdown = {},
.expectedDurations = {},
.expectedRebootFlow = {},
.expectedStat = {"Unknown", false},
.expectedTotal = 0.0},
};
for (auto testCase : testCases)
{
boot_time_monitor::NodeConfig nc{.node_name = "bmc", .tag = std::string(boot_time_monitor::kBootTimeTagHost)};
setCheckpointsInBootTime("bmc", testCase.bmcCheckpoints, api);
setDurationsInBootTime("bmc", testCase.bmcDurations, api);
setIsRebootingInBootTime("bmc", testCase.bmcRebooting, api);
populateBmcBootTimeData(share_async_resp_, api, nc);
RunIoUntilDone();
std::cout << nlohmann::json(share_async_resp_->res.jsonValue).dump()
<< '\n'
<< '\n';
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::ok);
EXPECT_EQ(share_async_resp_->res.jsonValue["@odata.id"],
"/redfish/v1/Managers/bmc/Oem/Google/BootTime");
BootTimeTestResponseCheck(testCase, share_async_resp_);
}
}
TEST_F(SnapshotFixture, CheckManagerBootTime)
{
handleManagerGet(app_, CreateRequest(), share_async_resp_);
nlohmann::json& json = share_async_resp_->res.jsonValue;
#ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME
EXPECT_EQ(json["Oem"]["Google"]["BootTime"]["@odata.id"],
"/redfish/v1/Managers/bmc/Oem/Google/BootTime");
#else
EXPECT_FALSE(json["Oem"]["Google"].contains("BootTime"));
#endif
}
TEST_F(SnapshotFixture, CheckComputerSystemBootTimeOemSingleHost)
{
handleComputerSystem(share_async_resp_, "", "system", false);
nlohmann::json& json = share_async_resp_->res.jsonValue;
#ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME
EXPECT_EQ(json["Oem"]["Google"]["BootTime"]["@odata.id"],
"/redfish/v1/Systems/system/Oem/Google/BootTime");
#else
EXPECT_FALSE(json["Oem"]["Google"].contains("BootTime"));
#endif
}
TEST_F(SnapshotFixture_Platform23, CheckComputerSystemBootTimeOemMultiHost)
{
handleComputerSystem(
share_async_resp_,
"/xyz/openbmc_project/inventory/system/board/platform23/system1",
"system1", true);
RunIoUntilDone();
EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
nlohmann::json& json = share_async_resp_->res.jsonValue;
#ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME
EXPECT_EQ(json["Oem"]["Google"]["BootTime"]["@odata.id"],
"/redfish/v1/Systems/system/Oem/Google/BootTime");
#else
EXPECT_FALSE(json["Oem"]["Google"].contains("BootTime"));
#endif
}
TEST_F(SnapshotFixture, ParseSystemNameToIndex)
{
int index = -1;
// Single host
EXPECT_TRUE(parseSystemNameToIndex("system", false, index));
EXPECT_EQ(index, 0);
EXPECT_FALSE(parseSystemNameToIndex("system0", false, index));
EXPECT_EQ(index, -1);
EXPECT_FALSE(parseSystemNameToIndex("wrongName", false, index));
EXPECT_EQ(index, -1);
EXPECT_FALSE(parseSystemNameToIndex("system1", false, index));
EXPECT_EQ(index, -1);
EXPECT_FALSE(parseSystemNameToIndex("system2", false, index));
EXPECT_EQ(index, -1);
// Multiple hosts
EXPECT_FALSE(parseSystemNameToIndex("system", true, index));
EXPECT_EQ(index, -1);
EXPECT_FALSE(parseSystemNameToIndex("system0", true, index));
EXPECT_EQ(index, -1);
EXPECT_FALSE(parseSystemNameToIndex("wrongName", true, index));
EXPECT_EQ(index, -1);
EXPECT_TRUE(parseSystemNameToIndex("system1", true, index));
EXPECT_EQ(index, 0);
EXPECT_TRUE(parseSystemNameToIndex("system2", true, index));
EXPECT_EQ(index, 1);
}
} // namespace
} // namespace redfish