PEL: LightPath: Choose callout location codes

LightPath uses the following rules to pick the location codes to turn on
LEDs for:

* If the PEL wasn't created by the BMC or Hostboot, and doesn't have the
  Service Action action flag set, then don't even check it and don't
  turn on the System Attention Indicator.

* Choose all location codes in the first group of callouts, where a
  group can be:
  * a single medium priority callout
  * one or more high priority callouts
  * one or more medium group A priority callouts

* All callouts in that group must be hardware callouts, meaning the FRU
  identity section's failing component type flag must either be hardware
  callout or symbolic FRU callout with trusted location code.  If there
  is a callout in the group that doesn't meet this requirement, then
  nothing in that group can be used.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ifbe7bddee14b69dc565a405e2f120fb5545f69e5
diff --git a/extensions/openpower-pels/service_indicators.cpp b/extensions/openpower-pels/service_indicators.cpp
index 97eb3fe..074251c 100644
--- a/extensions/openpower-pels/service_indicators.cpp
+++ b/extensions/openpower-pels/service_indicators.cpp
@@ -15,6 +15,7 @@
  */
 #include "service_indicators.hpp"
 
+#include <bitset>
 #include <phosphor-logging/log.hpp>
 
 namespace openpower::pels::service_indicators
@@ -30,8 +31,20 @@
 
 bool LightPath::ignore(const PEL& pel) const
 {
-    // TODO
-    return false;
+    auto creator = pel.privateHeader().creatorID();
+
+    // Don't ignore serviceable BMC or hostboot errors
+    if ((static_cast<CreatorID>(creator) == CreatorID::openBMC) ||
+        (static_cast<CreatorID>(creator) == CreatorID::hostboot))
+    {
+        std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
+        if (actionFlags.test(serviceActionFlagBit))
+        {
+            return false;
+        }
+    }
+
+    return true;
 }
 
 void LightPath::activate(const PEL& pel)
