boot-block: clear block on error resolve

If the blocking error is resolved then remove the blocking object and
it's property change callback

Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
Change-Id: I9acb844ef273e8390b4f5283f234990b3c008e70
diff --git a/log_manager.cpp b/log_manager.cpp
index 929ff5d..d636a67 100644
--- a/log_manager.cpp
+++ b/log_manager.cpp
@@ -281,6 +281,39 @@
     return false;
 }
 
+void Manager::findAndRemoveResolvedBlocks()
+{
+    for (auto& entry : entries)
+    {
+        if (entry.second->resolved())
+        {
+            checkAndRemoveBlockingError(entry.first);
+        }
+    }
+}
+
+void Manager::onEntryResolve(sdbusplus::message::message& msg)
+{
+    using Interface = std::string;
+    using Property = std::string;
+    using Value = std::string;
+    using Properties = std::map<Property, sdbusplus::message::variant<Value>>;
+
+    Interface interface;
+    Properties properties;
+
+    msg.read(interface, properties);
+
+    for (const auto& p : properties)
+    {
+        if (p.first == "Resolved")
+        {
+            findAndRemoveResolvedBlocks();
+            return;
+        }
+    }
+}
+
 void Manager::checkQuiesceOnError(const Entry& entry)
 {
 
@@ -292,16 +325,26 @@
     logging::log<logging::level::INFO>(
         "QuiesceOnError set and callout present");
 
-    std::string blockPath(OBJ_LOGGING);
-    blockPath += "/block";
-    blockPath += std::to_string(entry.id());
+    auto blockPath =
+        std::string(OBJ_LOGGING) + "/block" + std::to_string(entry.id());
     auto blockObj =
         std::make_unique<Block>(this->busLog, blockPath, entry.id());
     this->blockingErrors.push_back(std::move(blockObj));
 
+    // Register call back if log is resolved
+    using namespace sdbusplus::bus::match::rules;
+    auto entryPath = std::string(OBJ_ENTRY) + '/' + std::to_string(entry.id());
+    auto callback = std::make_unique<sdbusplus::bus::match::match>(
+        this->busLog,
+        propertiesChanged(entryPath, "xyz.openbmc_project.Logging.Entry"),
+        std::bind(std::mem_fn(&Manager::onEntryResolve), this,
+                  std::placeholders::_1));
+
+    propChangedEntryCallback.insert(
+        std::make_pair(entry.id(), std::move(callback)));
+
     // TODO in later commit in this series
     // Call systemd to quiesce host
-    // Ensure blockingErrors removes entries when log resolved
 }
 
 void Manager::doExtensionLogCreate(const Entry& entry, const FFDCEntries& ffdc)
@@ -354,6 +397,7 @@
 
 void Manager::checkAndRemoveBlockingError(uint32_t entryId)
 {
+    // First look for blocking object and remove
     auto it = find_if(
         blockingErrors.begin(), blockingErrors.end(),
         [&](std::unique_ptr<Block>& obj) { return obj->entryId == entryId; });
@@ -361,6 +405,14 @@
     {
         blockingErrors.erase(it);
     }
+
+    // Now remove the callback looking for the error to be resolved
+    auto resolveFind = propChangedEntryCallback.find(entryId);
+    if (resolveFind != propChangedEntryCallback.end())
+    {
+        propChangedEntryCallback.erase(resolveFind);
+    }
+
     return;
 }
 
diff --git a/log_manager.hpp b/log_manager.hpp
index 700a39a..87dd038 100644
--- a/log_manager.hpp
+++ b/log_manager.hpp
@@ -141,6 +141,15 @@
         return blockingErrors.size();
     }
 
+    /** @brief Returns the number of property change callback objects
+     *
+     *  @return int - count of property callback entries
+     */
+    int getEntryCallbackSize()
+    {
+        return propChangedEntryCallback.size();
+    }
+
     sdbusplus::bus::bus& getBus()
     {
         return busLog;
@@ -264,6 +273,19 @@
                      std::vector<std::string> additionalData,
                      const FFDCEntries& ffdc = FFDCEntries{});
 
