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/include/dbus_utility.hpp b/include/dbus_utility.hpp
new file mode 100644
index 0000000..e527e90
--- /dev/null
+++ b/include/dbus_utility.hpp
@@ -0,0 +1,87 @@
+/*
+ // Copyright (c) 2018 Intel Corporation
+ //
+ // Licensed under the Apache License, Version 2.0 (the "License");
+ // you may not use this file except in compliance with the License.
+ // You may obtain a copy of the License at
+ //
+ //      http://www.apache.org/licenses/LICENSE-2.0
+ //
+ // Unless required by applicable law or agreed to in writing, software
+ // distributed under the License is distributed on an "AS IS" BASIS,
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ // See the License for the specific language governing permissions and
+ // limitations under the License.
+ */
+#pragma once
+
+#include <regex>
+#include <sdbusplus/message.hpp>
+
+namespace dbus
+{
+
+namespace utility
+{
+
+using DbusVariantType = sdbusplus::message::variant<
+    std::vector<std::tuple<std::string, std::string, std::string>>,
+    std::vector<std::string>, std::string, int64_t, uint64_t, double, int32_t,
+    uint32_t, int16_t, uint16_t, uint8_t, bool>;
+
+using ManagedObjectType = std::vector<
+    std::pair<sdbusplus::message::object_path,
+              boost::container::flat_map<
+                  std::string,
+                  boost::container::flat_map<std::string, DbusVariantType>>>>;
+
+inline void escapePathForDbus(std::string& path)
+{
+    const std::regex reg("[^A-Za-z0-9_/]");
+    std::regex_replace(path.begin(), path.begin(), path.end(), reg, "_");
+}
+
+// gets the string N strings deep into a path
+// i.e.  /0th/1st/2nd/3rd
+inline bool getNthStringFromPath(const std::string& path, int index,
+                                 std::string& result)
+{
+    int count = 0;
+    auto first = path.begin();
+    auto last = path.end();
+    for (auto it = path.begin(); it < path.end(); it++)
+    {
+        // skip first character as it's either a leading slash or the first
+        // character in the word
+        if (it == path.begin())
+        {
+            continue;
+        }
+        if (*it == '/')
+        {
+            count++;
+            if (count == index)
+            {
+                first = it;
+            }
+            if (count == index + 1)
+            {
+                last = it;
+                break;
+            }
+        }
+    }
+    if (count < index)
+    {
+        return false;
+    }
+    if (first != path.begin())
+    {
+        first++;
+    }
+    result = path.substr(first - path.begin(), last - first);
+    return true;
+}
+
+} // namespace utility
+} // namespace dbus
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 110e6ac..aa6c95f 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -1,5 +1,18 @@
-#pragma once
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
+#pragma once
 #include <crow/app.h>
 #include <tinyxml2.h>
 
@@ -7,6 +20,7 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/container/flat_set.hpp>
 #include <dbus_singleton.hpp>
+#include <dbus_utility.hpp>
 #include <experimental/filesystem>
 #include <fstream>
 
@@ -77,19 +91,6 @@
         "Introspect");
 }
 
-// A smattering of common types to unpack.  TODO(ed) this should really iterate
-// the sdbusplus object directly and build the json response
-using DbusRestVariantType = sdbusplus::message::variant<
-    std::vector<std::tuple<std::string, std::string, std::string>>, std::string,
-    int64_t, uint64_t, double, int32_t, uint32_t, int16_t, uint16_t, uint8_t,
-    bool>;
-
-using ManagedObjectType = std::vector<std::pair<
-    sdbusplus::message::object_path,
-    boost::container::flat_map<
-        std::string,
-        boost::container::flat_map<std::string, DbusRestVariantType>>>>;
-
 void getManagedObjectsForEnumerate(const std::string &object_name,
                                    const std::string &object_manager_path,
                                    const std::string &connection_name,
@@ -97,9 +98,8 @@
                                    std::shared_ptr<nlohmann::json> transaction)
 {
     crow::connections::systemBus->async_method_call(
-        [&res, transaction, object_name{std::string(object_name)}](
-            const boost::system::error_code ec,
-            const ManagedObjectType &objects) {
+        [&res, transaction](const boost::system::error_code ec,
+                            const dbus::utility::ManagedObjectType &objects) {
             if (ec)
             {
                 BMCWEB_LOG_ERROR << ec;
@@ -840,8 +840,8 @@
                     crow::connections::systemBus->async_method_call(
                         [&res, response, propertyName](
                             const boost::system::error_code ec,
-                            const std::vector<
-                                std::pair<std::string, DbusRestVariantType>>
+                            const std::vector<std::pair<
+                                std::string, dbus::utility::DbusVariantType>>
                                 &properties) {
                             if (ec)
                             {
@@ -850,8 +850,9 @@
                             }
                             else
                             {
-                                for (const std::pair<std::string,
-                                                     DbusRestVariantType>
+                                for (const std::pair<
+                                         std::string,
+                                         dbus::utility::DbusVariantType>
                                          &property : properties)
                                 {
                                     // if property name is empty, or matches our
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