Systems: Add PowerMode support

The computer system power mode defines the behavior of a system
based on the performance and power saving requirements.
For example, a system could be set to MaximumPerformance to
run as fast as possible without regard to power consumption.
A system could also be configured to run in PowerSaving mode
which would be running at slower speeds to try to save power.
More information can be found at https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/38786

This commit will allow GET / PATCH operations to the PowerMode property

PowerMode was added in Redfish Release 2021.1:
https://www.dmtf.org/content/redfish-release-20211-now-available

Tested: I manually tested retrieving and setting the PowerMode property
on a Rainier system (with good and bad values):
  # curl -k https://$bmc/redfish/v1/Systems/system
  {
    "@odata.id": "/redfish/v1/Systems/system",
    "@odata.type": "#ComputerSystem.v1_15_0.ComputerSystem
  ...
    "PartNumber": "",
    "PowerMode": "MaximumPerformance",
    "PowerMode@Redfish.AllowableValues": [
      "Static",
      "MaximumPerformance",
      "PowerSaving"
    ],
    "PowerRestorePolicy": "AlwaysOff",
  ...
  # curl -k https://$bmc/xyz/openbmc_project/control/host0/power_mode
  {
    "data": {
      "PowerMode": "xyz.openbmc_project.Control.Power.Mode.PowerMode.MaximumPerformance"
    },
    "message": "200 OK",
    "status": "ok"
  }
  # curl -k -X PATCH -d '{ "PowerMode":"Static"}' https://$bmc/redfish/v1/Systems/system
  curl -k https://$bmc/xyz/openbmc_project/control/host0/power_mode
  {
    "data": {
      "PowerMode": "xyz.openbmc_project.Control.Power.Mode.PowerMode.Static"
    },
    "message": "200 OK",
    "status": "ok"
  }
Ran Validator on hardware and all tests passed:
Elapsed time: 0:05:07
Counter({'skipOptional': 7128, 'pass': 6020, 'metadataNamespaces': 2217, 'passGet': 315, 'warnDeprecated': 212, 'serviceNamespaces': 79, 'warningPresent': 47, 'warnTrailingSlashLink': 24, 'invalidPropertyValue': 18, 'passAction': 14, 'optionalAction': 11, 'repeat': 3, 'unverifiedComplexAdditional': 1})
Validation has succeeded.

Signed-off-by: Chris Cain <cjcain@us.ibm.com>
Change-Id: I5523a0ebe4a2a77ea4709a14863bff7b55f0303d
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index ce7bd5b..3e7b4739 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -1763,6 +1763,263 @@
 #endif
 
 /**
+ * @brief Translate the PowerMode to a response message.
+ *
+ * @param[in] aResp  Shared pointer for generating response message.
+ * @param[in] modeValue  PowerMode value to be translated
+ *
+ * @return None.
+ */
+inline void translatePowerMode(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                               const std::string& modeValue)
+{
+    std::string modeString;
+
+    if (modeValue == "xyz.openbmc_project.Control.Power.Mode."
+                     "PowerMode.Static")
+    {
+        aResp->res.jsonValue["PowerMode"] = "Static";
+    }
+    else if (modeValue == "xyz.openbmc_project.Control.Power.Mode."
+                          "PowerMode.MaximumPerformance")
+    {
+        aResp->res.jsonValue["PowerMode"] = "MaximumPerformance";
+    }
+    else if (modeValue == "xyz.openbmc_project.Control.Power.Mode."
+                          "PowerMode.PowerSaving")
+    {
+        aResp->res.jsonValue["PowerMode"] = "PowerSaving";
+    }
+    else if (modeValue == "xyz.openbmc_project.Control.Power.Mode."
+                          "PowerMode.OEM")
+    {
+        aResp->res.jsonValue["PowerMode"] = "OEM";
+    }
+    else
+    {
+        // Any other values would be invalid
+        BMCWEB_LOG_DEBUG << "PowerMode value was not valid: " << modeValue;
+        messages::internalError(aResp->res);
+    }
+}
+
+/**
+ * @brief Retrieves system power mode
+ *
+ * @param[in] aResp  Shared pointer for generating response message.
+ *
+ * @return None.
+ */
+inline void getPowerMode(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
+{
+    BMCWEB_LOG_DEBUG << "Get power mode.";
+
+    // Get Power Mode object path:
+    crow::connections::systemBus->async_method_call(
+        [aResp](
+            const boost::system::error_code ec,
+            const std::vector<std::pair<
+                std::string,
+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
+                subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG
+                    << "DBUS response error on Power.Mode GetSubTree " << ec;
+                // This is an optional D-Bus object so just return if
+                // error occurs
+                return;
+            }
+            if (subtree.empty())
+            {
+                // As noted above, this is an optional interface so just return
+                // if there is no instance found
+                return;
+            }
+            if (subtree.size() > 1)
+            {
+                // More then one PowerMode object is not supported and is an
+                // error
+                BMCWEB_LOG_DEBUG
+                    << "Found more than 1 system D-Bus Power.Mode objects: "
+                    << subtree.size();
+                messages::internalError(aResp->res);
+                return;
+            }
+            if ((subtree[0].first.empty()) || (subtree[0].second.size() != 1))
+            {
+                BMCWEB_LOG_DEBUG << "Power.Mode mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+            const std::string& path = subtree[0].first;
+            const std::string& service = subtree[0].second.begin()->first;
+            if (service.empty())
+            {
+                BMCWEB_LOG_DEBUG << "Power.Mode service mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+            // Valid Power Mode object found, now read the current value
+            crow::connections::systemBus->async_method_call(
+                [aResp](const boost::system::error_code ec,
+                        const std::variant<std::string>& pmode) {
+                    if (ec)
+                    {
+                        BMCWEB_LOG_DEBUG
+                            << "DBUS response error on PowerMode Get: " << ec;
+                        messages::internalError(aResp->res);
+                        return;
+                    }
+
+                    const std::string* s = std::get_if<std::string>(&pmode);
+                    if (s == nullptr)
+                    {
+                        BMCWEB_LOG_DEBUG << "Unable to get PowerMode value";
+                        messages::internalError(aResp->res);
+                        return;
+                    }
+
+                    aResp->res.jsonValue["PowerMode@Redfish.AllowableValues"] =
+                        {"Static", "MaximumPerformance", "PowerSaving"};
+
+                    BMCWEB_LOG_DEBUG << "Current power mode: " << *s;
+                    translatePowerMode(aResp, *s);
+                },
+                service, path, "org.freedesktop.DBus.Properties", "Get",
+                "xyz.openbmc_project.Control.Power.Mode", "PowerMode");
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
+        std::array<const char*, 1>{"xyz.openbmc_project.Control.Power.Mode"});
+}
+
+/**
+ * @brief Validate the specified mode is valid and return the PowerMode
+ * name associated with that string
+ *
+ * @param[in] aResp   Shared pointer for generating response message.
+ * @param[in] modeString  String representing the desired PowerMode
+ *
+ * @return PowerMode value or empty string if mode is not valid
+ */
+inline std::string
+    validatePowerMode(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                      const std::string& modeString)
+{
+    std::string mode;
+
+    if (modeString == "Static")
+    {
+        mode = "xyz.openbmc_project.Control.Power.Mode.PowerMode.Static";
+    }
+    else if (modeString == "MaximumPerformance")
+    {
+        mode = "xyz.openbmc_project.Control.Power.Mode.PowerMode."
+               "MaximumPerformance";
+    }
+    else if (modeString == "PowerSaving")
+    {
+        mode = "xyz.openbmc_project.Control.Power.Mode.PowerMode.PowerSaving";
+    }
+    else
+    {
+        messages::propertyValueNotInList(aResp->res, modeString, "PowerMode");
+    }
+    return mode;
+}
+
+/**
+ * @brief Sets system power mode.
+ *
+ * @param[in] aResp   Shared pointer for generating response message.
+ * @param[in] pmode   System power mode from request.
+ *
+ * @return None.
+ */
+inline void setPowerMode(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                         const std::string& pmode)
+{
+    BMCWEB_LOG_DEBUG << "Set power mode.";
+
+    std::string powerMode = validatePowerMode(aResp, pmode);
+    if (powerMode.empty())
+    {
+        return;
+    }
+
+    // Get Power Mode object path:
+    crow::connections::systemBus->async_method_call(
+        [aResp, powerMode](
+            const boost::system::error_code ec,
+            const std::vector<std::pair<
+                std::string,
+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
+                subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG
+                    << "DBUS response error on Power.Mode GetSubTree " << ec;
+                // This is an optional D-Bus object, but user attempted to patch
+                messages::internalError(aResp->res);
+                return;
+            }
+            if (subtree.empty())
+            {
+                // This is an optional D-Bus object, but user attempted to patch
+                messages::resourceNotFound(aResp->res, "ComputerSystem",
+                                           "PowerMode");
+                return;
+            }
+            if (subtree.size() > 1)
+            {
+                // More then one PowerMode object is not supported and is an
+                // error
+                BMCWEB_LOG_DEBUG
+                    << "Found more than 1 system D-Bus Power.Mode objects: "
+                    << subtree.size();
+                messages::internalError(aResp->res);
+                return;
+            }
+            if ((subtree[0].first.empty()) || (subtree[0].second.size() != 1))
+            {
+                BMCWEB_LOG_DEBUG << "Power.Mode mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+            const std::string& path = subtree[0].first;
+            const std::string& service = subtree[0].second.begin()->first;
+            if (service.empty())
+            {
+                BMCWEB_LOG_DEBUG << "Power.Mode service mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+
+            BMCWEB_LOG_DEBUG << "Setting power mode(" << powerMode << ") -> "
+                             << path;
+
+            // Set the Power Mode property
+            crow::connections::systemBus->async_method_call(
+                [aResp](const boost::system::error_code ec) {
+                    if (ec)
+                    {
+                        messages::internalError(aResp->res);
+                        return;
+                    }
+                },
+                service, path, "org.freedesktop.DBus.Properties", "Set",
+                "xyz.openbmc_project.Control.Power.Mode", "PowerMode",
+                std::variant<std::string>(powerMode));
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
+        std::array<const char*, 1>{"xyz.openbmc_project.Control.Power.Mode"});
+}
+
+/**
  * @brief Translates watchdog timeout action DBUS property value to redfish.
  *
  * @param[in] dbusAction    The watchdog timeout action in D-BUS.
@@ -2170,7 +2427,7 @@
                 get)([](const crow::Request&,
                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
             asyncResp->res.jsonValue["@odata.type"] =
-                "#ComputerSystem.v1_14_0.ComputerSystem";
+                "#ComputerSystem.v1_15_0.ComputerSystem";
             asyncResp->res.jsonValue["Name"] = "system";
             asyncResp->res.jsonValue["Id"] = "system";
             asyncResp->res.jsonValue["SystemType"] = "Physical";
@@ -2282,6 +2539,7 @@
             getProvisioningStatus(asyncResp);
 #endif
             getTrustedModuleRequiredToBoot(asyncResp);
+            getPowerMode(asyncResp);
         });
     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/")
         .privileges({{"ConfigureComponent"}})
@@ -2294,13 +2552,14 @@
                 std::optional<nlohmann::json> wdtTimerProps;
                 std::optional<std::string> assetTag;
                 std::optional<std::string> powerRestorePolicy;
+                std::optional<std::string> powerMode;
 
                 if (!json_util::readJson(
                         req, asyncResp->res, "IndicatorLED", indicatorLed,
                         "LocationIndicatorActive", locationIndicatorActive,
                         "Boot", bootProps, "WatchdogTimer", wdtTimerProps,
                         "PowerRestorePolicy", powerRestorePolicy, "AssetTag",
-                        assetTag))
+                        assetTag, "PowerMode", powerMode))
                 {
                     return;
                 }
@@ -2373,6 +2632,11 @@
                 {
                     setPowerRestorePolicy(asyncResp, *powerRestorePolicy);
                 }
+
+                if (powerMode)
+                {
+                    setPowerMode(asyncResp, *powerMode);
+                }
             });
 }