+    /** @brief Notified on entry property changes
+     *
+     * If an entry is blocking, this callback will be registered to monitor for
+     * the entry having it's Resolved field set to true. If it is then remove
+     * the blocking object.
+     *
+     * @param[in] msg - sdbusplus dbusmessage
+     */
+    void onEntryResolve(sdbusplus::message::message& msg);
+
+    /** @brief Remove block objects for any resolved entries  */
+    void findAndRemoveResolvedBlocks();
+
     /** @brief Persistent sdbusplus DBus bus connection. */
     sdbusplus::bus::bus& busLog;
 
@@ -284,6 +306,10 @@
 
     /** @brief Array of blocking errors */
     std::vector<std::unique_ptr<Block>> blockingErrors;
+
+    /** @brief Map of entry id to call back object on properties changed */
+    std::map<uint32_t, std::unique_ptr<sdbusplus::bus::match::match>>
+        propChangedEntryCallback;
 };
 
 } // namespace internal
diff --git a/test/elog_quiesce_test.cpp b/test/elog_quiesce_test.cpp
index 1f1cdeb..6537485 100644
--- a/test/elog_quiesce_test.cpp
+++ b/test/elog_quiesce_test.cpp
@@ -158,6 +158,63 @@
     EXPECT_EQ(manager.getBlockingErrSize(), 0);
 }
 
+// Test that a blocking error is created on entry with callout
+TEST_F(TestQuiesceOnError, testBlockingErrorsResolved)
+{
+    uint32_t id = 101;
+    uint64_t timestamp{100};
+    std::string message{"test error"};
+    std::string fwLevel{"level42"};
+    std::vector<std::string> testData{
+        "CALLOUT_INVENTORY_PATH=/xyz/openbmc_project/inventory/system/chassis/"
+        "motherboard/powersupply0/"};
+    phosphor::logging::AssociationList associations{};
+
+    // Ensure D-Bus object created for this blocking error
+    // First allow any number of sd_bus_emit_object_added calls
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(testing::_, testing::_))
+        .Times(testing::AnyNumber());
+    // Second verify the new block100 object is created once
+    EXPECT_CALL(sdbusMock,
+                sd_bus_emit_object_added(
+                    testing::_, testing::HasSubstr(
+                                    "/xyz/openbmc_project/logging/block101")))
+        .Times(1);
+
+    Entry elog{mockedBus,
+               std::string(OBJ_ENTRY) + '/' + std::to_string(id),
+               id,
+               timestamp,
+               Entry::Level::Informational,
+               std::move(message),
+               std::move(testData),
+               std::move(associations),
+               fwLevel,
+               manager};
+
+    manager.checkQuiesceOnError(elog);
+    // Created error with callout so expect a blocking error now
+    EXPECT_EQ(manager.getBlockingErrSize(), 1);
+    // Also should have a callback create looking for entry to be resolved
+    EXPECT_EQ(manager.getEntryCallbackSize(), 1);
+
+    // Now resolve the error and make sure the object and entry go away
+    EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(testing::_, testing::_))
+        .Times(testing::AnyNumber());
+    EXPECT_CALL(sdbusMock,
+                sd_bus_emit_object_removed(
+                    testing::_, testing::HasSubstr(
+                                    "/xyz/openbmc_project/logging/block101")))
+        .Times(1);
+
+    elog.resolved(true);
+    // Note that property signal callbacks do not work in unit test so directly
+    // call the interface to find and resolve blocking entries
+    manager.checkAndRemoveBlockingError(101);
+    EXPECT_EQ(manager.getBlockingErrSize(), 0);
+    EXPECT_EQ(manager.getEntryCallbackSize(), 0);
+}
+
 } // namespace test
 } // namespace logging
 } // namespace phosphor