@@ -78,8 +91,92 @@
 std::vector<std::string> LightPath::getLocationCodes(
     const std::vector<std::unique_ptr<src::Callout>>& callouts) const
 {
-    // TODO
-    return {};
+    std::vector<std::string> locCodes;
+    bool firstCallout = true;
+    uint8_t firstCalloutPriority;
+
+    // Collect location codes for the first group of callouts,
+    // where a group can be:
+    //  * a single medium priority callout
+    //  * one or more high priority callouts
+    //  * one or more medium group a priority callouts
+    //
+    // All callouts in the group must be hardware callouts.
+
+    for (const auto& callout : callouts)
+    {
+        if (firstCallout)
+        {
+            firstCallout = false;
+
+            firstCalloutPriority = callout->priority();
+
+            // If the first callout is High, Medium, or Medium
+            // group A, and is a hardware callout, then we
+            // want it.
+            if (isRequiredPriority(firstCalloutPriority) &&
+                isHardwareCallout(*callout))
+            {
+                locCodes.push_back(callout->locationCode());
+            }
+            else
+            {
+                break;
+            }
+
+            // By definition a medium priority callout can't be part
+            // of a group, so no need to look for more.
+            if (static_cast<CalloutPriority>(firstCalloutPriority) ==
+                CalloutPriority::medium)
+            {
+                break;
+            }
+        }
+        else
+        {
+            // Only continue while the callouts are the same
+            // priority as the first callout.
+            if (callout->priority() != firstCalloutPriority)
+            {
+                break;
+            }
+
+            // If any callout in the group isn't a hardware callout,
+            // then don't light up any LEDs at all.
+            if (!isHardwareCallout(*callout))
+            {
+                locCodes.clear();
+                break;
+            }
+
+            locCodes.push_back(callout->locationCode());
+        }
+    }
+
+    return locCodes;
+}
+
+bool LightPath::isRequiredPriority(uint8_t priority) const
+{
+    auto calloutPriority = static_cast<CalloutPriority>(priority);
+    return (calloutPriority == CalloutPriority::high) ||
+           (calloutPriority == CalloutPriority::medium) ||
+           (calloutPriority == CalloutPriority::mediumGroupA);
+}
+
+bool LightPath::isHardwareCallout(const src::Callout& callout) const
+{
+    const auto& fruIdentity = callout.fruIdentity();
+    if (fruIdentity)
+    {
+        return (callout.locationCodeSize() != 0) &&
+               ((fruIdentity->failingComponentType() ==
+                 src::FRUIdentity::hardwareFRU) ||
+                (fruIdentity->failingComponentType() ==
+                 src::FRUIdentity::symbolicFRUTrustedLocCode));
+    }
+
+    return false;
 }
 
 std::vector<std::string> LightPath::getLEDGroupPaths(
diff --git a/extensions/openpower-pels/service_indicators.hpp b/extensions/openpower-pels/service_indicators.hpp
index 36eacf8..292c30d 100644
--- a/extensions/openpower-pels/service_indicators.hpp
+++ b/extensions/openpower-pels/service_indicators.hpp
@@ -94,13 +94,30 @@
     void activate(const PEL& pel) override;
 
     /**
-     * @brief Description TODO
+     * @brief Returns the location codes for the FRU callouts in the
+     *        callouts list that need their LEDs turned on.
+     *
+     * This is public so it can be tested.
+     *
+     * @param[in] callouts - The Callout list from a PEL
+     *
+     * @return std::vector<std::string> - The location codes
      */
     std::vector<std::string> getLocationCodes(
         const std::vector<std::unique_ptr<src::Callout>>& callouts) const;
 
     /**
-     * @brief Description TODO
+     * @brief Function called to check if the code even needs to
+     *        bother looking in the callouts to find LEDs to turn on.
+     *
+     * It will ignore all PELs except for those created by the BMC or
+     * hostboot that have the Serviceable action flag set.
+     *
+     * This is public so it can be tested.
+     *
+     * @param[in] pel - The PEL
+     *
+     * @return bool - If the PEL should be ignored or not.
      */
     bool ignore(const PEL& pel) const;
 
@@ -115,6 +132,32 @@
      * @brief Description TODO
      */
     void assertLEDs(const std::vector<std::string>& ledGroups) const;
+
+    /**
+     * @brief Checks if the callout priority is one that the policy
+     *        may turn on an LED for.
+     *
+     * The priorities it cares about are high, medium, and medium
+     * group A.
+     *
+     * @param[in] priority - The priority value from the PEL
+     *
+     * @return bool - If LightPath cares about a callout with this
+     *                priority.
+     */
+    bool isRequiredPriority(uint8_t priority) const;
+
+    /**
+     * @brief Checks if the callout is either a normal FRU
+     *        callout or a symbolic FRU callout with a trusted
+     *        location code, which is one of the requirements for
+     *        LightPath to turn on an LED.
+     *
+     * @param[in] - callout - The Callout object
+     *
+     * @return bool - If the callout is a hardware callout
+     */
+    bool isHardwareCallout(const src::Callout& callout) const;
 };
 
 /**
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index b23abd7..ee7f99f 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -25,6 +25,7 @@
 	registry_test \
 	repository_test \
 	section_header_test \
+	service_indicators_test \
 	severity_test \
 	src_test \
 	src_callout_test \
@@ -392,3 +393,14 @@
 	$(pel_test_utils_ldadd) \
 	$(top_builddir)/extensions/openpower-pels/device_callouts.o
 device_callouts_test_LDFLAGS = $(test_ldflags)
+
+service_indicators_test_SOURCES = \
+	%reldir%/service_indicators_test.cpp
+service_indicators_test_CPPFLAGS = $(test_cppflags)
+service_indicators_test_CXXFLAGS = $(test_cxxflags)
+service_indicators_test_LDADD = \
+	$(test_ldflags) \
+	$(pel_objects) \
+	$(pel_test_utils_ldadd) \
+	$(top_builddir)/extensions/openpower-pels/service_indicators.o
+service_indicators_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/service_indicators_test.cpp b/test/openpower-pels/service_indicators_test.cpp
new file mode 100644
index 0000000..c391e92
--- /dev/null
+++ b/test/openpower-pels/service_indicators_test.cpp
@@ -0,0 +1,259 @@
+/**
+ * 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/service_indicators.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using CalloutVector = std::vector<std::unique_ptr<src::Callout>>;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::Throw;
+
+// Test the ignore() function works
+TEST(ServiceIndicatorsTest, IgnoreTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // PEL must have serviceable action flag set and be created
+    // by the BMC or Hostboot.
+    std::vector<std::tuple<char, uint16_t, bool>> testParams{
+        {'O', 0xA400, false}, // BMC serviceable, don't ignore
+        {'B', 0xA400, false}, // Hostboot serviceable, don't ignore
+        {'H', 0xA400, true},  // PHYP serviceable, ignore
+        {'O', 0x2400, true},  // BMC not serviceable, ignore
+        {'B', 0x2400, true},  // Hostboot not serviceable, ignore
+        {'H', 0x2400, true},  // PHYP not serviceable, ignore
+    };
+
+    for (const auto& test : testParams)
+    {
+        auto data = pelFactory(1, std::get<char>(test), 0x20,
+                               std::get<uint16_t>(test), 500);
+        PEL pel{data};
+
+        EXPECT_EQ(lightPath.ignore(pel), std::get<bool>(test));
+    }
+}
+
+// Test that only high, medium, and medium group A hardware
+// callouts have their location codes extracted.
+TEST(ServiceIndicatorsTest, OneCalloutPriorityTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // The priorities to test, with the expected getLocationCodes results.
+    std::vector<std::tuple<CalloutPriority, std::vector<std::string>>>
+        testCallouts{{CalloutPriority::high, {"U27-P1"}},
+                     {CalloutPriority::medium, {"U27-P1"}},
+                     {CalloutPriority::mediumGroupA, {"U27-P1"}},
+                     {CalloutPriority::mediumGroupB, {}},
+                     {CalloutPriority::mediumGroupC, {}},
+                     {CalloutPriority::low, {}}};
+
+    for (const auto& test : testCallouts)
+    {
+        auto callout = std::make_unique<src::Callout>(
+            std::get<CalloutPriority>(test), "U27-P1", "1234567", "aaaa",
+            "123456789ABC");
+
+        CalloutVector callouts;
+        callouts.push_back(std::move(callout));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::get<std::vector<std::string>>(test));
+    }
+}
+
+// Test that only normal hardware callouts and symbolic FRU
+// callouts with trusted location codes have their location
+// codes extracted.
+TEST(ServiceIndicatorsTest, OneCalloutTypeTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // Regular hardware callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // Symbolic FRU with trusted loc code callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P1", true));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // Symbolic FRU without trusted loc code callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P1", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{});
+    }
+
+    // Procedure callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "bmc_code"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{});
+    }
+}
+
+// Test that only the callouts in the first group have their location
+// codes extracted, where a group is one or more callouts listed
+// together with priorities of high, medium, or medium group A
+// (and medium is only ever contains 1 item).
+TEST(ServiceIndicatorsTest, CalloutGroupingTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // high/high/medium/high just grabs the first 2
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P2",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P3",
+                                           "1234567", "aaaa", "123456789ABC"));
+        // This high priority callout after a medium isn't actually valid, since
+        // callouts are sorted, but test it anyway.
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P4",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+
+    // medium/medium just grabs the first medium
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P2",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // mediumA/mediumA/medium just grabs the first 2
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::mediumGroupA, "U27-P1", "1234567", "aaaa",
+            "123456789ABC"));
+
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::mediumGroupA, "U27-P2", "1234567", "aaaa",
+            "123456789ABC"));
+
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P3",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+}
+
+// Test that if any callouts in group are not HW/trusted symbolic
+// FRU callouts then no location codes will be extracted
+TEST(ServiceIndicatorsTest, CalloutMixedTypesTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // Mixing FRU with trusted symbolic FRU is OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", true));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+
+    // Normal FRU callout with a non-trusted symbolic FRU callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+
+    // Normal FRU callout with a procedure callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "bmc_code"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+
+    // Trusted symbolic FRU callout with a non-trusted symbolic
+    // FRU callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", true));
+
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+}