psutil: Add PSU update validation logic

This commit adds the PSUUpdateValidator class implementation which
validates whether it is safe to proceed with a PSU firmware update
based on current system state. It verifies the following:

- All present PSUs match the model of the target PSU
- Count the number of present PSUs in the system
- Ensure that the number of PSUs currently present, none faulty and
  all of the same model, is sufficient to meet the PSU requirement
  specified in the system configuration

This validator fetches PSU inventory paths, properties such as
'SupportedModel' and 'RedundantCount', and checks the PSU presence and
not faulty via D-Bus.

The method `validToUpdate()` encapsulates the overall logic, returning
true if all criteria for a safe update are met.

This class is designed to be integrated before triggering PSU updates
to ensure system safety.

Tested:
  - Ran psutils with --validate on powered-on system, verified update
    blocked
  - Ran psutils with simulated PSUs of mismatched models, verified
    update blocked
  - Ran with matching models and sufficient present PSUs, verified
    update succeeded

Change-Id: I367ef6d1b2cd66e8209f6b67a325de2b1a6da12a
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/tools/power-utils/main.cpp b/tools/power-utils/main.cpp
index 5adbb1b..ca8a6fe 100644
--- a/tools/power-utils/main.cpp
+++ b/tools/power-utils/main.cpp
@@ -16,6 +16,7 @@
 #include "model.hpp"
 #include "updater.hpp"
 #include "utility.hpp"
+#include "validator.hpp"
 #include "version.hpp"
 
 #include <CLI/CLI.hpp>
@@ -31,6 +32,7 @@
 {
     std::string psuPathVersion, psuPathModel;
     std::vector<std::string> versions;
+    bool validateUpdate = false;
     bool rawOutput = false;
     std::vector<std::string> updateArguments;
 
@@ -42,11 +44,17 @@
                        "Get PSU model from inventory path");
     action->add_option("-c,--compare", versions,
                        "Compare and get the latest version");
-    action
-        ->add_option("-u,--update", updateArguments,
-                     "Update PSU firmware, expecting two arguments: "
-                     "<PSU inventory path> <image-dir>")
-        ->expected(2);
+    auto updateOpt =
+        action
+            ->add_option("-u,--update", updateArguments,
+                         "Update PSU firmware, expecting two arguments: "
+                         "<PSU inventory path> <image-dir>")
+            ->expected(2)
+            ->type_name("PSU_PATH IMAGE_DIR");
+    app.add_flag(
+           "--validate", validateUpdate,
+           "Validate number of present PSU vs number of required PSUs and all PSUs have same model  before updating firmware")
+        ->needs(updateOpt);
     action->require_option(1); // Only one option is supported
     app.add_flag("--raw", rawOutput, "Output raw text without linefeed");
     CLI11_PARSE(app, argc, argv);
@@ -69,7 +77,18 @@
     if (!updateArguments.empty())
     {
         assert(updateArguments.size() == 2);
-        if (updater::update(bus, updateArguments[0], updateArguments[1]))
+        bool updateStatus = false;
+        if (validateUpdate)
+        {
+            updateStatus = updater::validateAndUpdate(bus, updateArguments[0],
+                                                      updateArguments[1]);
+        }
+        else
+        {
+            updateStatus =
+                updater::update(bus, updateArguments[0], updateArguments[1]);
+        }
+        if (updateStatus)
         {
             ret = "Update successful";
             lg2::info("Successful update to PSU: {PSU}", "PSU",