Create the Callout objects

If a new error log has the association interface, and the
inventory item it points to implements the Asset interface,
create a Callout object for it and persist it.

It will be persisted to a file like:
/var/lib/ibm-logging/errors/N/callouts/M where N is an error
log entry ID and M is the callout instance number.

Tested: Verify new D-Bus objects for callouts.

Change-Id: I90e9cf76edd7c2517de22cee64c3b979c482efa8
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/configure.ac b/configure.ac
index 574bd1a..2074692 100644
--- a/configure.ac
+++ b/configure.ac
@@ -76,6 +76,10 @@
           [The xyz logging busname])
 AC_DEFINE(IBM_LOGGING_BUSNAME, "com.ibm.Logging",
           [The IBM log manager DBus busname to own])
+AC_DEFINE(ASSOC_IFACE, "org.openbmc.Associations",
+          [The associations interface])
+AC_DEFINE(ASSET_IFACE, "xyz.openbmc_project.Inventory.Decorator.Asset",
+          [The asset interface])
 
 AC_DEFINE(DEFAULT_POLICY_EID, "None",
           [The default event ID to use])
@@ -86,6 +90,12 @@
 AS_IF([test "x$POLICY_JSON_PATH" == "x"], [POLICY_JSON_PATH="/usr/share/ibm-logging/policy.json"])
 AC_DEFINE_UNQUOTED([POLICY_JSON_PATH], ["$POLICY_JSON_PATH"], [The path to the policy json file on the BMC])
 
