PEL: Find an entry in the message registry JSON

The message registry is a JSON file that holds data required to create a
PEL out of an OpenBMC event log. It includes fields like 'subsystem',
'event type', 'action flags', 'SRC reason code', etc.

Many fields in the message registry are optional, and a very minimal
entry make look like:

{
    "Name": "xyz.openbmc_project.Power.Error.Fault",
    "Subsystem": "power_supply",
    "ActionFlags": ["service_action", "report"],

    "SRC":
    {
        "ReasonCode": "0x2030"
    }
}

This commit adds support to look up a message registry entry based on an
OpenBMC event log's 'Message' property (i.e.
xyz.openbmc_project.Power.Error.Fault) and then fill in a structure with
the fields found.  Future commits will fill in the SRC related fields,
as well as actually create the PEL.

The message registry file can be found on the BMC at:
/usr/share/phosphor-logging/pels/message_registry.json.

For testing, users can put their own message_registry.json in
/etc/phosphor-logging, and that will take precedence.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ie4195ed7e58ab6a231271f6b295e63b1d0a4cd78
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 5705f73..18df9ec 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -10,6 +10,7 @@
 	pel_values_test \
 	pel_manager_test \
 	private_header_test \
+	registry_test \
 	repository_test \
 	section_header_test \
 	stream_test \
@@ -19,7 +20,9 @@
 	$(top_builddir)/extensions/openpower-pels/bcd_time.o \
 	$(top_builddir)/extensions/openpower-pels/log_id.o \
 	$(top_builddir)/extensions/openpower-pels/pel.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o \
 	$(top_builddir)/extensions/openpower-pels/private_header.o \
+	$(top_builddir)/extensions/openpower-pels/registry.o \
 	$(top_builddir)/extensions/openpower-pels/user_header.o
 
 additional_data_test_SOURCES = %reldir%/additional_data_test.cpp
@@ -108,6 +111,16 @@
 	$(top_builddir)/extensions/openpower-pels/repository.o
 pel_manager_test_LDFLAGS = $(test_ldflags)
 
+registry_test_SOURCES = \
+	%reldir%/registry_test.cpp %reldir%/paths.cpp
+registry_test_CPPFLAGS = $(test_cppflags)
+registry_test_CXXFLAGS = $(test_cxxflags)
+registry_test_LDADD = \
+	$(test_ldadd) \
+	$(top_builddir)/extensions/openpower-pels/registry.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o
+registry_test_LDFLAGS = $(test_ldflags)
+
 mtms_test_SOURCES = %reldir%/mtms_test.cpp
 mtms_test_CPPFLAGS = $(test_cppflags)
 mtms_test_CXXFLAGS = $(test_cxxflags)
diff --git a/test/openpower-pels/paths.cpp b/test/openpower-pels/paths.cpp
index 27f4bce..502fcf0 100644
--- a/test/openpower-pels/paths.cpp
+++ b/test/openpower-pels/paths.cpp
@@ -35,5 +35,18 @@
     return repoPath;
 }
 
