Add MissingIsAcceptable feature to avoid failsafe

This is a partial implementation of the ideas here:
https://github.com/openbmc/phosphor-pid-control/issues/31

A new configuration item is supported in the PID object, named
"MissingIsAcceptable" (for D-Bus) or "missingIsAcceptable" (for the old
config.json). The value is an array of strings. If these strings match
sensor names, those sensors will be flagged as "missing is acceptable",
that is, they can go missing and the zone will not be thrown into
failsafe mode as a result.

This can be handy for sensors that are not always available on your
particular machine. It is independent of the existing Availability
interface, because the decision to go into failsafe mode or not is a
property of the PID loop, not of the sensor itself.

If a PID loop consists of all sensors that are missing, the output
will be deemed to be the setpoint, thus essentially making the PID
loop a no-op. Now initializing sensor values to NaN, not zero, as zero
is not a good default if PID loop is margin, undoing a bug I made:
https://gerrit.openbmc.org/c/openbmc/phosphor-pid-control/+/38228

Tested: It worked for me. Also, added a unit test case.

Change-Id: Idc7978ab06fcc9ed8c6c9df9483101376e5df4d1
Signed-off-by: Josh Lehan <krellan@google.com>
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index 2473252..7b78280 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -670,6 +670,15 @@
                 std::vector<std::string> inputSensorNames(
                     std::get<std::vector<std::string>>(base.at("Inputs")));
                 std::vector<std::string> outputSensorNames;
+                std::vector<std::string> missingAcceptableSensorNames;
+
+                auto findMissingAcceptable = base.find("MissingIsAcceptable");
+                if (findMissingAcceptable != base.end())
+                {
+                    missingAcceptableSensorNames =
+                        std::get<std::vector<std::string>>(
+                            findMissingAcceptable->second);
+                }
 
                 // assumption: all fan pids must have at least one output
                 if (pidClass == "fan")
@@ -689,6 +698,9 @@
 
                 std::vector<SensorInterfaceType> inputSensorInterfaces;
                 std::vector<SensorInterfaceType> outputSensorInterfaces;
+                std::vector<SensorInterfaceType>
+                    missingAcceptableSensorInterfaces;
+
                 /* populate an interface list for different sensor direction
                  * types (input,output)
                  */
@@ -707,6 +719,12 @@
                     findSensors(sensors, sensorNameToDbusName(sensorName),
                                 outputSensorInterfaces);
                 }
+                for (const std::string& sensorName :
+                     missingAcceptableSensorNames)
+                {
+                    findSensors(sensors, sensorNameToDbusName(sensorName),
+                                missingAcceptableSensorInterfaces);
+                }
 
                 inputSensorNames.clear();
                 for (const SensorInterfaceType& inputSensorInterface :
@@ -752,6 +770,35 @@
                     }
                 }
 
+                // MissingIsAcceptable same postprocessing as Inputs
+                missingAcceptableSensorNames.clear();
+                for (const SensorInterfaceType&
+                         missingAcceptableSensorInterface :
+                     missingAcceptableSensorInterfaces)
+                {
+                    const std::string& dbusInterface =
+                        missingAcceptableSensorInterface.second;
+                    const std::string& missingAcceptableSensorPath =
+                        missingAcceptableSensorInterface.first;
+
+                    std::string missingAcceptableSensorName =
+                        getSensorNameFromPath(missingAcceptableSensorPath);
+                    missingAcceptableSensorNames.push_back(
+                        missingAcceptableSensorName);
+
+                    if (dbusInterface != sensorInterface)
+                    {
+                        /* MissingIsAcceptable same error checking as Inputs
+                         */
+                        throw std::runtime_error(
+                            "sensor at dbus path [" +
+                            missingAcceptableSensorPath +
+                            "] has an interface [" + dbusInterface +
+                            "] that does not match the expected interface of " +
+                            sensorInterface);
+                    }
+                }
+
                 /* fan pids need to pair up tach sensors with their pwm
                  * counterparts
                  */
@@ -864,7 +911,8 @@
                 }
 
                 std::vector<pid_control::conf::SensorInput> sensorInputs =
-                    spliceInputs(inputSensorNames, inputTempToMargin);
+                    spliceInputs(inputSensorNames, inputTempToMargin,
+                                 missingAcceptableSensorNames);
 
                 if (offsetType.empty())
                 {
@@ -903,9 +951,19 @@
                 conf::PIDConf& conf = zoneConfig[index];
 
                 std::vector<std::string> inputs;
+                std::vector<std::string> missingAcceptableSensors;
+                std::vector<std::string> missingAcceptableSensorNames;
                 std::vector<std::string> sensorNames =
                     std::get<std::vector<std::string>>(base.at("Inputs"));
 
+                auto findMissingAcceptable = base.find("MissingIsAcceptable");
+                if (findMissingAcceptable != base.end())
+                {
+                    missingAcceptableSensorNames =
+                        std::get<std::vector<std::string>>(
+                            findMissingAcceptable->second);
+                }
+
                 bool unavailableAsFailed = true;
                 auto findUnavailableAsFailed =
                     base.find("InputUnavailableAsFailed");
@@ -928,10 +986,8 @@
 
                     for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
                     {
-                        size_t idx =
-                            sensorPathIfacePair.first.find_last_of("/") + 1;
                         std::string shortName =
-                            sensorPathIfacePair.first.substr(idx);
+                            getSensorNameFromPath(sensorPathIfacePair.first);
 
                         inputs.push_back(shortName);
                         auto& config = sensorConfig[shortName];
@@ -950,6 +1006,30 @@
                 {
                     continue;
                 }
+
+                // MissingIsAcceptable same postprocessing as Inputs
+                for (const std::string& missingAcceptableSensorName :
+                     missingAcceptableSensorNames)
+                {
+                    std::vector<std::pair<std::string, std::string>>
+                        sensorPathIfacePairs;
+                    if (!findSensors(
+                            sensors,
+                            sensorNameToDbusName(missingAcceptableSensorName),
+                            sensorPathIfacePairs))
+                    {
+                        break;
+                    }
+
+                    for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
+                    {
+                        std::string shortName =
+                            getSensorNameFromPath(sensorPathIfacePair.first);
+
+                        missingAcceptableSensors.push_back(shortName);
+                    }
+                }
+
                 conf::ControllerInfo& info = conf[pidName];
 
                 std::vector<double> inputTempToMargin;
@@ -961,7 +1041,8 @@
                         std::get<std::vector<double>>(findTempToMargin->second);
                 }
 
-                info.inputs = spliceInputs(inputs, inputTempToMargin);
+                info.inputs = spliceInputs(inputs, inputTempToMargin,
+                                           missingAcceptableSensors);
 
                 info.type = "stepwise";
                 info.stepwiseInfo.ts = 1.0; // currently unused