Make specific UNA sensors not trigger failsafe

By convention, sensors at some states like 'not present',
'power state not matching' are marked as 'unavailable' on dbus.
At such states, some specific sensors should not be considered as
failed and trigger pid 'failsafe'.

A typical example is when a system is powered-off, its CPU/DIMM temp
sensors are 'unavailable', these sensors should not be treated as
'failed' and trigger pid 'failsafe'. This is necessary for systems
whose Fans will keep working when the CPU is off.

This feature is configurable per sensor (valid on thermal sensors). It
can be enabled by setting the Pid controller option
"InputUnavailableAsFailed" to 'false' when one configuring the PID module
via entity-manager, or by setting the sensor option "unavailableAsFailed"
to 'false' when one configuring the PID module via JSON. (These options are
optional and default to 'true')

Tested:
1. On a Fan 'always-on' system, enabale this feature on CPU temp sensors,
poweroff the system, 'unavailable' CPU temp sensors do not trigger the
failsafe mode.
2. 'Unavailable' Fans still trigger the failsafe mode.
3. 'Unfunctional' or 'failed' sensors still trigger the failsafe mode.

Signed-off-by: Zheng Song <zheng.song@intel.com>
Change-Id: I1dd1d76466f43e7dcf51c161c96714f1bcfae88d
diff --git a/conf.hpp b/conf.hpp
index e471f22..9122a28 100644
--- a/conf.hpp
+++ b/conf.hpp
@@ -27,6 +27,7 @@
     int64_t max;
     int64_t timeout;
     bool ignoreDbusMinMax;
