PEL: Add APIs to look up device path callouts

This commit adds the interfaces device_callouts::getCallouts() and
device_callouts::getI2CCallouts() that will be used to look up the FRU
callouts to add to PELs for errors stemming from accessing devices,
either by a sysfs path, or in the case of the latter interface an I2C
bus and address.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I573d04632fd9fc6356a0ff53f85c2a2c13038962
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 34db4e5..0ee298b 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -4,6 +4,7 @@
 	additional_data_test \
 	ascii_string_test \
 	bcd_time_test \
+	device_callouts_test \
 	event_logger_test \
 	extended_user_header_test \
 	failing_mtms_test \
@@ -37,6 +38,7 @@
 	$(top_builddir)/extensions/openpower-pels/bcd_time.o \
 	$(top_builddir)/extensions/openpower-pels/callout.o \
 	$(top_builddir)/extensions/openpower-pels/callouts.o \
+	$(top_builddir)/extensions/openpower-pels/device_callouts.o \
 	$(top_builddir)/extensions/openpower-pels/extended_user_header.o \
 	$(top_builddir)/extensions/openpower-pels/failing_mtms.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
@@ -309,6 +311,7 @@
 	$(top_builddir)/extensions/openpower-pels/ascii_string.o \
 	$(top_builddir)/extensions/openpower-pels/callout.o \
 	$(top_builddir)/extensions/openpower-pels/callouts.o \
+	$(top_builddir)/extensions/openpower-pels/device_callouts.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
 	$(top_builddir)/extensions/openpower-pels/json_utils.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \
@@ -330,6 +333,7 @@
 	$(top_builddir)/extensions/openpower-pels/bcd_time.o \
 	$(top_builddir)/extensions/openpower-pels/callout.o \
 	$(top_builddir)/extensions/openpower-pels/callouts.o \
+	$(top_builddir)/extensions/openpower-pels/device_callouts.o \
 	$(top_builddir)/extensions/openpower-pels/data_interface.o \
 	$(top_builddir)/extensions/openpower-pels/extended_user_header.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
@@ -377,3 +381,13 @@
 event_logger_test_LDADD = \
 	$(test_ldadd)
 event_logger_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS)
+
+device_callouts_test_SOURCES = \
+	%reldir%/device_callouts_test.cpp
+device_callouts_test_CPPFLAGS = $(test_cppflags)
+device_callouts_test_CXXFLAGS = $(test_cxxflags)
+device_callouts_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_test_utils_ldadd) \
+	$(top_builddir)/extensions/openpower-pels/device_callouts.o
+device_callouts_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/device_callouts_test.cpp b/test/openpower-pels/device_callouts_test.cpp
new file mode 100644
index 0000000..d2947a1
--- /dev/null
+++ b/test/openpower-pels/device_callouts_test.cpp
@@ -0,0 +1,242 @@
+/**
+ * Copyright © 2020 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/device_callouts.hpp"
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::device_callouts;
+namespace fs = std::filesystem;
+
+// The callout JSON looks like:
+// "I2C":
+//   "<bus>":
+//     "<address>":
+//       "Callouts": ...
+//
+// "FSI":
+//   "<fsi link>":
+//     "Callouts": ...
+//
+// "FSI-I2C":
+//    "<fsi link>":
+//      "<bus>":
+//        "<address>":
+//          "Callouts": ...
+//
+// "FSI-SPI":
+//    "<fsi link>":
+//      "<bus>":
+//        "Callouts": ...
+
+const auto calloutJSON = R"(
+{
+    "I2C":
+    {
+        "0":
+        {
+            "32":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            },
+            "81":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            }
+        },
+        "14":
+        {
+            "112":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            },
+            "114":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    },
+                    {
+                       "Name": "/chassis/motherboard",
+                       "LocationCode": "P1",
+                       "Priority": "M"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            }
+        }
+    },
+    "FSI":
+    {
+        "0":
+        {
+           "Callouts":[
+                {
+                    "Name": "/chassis/motherboard/cpu0",
+                    "LocationCode": "P1-C19",
+                    "Priority": "H"
+                }
+           ],
+           "Dest": "proc-0 target"
+        },
+        "0-1":
+        {
+           "Callouts":[
+                {
+                    "Name": "/chassis/motherboard/cpu0",
+                    "LocationCode": "P1-C19",
+                    "Priority": "H",
+                    "MRU": "core"
+                }
+           ],
+           "Dest": "proc-0 target"
+        }
+    },
+    "FSI-I2C":
+    {
+        "0-3":
+        {
+           "7":
+           {
+              "24":
+              {
+                 "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                 ],
+                 "Dest": "proc-0 target"
+              },
+              "25":
+              {
+                 "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu5",
+                       "LocationCode": "P1-C25",
+                       "Priority": "H"
+                    },
+                    {
+                       "Name": "/chassis/motherboard",
+                       "LocationCode": "P1",
+                       "Priority": "M"
+                    },
+                    {
+                        "Name": "/chassis/motherboard/bmc",
+                        "LocationCode": "P2",
+                        "Priority": "L"
+                    }
+                 ],
+                 "Dest": "proc-5 target"
+              }
+           }
+        }
+    },
+    "FSI-SPI":
+    {
+        "8":
+        {
+            "3":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            },
+            "4":
+            {
+                "Callouts":[
+                    {
+                       "Name": "/chassis/motherboard/cpu0",
+                       "LocationCode": "P1-C19",
+                       "Priority": "H"
+                    }
+                ],
+                "Dest": "proc-0 target"
+            }
+        }
+    }
+})"_json;
+
+class DeviceCalloutsTest : public ::testing::Test
+{
+  public:
+    static void SetUpTestCase()
+    {
+        dataPath = getPELReadOnlyDataPath();
+        std::ofstream file{dataPath / filename};
+        file << calloutJSON.dump();
+    }
+
+    static void TearDownTestCase()
+    {
+        fs::remove_all(dataPath);
+    }
+
+    static std::string filename;
+    static fs::path dataPath;
+};
+
+std::string DeviceCalloutsTest::filename = "systemA_dev_callouts.json";
+fs::path DeviceCalloutsTest::dataPath;
+
+// Test looking up the JSON file based on the system compatible names
+TEST_F(DeviceCalloutsTest, getJSONFilenameTest)
+{
+    {
+        std::vector<std::string> compatibles{"system1", "systemA", "system3"};
+        EXPECT_EQ(util::getJSONFilename(compatibles),
+                  fs::path{dataPath / filename});
+    }
+
+    // Actual filename not in compatibles
+    {
+        std::vector<std::string> compatibles{"system5", "system6"};
+        EXPECT_THROW(util::getJSONFilename(compatibles), std::invalid_argument);
+    }
+}