Add PID Get To Redfish

Add doGet to managers for PID configuration data.
Make sure passes schema validation.

Change-Id: Ieeb97bf76a3d8a3c06f59f79cc0887aec746675e
Signed-off-by: James Feist <james.feist@linux.intel.com>
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index ce21406..1fb2f69 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -17,31 +17,280 @@
 
 #include "node.hpp"
 
+#include <boost/algorithm/string/replace.hpp>
+#include <dbus_utility.hpp>
+
 namespace redfish
 {
+static constexpr const char* objectManagerIface =
+    "org.freedesktop.DBus.ObjectManager";
+static constexpr const char* pidConfigurationIface =
+    "xyz.openbmc_project.Configuration.Pid";
+static constexpr const char* pidZoneConfigurationIface =
+    "xyz.openbmc_project.Configuration.Pid.Zone";
 
-/**
- * DBus types primitives for several generic DBus interfaces
- * TODO consider move this to separate file into boost::dbus
- */
-using GetManagedObjectsType = boost::container::flat_map<
-    sdbusplus::message::object_path,
-    boost::container::flat_map<
-        std::string,
-        boost::container::flat_map<
-            std::string, sdbusplus::message::variant<
-                             std::string, bool, uint8_t, int16_t, uint16_t,
-                             int32_t, uint32_t, int64_t, uint64_t, double>>>>;
+static void asyncPopulatePid(const std::string& connection,
+                             const std::string& path,
+                             std::shared_ptr<AsyncResp> asyncResp)
+{
+
+    crow::connections::systemBus->async_method_call(
+        [asyncResp](const boost::system::error_code ec,
+                    const dbus::utility::ManagedObjectType& managedObj) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR << ec;
+                asyncResp->res.result(
+                    boost::beast::http::status::internal_server_error);
+                asyncResp->res.jsonValue.clear();
+                return;
+            }
+            nlohmann::json& configRoot =
+                asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
+            nlohmann::json& fans = configRoot["FanControllers"];
+            fans["@odata.type"] = "#OemManager.FanControllers";
+            fans["@odata.context"] =
+                "/redfish/v1/$metadata#OemManager.FanControllers";
+            fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
+                                "Fan/FanControllers";
+
+            nlohmann::json& pids = configRoot["PidControllers"];
+            pids["@odata.type"] = "#OemManager.PidControllers";
+            pids["@odata.context"] =
+                "/redfish/v1/$metadata#OemManager.PidControllers";
+            pids["@odata.id"] =
+                "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
+
+            nlohmann::json& zones = configRoot["FanZones"];
+            zones["@odata.id"] =
+                "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
+            zones["@odata.type"] = "#OemManager.FanZones";
+            zones["@odata.context"] =
+                "/redfish/v1/$metadata#OemManager.FanZones";
+            configRoot["@odata.id"] =
+                "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
+            configRoot["@odata.type"] = "#OemManager.Fan";
+            configRoot["@odata.context"] =
+                "/redfish/v1/$metadata#OemManager.Fan";
+
+            bool propertyError = false;
+            for (const auto& pathPair : managedObj)
+            {
+                for (const auto& intfPair : pathPair.second)
+                {
+                    if (intfPair.first != pidConfigurationIface &&
+                        intfPair.first != pidZoneConfigurationIface)
+                    {
+                        continue;
+                    }
+                    auto findName = intfPair.second.find("Name");
+                    if (findName == intfPair.second.end())
+                    {
+                        BMCWEB_LOG_ERROR << "Pid Field missing Name";
+                        asyncResp->res.result(
+                            boost::beast::http::status::internal_server_error);
+                        return;
+                    }
+                    const std::string* namePtr =
+                        mapbox::getPtr<const std::string>(findName->second);
+                    if (namePtr == nullptr)
+                    {
+                        BMCWEB_LOG_ERROR << "Pid Name Field illegal";
+                        return;
+                    }
+
+                    std::string name = *namePtr;
+                    dbus::utility::escapePathForDbus(name);
+                    if (intfPair.first == pidZoneConfigurationIface)
+                    {
+                        std::string chassis;
+                        if (!dbus::utility::getNthStringFromPath(
+                                pathPair.first.str, 5, chassis))
+                        {
+                            chassis = "#IllegalValue";
+                        }
+                        nlohmann::json& zone = zones[name];
+                        zone["Chassis"] = {
+                            {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
+                        zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
+                                            "OpenBmc/Fan/FanZones/" +
+                                            name;
+                        zone["@odata.type"] = "#OemManager.FanZone";
+                        zone["@odata.context"] =
+                            "/redfish/v1/$metadata#OemManager.FanZone";
+                    }
+
+                    for (const auto& propertyPair : intfPair.second)
+                    {
+                        if (propertyPair.first == "Type" ||
+                            propertyPair.first == "Class" ||
+                            propertyPair.first == "Name")
+                        {
+                            continue;
+                        }
+
+                        // zones
+                        if (intfPair.first == pidZoneConfigurationIface)
+                        {
+                            const double* ptr = mapbox::getPtr<const double>(
+                                propertyPair.second);
+                            if (ptr == nullptr)
+                            {
+                                BMCWEB_LOG_ERROR << "Field Illegal "
+                                                 << propertyPair.first;
+                                asyncResp->res.result(
+                                    boost::beast::http::status::
+                                        internal_server_error);
+                                return;
+                            }
+                            zones[name][propertyPair.first] = *ptr;
+                        }
+
+                        // pid and fans are off the same configuration
+                        if (intfPair.first == pidConfigurationIface)
+                        {
+                            const std::string* classPtr = nullptr;
+                            auto findClass = intfPair.second.find("Class");
+                            if (findClass != intfPair.second.end())
+                            {
+                                classPtr = mapbox::getPtr<const std::string>(
+                                    findClass->second);
+                            }
+                            if (classPtr == nullptr)
+                            {
+                                BMCWEB_LOG_ERROR << "Pid Class Field illegal";
+                                asyncResp->res.result(
+                                    boost::beast::http::status::
+                                        internal_server_error);
+                                return;
+                            }
+                            bool isFan = *classPtr == "fan";
+                            nlohmann::json& element =
+                                isFan ? fans[name] : pids[name];
+                            if (isFan)
+                            {
+                                element["@odata.id"] =
+                                    "/redfish/v1/Managers/bmc#/Oem/"
+                                    "OpenBmc/Fan/FanControllers/" +
+                                    std::string(name);
+                                element["@odata.type"] =
+                                    "#OemManager.FanController";
+
+                                element["@odata.context"] =
+                                    "/redfish/v1/"
+                                    "$metadata#OemManager.FanController";
+                            }
+                            else
+                            {
+                                element["@odata.id"] =
+                                    "/redfish/v1/Managers/bmc#/Oem/"
+                                    "OpenBmc/Fan/PidControllers/" +
+                                    std::string(name);
+                                element["@odata.type"] =
+                                    "#OemManager.PidController";
+                                element["@odata.context"] =
+                                    "/redfish/v1/$metadata"
+                                    "#OemManager.PidController";
+                            }
+
+                            if (propertyPair.first == "Zones")
+                            {
+                                const std::vector<std::string>* inputs =
+                                    mapbox::getPtr<
+                                        const std::vector<std::string>>(
+                                        propertyPair.second);
+
+                                if (inputs == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "Zones Pid Field Illegal";
+                                    asyncResp->res.result(
+                                        boost::beast::http::status::
+                                            internal_server_error);
+                                    return;
+                                }
+                                auto& data = element[propertyPair.first];
+                                data = nlohmann::json::array();
+                                for (std::string itemCopy : *inputs)
+                                {
+                                    dbus::utility::escapePathForDbus(itemCopy);
+                                    data.push_back(
+                                        {{"@odata.id",
+                                          "/redfish/v1/Managers/bmc#/Oem/"
+                                          "OpenBmc/Fan/FanZones/" +
+                                              itemCopy}});
+                                }
+                            }
+                            // todo(james): may never happen, but this
+                            // assumes configuration data referenced in the
+                            // PID config is provided by the same daemon, we
+                            // could add another loop to cover all cases,
+                            // but I'm okay kicking this can down the road a
+                            // bit
+
+                            else if (propertyPair.first == "Inputs" ||
+                                     propertyPair.first == "Outputs")
+                            {
+                                auto& data = element[propertyPair.first];
+                                const std::vector<std::string>* inputs =
+                                    mapbox::getPtr<
+                                        const std::vector<std::string>>(
+                                        propertyPair.second);
+
+                                if (inputs == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR << "Field Illegal "
+                                                     << propertyPair.first;
+                                    asyncResp->res.result(
+                                        boost::beast::http::status::
+                                            internal_server_error);
+                                    return;
+                                }
+                                data = *inputs;
+                            } // doubles
+                            else if (propertyPair.first ==
+                                         "FFGainCoefficient" ||
+                                     propertyPair.first == "FFOffCoefficient" ||
+                                     propertyPair.first == "ICoefficient" ||
+                                     propertyPair.first == "ILimitMax" ||
+                                     propertyPair.first == "ILimitMin" ||
+                                     propertyPair.first == "OutLimitMax" ||
+                                     propertyPair.first == "OutLimitMin" ||
+                                     propertyPair.first == "PCoefficient" ||
+                                     propertyPair.first == "SlewNeg" ||
+                                     propertyPair.first == "SlewPos")
+                            {
+                                const double* ptr =
+                                    mapbox::getPtr<const double>(
+                                        propertyPair.second);
+                                if (ptr == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR << "Field Illegal "
+                                                     << propertyPair.first;
+                                    asyncResp->res.result(
+                                        boost::beast::http::status::
+                                            internal_server_error);
+                                    return;
+                                }
+                                element[propertyPair.first] = *ptr;
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        connection, path, objectManagerIface, "GetManagedObjects");
+}
 
 class Manager : public Node
 {
   public:
-    Manager(CrowApp &app) : Node(app, "/redfish/v1/Managers/openbmc/")
+    Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
     {
-        Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc";
+        Node::json["@odata.id"] = "/redfish/v1/Managers/bmc";
         Node::json["@odata.type"] = "#Manager.v1_3_0.Manager";
         Node::json["@odata.context"] = "/redfish/v1/$metadata#Manager.Manager";
-        Node::json["Id"] = "openbmc";
+        Node::json["Id"] = "bmc";
         Node::json["Name"] = "OpenBmc Manager";
         Node::json["Description"] = "Baseboard Management Controller";
         Node::json["PowerState"] = "On";
@@ -51,7 +300,7 @@
                 .systemUuid;
         Node::json["Model"] = "OpenBmc"; // TODO(ed), get model
         Node::json["EthernetInterfaces"] = {
-            {"@odata.id", "/redfish/v1/Managers/openbmc/EthernetInterfaces"}};
+            {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
 
         entityPrivileges = {
             {boost::beast::http::verb::get, {{"Login"}}},
@@ -60,20 +309,89 @@
             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+
+        // default oem data
+        nlohmann::json& oem = Node::json["Oem"];
+        nlohmann::json& oemOpenbmc = oem["OpenBmc"];
+        oem["@odata.type"] = "#OemManager.Oem";
+        oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
+        oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
+        oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
+        oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
+        oemOpenbmc["@odata.context"] =
+            "/redfish/v1/$metadata#OemManager.OpenBmc";
     }
 
   private:
-    void doGet(crow::Response &res, const crow::Request &req,
-               const std::vector<std::string> &params) override
+    void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
+    {
+        crow::connections::systemBus->async_method_call(
+            [asyncResp](const boost::system::error_code ec,
+                        const crow::openbmc_mapper::GetSubTreeType& subtree) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << ec;
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+
+                // create map of <connection, path to objMgr>>
+                boost::container::flat_map<std::string, std::string>
+                    objectMgrPaths;
+                for (const auto& pathGroup : subtree)
+                {
+                    for (const auto& connectionGroup : pathGroup.second)
+                    {
+                        for (const std::string& interface :
+                             connectionGroup.second)
+                        {
+                            if (interface == objectManagerIface)
+                            {
+                                objectMgrPaths[connectionGroup.first] =
+                                    pathGroup.first;
+                            }
+                            // this list is alphabetical, so we
+                            // should have found the objMgr by now
+                            if (interface == pidConfigurationIface ||
+                                interface == pidZoneConfigurationIface)
+                            {
+                                auto findObjMgr =
+                                    objectMgrPaths.find(connectionGroup.first);
+                                if (findObjMgr == objectMgrPaths.end())
+                                {
+                                    BMCWEB_LOG_DEBUG << connectionGroup.first
+                                                     << "Has no Object Manager";
+                                    continue;
+                                }
+                                asyncPopulatePid(findObjMgr->first,
+                                                 findObjMgr->second, asyncResp);
+                                break;
+                            }
+                        }
+                    }
+                }
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
+            std::array<const char*, 3>{pidConfigurationIface,
+                                       pidZoneConfigurationIface,
+                                       objectManagerIface});
+    }
+
+    void doGet(crow::Response& res, const crow::Request& req,
+               const std::vector<std::string>& params) override
     {
         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
         asyncResp->res.jsonValue = Node::json;
 
         Node::json["DateTime"] = getDateTime();
         res.jsonValue = Node::json;
+
         crow::connections::systemBus->async_method_call(
             [asyncResp](const boost::system::error_code ec,
-                        const GetManagedObjectsType &resp) {
+                        const dbus::utility::ManagedObjectType& resp) {
                 if (ec)
                 {
                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
@@ -82,9 +400,9 @@
                     return;
                 }
 
-                for (auto &objpath : resp)
+                for (auto& objpath : resp)
                 {
-                    for (auto &interface : objpath.second)
+                    for (auto& interface : objpath.second)
                     {
                         // If interface is xyz.openbmc_project.Software.Version,
                         // this is what we're looking for.
@@ -92,12 +410,12 @@
                             "xyz.openbmc_project.Software.Version")
                         {
                             // Cut out everyting until last "/", ...
-                            const std::string &iface_id = objpath.first;
-                            for (auto &property : interface.second)
+                            const std::string& iface_id = objpath.first;
+                            for (auto& property : interface.second)
                             {
                                 if (property.first == "Version")
                                 {
-                                    const std::string *value =
+                                    const std::string* value =
                                         mapbox::getPtr<const std::string>(
                                             property.second);
                                     if (value == nullptr)
@@ -115,6 +433,12 @@
             "xyz.openbmc_project.Software.BMC.Updater",
             "/xyz/openbmc_project/software",
             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+        getPidValues(asyncResp);
+    }
+
+    void doPatch(crow::Response& res, const crow::Request& req,
+                 const std::vector<std::string>& params) override
+    {
     }
 
     std::string getDateTime() const
@@ -138,7 +462,7 @@
 class ManagerCollection : public Node
 {
   public:
-    ManagerCollection(CrowApp &app) : Node(app, "/redfish/v1/Managers/")
+    ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
     {
         Node::json["@odata.id"] = "/redfish/v1/Managers";
         Node::json["@odata.type"] = "#ManagerCollection.ManagerCollection";
@@ -146,8 +470,7 @@
             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
         Node::json["Name"] = "Manager Collection";
         Node::json["Members@odata.count"] = 1;
-        Node::json["Members"] = {
-            {{"@odata.id", "/redfish/v1/Managers/openbmc"}}};
+        Node::json["Members"] = {{{"@odata.id", "/redfish/v1/Managers/bmc"}}};
 
         entityPrivileges = {
             {boost::beast::http::verb::get, {{"Login"}}},
@@ -159,8 +482,8 @@
     }
 
   private:
-    void doGet(crow::Response &res, const crow::Request &req,
-               const std::vector<std::string> &params) override
+    void doGet(crow::Response& res, const crow::Request& req,
+               const std::vector<std::string>& params) override
     {
         // Collections don't include the static data added by SubRoute because
         // it has a duplicate entry for members
@@ -171,9 +494,8 @@
         res.jsonValue["Name"] = "Manager Collection";
         res.jsonValue["Members@odata.count"] = 1;
         res.jsonValue["Members"] = {
-            {{"@odata.id", "/redfish/v1/Managers/openbmc"}}};
+            {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
         res.end();
     }
 };
-
 } // namespace redfish