+    bool unavailableAsFailed;
 };
 
 /*
diff --git a/configure.md b/configure.md
index 91c4506..3dbb320 100644
--- a/configure.md
+++ b/configure.md
@@ -35,6 +35,7 @@
         "min": 0,
         "max": 255,
         "ignoreDbusMinMax": true
+        "unavailableAsFailed": true
     },
     {
         "name": "fan2",
@@ -49,7 +50,7 @@
 ```
 
 A sensor has a `name`, a `type`, a `readPath`, a `writePath`, a `minimum` value,
-a `maximum` value, a `timeout`, and a `ignoreDbusMinMax` value.
+a `maximum` value, a `timeout`, a `ignoreDbusMinMax` and a `unavailableAsFailed` value.
 
 The `name` is used to reference the sensor in the zone portion of the
 configuration.
@@ -133,6 +134,13 @@
 values via these.  Setting this property to true will ignore `MinValue` and
 `MaxValue` from dbus and therefore won't call any passive value scaling.
 
+The `unavailableAsFailed` value is optional and defaults to true. However,
+some specific thermal sensors should not be treated as Failed when they are
+unavailable. For example, when a system is powered-off, its CPU/DIMM Temp sensors
+are unavailable, in such state these sensors should not be treated as Failed and
+trigger FailSafe. This is important for systems whose Fans are always on.
+For these specific sensors set this property to false.
+
 ### Zones
 
 ```
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index e9a7991..1af938a 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -599,6 +599,15 @@
                         getPIDAttribute(base, "Outputs"));
                 }
 
+                bool unavailableAsFailed = true;
+                auto findUnavailableAsFailed =
+                    base.find("InputUnavailableAsFailed");
+                if (findUnavailableAsFailed != base.end())
+                {
+                    unavailableAsFailed =
+                        std::get<bool>(findUnavailableAsFailed->second);
+                }
+
                 std::vector<SensorInterfaceType> inputSensorInterfaces;
                 std::vector<SensorInterfaceType> outputSensorInterfaces;
                 /* populate an interface list for different sensor direction
@@ -640,6 +649,7 @@
                     {
                         config.timeout = 0;
                         config.ignoreDbusMinMax = true;
+                        config.unavailableAsFailed = unavailableAsFailed;
                     }
                     if (dbusInterface != sensorInterface)
                     {
@@ -788,6 +798,15 @@
                 std::vector<std::string> sensorNames =
                     std::get<std::vector<std::string>>(base.at("Inputs"));
 
+                bool unavailableAsFailed = true;
+                auto findUnavailableAsFailed =
+                    base.find("InputUnavailableAsFailed");
+                if (findUnavailableAsFailed != base.end())
+                {
+                    unavailableAsFailed =
+                        std::get<bool>(findUnavailableAsFailed->second);
+                }
+
                 bool sensorFound = false;
                 for (const std::string& sensorName : sensorNames)
                 {
@@ -811,6 +830,7 @@
                         config.readPath = sensorPathIfacePair.first;
                         config.type = "temp";
                         config.ignoreDbusMinMax = true;
+                        config.unavailableAsFailed = unavailableAsFailed;
                         // todo: maybe un-hardcode this if we run into slower
                         // timeouts with sensors
 
diff --git a/dbus/dbusconfiguration.hpp b/dbus/dbusconfiguration.hpp
index aef3622..318da68 100644
--- a/dbus/dbusconfiguration.hpp
+++ b/dbus/dbusconfiguration.hpp
@@ -28,7 +28,7 @@
 #include <vector>
 
 using DbusVariantType =
-    std::variant<uint64_t, int64_t, double, std::string,
+    std::variant<uint64_t, int64_t, double, std::string, bool,
                  std::vector<std::string>, std::vector<double>>;
 
 using ManagedObjectType = std::unordered_map<
diff --git a/dbus/dbushelper.cpp b/dbus/dbushelper.cpp
index 0e4ba67..d08f537 100644
--- a/dbus/dbushelper.cpp
+++ b/dbus/dbushelper.cpp
@@ -130,6 +130,17 @@
 
     prop->value = std::visit(VariantToDoubleVisitor(), propMap["Value"]);
 
+    bool available = true;
+    try
+    {
+        getProperty(service, path, availabilityIntf, "Available", available);
+    }
+    catch (const sdbusplus::exception::exception& ex)
+    {
+        // unsupported Available property, leaving reading at 'True'
+    }
+    prop->available = available;
+
     return;
 }
 
diff --git a/dbus/dbushelper.hpp b/dbus/dbushelper.hpp
index ffda223..3f3e688 100644
--- a/dbus/dbushelper.hpp
+++ b/dbus/dbushelper.hpp
@@ -18,6 +18,8 @@
     static constexpr char propertiesintf[] = "org.freedesktop.DBus.Properties";
     static constexpr char criticalThreshInf[] =
         "xyz.openbmc_project.Sensor.Threshold.Critical";
+    static constexpr char availabilityIntf[] =
+        "xyz.openbmc_project.State.Decorator.Availability";
 
     explicit DbusHelper(sdbusplus::bus::bus bus) : _bus(std::move(bus))
     {}
diff --git a/dbus/dbushelper_interface.hpp b/dbus/dbushelper_interface.hpp
index 3f7d744..6d7e506 100644
--- a/dbus/dbushelper_interface.hpp
+++ b/dbus/dbushelper_interface.hpp
@@ -13,6 +13,8 @@
     double min;
     double max;
     std::string unit;
+    bool available;
+    bool unavailableAsFailed;
 };
 
 class DbusHelperInterface
diff --git a/dbus/dbuspassive.cpp b/dbus/dbuspassive.cpp
index 0c73db2..a5da100 100644
--- a/dbus/dbuspassive.cpp
+++ b/dbus/dbuspassive.cpp
@@ -72,6 +72,8 @@
         settings.max = 0;
     }
 
+    settings.unavailableAsFailed = info->unavailableAsFailed;
+
     return std::make_unique<DbusPassive>(bus, type, id, std::move(helper),
                                          settings, failed, path, redundancy);
 }
@@ -90,9 +92,12 @@
     _scale = settings.scale;
     _min = settings.min * std::pow(10.0, _scale);
     _max = settings.max * std::pow(10.0, _scale);
+    _available = settings.available;
+    _unavailableAsFailed = settings.unavailableAsFailed;
 
     // Cache this type knowledge, to avoid repeated string comparison
     _typeMargin = (type == "margin");
+    _typeFan = (type == "fan");
 
     // Force value to be stored, otherwise member would be uninitialized
     updateValue(settings.value, true);
@@ -126,6 +131,19 @@
         }
     }
 
+    /*
+     * Unavailable thermal sensors, who are not present or
+     * power-state-not-matching, should not trigger the failSafe mode. For
+     * example, when a system stays at a powered-off state, its CPU Temp
+     * sensors will be unavailable, these unavailable sensors should not be
+     * treated as failed and trigger failSafe.
+     * This is important for systems whose Fans are always on.
+     */
+    if (!_typeFan && !_available && !_unavailableAsFailed)
+    {
+        return false;
+    }
+
     // If a reading has came in,
     // but its value bad in some way (determined by sensor type),
     // indicate this sensor has failed,
