| #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 |