AllowedHostTransitions: look for on dbus

Commit [1] introduced a new optional dbus property that OpenBMC
developers can populate to define which
redfish/v1/Systems/system/ResetActionInfo AllowableValues are.

Look for that new property on dbus. If not found, hard code the
previous values otherwise utilize the property to fill in the return
value.

Tested:
- Put new property on dbus and confirmed Redfish API returned expected
  values:
```
curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/ResetActionInfo
{
  "@odata.id": "/redfish/v1/Systems/system/ResetActionInfo",
  "@odata.type": "#ActionInfo.v1_1_2.ActionInfo",
  "Id": "ResetActionInfo",
  "Name": "Reset Action Info",
  "Parameters": [
    {
      "AllowableValues": [
        "ForceOff",
        "PowerCycle",
        "Nmi",
        "On",
        "ForceOn",
        "ForceRestart",
        "GracefulRestart",
        "GracefulShutdown"
      ],
      "DataType": "String",
      "Name": "ResetType",
      "Required": true
    }
  ]
}
```
- Did not run redfish validator as response was same as previous

[1]: https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/68933

Change-Id: Iecece14e7ff55db98d96df71b106ecc9e3f0ac33
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
Signed-off-by: Gunnar Mills <gmills@us.ibm.com>
diff --git a/Redfish.md b/Redfish.md
index cdec1f9..93105eb 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -936,6 +936,14 @@
 - TotalThreads
 - Version
 
+### /redfish/v1/Systems/system/ResetActionInfo/
+
+#### ActionInfo
+
+- Parameters/AllowableValues
+- Parameters/DataType
+- Parameters/Required
+
 ### /redfish/v1/Systems/system/Storage/
 
 #### StorageCollection
diff --git a/meson.build b/meson.build
index f4976c1..65aa99a 100644
--- a/meson.build
+++ b/meson.build
@@ -455,6 +455,7 @@
   'test/redfish-core/include/utils/time_utils_test.cpp',
   'test/redfish-core/lib/chassis_test.cpp',
   'test/redfish-core/lib/sensors_test.cpp',
+  'test/redfish-core/lib/system_test.cpp',
   'test/redfish-core/lib/log_services_dump_test.cpp',
   'test/redfish-core/lib/log_services_test.cpp',
   'test/redfish-core/lib/update_service_test.cpp',
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 24a8752..37599e7 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -21,6 +21,7 @@
 #include "dbus_singleton.hpp"
 #include "dbus_utility.hpp"
 #include "generated/enums/computer_system.hpp"
+#include "generated/enums/resource.hpp"
 #include "health.hpp"
 #include "hypervisor_system.hpp"
 #include "led.hpp"
@@ -36,6 +37,7 @@
 #include <boost/asio/error.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/system/error_code.hpp>
+#include <boost/system/linux_error.hpp>
 #include <boost/url/format.hpp>
 #include <generated/enums/computer_system.hpp>
 #include <sdbusplus/asio/property.hpp>
@@ -43,6 +45,7 @@
 #include <sdbusplus/unpack_properties.hpp>
 
 #include <array>
+#include <memory>
 #include <string>
 #include <string_view>
 #include <variant>
@@ -3585,6 +3588,98 @@
         boost::beast::http::field::link,
         "</redfish/v1/JsonSchemas/ActionInfo/ActionInfo.json>; rel=describedby");
 }