@@ -146,7 +164,7 @@
         return true;
     }
 
-    return _failed || !_functional;
+    return _failed || !_available || !_functional;
 }
 
 void DbusPassive::setFailed(bool value)
@@ -159,6 +177,11 @@
     _functional = value;
 }
 
+void DbusPassive::setAvailable(bool value)
+{
+    _available = value;
+}
+
 int64_t DbusPassive::getScale(void)
 {
     return _scale;
@@ -267,6 +290,24 @@
         }
         owner->setFailed(asserted);
     }
+    else if (msgSensor == "xyz.openbmc_project.State.Decorator.Availability")
+    {
+        auto available = msgData.find("Available");
+        if (available == msgData.end())
+        {
+            return 0;
+        }
+        bool asserted = std::get<bool>(available->second);
+        owner->setAvailable(asserted);
+        if (!asserted)
+        {
+            // A thermal controller will continue its PID calculation and not
+            // trigger a 'failsafe' when some inputs are unavailable.
+            // So, forced to clear the value here to prevent a historical
+            // value to participate in a latter PID calculation.
+            owner->updateValue(std::numeric_limits<double>::quiet_NaN(), true);
+        }
+    }
     else if (msgSensor ==
              "xyz.openbmc_project.State.Decorator.OperationalStatus")
     {
diff --git a/dbus/dbuspassive.hpp b/dbus/dbuspassive.hpp
index 346986b..bc16719 100644
--- a/dbus/dbuspassive.hpp
+++ b/dbus/dbuspassive.hpp
@@ -61,6 +61,7 @@
 
     void setFailed(bool value);
     void setFunctional(bool value);
+    void setAvailable(bool value);
 
     int64_t getScale(void);
     std::string getID(void);
@@ -79,8 +80,11 @@
     double _min = 0;
     bool _failed = false;
     bool _functional = true;
+    bool _available = true;
+    bool _unavailableAsFailed = true;
 
     bool _typeMargin = false;
+    bool _typeFan = false;
     bool _badReading = false;
     bool _marginHot = false;
 
diff --git a/sensors/buildjson.cpp b/sensors/buildjson.cpp
index c320fe1..b8120a8 100644
--- a/sensors/buildjson.cpp
+++ b/sensors/buildjson.cpp
@@ -49,6 +49,7 @@
      * sensors.
      */
     s.ignoreDbusMinMax = false;
+    s.unavailableAsFailed = true;
     s.min = 0;
     s.max = 0;
 
@@ -58,6 +59,12 @@
         j.at("ignoreDbusMinMax").get_to(s.ignoreDbusMinMax);
     }
 
+    auto findunAsF = j.find("unavailableAsFailed");
+    if (findunAsF != j.end())
+    {
+        j.at("unavailableAsFailed").get_to(s.unavailableAsFailed);
+    }
+
     /* The min field is optional in a configuration. */
     auto min = j.find("min");
     if (min != j.end())
diff --git a/test/dbus_passive_unittest.cpp b/test/dbus_passive_unittest.cpp
index e6f07a0..ce1f017 100644
--- a/test/dbus_passive_unittest.cpp
+++ b/test/dbus_passive_unittest.cpp
@@ -84,11 +84,13 @@
                     prop->unit = "x";
                     prop->min = 0;
                     prop->max = 0;
