Activation: Support to update multiple PSUs

Queue the update on multiple PSUs, and do the update one-by-one.

Tested: Write unit test cases and verify the cases pass.
        On witherspoon, verify the dummy update services are run on all
        PSUs.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Icfe0f6e74623e54840df8d731d852d53d6071768
diff --git a/src/activation.cpp b/src/activation.cpp
index 0ba7e64..f63352a 100644
--- a/src/activation.cpp
+++ b/src/activation.cpp
@@ -54,7 +54,7 @@
 {
     if (value == Status::Activating)
     {
-        startActivation();
+        value = startActivation();
     }
     else
     {
@@ -94,16 +94,67 @@
     {
         if (newStateResult == "done")
         {
-            finishActivation();
+            onUpdateDone();
         }
         if (newStateResult == "failed" || newStateResult == "dependency")
         {
-            activation(Status::Failed);
+            onUpdateFailed();
         }
     }
 }
 
-void Activation::startActivation()
+bool Activation::doUpdate(const std::string& psuInventoryPath)
+{
+    psuUpdateUnit = internal::getUpdateService(psuInventoryPath, versionId);
+    try
+    {
+        auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                          SYSTEMD_INTERFACE, "StartUnit");
+        method.append(psuUpdateUnit, "replace");
+        bus.call_noreply(method);
+        return true;
+    }
+    catch (const SdBusError& e)
+    {
+        log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
+        onUpdateFailed();
+        return false;
+    }
+}
+
+bool Activation::doUpdate()
+{
+    // When the queue is empty, all updates are done
+    if (psuQueue.empty())
+    {
+        finishActivation();
+        return true;
+    }
+
+    // Do the update on a PSU
+    const auto& psu = psuQueue.front();
+    return doUpdate(psu);
+}
+
+void Activation::onUpdateDone()
+{
+    auto progress = activationProgress->progress() + progressStep;
+    activationProgress->progress(progress);
+
+    psuQueue.pop();
+    doUpdate(); // Update the next psu
+}
+
+void Activation::onUpdateFailed()
+{
+    // TODO: report an event
+    log<level::ERR>("Failed to udpate PSU",
+                    entry("PSU=%s", psuQueue.front().c_str()));
+    std::queue<std::string>().swap(psuQueue); // Clear the queue
+    activation(Status::Failed);
+}
+
+Activation::Status Activation::startActivation()
 {
     if (!activationProgress)
     {
@@ -115,29 +166,40 @@
             std::make_unique<ActivationBlocksTransition>(bus, path);
     }
 
-    // TODO: for now only update one psu, future commits shall handle update
-    // multiple psus
     auto psuPaths = utils::getPSUInventoryPath(bus);
     if (psuPaths.empty())
     {
-        return;
+        log<level::WARNING>("No PSU inventory found");
+        return Status::Failed;
     }
 
-    psuUpdateUnit = internal::getUpdateService(psuPaths[0], versionId);
+    for (const auto& p : psuPaths)
+    {
+        psuQueue.push(p);
+    }
 
-    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
-                                      SYSTEMD_INTERFACE, "StartUnit");
-    method.append(psuUpdateUnit, "replace");
-    bus.call_noreply(method);
-
-    activationProgress->progress(10);
+    // The progress to be increased for each successful update of PSU
+    // E.g. in case we have 4 PSUs:
+    //   1. Initial progrss is 10
+    //   2. Add 20 after each update is done, so we will see progress to be 30,
+    //      50, 70, 90
+    //   3. When all PSUs are updated, it will be 100 and the interface is
+    //   removed.
+    progressStep = 80 / psuQueue.size();
+    if (doUpdate())
+    {
+        activationProgress->progress(10);
+        return Status::Activating;
+    }
+    else
+    {
+        return Status::Failed;
+    }
 }
 
 void Activation::finishActivation()
 {
     activationProgress->progress(100);
-    activationBlocksTransition.reset();
-    activationProgress.reset();
 
     // TODO: delete the old software object
     // TODO: create related associations