CreateDump: Accept dump object path

This commit contains changes to accept the object path instead of the
dumpId for user-initiated dumps.

Earlier a task would be started to monitor the 'interfaces added' signal
when a new dump object is created. Now, this change checks if the
created dump object has implemented Progress interface, if yes, then the
'Status' property is used to track the dump creation progress. Else,
task will be marked completed.

With this change failed dumps will now be returning an internal error
back to the user, as a part of the task message.

The createDump dbus call has been changed in the backend to accept
additional arguments with the below commits:
[1] https://gerrit.openbmc-project.xyz/c/openbmc/phosphor-dbus-interfaces/+/36331
[2] https://gerrit.openbmc-project.xyz/c/openbmc/phosphor-debug-collector/+/37792
[3] https://gerrit.openbmc-project.xyz/c/openbmc/phosphor-dbus-interfaces/+/44380

This commit also bumps up the task timeout.
During system boot, the BMC dump takes more than 3 minutes to complete
and task expires with a timeout. This commit increases this timeout to
be 6 minutes.

Tested-By:

* POST https://${bmc}/redfish/v1/Managers/bmc/LogServices/Dump/Actions/
LogService.CollectDiagnosticData -d '{"DiagnosticDataType":"Manager"}'
=> A task will be returned as the redfish response

* GET https://${bmc}/redfish/v1/TaskService/Tasks/1
=> Get on the task URI to verify the task status for success and failure
scenarios

Signed-off-by: Asmitha Karunanithi <asmitk01@in.ibm.com>
Change-Id: I1686823a86eae836f770c19f33ffb21c98dd79ef
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index ec873b1..5e732fb 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -24,6 +24,7 @@
 #include "task.hpp"
 
 #include <systemd/sd-journal.h>
+#include <tinyxml2.h>
 #include <unistd.h>
 
 #include <app.hpp>
@@ -63,6 +64,13 @@
 constexpr char const* crashdumpTelemetryInterface =
     "com.intel.crashdump.Telemetry";
 
+enum class DumpCreationProgress
+{
+    DUMP_CREATE_SUCCESS,
+    DUMP_CREATE_FAILED,
+    DUMP_CREATE_INPROGRESS
+};
+
 namespace registries
 {
 static const Message*
@@ -658,53 +666,182 @@
         "xyz.openbmc_project.Object.Delete", "Delete");
 }
 