+                    prop->available = true;
                 }));
         EXPECT_CALL(*helper, thresholdsAsserted(StrEq("asdf"), StrEq(path)))
             .WillOnce(Return(false));
 
         auto info = conf::SensorConfig();
+        info.unavailableAsFailed = true;
         ri = DbusPassive::createDbusPassive(bus_mock, type, id,
                                             std::move(helper), &info, nullptr);
         passive = reinterpret_cast<DbusPassive*>(ri.get());
@@ -287,6 +289,7 @@
     ReadReturn r = passive->read();
     EXPECT_EQ(0.01, r.value);
 }
+
 TEST_F(DbusPassiveTestObj, VerifyCriticalThresholdAssert)
 {
 
@@ -436,6 +439,359 @@
     EXPECT_EQ(failed, false);
 }
 
+TEST_F(DbusPassiveTestObj, VerifyAvailableDeassert)
+{
+
+    // Verifies when Availble is deasserted && unavailableAsFailed == true,
+    // the sensor goes into error state
+    EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
+        .WillOnce(Return(nullptr));
+    sdbusplus::message::message msg(nullptr, &sdbus_mock);
+
+    const char* property = "Available";
+    bool asserted = false;
+    const char* intf = "xyz.openbmc_project.State.Decorator.Availability";
+
+    passive->setAvailable(true);
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            // Read the first parameter, the string.
+            *s = intf;
+            return 0;
+        }))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            *s = property;
+            // Read the string in the pair (dictionary).
+            return 0;
+        }));
+
+    // std::map
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
+        .WillOnce(Return(0));
+
+    // while !at_end()
+    EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
+        .WillOnce(Return(0))
+        .WillOnce(Return(1)); // So it exits the loop after reading one pair.
+
+    // std::pair
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(1));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            bool* s = static_cast<bool*>(p);
+            *s = asserted;
+            return 0;
+        }));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
+        .WillOnce(Return(0))  /* variant. */
+        .WillOnce(Return(0))  /* std::pair */
+        .WillOnce(Return(0)); /* std::map */
+
+    int rv = handleSensorValue(msg, passive);
+    EXPECT_EQ(rv, 0); // It's always 0.
+    bool failed = passive->getFailed();
+    EXPECT_EQ(failed, true);
+}
+
+TEST_F(DbusPassiveTestObj, VerifyAvailableAssert)
+{
+
+    // Verifies when Availble is asserted && unavailableAsFailed == true,
+    // an error sensor goes back to normal state
+    EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
+        .WillOnce(Return(nullptr));
+    sdbusplus::message::message msg(nullptr, &sdbus_mock);
+
+    const char* property = "Available";
+    bool asserted = true;
+    const char* intf = "xyz.openbmc_project.State.Decorator.Availability";
+
+    passive->setAvailable(false);
+    bool failed = passive->getFailed();
+    EXPECT_EQ(failed, true);
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            // Read the first parameter, the string.
+            *s = intf;
+            return 0;
+        }))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            *s = property;
+            // Read the string in the pair (dictionary).
+            return 0;
+        }));
+
+    // std::map
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
+        .WillOnce(Return(0));
+
+    // while !at_end()
+    EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
+        .WillOnce(Return(0))
+        .WillOnce(Return(1)); // So it exits the loop after reading one pair.
+
+    // std::pair
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(1));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            bool* s = static_cast<bool*>(p);
+            *s = asserted;
+            return 0;
+        }));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
+        .WillOnce(Return(0))  /* variant. */
+        .WillOnce(Return(0))  /* std::pair */
+        .WillOnce(Return(0)); /* std::map */
+
+    int rv = handleSensorValue(msg, passive);
+    EXPECT_EQ(rv, 0); // It's always 0.
+    failed = passive->getFailed();
+    EXPECT_EQ(failed, false);
+}
+
+class DbusPassiveTestUnaSensorNotAsFailedObj : public ::testing::Test
+{
+  protected:
+    DbusPassiveTestUnaSensorNotAsFailedObj() :
+        sdbus_mock(),
+        bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))),
+        helper(std::make_unique<DbusHelperMock>())
+    {
+        EXPECT_CALL(*helper, getService(StrEq(SensorIntf), StrEq(path)))
+            .WillOnce(Return("asdf"));
+
+        EXPECT_CALL(*helper,
+                    getProperties(StrEq("asdf"), StrEq(path), NotNull()))
+            .WillOnce(
+                Invoke([&](const std::string& service, const std::string& path,
+                           SensorProperties* prop) {
+                    prop->scale = _scale;
+                    prop->value = _value;
+                    prop->unit = "x";
+                    prop->min = 0;
+                    prop->max = 0;
+                    prop->available = true;
+                }));
+        EXPECT_CALL(*helper, thresholdsAsserted(StrEq("asdf"), StrEq(path)))
+            .WillOnce(Return(false));
+
+        auto info = conf::SensorConfig();
+        info.unavailableAsFailed = false;
+        ri = DbusPassive::createDbusPassive(bus_mock, type, id,
+                                            std::move(helper), &info, nullptr);
+        passive = reinterpret_cast<DbusPassive*>(ri.get());
+        EXPECT_FALSE(passive == nullptr);
+    }
+
+    sdbusplus::SdBusMock sdbus_mock;
+    sdbusplus::bus::bus bus_mock;
+    std::unique_ptr<DbusHelperMock> helper;
+    std::string type = "temp";
+    std::string id = "id";
+    std::string path = "/xyz/openbmc_project/sensors/temperature/id";
+    int64_t _scale = -3;
+    int64_t _value = 10;
+
+    std::unique_ptr<ReadInterface> ri;
+    DbusPassive* passive;
+};
+
+TEST_F(DbusPassiveTestUnaSensorNotAsFailedObj, VerifyAvailableDeassert)
+{
+
+    // Verifies when Availble is deasserted && unavailableAsFailed == false,
+    // the sensor remains at OK state but reading goes to NaN.
+    EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
+        .WillOnce(Return(nullptr));
+    sdbusplus::message::message msg(nullptr, &sdbus_mock);
+
+    const char* property = "Available";
+    bool asserted = false;
+    const char* intf = "xyz.openbmc_project.State.Decorator.Availability";
+
+    passive->setAvailable(true);
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            // Read the first parameter, the string.
+            *s = intf;
+            return 0;
+        }))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            *s = property;
+            // Read the string in the pair (dictionary).
+            return 0;
+        }));
+
+    // std::map
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
+        .WillOnce(Return(0));
+
+    // while !at_end()
+    EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
+        .WillOnce(Return(0))
+        .WillOnce(Return(1)); // So it exits the loop after reading one pair.
+
+    // std::pair
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(1));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            bool* s = static_cast<bool*>(p);
+            *s = asserted;
+            return 0;
+        }));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
+        .WillOnce(Return(0))  /* variant. */
+        .WillOnce(Return(0))  /* std::pair */
+        .WillOnce(Return(0)); /* std::map */
+
+    int rv = handleSensorValue(msg, passive);
+    EXPECT_EQ(rv, 0); // It's always 0.
+    bool failed = passive->getFailed();
+    EXPECT_EQ(failed, false);
+    ReadReturn r = passive->read();
+    EXPECT_FALSE(std::isfinite(r.value));
+}
+
+TEST_F(DbusPassiveTestUnaSensorNotAsFailedObj, VerifyAvailableAssert)
+{
+
+    // Verifies when a sensor's state goes from unavailble to available
+    // && unavailableAsFailed == false, this sensor remains at OK state.
+    EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
+        .WillOnce(Return(nullptr));
+    sdbusplus::message::message msg(nullptr, &sdbus_mock);
+
+    const char* property = "Available";
+    bool asserted = true;
+    const char* intf = "xyz.openbmc_project.State.Decorator.Availability";
+
+    passive->setAvailable(false);
+    bool failed = passive->getFailed();
+    EXPECT_EQ(failed, false);
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            // Read the first parameter, the string.
+            *s = intf;
+            return 0;
+        }))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            const char** s = static_cast<const char**>(p);
+            *s = property;
+            // Read the string in the pair (dictionary).
+            return 0;
+        }));
+
+    // std::map
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
+        .WillOnce(Return(0));
+
+    // while !at_end()
+    EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
+        .WillOnce(Return(0))
+        .WillOnce(Return(1)); // So it exits the loop after reading one pair.
+
+    // std::pair
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(1));
+    EXPECT_CALL(sdbus_mock,
+                sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
+        .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
+            bool* s = static_cast<bool*>(p);
+            *s = asserted;
+            return 0;
+        }));
+
+    EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
+        .WillOnce(Return(0))  /* variant. */
+        .WillOnce(Return(0))  /* std::pair */
+        .WillOnce(Return(0)); /* std::map */
+
+    int rv = handleSensorValue(msg, passive);
+    EXPECT_EQ(rv, 0); // It's always 0.
+    failed = passive->getFailed();
+    EXPECT_EQ(failed, false);
+}
+
 void GetPropertiesMax3k(const std::string& service, const std::string& path,
                         SensorProperties* prop)
 {
diff --git a/test/sensors_json_unittest.cpp b/test/sensors_json_unittest.cpp
index 7b3dbc3..1064d23 100644
--- a/test/sensors_json_unittest.cpp
+++ b/test/sensors_json_unittest.cpp
@@ -81,6 +81,32 @@
     EXPECT_EQ(output["fan1"].ignoreDbusMinMax, true);
 }
 
