Verify PSU is present before code update

When an Activation is set to the Activating state, it checks all the
PSUs in the system to see which ones are compatible. Compatibility is
determined based on the Model and Manufacturer properties. It performs a
code update only on compatible PSUs.

Update the Activation class to also check whether a PSU is present. A
code update should not be attempted on a missing PSU. The attempt will
fail, and that will cause the remaining PSUs to be skipped.

Tested:
* Activation::startActivation()
  * Test where PSU is not present
    * Verify it is skipped
  * Test where PSU is present
    * Verify the compatibility is checked
      * Test where PSU is not compatible
        * Verify it is skipped
      * Test where PSU is compatible
        * Verify it is code updated
* Activation::isPresent()
  * Test where PSU is present
  * Test where PSU is not present
  * Test where a D-Bus error occurs
    * Verify error is written to the journal
    * Verify false is returned

Change-Id: I5647e51177d84d22f6ae5a38afa970243fe9ecdd
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/src/activation.cpp b/src/activation.cpp
index 400f03c..65db23b 100644
--- a/src/activation.cpp
+++ b/src/activation.cpp
@@ -176,6 +176,10 @@
 
     for (const auto& p : psuPaths)
     {
+        if (!isPresent(p))
+        {
+            continue;
+        }
         if (isCompatible(p))
         {
             if (utils::isAssociated(p, associations()))
@@ -293,6 +297,27 @@
     }
 }
 
+bool Activation::isPresent(const std::string& psuInventoryPath)
+{
+    bool isPres{false};
+    try
+    {
+        auto service =
+            utils::getService(bus, psuInventoryPath.c_str(), ITEM_IFACE);
+        isPres = utils::getProperty<bool>(bus, service.c_str(),
+                                          psuInventoryPath.c_str(), ITEM_IFACE,
+                                          PRESENT);
+    }
+    catch (const std::exception& e)
+    {
+        // Treat as a warning condition and assume the PSU is missing.  The
+        // D-Bus information might not be available if the PSU is missing.
+        lg2::warning("Unable to determine if PSU {PSU} is present: {ERROR}",
+                     "PSU", psuInventoryPath, "ERROR", e);
+    }
+    return isPres;
+}
+
 bool Activation::isCompatible(const std::string& psuInventoryPath)
 {
     bool isCompat{false};
diff --git a/src/activation.hpp b/src/activation.hpp
index 015f524..092fe3d 100644
--- a/src/activation.hpp
+++ b/src/activation.hpp
@@ -231,6 +231,9 @@
     /** @brief Finish PSU update */
     void finishActivation();
 
+    /** @brief Check if the PSU is present */
+    bool isPresent(const std::string& psuInventoryPath);
+
     /** @brief Check if the PSU is compatible with this software*/
     bool isCompatible(const std::string& psuInventoryPath);
 
diff --git a/test/test_activation.cpp b/test/test_activation.cpp
index 273f132..0af1b71 100644
--- a/test/test_activation.cpp
+++ b/test/test_activation.cpp
@@ -32,7 +32,11 @@
         mockedUtils(
             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
     {
-        // By default make it compatible with the test software
+        // By default make PSU present
+        ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
+            .WillByDefault(Return(any(PropertyType(true))));
+
+        // By default make PSU compatible with the test software
         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
             .WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
         ON_CALL(mockedUtils, getModel(_))
@@ -259,6 +263,21 @@
     EXPECT_EQ(Status::Failed, activation->activation());
 }
 
+TEST_F(TestActivation, doUpdateOnePSUNotPresent)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    activation = std::make_unique<Activation>(
+        mockedBus, dBusPath, versionId, extVersion, status, associations,
+        filePath, &mockedAssociationInterface, &mockedActivationListener);
+    ON_CALL(mockedUtils, getPSUInventoryPaths(_))
+        .WillByDefault(Return(std::vector<std::string>({psu0})));
+    EXPECT_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(false)))); // not present
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Ready, activation->activation());
+}
+
 TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible)
 {
     constexpr auto psu0 = "/com/example/inventory/psu0";
diff --git a/test/test_item_updater.cpp b/test/test_item_updater.cpp
index 02a8409..bf1fd9c 100644
--- a/test/test_item_updater.cpp
+++ b/test/test_item_updater.cpp
@@ -668,13 +668,13 @@
     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
         .WillByDefault(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
-        .WillOnce(Return(service))
         .WillOnce(Return(service));
     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
         .WillOnce(Return(std::string(version)));
-    ON_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath), _,
-                                         StrEq(PRESENT)))
-        .WillByDefault(Return(any(PropertyType(true)))); // present
+    EXPECT_CALL(mockedUtils,
+                getPropertyImpl(_, StrEq(service), StrEq(psuPath), _,
+                                StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
     EXPECT_CALL(mockedUtils, getModel(StrEq(psuPath)))
         .WillOnce(Return(std::string("dummyModel")));
 
@@ -692,6 +692,13 @@
 
     // On PSU inserted, it checks and finds a newer version
     auto oldVersion = "old-version";
+    EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
+        .WillOnce(Return(service))
+        .WillOnce(Return(service));
+    EXPECT_CALL(mockedUtils,
+                getPropertyImpl(_, StrEq(service), StrEq(psuPath), _,
+                                StrEq(PRESENT)))
+        .WillOnce(Return(any(PropertyType(true)))); // present
     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
         .WillOnce(Return(std::string(oldVersion)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),