Add persistency for events

Persist event D-bus objects using cereal

Resolves openbmc/openbmc#2319

Change-Id: Ifa15d944fe1d1026761f65eeb647dcbdf6afdba0
Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
diff --git a/configure.ac b/configure.ac
index f5e8b3a..d0adf4e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,5 +70,16 @@
 AS_IF([test "x$MAX_EVENTS" == "x"], [MAX_EVENTS=20])
 AC_DEFINE_UNQUOTED([MAX_EVENTS], [$MAX_EVENTS], [Maximum number of events.])
 
+AC_ARG_VAR(CLASS_VERSION, [Class version to register with Cereal])
+AS_IF([test "x$CLASS_VERSION" == "x"], [CLASS_VERSION=1])
+AC_DEFINE_UNQUOTED([CLASS_VERSION], [$CLASS_VERSION], [Class version to register with Cereal])
+
+AC_ARG_VAR(EVENTS_PERSIST_PATH, [Path of directory housing persisted events.])
+AS_IF([test "x$EVENTS_PERSIST_PATH" == "x"], \
+    [EVENTS_PERSIST_PATH="/var/lib/phosphor-dbus-monitor/events"])
+AC_DEFINE_UNQUOTED([EVENTS_PERSIST_PATH], ["$EVENTS_PERSIST_PATH"], \
+    [Path of directory housing persisted events])
+
+
 AC_CONFIG_FILES([Makefile src/Makefile src/test/Makefile mslverify/Makefile])
 AC_OUTPUT
diff --git a/src/Makefile.am b/src/Makefile.am
index 73ebca8..e5827e6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,7 +11,9 @@
 	main.cpp \
 	propertywatch.cpp \
 	resolve_errors.cpp \
-	event_manager.cpp
+	event_manager.cpp \
+	event_serialize.cpp
+
 phosphor_dbus_monitor_LDADD = \
 	$(SDBUSPLUS_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
diff --git a/src/event_entry.hpp b/src/event_entry.hpp
index e39f1e2..899e5ec 100644
--- a/src/event_entry.hpp
+++ b/src/event_entry.hpp
@@ -50,6 +50,17 @@
         this->emit_object_added();
     }
 
+    /** @brief Constructor to create an empty event object with only
+     *  timestamp, caller should make a call to emit added signal.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] timestamp - timestamp when the event created.
+     */
+    Entry(const std::string& path, uint64_t eventTimestamp) :
+        EntryIface(SDBusPlus::getBus(), path.c_str(), true), objectPath(path)
+    {
+        timestamp(eventTimestamp);
+    }
+
     /** @brief Path of Object. */
     std::string objectPath;
 };
diff --git a/src/event_manager.cpp b/src/event_manager.cpp
index 6e7d302..57a8478 100644
--- a/src/event_manager.cpp
+++ b/src/event_manager.cpp
@@ -17,6 +17,7 @@
 #include "config.h"
 #include "event.hpp"
 #include "event_manager.hpp"
+#include "event_serialize.hpp"
 
 #include <experimental/filesystem>
 
@@ -66,13 +67,62 @@
     // event.
     if (eventQueue.size() == MAX_EVENTS)
     {
+        fs::path eventPath(EVENTS_PERSIST_PATH);
+        eventPath /= eventName;
+        eventPath /= std::to_string(eventQueue.front()->timestamp());
         eventQueue.pop();
+        std::error_code ec;
+        fs::remove(eventPath, ec);
     }
 
-    eventQueue.emplace(std::make_unique<Entry>(objPath,
-                                               ms, // Milliseconds since 1970
-                                               std::move(msg),
-                                               std::move(additionalData)));
+    auto event =
+        std::make_unique<Entry>(objPath,
+                                ms, // Milliseconds since 1970
+                                std::move(msg), std::move(additionalData));
+    serialize(*event, eventName);
+    eventQueue.push(std::move(event));
+}
+
+void Manager::restore()
+{
+    if (!fs::exists(EVENTS_PERSIST_PATH) || fs::is_empty(EVENTS_PERSIST_PATH))
+    {
+        return;
+    }
+
+    for (auto& eventFile :
+         fs::recursive_directory_iterator(EVENTS_PERSIST_PATH))
+    {
+        if (!fs::is_regular_file(eventFile))
+        {
+            continue;
+        }
+
+        EventQueue events;
+
+        auto eventPath = eventFile.path().string();
+        auto pos1 = eventPath.rfind("/");
+        auto pos2 = eventPath.rfind("/", pos1 - 1) + 1;
+        auto eventName = eventPath.substr(pos2, (pos1 - pos2));
+        auto validEvent = false;
+        auto timestamp = eventFile.path().filename().string();
+        auto tsNum = std::stoll(timestamp);
+        auto objPath =
+            std::string(OBJ_EVENT) + '/' + eventName + '/' + timestamp;
+
+        auto event = std::make_unique<Entry>(objPath, tsNum);
+        if (deserialize(eventFile.path(), *event))
+        {
+            event->emit_object_added();
+            events.push(std::move(event));
+            validEvent = true;
+        }
+
+        if (validEvent)
+        {
+            eventMap[eventName] = std::move(events);
+        }
+    }
 }
 
 Manager& getManager()