+TEST(SensorsFromJson, TempDbusSensor)
+{
+    auto j2 = R"(
+      {
+        "sensors": [{
+            "name": "CPU_DTS",
+            "type": "temp",
+            "readPath": "/xyz/openbmc_project/sensors/temperature/CPU_DTS",
+            "unavailableAsFailed": false
+        }]
+      }
+    )"_json;
+
+    auto output = buildSensorsFromJson(j2);
+    EXPECT_EQ(1, output.size());
+    EXPECT_EQ(output["CPU_DTS"].type, "temp");
+    EXPECT_EQ(output["CPU_DTS"].readPath,
+              "/xyz/openbmc_project/sensors/temperature/CPU_DTS");
+    EXPECT_EQ(output["CPU_DTS"].writePath, "");
+    EXPECT_EQ(output["CPU_DTS"].min, 0);
+    EXPECT_EQ(output["CPU_DTS"].max, 0);
+    EXPECT_EQ(output["CPU_DTS"].timeout,
+              Sensor::getDefaultTimeout(output["CPU_DTS"].type));
+    EXPECT_EQ(output["CPU_DTS"].unavailableAsFailed, false);
+}
+
 TEST(SensorsFromJson, validateOptionalFields)
 {
     // The writePath, min, max, timeout, and ignoreDbusMinMax fields are
@@ -107,6 +133,7 @@
     EXPECT_EQ(output["fan1"].timeout,
               Sensor::getDefaultTimeout(output["fan1"].type));
     EXPECT_EQ(output["fan1"].ignoreDbusMinMax, false);
+    EXPECT_EQ(output["fan1"].unavailableAsFailed, true);
 }
 
 TEST(SensorsFromJson, twoSensors)
diff --git a/util.cpp b/util.cpp
index 4ecd8ce..5a6336e 100644
--- a/util.cpp
+++ b/util.cpp
@@ -45,7 +45,8 @@
         std::cout << pair.second.writePath << ", ";
         std::cout << pair.second.min << ", ";
         std::cout << pair.second.max << ", ";
-        std::cout << pair.second.timeout << "},\n\t},\n";
+        std::cout << pair.second.timeout << ", ";
+        std::cout << pair.second.unavailableAsFailed << "},\n\t},\n";
     }
     std::cout << "}\n\n";
     std::cout << "ZoneDetailsConfig\n";