PEL: Add repository to save PELs

Create the Repository class that can save PELs in (and later retrieve
them from) the filesystem.  It provides an add() method that can add
a PEL object to the repository.

Now, when the Manager class sees an OpenBMC event log created with the
RAWPEL metadata in the AdditionalData property that points at a file
that contains a PEL, it can save that PEL.  Before the PEL is saved, the
log ID and commit timestamp fields in the PEL will be updated - the log
ID to a unique value, and the timestamp to the current time.

Change-Id: I8dbaddf0f155bcb6d40b933294ada83feb75ce53
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 052aa2a..48e1872 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -5,7 +5,9 @@
 	bcd_time_test \
 	log_id_test \
 	pel_test \
+	pel_manager_test \
 	private_header_test \
+	repository_test \
 	section_header_test \
 	stream_test \
 	user_header_test
@@ -80,3 +82,24 @@
 	$(test_ldadd) \
 	$(pel_objects)
 pel_test_LDFLAGS = $(test_ldflags)
+
+repository_test_SOURCES = \
+	%reldir%/repository_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+repository_test_CPPFLAGS = $(test_cppflags)
+repository_test_CXXFLAGS = $(test_cxxflags)
+repository_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_objects) \
+	$(top_builddir)/extensions/openpower-pels/repository.o
+repository_test_LDFLAGS = $(test_ldflags)
+
+pel_manager_test_SOURCES = \
+	%reldir%/pel_manager_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+pel_manager_test_CPPFLAGS = $(test_cppflags)
+pel_manager_test_CXXFLAGS = $(test_cxxflags)
+pel_manager_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_objects) \
+	$(top_builddir)/extensions/openpower-pels/manager.o \
+	$(top_builddir)/extensions/openpower-pels/repository.o
+pel_manager_test_LDFLAGS = $(test_ldflags)
\ No newline at end of file
diff --git a/test/openpower-pels/paths.cpp b/test/openpower-pels/paths.cpp
index 464b92c..27f4bce 100644
--- a/test/openpower-pels/paths.cpp
+++ b/test/openpower-pels/paths.cpp
@@ -22,5 +22,18 @@
     return idFile;
 }
 
