psu-ng: Missing power supply helper

The PowerSupply object will update a power supply to present when the
default value of missing (present=false) changes to present
(present=true). The PowerSupply objects do not know if a missing power
supply is required, so it does not mark power supplies as missing,
unless the present property changes from true to false.

Add a function to the PSUManager for getting required power supplies
that are missing marked as missing in the inventory.

We need the entity-manager configuration information, to know how many
power supplies are required.

Do not skip over power supplies that have no input voltage.

Read the D-Bus Present property to see if it was previously set. If no
read failure is encountered attempting to read that D-Bus property, and
the value returned does not match the value returned by the PowerSupply
object, and the local configuration checking model variable indicates it
is empty, then set the D-Bus Present property to false (missing). This
should fix Present true after it moved. Skip setting to false if
property read failed (never set to true or false).

Call updateMissingPSUs() in initialize() to handle when the process
starts up after entity-manager has already finished adding the
configuration information to D-Bus. Call updateMissingPSUs() after
entity-manager adds interface, possible configuration information.

Tested:
  Rainier 1S4U (simulated):
    Startup with bottom two power supplies present
    Verify presence after BMC_READY
      i2cdetect to scan bus, verify 0x6a and 0x6b.
      D-Bus get-property Present for each supply.
        powersupply0 - not set.
        powersupply1 - not set.
        powersupply2 - Present=true.
        powersupply3 - Present=true.
    Chassison
    Verify no errors for missing power supplies.
    Chassisoff
    Verify no power supply errors....
    Remove powersupply2.
    Reboot BMC (substitute for AC reset)
    Remove powersupply3 during reboot.
    Install powersupply0.
    Install powersupply1.
    Verify at BMC_READY
      No unexpected errors.
      No dumps.
      powersupply0 - Present=true, in bus scan.
      powersupply1 - Present=true, in bus scan.
      powersupply2 - Present=false, NOT in bus scan.
      powersupply3 - Present=false, NOT in bus scan.
    Chassison.
    Verify no errors, same presence and scan results.
    Chassisoff.
    Swap power supplies back
      Remove powersupply0
      Reboot BMC...
      Remove powersupply1
      Install powersupply2
      Install powersupply3
    Verify at BMC_READY
      No unexpected errors.
      No dumps.
      powersupply0 - Present=false, NOT in bus scan.
      powersupply1 - Present=false, NOT in bus scan.
      powersupply2 - Present=true, in bus scan.
      powersupply3 - Present=true, in bus scan.
    Chassison.
    Verify no errors, same presence and scan results.
    Remove powersupply2.
    Install powersupply0.
    Verify change.
      110015F6 with powersupply2 callout
      powersupply2 missing in scan and D-Bus property.
      powersupply0 present in scan and B-Bus property.
    Remove powersupply3.
    Install powersupply1.
    Verify change.
      110015F6 with powersupply3 callout
      powersupply3 missing in scan and D-Bus property.
      powersupply1 present in scan and B-Bus property.
    Remove powersupply0.
    Install powersupply2.
    Verify change.
      110015F6 with powersupply0 callout
      powersupply0 missing in scan and D-Bus property.
      powersupply2 present in scan and B-Bus property.
    Remove powersupply1.
    Install powersupply3.
    Verify change.
      110015F6 with powersupply1 callout
      powersupply1 missing in scan and D-Bus property.
      powersupply3 present in scan and B-Bus property.

  Rainier 2S4U (simulated):
     1. Startup Rainier 2S4U with one power supply.
     2. Verify 3 missing, 1 present, no errors at standby.
     3. Add in 3 missing supplies, verify presence.
     4. Reboot, verify presence, all four should be present.
     5. Chassison.
     6. Verify no errors.
     7. Remove, verify, error, insert, for each supply.
     8. Chassisoff.
     9. Verify no errors.
    10. Verify all four power supplies present.

  Rainier 2S2U (simulated):
     1. Startup Rainier 2S4U with one power supply.
     2. Verify psu0 missing, psu1 present, no errors at standby.
     3. Add in missing psu0, verify presence.
     4. Reboot, verify presence, both should be present (no extras)
     5. Verify all good, then Chassison.
     6. Verify no errors after chassison.
     7. Remove, verify, error, insert, for each supply.
     8. Chassisoff.
     9. Verify no errors.
    10. Verify both power supplies present.
    11. Remove psu1.
    12. Reboot, remove psu0, install psu1 (AC reset due to all remove,
        and install psu1).
    13. Check status...Verify psu0 missing, psu1 present.
    14. Chassison, verify 110015F6...
        # Hit other bug, powersupply2 and powersupply3 missing errors.
    15. Install psu0, verify...
    16. off/on/error check.

Change-Id: I4ee12c61e0ec9b105247b4ec40b0925e0af329f4
Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index efea9f4..13a35a3 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -331,6 +331,8 @@
             getPSUProperties(itIntf->second);
         }
 
+        updateMissingPSUs();
+
         // Call to validate the psu configuration if the power is on and both
         // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
         // processed
@@ -715,6 +717,77 @@
     }
 }
 
+void PSUManager::updateMissingPSUs()
+{
+    if (supportedConfigs.empty() || psus.empty())
+    {
+        return;
+    }
+
+    // Power supplies default to missing. If the power supply is present,
+    // the PowerSupply object will update the inventory Present property to
+    // true. If we have less than the required number of power supplies, and
+    // this power supply is missing, update the inventory Present property
+    // to false to indicate required power supply is missing. Avoid
+    // indicating power supply missing if not required.
+
+    auto presentCount =
+        std::count_if(psus.begin(), psus.end(),
+                      [](const auto& psu) { return psu->isPresent(); });
+
+    for (const auto& config : supportedConfigs)
+    {
+        for (const auto& psu : psus)
+        {
+            auto psuModel = psu->getModelName();
+            auto psuShortName = psu->getShortName();
+            auto psuInventoryPath = psu->getInventoryPath();
+            auto relativeInvPath =
+                psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+            auto psuPresent = psu->isPresent();
+            auto presProperty = false;
+            auto propReadFail = false;
+
+            try
+            {
+                presProperty = getPresence(bus, psuInventoryPath);
+                propReadFail = false;
+            }
+            catch (const sdbusplus::exception::exception& e)
+            {
+                propReadFail = true;
+                // Relying on property change or interface added to retry.
+                // Log an informational trace to the journal.
+                log<level::INFO>(
+                    fmt::format("D-Bus property {} access failure exception",
+                                psuInventoryPath)
+                        .c_str());
+            }
+
+            if (psuModel.empty())
+            {
+                if (!propReadFail && (presProperty != psuPresent))
+                {
+                    // We already have this property, and it is not false
+                    // set Present to false
+                    setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+                }
+                continue;
+            }
+
+            if (config.first != psuModel)
+            {
+                continue;
+            }
+
+            if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
+            {
+                setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+            }
+        }
+    }
+}
+
 void PSUManager::validateConfig()
 {
     if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
@@ -772,6 +845,7 @@
         {
             continue;
         }
+
         if (presentCount != config.second.powerSupplyCount)
         {
             tmpAdditionalData.clear();