Implement Multi System LastResetTime
```
curl localhost/redfish/v1/Systems/system1 | jq .LastResetTime
"2025-06-06T05:51:41+00:00"
curl localhost/redfish/v1/Systems/system2 | jq .LastResetTime
"2025-06-06T05:51:20+00:00"
```
after reset host1 and host2, reset time is updated
```
curl localhost/redfish/v1/Systems/system1 | jq .LastResetTime
"2025-06-06T22:36:20+00:00"
curl localhost/redfish/v1/Systems/system2 | jq .LastResetTime
"2025-06-06T22:37:22+00:00"
```
Tested: tested on multhost system
Google-Bug-Id: 415354941
Change-Id: I7eefbdb2851bf693145f0625ad07c107e5bfe6a8
Signed-off-by: Hao Zhou <haoooamazing@google.com>
diff --git a/meson.build b/meson.build
index 60cd322..55a5fba 100644
--- a/meson.build
+++ b/meson.build
@@ -592,6 +592,7 @@
'test/redfish-core/lib/log_services_test.cpp',
'test/redfish-core/lib/service_root_test.cpp',
'test/redfish-core/lib/system_test.cpp',
+ 'test/redfish-core/lib/system_multi_host_test.cpp',
'test/redfish-core/lib/thermal_subsystem_test.cpp',
'test/redfish-core/lib/power_test.cpp',
'test/redfish-core/lib/power_supply_test.cpp',
diff --git a/meson_options.txt b/meson_options.txt
index 4c56ba8..d0a9c39 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -808,7 +808,7 @@
option(
'snapshot-filepath-platform23',
type : 'string',
- value : '/tmpfs/src/ci_workspace/openbmc-build-scripts/additional/scrubbed_gBMCwebManagedStore_platform23_0_2025_05_09.json',
+ value : '/tmpfs/src/ci_workspace/openbmc-build-scripts/additional/scrubbed_gBMCwebManagedStore_platform23_0_2025_06_11.json',
description : 'File location to the snapshot used for unit tests.'
)
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 6f0d96d..7a0add1 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -63,6 +63,9 @@
constexpr std::array<std::string_view, 1> hostStateInterfaces{
{"xyz.openbmc_project.State.Host"}};
+constexpr std::array<std::string_view, 1> chassisStateInterfaces{
+ {"xyz.openbmc_project.State.Chassis"}};
+
/**
* @brief Updates the Functional State of DIMMs
*
@@ -3850,31 +3853,67 @@
sdbusplus::message::object_path(systemObjectPath + "/host_power"),
sdbusplus::message::object_path("/xyz/openbmc_project/state"), 0,
hostStateInterfaces, requestContext,
- [asyncResp, systemObjectPath](
- const boost::system::error_code& ec,
- const dbus::utility::MapperGetSubTreeResponse& objects) {
+ [asyncResp, systemObjectPath,
+ multiHost](const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& objects) {
if (ec)
{
BMCWEB_LOG_ERROR
- << "Chassis power control not associated with system object";
+ << "Host power control not associated with system object";
messages::internalError(asyncResp->res);
return;
}
- // There should only be one chassis power control object associated with
+ // There should only be one host power control object associated with
// this system
if (objects.size() > 1)
{
BMCWEB_LOG_ERROR
- << "Too many chassis power control objects associated with the system";
+ << "Too many host power control objects associated with the system";
messages::internalError(asyncResp->res);
return;
}
getHostState(asyncResp, systemObjectPath, objects);
- getLastResetTime(asyncResp, systemObjectPath, objects);
+ if (!multiHost)
+ {
+ getLastResetTime(asyncResp, systemObjectPath, objects);
+ }
});
+ // Handling last reset time for multi-system
+ if (multiHost)
+ {
+ managedStore::GetManagedObjectStore()->getAssociatedSubTree(
+ sdbusplus::message::object_path(systemObjectPath +
+ "/chassis_power"),
+ sdbusplus::message::object_path("/xyz/openbmc_project/state"), 0,
+ chassisStateInterfaces, requestContext,
+ [asyncResp, systemObjectPath](
+ const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& objects) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR
+ << "Chassis power control not associated with system object";
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ // There should only be one chassis power control object associated
+ // with this system
+ if (objects.size() > 1)
+ {
+ BMCWEB_LOG_ERROR
+ << "Too many chassis power control objects associated with the system";
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ getLastResetTime(asyncResp, systemObjectPath, objects);
+ });
+ }
+
getPCIeDeviceList(asyncResp, systemName, "PCIeDevices");
// Handling multi-system and single-system separately. In the future, the
diff --git a/test/redfish-core/lib/bios_test.cpp b/test/redfish-core/lib/bios_test.cpp
index 06dd428..306f5e0 100644
--- a/test/redfish-core/lib/bios_test.cpp
+++ b/test/redfish-core/lib/bios_test.cpp
@@ -98,7 +98,7 @@
key);
ASSERT_TRUE(systemObjectsPathsStatusOr.ok());
- // TODO(b/416746677): Uncomment until the bug fixed
+ // TODO(b/416746677): Uncomment after the bug is fixed
// ASSERT_EQ(systemObjectsPathsStatusOr.value()->managedType,
// ManagedType::kManagedSubtreePaths);
@@ -307,7 +307,7 @@
key);
ASSERT_TRUE(systemObjectsPathsStatusOr.ok());
- // TODO(b/416746677): Uncomment until the bug fixed
+ // TODO(b/416746677): Uncomment after the bug is fixed
// ASSERT_EQ(systemObjectsPathsStatusOr.value()->managedType,
// ManagedType::kManagedSubtreePaths);
diff --git a/test/redfish-core/lib/system_multi_host_test.cpp b/test/redfish-core/lib/system_multi_host_test.cpp
new file mode 100644
index 0000000..aad7d11
--- /dev/null
+++ b/test/redfish-core/lib/system_multi_host_test.cpp
@@ -0,0 +1,150 @@
+#include "absl/cleanup/cleanup.h"
+#include "bmcweb_config.h"
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "http_response.hpp"
+#include "nlohmann/json.hpp"
+#include "snapshot_fixture.hpp"
+#include "systems.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::ManagedObjectStoreContext;
+using ::managedStore::ManagedType;
+using ::managedStore::SimulateFailedAsyncPostDbusCallThreadSafeAction;
+using ::managedStore::SimulateSuccessfulAsyncPostDbusCallThreadSafeAction;
+using ::managedStore::ValueType;
+using ::testing::_;
+using ::testing::An;
+
+std::shared_ptr<ValueType> CreateBadMockAssociatedSubtreeForMultiSystem()
+{
+ dbus::utility::MapperGetSubTreeResponse mockAssociatedSubtree = {
+ std::make_pair(
+ "/xyz/openbmc_project/state/chassis1",
+ dbus::utility::MapperServiceMap{
+ {std::make_pair("xyz.openbmc_project.Chassis.Buttons1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Chassis",
+ }),
+ std::make_pair(
+ "xyz.openbmc_project.Control.Host.RestartCause1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Chassis",
+ })}}),
+ std::make_pair(
+ "/xyz/openbmc_project/state/chassis2",
+ dbus::utility::MapperServiceMap{
+ {std::make_pair("xyz.openbmc_project.Chassis.Buttons1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Chassis",
+ }),
+ std::make_pair(
+ "xyz.openbmc_project.Control.Host.RestartCause1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Chassis",
+ })}}),
+ };
+ return managedStore::MockManagedStoreTest::CreateValueType(
+ std::move(mockAssociatedSubtree));
+}
+
+TEST_F(SnapshotFixture_Platform23,
+ GetSystemHostPowerAssociatedSubTreeHasMoreThanOneObjectsForMultiSystem)
+{
+ // Looking for
+ // kManagedAssociatedSubtree|/xyz/openbmc_project/state|0|xyz.openbmc_project.State.Chassis|/xyz/openbmc_project/inventory/system/board/platform23/system1/chassis_power
+ KeyType key(
+ ManagedType::kManagedAssociatedSubtree,
+ "/xyz/openbmc_project/inventory/system/board/platform23/system1/chassis_power",
+ "/xyz/openbmc_project/state", 0, {"xyz.openbmc_project.State.Chassis"});
+
+ absl::StatusOr<std::shared_ptr<ValueType>>
+ chassisPowerAssociatedSubtreeStatusOr =
+ managedStore::GetManagedObjectStore()
+ ->getMockObjectFromManagedStore(key);
+ ASSERT_TRUE(chassisPowerAssociatedSubtreeStatusOr.ok());
+
+ // TODO(b/416746677): Uncomment after the bug is fixed
+ // ASSERT_EQ(systemObjectsPathsStatusOr.value()->managedType,
+ // ManagedType::kManagedSubtreePaths);
+
+ std::optional<dbus::utility::MapperGetSubTreeResponse>
+ originalAssociatedSubtree =
+ chassisPowerAssociatedSubtreeStatusOr.value()->managedSubtree;
+
+ ASSERT_TRUE(originalAssociatedSubtree.has_value());
+
+ ASSERT_TRUE(managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, CreateBadMockAssociatedSubtreeForMultiSystem())
+ .ok());
+
+ absl::Cleanup mockManagedStoreResetter = [key,
+ originalAssociatedSubtree]() {
+ ASSERT_TRUE(
+ managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, managedStore::MockManagedStoreTest::CreateValueType(
+ originalAssociatedSubtree.value()))
+ .ok());
+ };
+
+ 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::internal_server_error);
+}
+
+TEST_F(SnapshotFixture_Platform23, GetSystemHostPowerAssociatedSubTreeSuccess)
+{
+ std::cerr << "Hao: start \n";
+
+ 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);
+ std::cerr << "Hao: Done \n";
+}
+
+} // namespace
+} // namespace redfish
diff --git a/test/redfish-core/lib/system_test.cpp b/test/redfish-core/lib/system_test.cpp
index 6f8bb6d..ed209f3 100644
--- a/test/redfish-core/lib/system_test.cpp
+++ b/test/redfish-core/lib/system_test.cpp
@@ -1,3 +1,4 @@
+#include "absl/cleanup/cleanup.h"
#include "app.hpp"
#include "bmcweb_config.h"
@@ -315,6 +316,152 @@
}
}
+TEST_F(SnapshotFixture, GetSystemHostPowerAssociatedSubTreeFailed)
+{
+ // Looking for
+ // kManagedAssociatedSubtree|/xyz/openbmc_project/state|0|xyz.openbmc_project.State.Host|/host_power
+ KeyType key(ManagedType::kManagedAssociatedSubtree, "/host_power",
+ "/xyz/openbmc_project/state", 0,
+ {"xyz.openbmc_project.State.Host"});
+
+ absl::StatusOr<std::shared_ptr<ValueType>>
+ associatedSubtreeObjectsStatusOr =
+ managedStore::GetManagedObjectStore()
+ ->getMockObjectFromManagedStore(key);
+ ASSERT_TRUE(associatedSubtreeObjectsStatusOr.ok());
+
+ // TODO(b/416746677): Uncomment after the bug is fixed
+ // ASSERT_EQ(associatedSubtreeObjectsStatusOr.value()->managedType,
+ // ManagedType::kManagedSubtreePaths);
+
+ std::optional<dbus::utility::MapperGetSubTreeResponse>
+ originalAssociatedSubtree =
+ associatedSubtreeObjectsStatusOr.value()->managedSubtree;
+
+ ASSERT_TRUE(originalAssociatedSubtree.has_value());
+
+ ASSERT_TRUE(
+ managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, managedStore::MockManagedStoreTest::CreateErrorValueType(
+ originalAssociatedSubtree.value(),
+ boost::system::errc::make_error_code(
+ boost::system::errc::io_error)))
+ .ok());
+
+ absl::Cleanup mockManagedStoreResetter = [key,
+ originalAssociatedSubtree]() {
+ ASSERT_TRUE(
+ managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, managedStore::MockManagedStoreTest::CreateValueType(
+ originalAssociatedSubtree.value()))
+ .ok());
+ };
+
+ handleComputerSystem(share_async_resp_, "", "system", false);
+
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
+std::shared_ptr<ValueType> CreateMockAssociatedSubtree()
+{
+ dbus::utility::MapperGetSubTreeResponse mockAssociatedSubtree = {
+ std::make_pair(
+ "/xyz/openbmc_project/state/host1",
+ dbus::utility::MapperServiceMap{
+ {std::make_pair(
+ "xyz.openbmc_project.Chassis.Buttons1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Host",
+ }),
+ std::make_pair(
+ "xyz.openbmc_project.Control.Host.RestartCause1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Host",
+ })}}),
+ std::make_pair(
+ "/xyz/openbmc_project/state/host2",
+ dbus::utility::MapperServiceMap{
+ {std::make_pair(
+ "xyz.openbmc_project.Chassis.Buttons1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Host",
+ }),
+ std::make_pair(
+ "xyz.openbmc_project.Control.Host.RestartCause1",
+ std::vector<std::string>{
+ "org.freedesktop.DBus.Introspectable",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Properties",
+ "xyz.openbmc_project.State.Host",
+ })}}),
+ };
+ return managedStore::MockManagedStoreTest::CreateValueType(
+ std::move(mockAssociatedSubtree));
+}
+
+TEST_F(SnapshotFixture,
+ GetSystemHostPowerAssociatedSubTreeHasMoreThanOneObjects)
+{
+ crow::Logger::setLogLevel(crow::LogLevel::Error);
+ // Looking for
+ // kManagedAssociatedSubtree|/xyz/openbmc_project/state|0|xyz.openbmc_project.State.Host|/host_power
+ KeyType key(ManagedType::kManagedAssociatedSubtree, "/host_power",
+ "/xyz/openbmc_project/state", 0,
+ {"xyz.openbmc_project.State.Host"});
+
+ absl::StatusOr<std::shared_ptr<ValueType>>
+ associatedSubtreeObjectsStatusOr =
+ managedStore::GetManagedObjectStore()
+ ->getMockObjectFromManagedStore(key);
+ ASSERT_TRUE(associatedSubtreeObjectsStatusOr.ok());
+
+ // TODO(b/416746677): Uncomment after the bug is fixed
+ // ASSERT_EQ(associatedSubtreeObjectsStatusOr.value()->managedType,
+ // ManagedType::kManagedSubtreePaths);
+
+ std::optional<dbus::utility::MapperGetSubTreeResponse>
+ originalAssociatedSubtree =
+ associatedSubtreeObjectsStatusOr.value()->managedSubtree;
+
+ ASSERT_TRUE(originalAssociatedSubtree.has_value());
+
+ ASSERT_TRUE(managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, CreateMockAssociatedSubtree())
+ .ok());
+
+ absl::Cleanup mockManagedStoreResetter = [key,
+ originalAssociatedSubtree]() {
+ ASSERT_TRUE(
+ managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(
+ key, managedStore::MockManagedStoreTest::CreateValueType(
+ originalAssociatedSubtree.value()))
+ .ok());
+ };
+
+ handleComputerSystem(share_async_resp_, "", "system", false);
+
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(),
+ boost::beast::http::status::internal_server_error);
+}
+
TEST_F(SnapshotFixture, PostSystemResetSingleSystemChassisResetOnSuccessDelay){
handlePostComputerSystemReset(app_, CreateRequest("{\"ResetType\":\"ForceOff\", \"Delay\":1} "), share_async_resp_, "system");