-inline void
-    createDumpTaskCallback(task::Payload&& payload,
-                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-                           const uint32_t& dumpId, const std::string& dumpPath,
-                           const std::string& dumpType)
+inline DumpCreationProgress
+    mapDbusStatusToDumpProgress(const std::string& status)
 {
-    std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
-        [dumpId, dumpPath,
-         dumpType](boost::system::error_code err, sdbusplus::message_t& m,
-                   const std::shared_ptr<task::TaskData>& taskData) {
-        if (err)
+    if (status ==
+            "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
+        status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
+    {
+        return DumpCreationProgress::DUMP_CREATE_FAILED;
+    }
+    if (status ==
+        "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
+    {
+        return DumpCreationProgress::DUMP_CREATE_SUCCESS;
+    }
+    return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
+}
+
+inline DumpCreationProgress
+    getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values)
+{
+    for (const auto& [key, val] : values)
+    {
+        if (key == "Status")
         {
-            BMCWEB_LOG_ERROR << "Error in creating a dump";
-            taskData->state = "Cancelled";
-            return task::completed;
+            const std::string* value = std::get_if<std::string>(&val);
+            if (value == nullptr)
+            {
+                BMCWEB_LOG_ERROR << "Status property value is null";
+                return DumpCreationProgress::DUMP_CREATE_FAILED;
+            }
+            return mapDbusStatusToDumpProgress(*value);
+        }
+    }
+    return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
+}
+
+inline std::string getDumpEntryPath(const std::string& dumpPath)
+{
+    if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry")
+    {
+        return "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
+    }
+    if (dumpPath == "/xyz/openbmc_project/dump/system/entry")
+    {
+        return "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
+    }
+    return "";
+}
+
+inline void createDumpTaskCallback(
+    task::Payload&& payload,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const sdbusplus::message::object_path& createdObjPath)
+{
+    const std::string dumpPath = createdObjPath.parent_path().str;
+    const std::string dumpId = createdObjPath.filename();
+
+    std::string dumpEntryPath = getDumpEntryPath(dumpPath);
+
+    if (dumpEntryPath.empty())
+    {
+        BMCWEB_LOG_ERROR << "Invalid dump type received";
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    crow::connections::systemBus->async_method_call(
+        [asyncResp, payload, createdObjPath,
+         dumpEntryPath{std::move(dumpEntryPath)},
+         dumpId](const boost::system::error_code ec,
+                 const std::string& introspectXml) {
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR << "Introspect call failed with error: "
+                             << ec.message();
+            messages::internalError(asyncResp->res);
+            return;
         }
 
-        dbus::utility::DBusInteracesMap interfacesList;
+        // Check if the created dump object has implemented Progress
+        // interface to track dump completion. If yes, fetch the "Status"
+        // property of the interface, modify the task state accordingly.
+        // Else, return task completed.
+        tinyxml2::XMLDocument doc;
 
-        sdbusplus::message::object_path objPath;
-
-        m.read(objPath, interfacesList);
-
-        if (objPath.str ==
-            "/xyz/openbmc_project/dump/" +
-                std::string(boost::algorithm::to_lower_copy(dumpType)) +
-                "/entry/" + std::to_string(dumpId))
+        doc.Parse(introspectXml.data(), introspectXml.size());
+        tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
+        if (pRoot == nullptr)
         {
+            BMCWEB_LOG_ERROR << "XML document failed to parse";
+            messages::internalError(asyncResp->res);
+            return;
+        }
+        tinyxml2::XMLElement* interfaceNode =
+            pRoot->FirstChildElement("interface");
+
+        bool isProgressIntfPresent = false;
+        while (interfaceNode != nullptr)
+        {
+            const char* thisInterfaceName = interfaceNode->Attribute("name");
+            if (thisInterfaceName != nullptr)
+            {
+                if (thisInterfaceName ==
+                    std::string_view("xyz.openbmc_project.Common.Progress"))
+                {
+                    interfaceNode =
+                        interfaceNode->NextSiblingElement("interface");
+                    continue;
+                }
+                isProgressIntfPresent = true;
+                break;
+            }
+            interfaceNode = interfaceNode->NextSiblingElement("interface");
+        }
+
+        std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
+            [createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent](
+                boost::system::error_code err, sdbusplus::message::message& msg,
+                const std::shared_ptr<task::TaskData>& taskData) {
+            if (err)
+            {
+                BMCWEB_LOG_ERROR << createdObjPath.str
+                                 << ": Error in creating dump";
+                taskData->messages.emplace_back(messages::internalError());
+                taskData->state = "Cancelled";
+                return task::completed;
+            }
+
+            if (isProgressIntfPresent)
+            {
+                dbus::utility::DBusPropertiesMap values;
+                std::string prop;
+                msg.read(prop, values);
+
+                DumpCreationProgress dumpStatus =
+                    getDumpCompletionStatus(values);
+                if (dumpStatus == DumpCreationProgress::DUMP_CREATE_FAILED)
+                {
+                    BMCWEB_LOG_ERROR << createdObjPath.str
+                                     << ": Error in creating dump";
+                    taskData->state = "Cancelled";
+                    return task::completed;
+                }
+
+                if (dumpStatus == DumpCreationProgress::DUMP_CREATE_INPROGRESS)
+                {
+                    BMCWEB_LOG_DEBUG << createdObjPath.str
+                                     << ": Dump creation task is in progress";
+                    return !task::completed;
+                }
+            }
+
             nlohmann::json retMessage = messages::success();
             taskData->messages.emplace_back(retMessage);
 
             std::string headerLoc =
-                "Location: " + dumpPath + std::to_string(dumpId);
+                "Location: " + dumpEntryPath + http_helpers::urlEncode(dumpId);
             taskData->payload->httpHeaders.emplace_back(std::move(headerLoc));
 
+            BMCWEB_LOG_DEBUG << createdObjPath.str
+                             << ": Dump creation task completed";
             taskData->state = "Completed";
             return task::completed;
-        }
-        return task::completed;
-        },
-        "type='signal',interface='org.freedesktop.DBus.ObjectManager',"
-        "member='InterfacesAdded', "
-        "path='/xyz/openbmc_project/dump'");
+            },
+            "type='signal',interface='org.freedesktop.DBus.Properties',"
+            "member='PropertiesChanged',path='" +
+                createdObjPath.str + "'");
 
-    task->startTimer(std::chrono::minutes(3));
-    task->populateResp(asyncResp->res);
-    task->payload.emplace(std::move(payload));
+        // The task timer is set to max time limit within which the
+        // requested dump will be collected.
+        task->startTimer(std::chrono::minutes(6));
+        task->populateResp(asyncResp->res);
+        task->payload.emplace(payload);
+        },
+        "xyz.openbmc_project.Dump.Manager", createdObjPath,
+        "org.freedesktop.DBus.Introspectable", "Introspect");
 }
 
 inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
@@ -773,11 +910,14 @@
         return;
     }
 
+    std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
+        createDumpParamVec;
+
     crow::connections::systemBus->async_method_call(
-        [asyncResp, payload(task::Payload(req)), dumpPath,
-         dumpType](const boost::system::error_code ec,
+        [asyncResp, payload(task::Payload(req)),
+         dumpPath](const boost::system::error_code ec,
                    const sdbusplus::message::message& msg,
-                   const uint32_t& dumpId) mutable {
+                   const sdbusplus::message::object_path& objPath) mutable {
         if (ec)
         {
             BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec;
@@ -821,15 +961,13 @@
             messages::internalError(asyncResp->res);
             return;
         }
-        BMCWEB_LOG_DEBUG << "Dump Created. Id: " << dumpId;
-
-        createDumpTaskCallback(std::move(payload), asyncResp, dumpId, dumpPath,
-                               dumpType);
+        BMCWEB_LOG_DEBUG << "Dump Created. Path: " << objPath.str;
+        createDumpTaskCallback(std::move(payload), asyncResp, objPath);
         },
         "xyz.openbmc_project.Dump.Manager",
         "/xyz/openbmc_project/dump/" +
             std::string(boost::algorithm::to_lower_copy(dumpType)),
-        "xyz.openbmc_project.Dump.Create", "CreateDump");
+        "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
 }
 
 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,