diff --git a/src/event_manager.hpp b/src/event_manager.hpp
index a177739..a4ccf18 100644
--- a/src/event_manager.hpp
+++ b/src/event_manager.hpp
@@ -38,6 +38,11 @@
                 const std::string& objectPath, const std::string& propertyName,
                 const std::string& propertyValue);
 
+    /** @brief Construct event d-bus objects from their persisted
+     *         representations.
+     */
+    void restore();
+
   private:
     using EventName = std::string;
     /** @brief Queue of events */
diff --git a/src/event_serialize.cpp b/src/event_serialize.cpp
new file mode 100644
index 0000000..c7e78d9
--- /dev/null
+++ b/src/event_serialize.cpp
@@ -0,0 +1,97 @@
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/archives/binary.hpp>
+#include <fstream>
+
+#include "event_serialize.hpp"
+#include <phosphor-logging/log.hpp>
+#include "config.h"
+
+// Register class version
+// From cereal documentation;
+// "This macro should be placed at global scope"
+CEREAL_CLASS_VERSION(phosphor::events::Entry, CLASS_VERSION);
+
+namespace phosphor
+{
+namespace events
+{
+
+using namespace phosphor::logging;
+
+/** @brief Function required by Cereal to perform serialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] archive - reference to Cereal archive.
+ *  @param[in] event - const reference to event entry.
+ *  @param[in] version - Class version that enables handling
+ *                       a serialized data across code levels
+ */
+template <class Archive>
+void save(Archive& archive, const Entry& event, const std::uint32_t version)
+{
+    archive(event.timestamp(), event.message(), event.additionalData());
+}
+
+/** @brief Function required by Cereal to perform deserialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] archive - reference to Cereal archive.
+ *  @param[in] event - reference to event entry.
+ *  @param[in] version - Class version that enables handling
+ *                       a serialized data across code levels
+ */
+template <class Archive>
+void load(Archive& archive, Entry& event, const std::uint32_t version)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Logging::server;
+
+    uint64_t timestamp{};
+    std::string message{};
+    std::vector<std::string> additionalData{};
+
+    archive(timestamp, message, additionalData);
+
+    event.timestamp(timestamp);
+    event.message(message);
+    event.additionalData(additionalData);
+}
+
+fs::path serialize(const Entry& event, const std::string& eventName)
+{
+    fs::path dir(EVENTS_PERSIST_PATH);
+    auto path = dir / eventName;
+    fs::create_directories(path);
+    path /= std::to_string(event.timestamp());
+    std::ofstream os(path.string(), std::ios::binary);
+    cereal::BinaryOutputArchive oarchive(os);
+    oarchive(event);
+    return path;
+}
+
+bool deserialize(const fs::path& path, Entry& event)
+{
+    try
+    {
+        if (fs::exists(path))
+        {
+            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+            cereal::BinaryInputArchive iarchive(is);
+            iarchive(event);
+            return true;
+        }
+        return false;
+    }
+    catch (cereal::Exception& e)
+    {
+        log<level::ERR>(e.what());
+        std::error_code ec;
+        fs::remove(path, ec);
+        return false;
+    }
+    catch (const fs::filesystem_error& e)
+    {
+        return false;
+    }
+}
+
+} // namespace event
+} // namespace phosphor
diff --git a/src/event_serialize.hpp b/src/event_serialize.hpp
new file mode 100644
index 0000000..f052845
--- /dev/null
+++ b/src/event_serialize.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include "event_entry.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace events
+{
+
+namespace fs = std::experimental::filesystem;
+
+/** @brief Serialize and persist event d-bus object
+ *  @param[in] event - const reference to event entry.
+ *  @param[in] eventName - Name of the event.
+ *  @return fs::path - pathname of persisted events file
+ */
+fs::path serialize(const Entry& event, const std::string& eventName);
+
+/** @brief Deserialze a persisted event into a d-bus object
+ *  @param[in] path - pathname of persisted event file
+ *  @param[in] event - reference to event object which is the target of
+ *             deserialization.
+ *  @return bool - true if the deserialization was successful, false otherwise.
+ */
+bool deserialize(const fs::path& path, Entry& event);
+
+} // namespace events
+} // namespace phosphor
diff --git a/src/main.cpp b/src/main.cpp
index 5e191e1..68cef3e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -43,6 +43,8 @@
     sdbusplus::server::manager::manager objManager(bus, OBJ_EVENT);
     bus.request_name(BUSNAME_EVENT);
 
+    phosphor::events::getManager().restore();
+
     for (auto& watch : ConfigPropertyWatches::get())
     {
         watch->start();
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
index 717bacc..8f3cd02 100644
--- a/src/test/Makefile.am
+++ b/src/test/Makefile.am
@@ -199,4 +199,5 @@
 	$(builddir)/../elog.o \
 	$(builddir)/../resolve_errors.o \
 	$(builddir)/../event_manager.o \
+	$(builddir)/../event_serialize.o \
 	-lstdc++fs