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