| #include "bmcweb_config.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::An; |
| using ::testing::_; |
| |
| void setCheckpointsInBootTime(const std::string& host, |
| const std::vector<CheckpointTuple> checkpoints, |
| bool dbusException) |
| { |
| if (dbusException) |
| { |
| EXPECT_CALL(*managedStore::GetManagedObjectStore(), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, |
| const std::vector<CheckpointTuple>&)>&&>(), |
| "com.google.gbmc.boot_time_monitor", |
| "/xyz/openbmc_project/time/boot/" + host, |
| "xyz.openbmc_project.Time.Boot.Checkpoint", |
| "GetCheckpointList")) |
| .Times(testing::AtMost(1)) |
| .WillOnce(SimulateFailedAsyncPostDbusCallThreadSafeWithEmptyValueAction:: |
| SimulateFailedAsyncPostDbusCallWithEmptyValue()); |
| } |
| else |
| { |
| EXPECT_CALL(*managedStore::GetManagedObjectStore(), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, |
| const std::vector<CheckpointTuple>&)>&&>(), |
| "com.google.gbmc.boot_time_monitor", |
| "/xyz/openbmc_project/time/boot/" + host, |
| "xyz.openbmc_project.Time.Boot.Checkpoint", |
| "GetCheckpointList")) |
| .Times(testing::AtMost(1)) |
| .WillOnce(SimulateSuccessfulAsyncPostDbusCallThreadSafeWithValueAction< |
| std::vector<CheckpointTuple>>:: |
| SimulateSuccessfulAsyncPostDbusCallWithValue( |
| std::make_shared<std::vector<CheckpointTuple>>( |
| checkpoints))); |
| } |
| } |
| |
| void setDurationsInBootTime(const std::string& host, |
| const std::vector<DurationTuple>& durations, |
| bool dbusException) |
| { |
| if (dbusException) |
| { |
| EXPECT_CALL(*managedStore::GetManagedObjectStore(), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, |
| const std::vector<DurationTuple>&)>&&>(), |
| "com.google.gbmc.boot_time_monitor", |
| "/xyz/openbmc_project/time/boot/" + host, |
| "xyz.openbmc_project.Time.Boot.Duration", " ")) |
| .Times(testing::AtMost(1)) |
| .WillOnce(SimulateFailedAsyncPostDbusCallThreadSafeWithEmptyValueAction:: |
| SimulateFailedAsyncPostDbusCallWithEmptyValue()); |
| } |
| else |
| { |
| |
| EXPECT_CALL(*managedStore::GetManagedObjectStore(), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, |
| const std::vector<DurationTuple>&)>&&>(), |
| "com.google.gbmc.boot_time_monitor", |
| "/xyz/openbmc_project/time/boot/" + host, |
| "xyz.openbmc_project.Time.Boot.Duration", |
| "GetAdditionalDurations")) |
| .Times(testing::AtMost(1)) |
| .WillOnce(SimulateSuccessfulAsyncPostDbusCallThreadSafeWithValueAction< |
| std::vector<DurationTuple>>:: |
| SimulateSuccessfulAsyncPostDbusCallWithValue( |
| std::make_shared<std::vector<DurationTuple>>( |
| durations))); |
| } |
| } |
| |
| void setIsRebootingInBootTime(const std::string& host, const bool& isRebooting, |
| bool dbusException) |
| { |
| if (!dbusException) |
| { |
| KeyType key(ManagedType::kManagedProperty, |
| "com.google.gbmc.boot_time_monitor", |
| sdbusplus::message::object_path( |
| "/xyz/openbmc_project/time/boot/" + host), |
| "xyz.openbmc_project.Time.Boot.Statistic", "IsRebooting"); |
| std::shared_ptr<ValueType> mockIsRebooting = |
| managedStore::MockManagedStoreTest::CreateValueType( |
| std::move(isRebooting)); |
| ASSERT_TRUE(managedStore::GetManagedObjectStore() |
| ->upsertMockObjectIntoManagedStore(key, mockIsRebooting) |
| .ok()); |
| } |
| else |
| { |
| EXPECT_CALL(*managedStore::GetManagedObjectStore(), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&)>&&>(), |
| "com.google.gbmc.boot_time_monitor", |
| "/xyz/openbmc_project/time/boot/" + host, |
| "org.freedesktop.DBus.Properties", "Get", |
| "xyz.openbmc_project.Time.Boot.Statistic", |
| "IsRebooting", dbus::utility::DbusVariantType(true))) |
| .Times(testing::AtMost(1)) |
| .WillOnce(SimulateFailedAsyncPostDbusCallThreadSafeAction:: |
| SimulateFailedAsyncPostDbusCall()); |
| } |
| } |
| |
| 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::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({"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 = |
| {CheckpointTuple({"RebootStart", 1731001558345, 970750}), |
| CheckpointTuple({"TrayPowerCycle", 1731001558516, 970920}), |
| CheckpointTuple({"HostReleased", 1731001721661, 164160}), |
| CheckpointTuple({"UserspaceEnd", 1731002073055, 405110}) |
| |
| }, |
| .hostDurations = {DurationTuple({"NerfKernel", 143000}), |
| DurationTuple({"NerfUser", 36000}), |
| DurationTuple({"NerfDHCP", 34000}), |
| DurationTuple({"TimeOffset", 17650}), |
| DurationTuple({"Kernel", 36870}) |
| |
| }, |
| .bmcDurations = {DurationTuple({"BootToHost", 164120}), |
| DurationTuple({"Firmware", 0}), |
| DurationTuple({"Loader", 0}), |
| DurationTuple({"Kernel", 8146}), |
| DurationTuple({"InitRD", 0}), |
| DurationTuple({"Userspace", 389993}), |
| DurationTuple({"FirmwarePlusLoader", 44890})}, |
| .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) |
| { |
| setCheckpointsInBootTime("host0", testCase.hostCheckpoints, false); |
| setCheckpointsInBootTime("bmc", testCase.bmcCheckpoints, false); |
| setDurationsInBootTime("host0", testCase.hostDurations, false); |
| setDurationsInBootTime("bmc", testCase.bmcDurations, false); |
| setIsRebootingInBootTime("host0", testCase.hostRebooting, false); |
| setIsRebootingInBootTime("bmc", testCase.bmcRebooting, false); |
| handleHostBootTimeDataGet(app_, CreateRequest(), share_async_resp_, |
| "system"); |
| |
| 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::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) |
| { |
| setCheckpointsInBootTime("bmc", testCase.bmcCheckpoints, false); |
| setDurationsInBootTime("bmc", testCase.bmcDurations, false); |
| setIsRebootingInBootTime("bmc", testCase.bmcRebooting, false); |
| handleBmcBootTimeDataGet(app_, CreateRequest(), share_async_resp_); |
| |
| 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, 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, CheckComputerSystemBootTimeOemMultiHost) |
| { |
| handleComputerSystem(share_async_resp_, "", "system", true); |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_FALSE(json["Oem"]["Google"].contains("BootTime")); |
| } |
| |
| 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 |
| } |
| |
| } // namespace |
| } // namespace redfish |