+
+/**
+ * @brief Translates allowed host transitions to redfish string
+ *
+ * @param[in]  dbusAllowedHostTran The allowed host transition on dbus
+ * @param[out] allowableValues     The translated host transition(s)
+ *
+ * @return Emplaces correpsonding Redfish translated value(s) in
+ * allowableValues. If translation not possible, does nothing to
+ * allowableValues.
+ */
+inline void
+    dbusToRfAllowedHostTransitions(const std::string& dbusAllowedHostTran,
+                                   nlohmann::json::array_t& allowableValues)
+{
+    if (dbusAllowedHostTran == "xyz.openbmc_project.State.Host.Transition.On")
+    {
+        allowableValues.emplace_back(resource::ResetType::On);
+        allowableValues.emplace_back(resource::ResetType::ForceOn);
+    }
+    else if (dbusAllowedHostTran ==
+             "xyz.openbmc_project.State.Host.Transition.Off")
+    {
+        allowableValues.emplace_back(resource::ResetType::GracefulShutdown);
+    }
+    else if (dbusAllowedHostTran ==
+             "xyz.openbmc_project.State.Host.Transition.GracefulWarmReboot")
+    {
+        allowableValues.emplace_back(resource::ResetType::GracefulRestart);
+    }
+    else if (dbusAllowedHostTran ==
+             "xyz.openbmc_project.State.Host.Transition.ForceWarmReboot")
+    {
+        allowableValues.emplace_back(resource::ResetType::ForceRestart);
+    }
+    else
+    {
+        BMCWEB_LOG_WARNING("Unsupported host tran {}", dbusAllowedHostTran);
+    }
+}
+
+inline void afterGetAllowedHostTransitions(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const boost::system::error_code& ec,
+    const std::vector<std::string>& allowedHostTransitions)
+{
+    nlohmann::json::array_t allowableValues;
+
+    // Supported on all systems currently
+    allowableValues.emplace_back(resource::ResetType::ForceOff);
+    allowableValues.emplace_back(resource::ResetType::PowerCycle);
+    allowableValues.emplace_back(resource::ResetType::Nmi);
+
+    if (ec)
+    {
+        if (ec == boost::system::linux_error::bad_request_descriptor ||
+            ec == boost::asio::error::basic_errors::host_unreachable)
+        {
+            // Property not implemented so just return defaults
+            BMCWEB_LOG_DEBUG("Property not available {}", ec);
+            allowableValues.emplace_back(resource::ResetType::On);
+            allowableValues.emplace_back(resource::ResetType::ForceOn);
+            allowableValues.emplace_back(resource::ResetType::ForceRestart);
+            allowableValues.emplace_back(resource::ResetType::GracefulRestart);
+            allowableValues.emplace_back(resource::ResetType::GracefulShutdown);
+        }
+        else
+        {
+            BMCWEB_LOG_ERROR("DBUS response error {}", ec);
+            messages::internalError(asyncResp->res);
+            return;
+        }
+    }
+    else
+    {
+        for (const std::string& transition : allowedHostTransitions)
+        {
+            BMCWEB_LOG_DEBUG("Found allowed host tran {}", transition);
+            dbusToRfAllowedHostTransitions(transition, allowableValues);
+        }
+    }
+
+    nlohmann::json::object_t parameter;
+    parameter["Name"] = "ResetType";
+    parameter["Required"] = true;
+    parameter["DataType"] = "String";
+    parameter["AllowableValues"] = std::move(allowableValues);
+    nlohmann::json::array_t parameters;
+    parameters.emplace_back(std::move(parameter));
+    asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
+}
+
 inline void handleSystemCollectionResetActionGet(
     crow::App& app, const crow::Request& req,
     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
@@ -3625,25 +3720,15 @@
     asyncResp->res.jsonValue["Name"] = "Reset Action Info";
     asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
 
-    nlohmann::json::array_t parameters;
-    nlohmann::json::object_t parameter;
-
-    parameter["Name"] = "ResetType";
-    parameter["Required"] = true;
-    parameter["DataType"] = "String";
-    nlohmann::json::array_t allowableValues;
-    allowableValues.emplace_back("On");
-    allowableValues.emplace_back("ForceOff");
-    allowableValues.emplace_back("ForceOn");
-    allowableValues.emplace_back("ForceRestart");
-    allowableValues.emplace_back("GracefulRestart");
-    allowableValues.emplace_back("GracefulShutdown");
-    allowableValues.emplace_back("PowerCycle");
-    allowableValues.emplace_back("Nmi");
-    parameter["AllowableValues"] = std::move(allowableValues);
-    parameters.emplace_back(std::move(parameter));
-
-    asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
+    // Look to see if system defines AllowedHostTransitions
+    sdbusplus::asio::getProperty<std::vector<std::string>>(
+        *crow::connections::systemBus, "xyz.openbmc_project.State.Host",
+        "/xyz/openbmc_project/state/host0", "xyz.openbmc_project.State.Host",
+        "AllowedHostTransitions",
+        [asyncResp](const boost::system::error_code& ec,
+                    const std::vector<std::string>& allowedHostTransitions) {
+        afterGetAllowedHostTransitions(asyncResp, ec, allowedHostTransitions);
+    });
 }
 /**
  * SystemResetActionInfo derived class for delivering Computer Systems
diff --git a/test/redfish-core/lib/system_test.cpp b/test/redfish-core/lib/system_test.cpp
new file mode 100644
index 0000000..481c0df
--- /dev/null
+++ b/test/redfish-core/lib/system_test.cpp
@@ -0,0 +1,132 @@
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+#include "systems.hpp"
+
+#include <boost/beast/core/string_type.hpp>
+#include <boost/beast/http/message.hpp>
+#include <boost/system/error_code.hpp>
+#include <nlohmann/json.hpp>
+
+#include <memory>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+TEST(GetAllowedHostTransition, UnexpectedError)
+{
+    auto response = std::make_shared<bmcweb::AsyncResp>();
+    boost::system::error_code ec = boost::asio::error::invalid_argument;
+    std::vector<std::string> allowedHostTransitions;
+
+    afterGetAllowedHostTransitions(response, ec, allowedHostTransitions);
+
+    EXPECT_EQ(response->res.result(),
+              boost::beast::http::status::internal_server_error);
+}
+
+TEST(GetAllowedHostTransition, NoPropOnDbus)
+{
+    auto response = std::make_shared<bmcweb::AsyncResp>();
+    boost::system::error_code ec =
+        boost::system::linux_error::bad_request_descriptor;
+    std::vector<std::string> allowedHostTransitions;
+
+    afterGetAllowedHostTransitions(response, ec, allowedHostTransitions);
+
+    nlohmann::json::array_t parameters;
+    nlohmann::json::object_t parameter;
+    parameter["Name"] = "ResetType";
+    parameter["Required"] = true;
+    parameter["DataType"] = "String";
+    nlohmann::json::array_t allowed;
+    allowed.emplace_back(resource::ResetType::ForceOff);
+    allowed.emplace_back(resource::ResetType::PowerCycle);
+    allowed.emplace_back(resource::ResetType::Nmi);
+    allowed.emplace_back(resource::ResetType::On);
+    allowed.emplace_back(resource::ResetType::ForceOn);
+    allowed.emplace_back(resource::ResetType::ForceRestart);
+    allowed.emplace_back(resource::ResetType::GracefulRestart);
+    allowed.emplace_back(resource::ResetType::GracefulShutdown);
+    parameter["AllowableValues"] = std::move(allowed);
+    parameters.emplace_back(std::move(parameter));
+
+    EXPECT_EQ(response->res.jsonValue["Parameters"], parameters);
+}
+
+TEST(GetAllowedHostTransition, NoForceRestart)
+{
+    auto response = std::make_shared<bmcweb::AsyncResp>();
+    boost::system::error_code ec;
+
+    std::vector<std::string> allowedHostTransitions = {
+        "xyz.openbmc_project.State.Host.Transition.On",
+        "xyz.openbmc_project.State.Host.Transition.Off",
+        "xyz.openbmc_project.State.Host.Transition.GracefulWarmReboot",
+    };
+
+    afterGetAllowedHostTransitions(response, ec, allowedHostTransitions);
+
+    nlohmann::json::array_t parameters;
+    nlohmann::json::object_t parameter;
+    parameter["Name"] = "ResetType";
+    parameter["Required"] = true;
+    parameter["DataType"] = "String";
+    nlohmann::json::array_t allowed;
+    allowed.emplace_back(resource::ResetType::ForceOff);
+    allowed.emplace_back(resource::ResetType::PowerCycle);
+    allowed.emplace_back(resource::ResetType::Nmi);
+    allowed.emplace_back(resource::ResetType::On);
+    allowed.emplace_back(resource::ResetType::ForceOn);
+    allowed.emplace_back(resource::ResetType::GracefulShutdown);
+    allowed.emplace_back(resource::ResetType::GracefulRestart);
+    parameter["AllowableValues"] = std::move(allowed);
+    parameters.emplace_back(std::move(parameter));
+
+    EXPECT_EQ(response->res.jsonValue["Parameters"], parameters);
+}
+
+TEST(GetAllowedHostTransition, AllSupported)
+{
+    auto response = std::make_shared<bmcweb::AsyncResp>();
+    boost::system::error_code ec;
+
+    std::vector<std::string> allowedHostTransitions = {
+        "xyz.openbmc_project.State.Host.Transition.On",
+        "xyz.openbmc_project.State.Host.Transition.Off",
+        "xyz.openbmc_project.State.Host.Transition.GracefulWarmReboot",
+        "xyz.openbmc_project.State.Host.Transition.ForceWarmReboot",
+    };
+
+    afterGetAllowedHostTransitions(response, ec, allowedHostTransitions);
+
+    nlohmann::json::array_t parameters;
+    nlohmann::json::object_t parameter;
+    parameter["Name"] = "ResetType";
+    parameter["Required"] = true;
+    parameter["DataType"] = "String";
+    nlohmann::json::array_t allowed;
+    allowed.emplace_back(resource::ResetType::ForceOff);
+    allowed.emplace_back(resource::ResetType::PowerCycle);
+    allowed.emplace_back(resource::ResetType::Nmi);
+    allowed.emplace_back(resource::ResetType::On);
+    allowed.emplace_back(resource::ResetType::ForceOn);
+    allowed.emplace_back(resource::ResetType::GracefulShutdown);
+    allowed.emplace_back(resource::ResetType::GracefulRestart);
+    allowed.emplace_back(resource::ResetType::ForceRestart);
+    parameter["AllowableValues"] = std::move(allowed);
+    parameters.emplace_back(std::move(parameter));
+
+    EXPECT_EQ(response->res.jsonValue["Parameters"], parameters);
+}
+
+} // namespace
+} // namespace redfish