+std::filesystem::path getPELRepoPath()
+{
+    static std::string repoPath;
+
+    if (repoPath.empty())
+    {
+        char templ[] = "/tmp/repopathtestXXXXXX";
+        std::filesystem::path dir = mkdtemp(templ);
+        repoPath = dir;
+    }
+    return repoPath;
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
new file mode 100644
index 0000000..a5b16f5
--- /dev/null
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -0,0 +1,66 @@
+#include "extensions/openpower-pels/manager.hpp"
+#include "log_manager.hpp"
+#include "pel_utils.hpp"
+
+#include <fstream>
+#include <regex>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+class ManagerTest : public CleanPELFiles
+{
+};
+
+fs::path makeTempDir()
+{
+    char path[] = "/tmp/tempnameXXXXXX";
+    std::filesystem::path dir = mkdtemp(path);
+    return dir;
+}
+
+// Test that using the RAWPEL=<file> with the Manager::create() call gets
+// a PEL saved in the repository.
+TEST_F(ManagerTest, TestCreateWithPEL)
+{
+    auto bus = sdbusplus::bus::new_default();
+    phosphor::logging::internal::Manager logManager(bus, "logging_path");
+
+    openpower::pels::Manager manager{logManager};
+
+    // Create a PEL, write it to a file, and pass that filename into
+    // the create function.
+    auto data = pelDataFactory(TestPelType::pelSimple);
+
+    fs::path pelFilename = makeTempDir() / "rawpel";
+    std::ofstream pelFile{pelFilename};
+    pelFile.write(reinterpret_cast<const char*>(data->data()), data->size());
+    pelFile.close();
+
+    std::string adItem = "RAWPEL=" + pelFilename.string();
+    std::vector<std::string> additionalData{adItem};
+    std::vector<std::string> associations;
+
+    manager.create("error message", 42, 0, Entry::Level::Error, additionalData,
+                   associations);
+
+    // We don't know the exact name, but a file should have been added to the
+    // repo of the form <timestamp>_<ID>
+    std::regex expr{"\\d+_\\d+"};
+
+    bool found = false;
+    for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
+    {
+        if (std::regex_search(f.path().string(), expr))
+        {
+            found = true;
+            break;
+        }
+    }
+
+    EXPECT_TRUE(found);
+
+    fs::remove_all(pelFilename.parent_path());
+}
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
index 4541694..46ec8ed 100644
--- a/test/openpower-pels/pel_utils.cpp
+++ b/test/openpower-pels/pel_utils.cpp
@@ -11,6 +11,8 @@
 using namespace openpower::pels;
 
 std::filesystem::path CleanLogID::pelIDFile{};
+std::filesystem::path CleanPELFiles::pelIDFile{};
+std::filesystem::path CleanPELFiles::repoPath{};
 
 constexpr uint8_t simplePEL[] = {
     // private header section header
diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp
index 7475720..262a6e7 100644
--- a/test/openpower-pels/pel_utils.hpp
+++ b/test/openpower-pels/pel_utils.hpp
@@ -26,6 +26,26 @@
     static std::filesystem::path pelIDFile;
 };
 
+class CleanPELFiles : public ::testing::Test
+{
+  protected:
+    static void SetUpTestCase()
+    {
+        pelIDFile = openpower::pels::getPELIDFile();
+        repoPath = openpower::pels::getPELRepoPath();
+    }
+
+    static void TearDownTestCase()
+    {
+        std::filesystem::remove_all(
+            std::filesystem::path{pelIDFile}.parent_path());
+        std::filesystem::remove_all(repoPath);
+    }
+
+    static std::filesystem::path pelIDFile;
+    static std::filesystem::path repoPath;
+};
+
 /**
  * @brief Tells the factory which PEL to create
  */
diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp
new file mode 100644
index 0000000..97019e8
--- /dev/null
+++ b/test/openpower-pels/repository_test.cpp
@@ -0,0 +1,73 @@
+#include "extensions/openpower-pels/paths.hpp"
+#include "extensions/openpower-pels/repository.hpp"
+#include "pel_utils.hpp"
+
+#include <ext/stdio_filebuf.h>
+
+#include <filesystem>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+/**
+ * Clean the Repo after every testcase.
+ * And because we have PEL object, also clean up
+ * the log ID.
+ */
+class RepositoryTest : public CleanLogID
+{
+  protected:
+    void SetUp() override
+    {
+        repoPath = getPELRepoPath();
+    }
+
+    void TearDown() override
+    {
+        fs::remove_all(repoPath);
+    }
+
+    fs::path repoPath;
+};
+
+TEST_F(RepositoryTest, FilenameTest)
+{
+    BCDTime date = {0x20, 0x30, 0x11, 0x28, 0x13, 0x6, 0x7, 0x8};
+
+    EXPECT_EQ(Repository::getPELFilename(0x12345678, date),
+              "2030112813060708_12345678");
+
+    EXPECT_EQ(Repository::getPELFilename(0xAABBCCDD, date),
+              "2030112813060708_AABBCCDD");
+
+    EXPECT_EQ(Repository::getPELFilename(0x3AFF1, date),
+              "2030112813060708_0003AFF1");
+
+    EXPECT_EQ(Repository::getPELFilename(100, date),
+              "2030112813060708_00000064");
+
+    EXPECT_EQ(Repository::getPELFilename(0, date), "2030112813060708_00000000");
+}
+
+TEST_F(RepositoryTest, AddTest)
+{
+    Repository repo{repoPath};
+    auto data = pelDataFactory(TestPelType::pelSimple);
+    auto pel = std::make_unique<PEL>(*data);
+
+    repo.add(pel);
+
+    // Check that the PEL was stored where it was supposed to be,
+    // and that it wrote the PEL data.
+    const auto& ts = pel->privateHeader()->commitTimestamp();
+    auto name = Repository::getPELFilename(pel->id(), ts);
+
+    fs::path file = repoPath / "logs" / name;
+    EXPECT_TRUE(fs::exists(file));
+
+    auto newData = readPELFile(file);
+    auto pelData = pel->data();
+    EXPECT_EQ(*newData, pelData);
+}