+AC_ARG_VAR(ERRLOG_PERSIST_PATH, [Path to save errors in])
+AS_IF([test "x$ERRLOG_PERSIST_PATH" == "x"], \
+    [ERRLOG_PERSIST_PATH="/var/lib/ibm-logging/errors"])
+AC_DEFINE_UNQUOTED([ERRLOG_PERSIST_PATH], ["$ERRLOG_PERSIST_PATH"], \
+    [Path to save errors in])
+
 AC_ARG_VAR(CALLOUT_CLASS_VERSION,
            [Callout class version to register with Cereal])
 AS_IF([test "x$CALLOUT_CLASS_VERSION" == "x"],
diff --git a/dbus.hpp b/dbus.hpp
index 392dd8a..c572d99 100644
--- a/dbus.hpp
+++ b/dbus.hpp
@@ -14,8 +14,16 @@
 using DbusProperty = std::string;
 using DbusService = std::string;
 using DbusPath = std::string;
+
+static constexpr auto forwardPos = 0;
+static constexpr auto reversePos = 1;
+static constexpr auto endpointPos = 2;
+using AssociationsPropertyType =
+    std::vector<std::tuple<std::string, std::string, std::string>>;
+
 using Value = sdbusplus::message::variant<bool, uint32_t, uint64_t, std::string,
-                                          std::vector<std::string>>;
+                                          std::vector<std::string>,
+                                          AssociationsPropertyType>;
 
 using DbusPropertyMap = std::map<DbusProperty, Value>;
 using DbusInterfaceMap = std::map<DbusInterface, DbusPropertyMap>;
diff --git a/manager.cpp b/manager.cpp
index a2c262d..44c1adf 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include "callout.hpp"
 #include "config.h"
 #include "manager.hpp"
 #include "policy_find.hpp"
@@ -22,6 +23,8 @@
 namespace logging
 {
 
+namespace fs = std::experimental::filesystem;
+
 Manager::Manager(sdbusplus::bus::bus& bus) :
     bus(bus),
     addMatch(bus,
@@ -73,8 +76,7 @@
 {
     createObject(objectPath, interfaces);
 
-    // TODO
-    // createCalloutObjects(objectPath, interfaces);
+    createCalloutObjects(objectPath, interfaces);
 }
 
 void Manager::createObject(const std::string& objectPath,
@@ -88,6 +90,7 @@
 
 void Manager::erase(EntryID id)
 {
+    fs::remove_all(getSaveDir(id));
     childEntries.erase(id);
     entries.erase(id);
 }
@@ -163,6 +166,77 @@
 }
 #endif
 
+void Manager::createCalloutObjects(const std::string& objectPath,
+                                   const DbusInterfaceMap& interfaces)
+{
+    // Use the associations property in the org.openbmc.Associations
+    // interface to find any callouts.  Then grab all properties on
+    // the Asset interface for that object in the inventory to use
+    // in our callout objects.
+
+    auto associations = interfaces.find(ASSOC_IFACE);
+    if (associations == interfaces.end())
+    {
+        return;
+    }
+
+    const auto& properties = associations->second;
+    auto assocProperty = properties.find("associations");
+    auto assocValue = assocProperty->second.get<AssociationsPropertyType>();
+
+    auto id = getEntryID(objectPath);
+    auto calloutNum = 0;
+    DbusSubtree subtree;
+
+    for (const auto& association : assocValue)
+    {
+        if (std::get<forwardPos>(association) != "callout")
+        {
+            continue;
+        }
+
+        auto callout = std::get<endpointPos>(association);
+
+        if (subtree.empty())
+        {
+            subtree = getSubtree(bus, "/", 0, ASSET_IFACE);
+            if (subtree.empty())
+            {
+                break;
+            }
+        }
+
+        auto service = getService(callout, ASSET_IFACE, subtree);
+        if (service.empty())
+        {
+            continue;
+        }
+
+        auto properties = getAllProperties(bus, service, callout, ASSET_IFACE);
+        if (properties.empty())
+        {
+            continue;
+        }
+
+        auto calloutPath = getCalloutObjectPath(objectPath, calloutNum);
+
+        auto object =
+            std::make_shared<Callout>(bus, calloutPath, callout, calloutNum,
+                                      getLogTimestamp(interfaces), properties);
+
+        auto dir = getCalloutSaveDir(id);
+        if (!fs::exists(dir))
+        {
+            fs::create_directories(dir);
+        }
+        object->serialize(dir);
+
+        std::experimental::any anyObject = object;
+        addChildInterface(objectPath, InterfaceType::CALLOUT, anyObject);
+        calloutNum++;
+    }
+}
+
 void Manager::interfaceAdded(sdbusplus::message::message& msg)
 {
     sdbusplus::message::object_path path;
@@ -178,6 +252,37 @@
     }
 }
 
+uint64_t Manager::getLogTimestamp(const DbusInterfaceMap& interfaces)
+{
+    auto interface = interfaces.find(LOGGING_IFACE);
+    if (interface != interfaces.end())
+    {
+        auto property = interface->second.find("Timestamp");
+        if (property != interface->second.end())
+        {
+            return property->second.get<uint64_t>();
+        }
+    }
+
+    return 0;
+}
+
+fs::path Manager::getSaveDir(EntryID id)
+{
+    return fs::path{ERRLOG_PERSIST_PATH} / std::to_string(id);
+}
+
+fs::path Manager::getCalloutSaveDir(EntryID id)
+{
+    return getSaveDir(id) / "callouts";
+}
+
+std::string Manager::getCalloutObjectPath(const std::string& objectPath,
+                                          uint32_t calloutNum)
+{
+    return fs::path{objectPath} / "callouts" / std::to_string(calloutNum);
+}
+
 void Manager::interfaceRemoved(sdbusplus::message::message& msg)
 {
     sdbusplus::message::object_path path;
diff --git a/manager.hpp b/manager.hpp
index 0c8a095..b84a4c7 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -124,6 +124,47 @@
                       const DbusInterfaceMap& interfaces);
 
     /**
+     * Returns the error log timestamp property value from
+     * the passed in map of all interfaces and property names/values
+     * on an error log D-Bus object.
+     *
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log.
+     *
+     * @return uint64_t - the timestamp
+     */
+    uint64_t getLogTimestamp(const DbusInterfaceMap& interfaces);
+
+    /**
+     * Returns the filesystem directory to use for persisting
+     * information about a particular error log.
+     *
+     * @param[in] id - the error log ID
+     * @return path - the directory path
+     */
+    std::experimental::filesystem::path getSaveDir(EntryID id);
+
+    /**
+     * Returns the directory to use to save the callout information in
+     *
+     * @param[in] id - the error log ID
+     *
+     * @return path - the directory path
+     */
+    std::experimental::filesystem::path getCalloutSaveDir(EntryID id);
+
+    /**
+     * Returns the D-Bus object path to use for a callout D-Bus object.
+     *
+     * @param[in] objectPath - the object path for the error log
+     * @param[in] calloutNum - the callout instance number
+     *
+     * @return path - the object path to use for a callout object
+     */
+    std::string getCalloutObjectPath(const std::string& objectPath,
+                                     uint32_t calloutNum);
+
+    /**
      * Creates the IBM policy interface for a single error log
      * and saves it in the list of interfaces.
      *
@@ -137,6 +178,25 @@
 #endif
 
     /**
+     * Creates D-Bus objects for any callouts in an error log
+     * that map to an inventory object with an Asset interface.
+     *
+     * The created object will also host the Asset interface.
+     *
+     * A callout object path would look like:
+     * /xyz/openbmc_project/logging/entry/5/callouts/0.
+     *
+     * Any objects created are serialized so the asset information
+     * can always be restored.
+     *
+     * @param[in] objectPath - object path of the error log
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log.
+     */
+    void createCalloutObjects(const std::string& objectPath,
+                              const DbusInterfaceMap& interfaces);
+
+    /**
      * Returns the entry ID for a log
      *
      * @param[in] objectPath - the object path of the log