+std::filesystem::path getMessageRegistryPath()
+{
+    static std::string registryPath;
+
+    if (registryPath.empty())
+    {
+        char templ[] = "/tmp/msgregtestXXXXXX";
+        registryPath = mkdtemp(templ);
+    }
+
+    return registryPath;
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
index 0a82141..61fbedd 100644
--- a/test/openpower-pels/pel_manager_test.cpp
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -45,7 +45,8 @@
     std::vector<std::string> additionalData{adItem};
     std::vector<std::string> associations;
 
-    manager.create("error message", 42, 0, Entry::Level::Error, additionalData,
+    manager.create("error message", 42, 0,
+                   phosphor::logging::Entry::Level::Error, additionalData,
                    associations);
 
     // We don't know the exact name, but a file should have been added to the
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
index 46ec8ed..3e0ac01 100644
--- a/test/openpower-pels/pel_utils.cpp
+++ b/test/openpower-pels/pel_utils.cpp
@@ -13,6 +13,7 @@
 std::filesystem::path CleanLogID::pelIDFile{};
 std::filesystem::path CleanPELFiles::pelIDFile{};
 std::filesystem::path CleanPELFiles::repoPath{};
+std::filesystem::path CleanPELFiles::registryPath{};
 
 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 262a6e7..e61bea2 100644
--- a/test/openpower-pels/pel_utils.hpp
+++ b/test/openpower-pels/pel_utils.hpp
@@ -33,6 +33,7 @@
     {
         pelIDFile = openpower::pels::getPELIDFile();
         repoPath = openpower::pels::getPELRepoPath();
+        registryPath = openpower::pels::getMessageRegistryPath();
     }
 
     static void TearDownTestCase()
@@ -40,10 +41,12 @@
         std::filesystem::remove_all(
             std::filesystem::path{pelIDFile}.parent_path());
         std::filesystem::remove_all(repoPath);
+        std::filesystem::remove_all(registryPath);
     }
 
     static std::filesystem::path pelIDFile;
     static std::filesystem::path repoPath;
+    static std::filesystem::path registryPath;
 };
 
 /**
diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp
new file mode 100644
index 0000000..2107725
--- /dev/null
+++ b/test/openpower-pels/registry_test.cpp
@@ -0,0 +1,166 @@
+#include "extensions/openpower-pels/registry.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels::message;
+namespace fs = std::filesystem;
+
+const auto registryData = R"(
+{
+    "PELs":
+    [
+        {
+            "Name": "xyz.openbmc_project.Power.Fault",
+            "Subsystem": "power_supply",
+            "ActionFlags": ["service_action", "report"],
+
+            "SRC":
+            {
+                "ReasonCode": "0x2030"
+            }
+        },
+
+        {
+            "Name": "xyz.openbmc_project.Power.OverVoltage",
+            "Subsystem": "power_control_hw",
+            "Severity": "unrecoverable",
+            "MfgSeverity": "non_error",
+            "ActionFlags": ["service_action", "report", "call_home"],
+            "MfgActionFlags": ["hidden"],
+
+            "SRC":
+            {
+                "ReasonCode": "0x2333",
+                "Type": "BD",
+                "SymptomIDFields": ["SRCWord5", "SRCWord6", "SRCWord7"],
+                "PowerFault": true,
+                "Words6To9":
+                {
+                    "6":
+                    {
+                        "description": "Failing unit number",
+                        "AdditionalDataPropSource": "PS_NUM"
+                    },
+
+                    "7":
+                    {
+                        "description": "bad voltage",
+                        "AdditionalDataPropSource": "VOLTAGE"
+                    }
+                }
+            }
+        }
+    ]
+}
+)";
+
+class RegistryTest : public ::testing::Test
+{
+  protected:
+    static void SetUpTestCase()
+    {
+        char path[] = "/tmp/regtestXXXXXX";
+        regDir = mkdtemp(path);
+    }
+
+    static void TearDownTestCase()
+    {
+        fs::remove_all(regDir);
+    }
+
+    static std::string writeData(const char* data)
+    {
+        fs::path path = regDir / "registry.json";
+        std::ofstream stream{path};
+        stream << data;
+        return path;
+    }
+
+    static fs::path regDir;
+};
+
+fs::path RegistryTest::regDir{};
+
+TEST_F(RegistryTest, TestNoEntry)
+{
+    auto path = RegistryTest::writeData(registryData);
+    Registry registry{path};
+
+    auto entry = registry.lookup("foo");
+    EXPECT_FALSE(entry);
+}
+
+TEST_F(RegistryTest, TestFindEntry)
+{
+    auto path = RegistryTest::writeData(registryData);
+    Registry registry{path};
+
+    auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage");
+    ASSERT_TRUE(entry);
+    EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
+    EXPECT_EQ(entry->subsystem, 0x62);
+    EXPECT_EQ(*(entry->severity), 0x40);
+    EXPECT_EQ(*(entry->mfgSeverity), 0x00);
+    EXPECT_EQ(entry->actionFlags, 0xA800);
+    EXPECT_EQ(*(entry->mfgActionFlags), 0x4000);
+    EXPECT_FALSE(entry->eventType);
+    EXPECT_FALSE(entry->eventScope);
+
+    // TODO: compare SRC fields
+}
+
+// Check the entry that mostly uses defaults
+TEST_F(RegistryTest, TestFindEntryMinimal)
+{
+    auto path = RegistryTest::writeData(registryData);
+    Registry registry{path};
+
+    auto entry = registry.lookup("xyz.openbmc_project.Power.Fault");
+    ASSERT_TRUE(entry);
+    EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault");
+    EXPECT_EQ(entry->subsystem, 0x61);
+    EXPECT_FALSE(entry->severity);
+    EXPECT_FALSE(entry->mfgSeverity);
+    EXPECT_FALSE(entry->mfgActionFlags);
+    EXPECT_EQ(entry->actionFlags, 0xA000);
+    EXPECT_FALSE(entry->eventType);
+    EXPECT_FALSE(entry->eventScope);
+}
+
+TEST_F(RegistryTest, TestBadJSON)
+{
+    auto path = RegistryTest::writeData("bad {} json");
+
+    Registry registry{path};
+
+    EXPECT_FALSE(registry.lookup("foo"));
+}
+
+// Test the helper functions the use the pel_values data.
+TEST_F(RegistryTest, TestHelperFunctions)
+{
+    using namespace openpower::pels::message::helper;
+    EXPECT_EQ(getSubsystem("input_power_source"), 0xA1);
+    EXPECT_THROW(getSubsystem("foo"), std::runtime_error);
+
+    EXPECT_EQ(getSeverity("symptom_recovered"), 0x71);
+    EXPECT_THROW(getSeverity("foo"), std::runtime_error);
+
+    EXPECT_EQ(getEventType("dump_notification"), 0x08);
+    EXPECT_THROW(getEventType("foo"), std::runtime_error);
+
+    EXPECT_EQ(getEventScope("possibly_multiple_platforms"), 0x04);
+    EXPECT_THROW(getEventScope("foo"), std::runtime_error);
+
+    std::vector<std::string> flags{"service_action", "dont_report",
+                                   "termination"};
+    EXPECT_EQ(getActionFlags(flags), 0x9100);
+
+    flags.clear();
+    flags.push_back("foo");
+    EXPECT_THROW(getActionFlags(flags), std::runtime_error);
+}