PEL: Add HostNotifier class

This class will watch for new PELs being created, and handle sending
them up to the host.  This first commit for this class mostly just fills
in the constructor to set up the various callbacks it will use.

It is only instantiated in the Manager class if the Manager constructor
used is the one that passes in the HostInterface object, to allow for
configurations that don't need PELs passed up.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I0ddcf94d047979eb78209d396c2351566c634dbe
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index d85f215..2ce9498 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -8,6 +8,7 @@
 	failing_mtms_test \
 	fru_identity_test \
 	generic_section_test \
+	host_notifier_test \
 	log_id_test \
 	mru_test \
 	mtms_test \
@@ -145,6 +146,7 @@
 	$(test_ldadd) \
 	$(pel_objects) \
 	$(top_builddir)/extensions/openpower-pels/data_interface.o \
+	$(top_builddir)/extensions/openpower-pels/host_notifier.o \
 	$(top_builddir)/extensions/openpower-pels/manager.o \
 	$(top_builddir)/extensions/openpower-pels/repository.o
 pel_manager_test_LDFLAGS = $(test_ldflags)
@@ -316,3 +318,16 @@
 	$(test_ldflags) \
 	$(top_builddir)/extensions/openpower-pels/pel_rules.o
 pel_rules_test_LDFLAGS = $(test_ldflags)
+
+host_notifier_test_SOURCES = \
+	%reldir%/host_notifier_test.cpp \
+	%reldir%/paths.cpp \
+	%reldir%/pel_utils.cpp
+host_notifier_test_CPPFLAGS = $(test_cppflags)
+host_notifier_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS)
+host_notifier_test_LDADD = \
+	$(test_ldflags) \
+	$(pel_objects) \
+	$(top_builddir)/extensions/openpower-pels/host_notifier.o \
+	$(top_builddir)/extensions/openpower-pels/repository.o
+host_notifier_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS)
diff --git a/test/openpower-pels/host_notifier_test.cpp b/test/openpower-pels/host_notifier_test.cpp
new file mode 100644
index 0000000..2d50dcf
--- /dev/null
+++ b/test/openpower-pels/host_notifier_test.cpp
@@ -0,0 +1,135 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/data_interface.hpp"
+#include "extensions/openpower-pels/host_notifier.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+namespace fs = std::filesystem;
+using namespace std::chrono;
+
+const size_t actionFlags0Offset = 66;
+const size_t actionFlags1Offset = 67;
+
+class HostNotifierTest : public CleanPELFiles
+{
+};
+
+/**
+ * @brief Create PEL with the specified action flags
+ *
+ * @param[in] actionFlagsMask - Optional action flags to use
+ *
+ * @return std::unique_ptr<PEL>
+ */
+std::unique_ptr<PEL> makePEL(uint16_t actionFlagsMask = 0)
+{
+    static uint32_t obmcID = 1;
+    auto data = pelDataFactory(TestPELType::pelSimple);
+
+    data[actionFlags0Offset] |= actionFlagsMask >> 8;
+    data[actionFlags1Offset] |= actionFlagsMask & 0xFF;
+
+    auto pel = std::make_unique<PEL>(data, obmcID++);
+    pel->assignID();
+    pel->setCommitTime();
+    return pel;
+}
+
+// Test that host state change callbacks work
+TEST_F(HostNotifierTest, TestHostStateChange)
+{
+    MockDataInterface dataIface;
+
+    bool hostState = false;
+    bool called = false;
+    DataInterfaceBase::HostStateChangeFunc func = [&hostState,
+                                                   &called](bool state) {
+        hostState = state;
+        called = true;
+    };
+
+    dataIface.subscribeToHostStateChange("test", func);
+
+    // callback called
+    dataIface.changeHostState(true);
+    EXPECT_TRUE(called);
+    EXPECT_TRUE(hostState);
+
+    // No change, not called
+    called = false;
+    dataIface.changeHostState(true);
+    EXPECT_FALSE(called);
+
+    // Called again
+    dataIface.changeHostState(false);
+    EXPECT_FALSE(hostState);
+    EXPECT_TRUE(called);
+
+    // Shouldn't get called after an unsubscribe
+    dataIface.unsubscribeFromHostStateChange("test");
+
+    called = false;
+
+    dataIface.changeHostState(true);
+    EXPECT_FALSE(called);
+}
+
+// Test that PELs are enqueued on startup
+TEST_F(HostNotifierTest, TestStartup)
+{
+    Repository repo{repoPath};
+    MockDataInterface dataIface;
+
+    // Give the repo 10 PELs to start with
+    for (int i = 0; i < 10; i++)
+    {
+        auto pel = makePEL();
+        repo.add(pel);
+    }
+
+    sd_event* event = nullptr;
+    auto r = sd_event_default(&event);
+    ASSERT_TRUE(r >= 0);
+
+    std::unique_ptr<HostInterface> hostIface =
+        std::make_unique<MockHostInterface>(event, dataIface);
+
+    HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+    ASSERT_EQ(notifier.queueSize(), 10);
+
+    // Now add 10 more after the notifier is watching
+    for (int i = 0; i < 10; i++)
+    {
+        auto pel = makePEL();
+        repo.add(pel);
+    }
+
+    ASSERT_EQ(notifier.queueSize(), 20);
+}
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index 1dc0902..c2a84a0 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -1,4 +1,10 @@
 #include "extensions/openpower-pels/data_interface.hpp"
+#include "extensions/openpower-pels/host_interface.hpp"
+
+#include <fcntl.h>
+
+#include <filesystem>
+#include <sdeventplus/source/io.hpp>
 
 #include <gmock/gmock.h>
 
@@ -13,10 +19,53 @@
     MockDataInterface()
     {
     }
-    MOCK_CONST_METHOD0(getMachineTypeModel, std::string());
-    MOCK_CONST_METHOD0(getMachineSerialNumber, std::string());
-    MOCK_CONST_METHOD0(getServerFWVersion, std::string());
-    MOCK_CONST_METHOD0(getBMCFWVersion, std::string());
+    MOCK_METHOD(std::string, getMachineTypeModel, (), (const override));
+    MOCK_METHOD(std::string, getMachineSerialNumber, (), (const override));
+    MOCK_METHOD(std::string, getServerFWVersion, (), (const override));
+    MOCK_METHOD(std::string, getBMCFWVersion, (), (const override));
+
+    void changeHostState(bool newState)
+    {
+        setHostState(newState);
+    }
+
+    void setHMCManaged(bool managed)
+    {
+        _hmcManaged = managed;
+    }
+};
+
+/**
+ * @brief The mock HostInterface class
+ */
+class MockHostInterface : public HostInterface
+{
+  public:
+    MockHostInterface(sd_event* event, DataInterfaceBase& dataIface) :
+        HostInterface(event, dataIface)
+    {
+    }
+
+    virtual ~MockHostInterface()
+    {
+    }
+
+    virtual void cancelCmd() override
+    {
+    }
+
+    MOCK_METHOD(CmdStatus, sendNewLogCmd, (uint32_t, uint32_t), (override));
+
+  protected:
+    void receive(sdeventplus::source::IO& source, int fd,
+                 uint32_t events) override
+    {
+        // Keep account of the number of commands responses for testing.
+        _cmdsProcessed++;
+    }
+
+  private:
+    size_t _cmdsProcessed = 0;
 };
 
 } // namespace pels