regulators: Treat missing VPD keyword as empty

Modify the VPD service to return an empty value if the specified VPD
interface or keyword does not exist on the specified object path.

This condition can occur when the system is operating normally and
should not result in an error being logged.

Tested:
* Verified an empty value is returned if the VPD interface does not
  exist on the object path.
* Verified an empty value is returned if the VPD keyword does not exist
  on the object path.
* Verified correct value is returned if the VPD interface and keyword
  exist
* See complete test plan at
  https://gist.github.com/smccarney/47b167706da49c7d78c4df818040ae72

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: Iaa7c24f4e00ee17506fd5acfd0145c733fa0d57b
diff --git a/phosphor-regulators/src/vpd.cpp b/phosphor-regulators/src/vpd.cpp
index 5f5454e..02cc0fc 100644
--- a/phosphor-regulators/src/vpd.cpp
+++ b/phosphor-regulators/src/vpd.cpp
@@ -34,32 +34,13 @@
     auto it = cachedKeywords.find(keyword);
     if (it != cachedKeywords.end())
     {
+        // Get keyword value from cache
         value = it->second;
     }
     else
     {
-        if (keyword == "HW")
-        {
-            // HW is a vector<uint8_t>, the others are a string.
-            util::getProperty("com.ibm.ipzvpd.VINI", "HW", inventoryPath,
-                              INVENTORY_MGR_IFACE, bus, value);
-        }
-        else
-        {
-            // Get keyword value from D-Bus interface/property.  The property
-            // name is normally the same as the VPD keyword name.  However, the
-            // CCIN keyword is stored in the Model property.
-            std::string property{(keyword == "CCIN") ? "Model" : keyword};
-            std::string stringValue;
-            util::getProperty(ASSET_IFACE, property, inventoryPath,
-                              INVENTORY_MGR_IFACE, bus, stringValue);
-
-            if (!stringValue.empty())
-            {
-                value.insert(value.begin(), stringValue.begin(),
-                             stringValue.end());
-            }
-        }
+        // Get keyword value from D-Bus interface/property
+        getDBusProperty(inventoryPath, keyword, value);
 
         // Cache keyword value
         cachedKeywords[keyword] = value;
@@ -68,4 +49,67 @@
     return value;
 }
 
+void DBusVPD::getDBusProperty(const std::string& inventoryPath,
+                              const std::string& keyword,
+                              std::vector<uint8_t>& value)
+{
+    // Determine the D-Bus property name.  Normally this is the same as the VPD
+    // keyword name.  However, the CCIN keyword is stored in the Model property.
+    std::string property{(keyword == "CCIN") ? "Model" : keyword};
+
+    value.clear();
+    try
+    {
+        if (property == "HW")
+        {
+            // HW property in non-standard interface and has byte vector value
+            util::getProperty("com.ibm.ipzvpd.VINI", property, inventoryPath,
+                              INVENTORY_MGR_IFACE, bus, value);
+        }
+        else
+        {
+            // Other properties in standard interface and have string value
+            std::string stringValue;
+            util::getProperty(ASSET_IFACE, property, inventoryPath,
+                              INVENTORY_MGR_IFACE, bus, stringValue);
+            value.insert(value.begin(), stringValue.begin(), stringValue.end());
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        // If exception indicates VPD interface or property doesn't exist
+        if (isUnknownPropertyException(e))
+        {
+            // Treat this as an empty keyword value
+            value.clear();
+        }
+        else
+        {
+            // Re-throw other exceptions
+            throw;
+        }
+    }
+}
+
+bool DBusVPD::isUnknownPropertyException(const sdbusplus::exception_t& e)
+{
+    // Initially assume exception was due to some other type of error
+    bool isUnknownProperty{false};
+
+    // If the D-Bus error name is set within the exception
+    if (e.name() != nullptr)
+    {
+        // Check if the error name indicates the specified interface or property
+        // does not exist on the specified object path
+        std::string name = e.name();
+        if ((name == SD_BUS_ERROR_UNKNOWN_INTERFACE) ||
+            (name == SD_BUS_ERROR_UNKNOWN_PROPERTY))
+        {
+            isUnknownProperty = true;
+        }
+    }
+
+    return isUnknownProperty;
+}
+
 } // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/vpd.hpp b/phosphor-regulators/src/vpd.hpp
index 4af3fa7..f2741d1 100644
--- a/phosphor-regulators/src/vpd.hpp
+++ b/phosphor-regulators/src/vpd.hpp
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
 
 #include <cstdint>
 #include <map>
@@ -102,6 +103,32 @@
 
   private:
     /**
+     * Gets the value of the specified VPD keyword from a D-Bus interface and
+     * property.
+     *
+     * Throws an exception if an error occurs while obtaining the VPD
+     * value.
+     *
+     * @param inventoryPath D-Bus inventory path of the hardware
+     * @param keyword VPD keyword
+     * @param value the resulting keyword value
+     */
+    void getDBusProperty(const std::string& inventoryPath,
+                         const std::string& keyword,
+                         std::vector<uint8_t>& value);
+
+    /**
+     * Returns whether the specified D-Bus exception indicates the VPD interface
+     * or property does not exist for the specified inventory path.
+     *
+     * This is treated as an "empty" keyword value rather than an error
+     * condition.
+     *
+     * @return true if exception indicates interface/property does not exist
+     */
+    bool isUnknownPropertyException(const sdbusplus::exception_t& e);
+
+    /**
      * Type alias for map from keyword names to values.
      */
     using KeywordMap = std::map<std::string, std::vector<uint8_t>>;