| #include "health.hpp" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #include "http_response.hpp" |
| #include "dbus_utility.hpp" |
| #include "test/redfish-core/lib/snapshot_fixture.hpp" |
| #include <nlohmann/json.hpp> |
| #include "managed_store.hpp" |
| #include "managed_store_types.hpp" |
| #include "test/g3/mock_managed_store.hpp" |
| #include "sdbusplus/message/native_types.hpp" |
| |
| namespace redfish { |
| namespace { |
| |
| using ::dbus::utility::DBusInteracesMap; |
| using ::dbus::utility::DBusPropertiesMap; |
| using ::dbus::utility::DbusVariantType; |
| using ::dbus::utility::ManagedObjectType; |
| using ::dbus::utility::MapperGetSubTreeResponse; |
| using ::dbus::utility::MapperServiceMap; |
| using ::managedStore::KeyType; |
| using ::managedStore::ManagedType; |
| |
| TEST_F(SnapshotFixture, HealthPopulateFillsOkStatusWhenNoErrors) { |
| dbus::utility::MapperGetSubTreePathsResponse healthPaths = { |
| "/xyz/openbmc_project/inventory/system/board/LeakDetector_0/" |
| "EMMC_PRSNT"}; |
| |
| sdbusplus::message::object_path healthPathObj(healthPaths[0]); |
| DBusPropertiesMap configProperties{ |
| {"Name", DbusVariantType("EMMC_PRSNT")}, |
| {"Severity", DbusVariantType("Critical")}, |
| {"BoardName", DbusVariantType("LeakDetector_0")}, |
| {"FailureMessageId", |
| DbusVariantType("ResourceEvent.1.0.ResourceErrorsDetected")}, |
| {"OkMessageId", |
| DbusVariantType("ResourceEvent.1.0.ResourceErrorsCorrected")}, |
| {"MessageArgs", |
| DbusVariantType(std::vector<std::string>{"Fan0", "Efuse failure"})}, |
| {"Resolution", DbusVariantType("Replace")}, |
| {"RelatedEntityName", DbusVariantType("LeakDetector_0")}, |
| {"RelatedEntityType", DbusVariantType("Board")}, |
| {"ODataID", DbusVariantType("/redfish/v1/Chassis/LeakDetector_0")}}; |
| |
| ASSERT_TRUE( |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.EntityManager", healthPathObj, |
| "xyz.openbmc_project.Configuration.HealthMonitor"), |
| CreateValueType(configProperties)) |
| .ok()); |
| |
| DBusPropertiesMap statusProperties{ |
| {"healthy", DbusVariantType(true)}, |
| {"toggle_count", DbusVariantType(uint64_t{0})}}; |
| sdbusplus::message::object_path statusPath( |
| "/xyz/openbmc_project/HealthMonitor/EMMC_PRSNT"); |
| ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.HealthMonitor", statusPath, |
| "xyz.openbmc_project.HealthMonitor.GetStatus"), |
| CreateValueType(statusProperties)) |
| .ok()); |
| auto health = std::make_shared<HealthPopulate>(share_async_resp_); |
| health->populate(); |
| RunIoUntilDone(); |
| health.reset(); // Destroy HealthPopulate to trigger destructor |
| RunIoUntilDone(); // Drain any callbacks posted by destructor |
| |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_EQ(json["Status"]["Health"], "OK"); |
| EXPECT_EQ(json["Status"]["HealthRollup"], "OK"); |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(SnapshotFixture, HealthPopulateConditions) { |
| dbus::utility::MapperGetSubTreePathsResponse healthPaths = { |
| "/xyz/openbmc_project/inventory/system/board/LeakDetector_0/" |
| "EMMC_PRSNT"}; |
| sdbusplus::message::object_path healthPathObj(healthPaths[0]); |
| |
| ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedSubtreePaths, |
| "/xyz/openbmc_project/inventory", 0, |
| {"xyz.openbmc_project.Configuration." |
| "HealthMonitor"}), |
| CreateValueType(healthPaths)) |
| .ok()); |
| |
| DBusPropertiesMap configProperties{ |
| {"Name", DbusVariantType("EMMC_PRSNT")}, |
| {"Severity", DbusVariantType("Critical")}, |
| {"BoardName", DbusVariantType("LeakDetector_0")}, |
| {"FailureMessageId", |
| DbusVariantType("ResourceEvent.1.0.ResourceErrorsDetected")}, |
| {"OkMessageId", |
| DbusVariantType("ResourceEvent.1.0.ResourceErrorsCorrected")}, |
| {"MessageArgs", |
| DbusVariantType(std::vector<std::string>{"Fan0", "Efuse failure"})}, |
| {"Resolution", DbusVariantType("Replace")}, |
| {"RelatedEntityName", DbusVariantType("LeakDetector_0")}, |
| {"RelatedEntityType", DbusVariantType("Board")}, |
| {"ODataID", DbusVariantType("/redfish/v1/Chassis/LeakDetector_0")}}; |
| |
| ASSERT_TRUE( |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.EntityManager", healthPathObj, |
| "xyz.openbmc_project.Configuration.HealthMonitor"), |
| CreateValueType(configProperties)) |
| .ok()); |
| |
| DBusPropertiesMap statusProperties{ |
| {"healthy", DbusVariantType(false)}, |
| {"toggle_count", DbusVariantType(uint64_t{1})}}; |
| sdbusplus::message::object_path statusPath( |
| "/xyz/openbmc_project/HealthMonitor/EMMC_PRSNT"); |
| ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.HealthMonitor", statusPath, |
| "xyz.openbmc_project.HealthMonitor.GetStatus"), |
| CreateValueType(statusProperties)) |
| .ok()); |
| |
| HealthStatusCache::getInstance().getHealthAttributes(healthPaths[0]); |
| RunIoUntilDone(); |
| |
| auto health = std::make_shared<HealthPopulate>( |
| share_async_resp_, "/redfish/v1/Chassis/LeakDetector_0", |
| "/xyz/openbmc_project/inventory/system/board/LeakDetector_0", "Board", |
| "LeakDetector_0"); |
| health->populate(); |
| RunIoUntilDone(); |
| health.reset(); // Destroy HealthPopulate to trigger destructor |
| |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_EQ(json["Status"]["Health"], "Critical"); |
| EXPECT_EQ(json["Status"]["HealthRollup"], "Critical"); |
| ASSERT_EQ(json["Status"]["Conditions"].size(), 1); |
| EXPECT_EQ(json["Status"]["Conditions"][0]["MessageId"], |
| "ResourceEvent.1.0.ResourceErrorsDetected"); |
| EXPECT_EQ(json["Status"]["Conditions"][0]["Severity"], "Critical"); |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(SnapshotFixture, |
| HealthPopulateCorrectlyProcessesChildrenAndAssociations) { |
| // This test is designed to improve code coverage by exercising complex |
| // logic in the HealthPopulate destructor. It verifies the handling of: |
| // - Direct child inventory items. |
| // - Items associated with child inventory via 'endpoints'. |
| // - A 'self' path health status. |
| // - An item that is not a child and has no relevant association (should be |
| // skipped). |
| // - Malformed association data. |
| |
| auto health = std::make_shared<HealthPopulate>(share_async_resp_); |
| |
| // 1. Define inventory, including 'self' and 'child' items. |
| const std::string selfPath = "/xyz/openbmc_project/inventory/system/chassis"; |
| const std::string childPath1 = "/xyz/openbmc_project/inventory/system/cpu0"; |
| const std::string childPath2 = "/xyz/openbmc_project/inventory/system/dimm4"; |
| health->selfPath = selfPath; |
| health->inventory = {childPath1, childPath2}; |
| health->isManagersHealth = false; |
| |
| // 2. Build the 'statuses' map to trigger different logic paths. |
| // The types are vector<pair<...>>, so we use emplace_back. |
| const std::string criticalAssocPath = |
| "/xyz/openbmc_project/associations/cpu0_thermals/status/critical"; |
| const std::string selfWarningPath = selfPath + "/status/warning"; |
| const std::string skippedAssocPath = |
| "/xyz/openbmc_project/associations/unrelated/status/critical"; |
| const std::string malformedAssocPath = |
| "/xyz/openbmc_project/associations/malformed/status/critical"; |
| |
| // Case A: A critical status on a path associated with a child inventory |
| // item. |
| DBusPropertiesMap criticalAssocProperties; |
| criticalAssocProperties.emplace_back( |
| "endpoints", DbusVariantType(std::vector<std::string>{childPath1})); |
| DBusInteracesMap criticalAssocInterfaces; |
| criticalAssocInterfaces.emplace_back("xyz.openbmc_project.Association", |
| criticalAssocProperties); |
| |
| // Case B: A warning status directly on the 'self' path. |
| DBusInteracesMap selfWarningInterfaces; // Empty is sufficient |
| |
| // Case C: An association pointing to a non-child item. |
| DBusPropertiesMap skippedAssocProperties; |
| skippedAssocProperties.emplace_back( |
| "endpoints", DbusVariantType(std::vector<std::string>{ |
| "/xyz/openbmc_project/inventory/system/fan1"})); |
| DBusInteracesMap skippedAssocInterfaces; |
| skippedAssocInterfaces.emplace_back("xyz.openbmc_project.Association", |
| skippedAssocProperties); |
| |
| // Case D: An association with a malformed 'endpoints' property. |
| DBusPropertiesMap malformedAssocProperties; |
| malformedAssocProperties.emplace_back( |
| "endpoints", DbusVariantType(std::string("this is not a vector"))); |
| DBusInteracesMap malformedAssocInterfaces; |
| malformedAssocInterfaces.emplace_back("xyz.openbmc_project.Association", |
| malformedAssocProperties); |
| |
| // Construct the final ManagedObjectType vector |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(criticalAssocPath), |
| criticalAssocInterfaces); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(selfWarningPath), selfWarningInterfaces); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(skippedAssocPath), |
| skippedAssocInterfaces); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(malformedAssocPath), |
| malformedAssocInterfaces); |
| |
| // 3. Trigger the destructor where the health calculation logic resides. |
| health.reset(); |
| |
| // 4. Verify the final calculated health status. |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| |
| // The main 'Health' is "Warning" because of Case B (direct self path). |
| EXPECT_EQ(json["Status"]["Health"], "Warning"); |
| |
| // The 'HealthRollup' is "Critical" because of Case A, which has higher |
| // severity. |
| EXPECT_EQ(json["Status"]["HealthRollup"], "Critical"); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(SnapshotFixture, ProcessHealthStatusChange) { |
| // 1. Setup |
| HealthStatusCache& cache = HealthStatusCache::getInstance(); |
| cache.clear(); |
| |
| // Define D-Bus paths and properties for a mock health criteria |
| const std::string criteriaPath = |
| "/xyz/openbmc_project/inventory/system/board/TestBoard/TestCriteria"; |
| sdbusplus::message::object_path criteriaPathObj(criteriaPath); |
| |
| const DBusPropertiesMap configProperties{ |
| {"Name", DbusVariantType("TestCriteria")}, |
| {"Severity", DbusVariantType("Warning")}, |
| {"FailureMessageId", DbusVariantType("Test.Failure.Message")}, |
| {"OkMessageId", DbusVariantType("Test.OK.Message")}, |
| {"MessageArgs", |
| DbusVariantType(std::vector<std::string>{"Power", "Fault"})}, |
| {"Resolution", DbusVariantType("Replace the unit.")}, |
| {"RelatedEntityName", DbusVariantType("TestBoard")}, |
| {"RelatedEntityType", DbusVariantType("Board")}}; |
| |
| ASSERT_TRUE( |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.EntityManager", criteriaPathObj, |
| "xyz.openbmc_project.Configuration.HealthMonitor"), |
| CreateValueType(configProperties)) |
| .ok()); |
| |
| const DBusPropertiesMap initialStatusProperties{ |
| {"healthy", DbusVariantType(true)}, |
| {"toggle_count", DbusVariantType(uint64_t{0})}}; |
| sdbusplus::message::object_path statusPath( |
| "/xyz/openbmc_project/HealthMonitor/TestCriteria"); |
| ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| KeyType(ManagedType::kManagedPropertyMap, |
| "xyz.openbmc_project.HealthMonitor", statusPath, |
| "xyz.openbmc_project.HealthMonitor.GetStatus"), |
| CreateValueType(initialStatusProperties)) |
| .ok()); |
| |
| // Populate the cache by calling the method that reads from mocked D-Bus |
| cache.getHealthAttributes(criteriaPath); |
| RunIoUntilDone(); |
| |
| // 2. First call: Simulates a health change from Healthy to Unhealthy |
| cache.processHealthStatusChange(criteriaPath); |
| |
| // 3. Verification 1: Check the generated log entry |
| nlohmann::json logEntryArray; |
| cache.fillLogEntries(logEntryArray, "system"); |
| |
| ASSERT_EQ(logEntryArray.size(), 1); |
| const nlohmann::json& log1 = logEntryArray[0]; |
| |
| EXPECT_EQ(log1["MessageId"], "Test.Failure.Message"); |
| EXPECT_EQ(log1["Severity"], "Warning"); |
| EXPECT_EQ(log1["Resolved"], false); |
| // toggleCount becomes 1, so ErrorCount is (1+1)/2 = 1 |
| EXPECT_EQ(log1["ErrorCount"], 1); |
| |
| // 4. Second call: Simulates a health change from Unhealthy to Healthy |
| cache.processHealthStatusChange(criteriaPath); |
| |
| // 5. Verification 2: Check the new (resolved) log entry |
| cache.fillLogEntries(logEntryArray, "system"); |
| |
| ASSERT_EQ(logEntryArray.size(), 2); |
| const nlohmann::json& log2 = logEntryArray[1]; |
| |
| EXPECT_EQ(log2["MessageId"], "Test.OK.Message"); |
| EXPECT_EQ(log2["Severity"], "OK"); |
| EXPECT_EQ(log2["Resolved"], true); |
| EXPECT_EQ(log2["ResolvedEntryId"], "0"); |
| // ErrorCount should not be present in a resolution log entry |
| EXPECT_FALSE(log2.contains("ErrorCount")); |
| } |
| |
| TEST_F(SnapshotFixture, HealthPopulateCorrectlyHandlesAllStatusPathTypes) { |
| // This test covers the complex branching logic in the HealthPopulate |
| // destructor by checking the precedence of different status path types. |
| // The expected outcome is that a 'self' warning sets the main Health, |
| // but a non-global, non-self critical status sets the final Rollup. |
| auto health = std::make_shared<HealthPopulate>(share_async_resp_); |
| |
| // 1. Setup paths to simulate different inventory types. |
| health->globalInventoryPath = "/global/inventory"; |
| health->selfPath = "/self/path"; |
| health->isManagersHealth = false; |
| health->inventory = {}; // Keep inventory empty to isolate path logic. |
| |
| // 2. Define various status paths to trigger all logic branches. |
| // This path should set rollup to "Warning" initially. |
| const std::string nonGlobalWarningPath = "/some/other/path/warning"; |
| // This path should set health to "Warning" because isSelf is true. |
| const std::string selfWarningPath = "/self/path/status/warning"; |
| // This path should upgrade rollup to "Critical". Health remains "Warning". |
| const std::string nonGlobalCriticalPath = "/another/unrelated/path/critical"; |
| // This path would set health to "Warning", but the critical rollup above |
| // should prevent the rollup from being downgraded. |
| const std::string globalWarningPath = "/global/inventory/item/warning"; |
| |
| // 3. Populate the statuses map. Order matters for testing precedence. |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(nonGlobalWarningPath), |
| DBusInteracesMap{}); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(selfWarningPath), DBusInteracesMap{}); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(nonGlobalCriticalPath), |
| DBusInteracesMap{}); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path(globalWarningPath), DBusInteracesMap{}); |
| |
| // 4. Trigger the destructor to run the health calculation. |
| health.reset(); |
| |
| // 5. Verify the final computed health status. |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| |
| // Health is "Warning" due to the selfWarningPath. |
| EXPECT_EQ(json["Status"]["Health"], "Warning"); |
| // HealthRollup is "Critical" due to the nonGlobalCriticalPath. |
| EXPECT_EQ(json["Status"]["HealthRollup"], "Critical"); |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(SnapshotFixture, HealthPopulateReturnsEarlyOnCriticalPaths) { |
| // This test verifies that the health calculation loop terminates early |
| // when a critical status is encountered, as specified by the 'return' |
| // statements in the logic. |
| |
| // Scenario 1: Global Critical Path |
| { |
| auto health = std::make_shared<HealthPopulate>(share_async_resp_); |
| health->globalInventoryPath = "/global/inventory"; |
| |
| // The first path is global and critical, which should cause an |
| // immediate return. The second warning path should never be processed. |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path("/global/inventory/item/critical"), |
| DBusInteracesMap{}); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path("/some/other/path/warning"), |
| DBusInteracesMap{}); |
| |
| health.reset(); // Trigger destructor |
| |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_EQ(json["Status"]["Health"], "Critical"); |
| EXPECT_EQ(json["Status"]["HealthRollup"], "Critical"); |
| } |
| |
| // Reset response for the next scenario |
| SetUp(); |
| |
| // Scenario 2: Self Critical Path |
| { |
| auto health = std::make_shared<HealthPopulate>(share_async_resp_); |
| health->selfPath = "/self/path"; |
| |
| // The first path is a 'self' critical path, which should cause an |
| // immediate return. The following warning path should be ignored. |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path("/self/path/status/critical"), |
| DBusInteracesMap{}); |
| health->statuses.emplace_back( |
| sdbusplus::message::object_path("/self/path/status/warning"), |
| DBusInteracesMap{}); |
| |
| health.reset(); // Trigger destructor |
| |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_EQ(json["Status"]["Health"], "Critical"); |
| EXPECT_EQ(json["Status"]["HealthRollup"], "Critical"); |
| } |
| } |
| |
| } // namespace |
| } // namespace redfish |