1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/algorithm/string/replace.hpp>
21 #include <dbus_utility.hpp>
22 
23 namespace redfish
24 {
25 static constexpr const char* objectManagerIface =
26     "org.freedesktop.DBus.ObjectManager";
27 static constexpr const char* pidConfigurationIface =
28     "xyz.openbmc_project.Configuration.Pid";
29 static constexpr const char* pidZoneConfigurationIface =
30     "xyz.openbmc_project.Configuration.Pid.Zone";
31 
32 static void asyncPopulatePid(const std::string& connection,
33                              const std::string& path,
34                              std::shared_ptr<AsyncResp> asyncResp)
35 {
36 
37     crow::connections::systemBus->async_method_call(
38         [asyncResp](const boost::system::error_code ec,
39                     const dbus::utility::ManagedObjectType& managedObj) {
40             if (ec)
41             {
42                 BMCWEB_LOG_ERROR << ec;
43                 asyncResp->res.result(
44                     boost::beast::http::status::internal_server_error);
45                 asyncResp->res.jsonValue.clear();
46                 return;
47             }
48             nlohmann::json& configRoot =
49                 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"];
50             nlohmann::json& fans = configRoot["FanControllers"];
51             fans["@odata.type"] = "#OemManager.FanControllers";
52             fans["@odata.context"] =
53                 "/redfish/v1/$metadata#OemManager.FanControllers";
54             fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/"
55                                 "Fan/FanControllers";
56 
57             nlohmann::json& pids = configRoot["PidControllers"];
58             pids["@odata.type"] = "#OemManager.PidControllers";
59             pids["@odata.context"] =
60                 "/redfish/v1/$metadata#OemManager.PidControllers";
61             pids["@odata.id"] =
62                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers";
63 
64             nlohmann::json& zones = configRoot["FanZones"];
65             zones["@odata.id"] =
66                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones";
67             zones["@odata.type"] = "#OemManager.FanZones";
68             zones["@odata.context"] =
69                 "/redfish/v1/$metadata#OemManager.FanZones";
70             configRoot["@odata.id"] =
71                 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan";
72             configRoot["@odata.type"] = "#OemManager.Fan";
73             configRoot["@odata.context"] =
74                 "/redfish/v1/$metadata#OemManager.Fan";
75 
76             bool propertyError = false;
77             for (const auto& pathPair : managedObj)
78             {
79                 for (const auto& intfPair : pathPair.second)
80                 {
81                     if (intfPair.first != pidConfigurationIface &&
82                         intfPair.first != pidZoneConfigurationIface)
83                     {
84                         continue;
85                     }
86                     auto findName = intfPair.second.find("Name");
87                     if (findName == intfPair.second.end())
88                     {
89                         BMCWEB_LOG_ERROR << "Pid Field missing Name";
90                         asyncResp->res.result(
91                             boost::beast::http::status::internal_server_error);
92                         return;
93                     }
94                     const std::string* namePtr =
95                         mapbox::getPtr<const std::string>(findName->second);
96                     if (namePtr == nullptr)
97                     {
98                         BMCWEB_LOG_ERROR << "Pid Name Field illegal";
99                         return;
100                     }
101 
102                     std::string name = *namePtr;
103                     dbus::utility::escapePathForDbus(name);
104                     if (intfPair.first == pidZoneConfigurationIface)
105                     {
106                         std::string chassis;
107                         if (!dbus::utility::getNthStringFromPath(
108                                 pathPair.first.str, 5, chassis))
109                         {
110                             chassis = "#IllegalValue";
111                         }
112                         nlohmann::json& zone = zones[name];
113                         zone["Chassis"] = {
114                             {"@odata.id", "/redfish/v1/Chassis/" + chassis}};
115                         zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/"
116                                             "OpenBmc/Fan/FanZones/" +
117                                             name;
118                         zone["@odata.type"] = "#OemManager.FanZone";
119                         zone["@odata.context"] =
120                             "/redfish/v1/$metadata#OemManager.FanZone";
121                     }
122 
123                     for (const auto& propertyPair : intfPair.second)
124                     {
125                         if (propertyPair.first == "Type" ||
126                             propertyPair.first == "Class" ||
127                             propertyPair.first == "Name")
128                         {
129                             continue;
130                         }
131 
132                         // zones
133                         if (intfPair.first == pidZoneConfigurationIface)
134                         {
135                             const double* ptr = mapbox::getPtr<const double>(
136                                 propertyPair.second);
137                             if (ptr == nullptr)
138                             {
139                                 BMCWEB_LOG_ERROR << "Field Illegal "
140                                                  << propertyPair.first;
141                                 asyncResp->res.result(
142                                     boost::beast::http::status::
143                                         internal_server_error);
144                                 return;
145                             }
146                             zones[name][propertyPair.first] = *ptr;
147                         }
148 
149                         // pid and fans are off the same configuration
150                         if (intfPair.first == pidConfigurationIface)
151                         {
152                             const std::string* classPtr = nullptr;
153                             auto findClass = intfPair.second.find("Class");
154                             if (findClass != intfPair.second.end())
155                             {
156                                 classPtr = mapbox::getPtr<const std::string>(
157                                     findClass->second);
158                             }
159                             if (classPtr == nullptr)
160                             {
161                                 BMCWEB_LOG_ERROR << "Pid Class Field illegal";
162                                 asyncResp->res.result(
163                                     boost::beast::http::status::
164                                         internal_server_error);
165                                 return;
166                             }
167                             bool isFan = *classPtr == "fan";
168                             nlohmann::json& element =
169                                 isFan ? fans[name] : pids[name];
170                             if (isFan)
171                             {
172                                 element["@odata.id"] =
173                                     "/redfish/v1/Managers/bmc#/Oem/"
174                                     "OpenBmc/Fan/FanControllers/" +
175                                     std::string(name);
176                                 element["@odata.type"] =
177                                     "#OemManager.FanController";
178 
179                                 element["@odata.context"] =
180                                     "/redfish/v1/"
181                                     "$metadata#OemManager.FanController";
182                             }
183                             else
184                             {
185                                 element["@odata.id"] =
186                                     "/redfish/v1/Managers/bmc#/Oem/"
187                                     "OpenBmc/Fan/PidControllers/" +
188                                     std::string(name);
189                                 element["@odata.type"] =
190                                     "#OemManager.PidController";
191                                 element["@odata.context"] =
192                                     "/redfish/v1/$metadata"
193                                     "#OemManager.PidController";
194                             }
195 
196                             if (propertyPair.first == "Zones")
197                             {
198                                 const std::vector<std::string>* inputs =
199                                     mapbox::getPtr<
200                                         const std::vector<std::string>>(
201                                         propertyPair.second);
202 
203                                 if (inputs == nullptr)
204                                 {
205                                     BMCWEB_LOG_ERROR
206                                         << "Zones Pid Field Illegal";
207                                     asyncResp->res.result(
208                                         boost::beast::http::status::
209                                             internal_server_error);
210                                     return;
211                                 }
212                                 auto& data = element[propertyPair.first];
213                                 data = nlohmann::json::array();
214                                 for (std::string itemCopy : *inputs)
215                                 {
216                                     dbus::utility::escapePathForDbus(itemCopy);
217                                     data.push_back(
218                                         {{"@odata.id",
219                                           "/redfish/v1/Managers/bmc#/Oem/"
220                                           "OpenBmc/Fan/FanZones/" +
221                                               itemCopy}});
222                                 }
223                             }
224                             // todo(james): may never happen, but this
225                             // assumes configuration data referenced in the
226                             // PID config is provided by the same daemon, we
227                             // could add another loop to cover all cases,
228                             // but I'm okay kicking this can down the road a
229                             // bit
230 
231                             else if (propertyPair.first == "Inputs" ||
232                                      propertyPair.first == "Outputs")
233                             {
234                                 auto& data = element[propertyPair.first];
235                                 const std::vector<std::string>* inputs =
236                                     mapbox::getPtr<
237                                         const std::vector<std::string>>(
238                                         propertyPair.second);
239 
240                                 if (inputs == nullptr)
241                                 {
242                                     BMCWEB_LOG_ERROR << "Field Illegal "
243                                                      << propertyPair.first;
244                                     asyncResp->res.result(
245                                         boost::beast::http::status::
246                                             internal_server_error);
247                                     return;
248                                 }
249                                 data = *inputs;
250                             } // doubles
251                             else if (propertyPair.first ==
252                                          "FFGainCoefficient" ||
253                                      propertyPair.first == "FFOffCoefficient" ||
254                                      propertyPair.first == "ICoefficient" ||
255                                      propertyPair.first == "ILimitMax" ||
256                                      propertyPair.first == "ILimitMin" ||
257                                      propertyPair.first == "OutLimitMax" ||
258                                      propertyPair.first == "OutLimitMin" ||
259                                      propertyPair.first == "PCoefficient" ||
260                                      propertyPair.first == "SlewNeg" ||
261                                      propertyPair.first == "SlewPos")
262                             {
263                                 const double* ptr =
264                                     mapbox::getPtr<const double>(
265                                         propertyPair.second);
266                                 if (ptr == nullptr)
267                                 {
268                                     BMCWEB_LOG_ERROR << "Field Illegal "
269                                                      << propertyPair.first;
270                                     asyncResp->res.result(
271                                         boost::beast::http::status::
272                                             internal_server_error);
273                                     return;
274                                 }
275                                 element[propertyPair.first] = *ptr;
276                             }
277                         }
278                     }
279                 }
280             }
281         },
282         connection, path, objectManagerIface, "GetManagedObjects");
283 }
284 
285 class Manager : public Node
286 {
287   public:
288     Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/")
289     {
290         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc";
291         Node::json["@odata.type"] = "#Manager.v1_3_0.Manager";
292         Node::json["@odata.context"] = "/redfish/v1/$metadata#Manager.Manager";
293         Node::json["Id"] = "bmc";
294         Node::json["Name"] = "OpenBmc Manager";
295         Node::json["Description"] = "Baseboard Management Controller";
296         Node::json["PowerState"] = "On";
297         Node::json["ManagerType"] = "BMC";
298         Node::json["UUID"] =
299             app.template getMiddleware<crow::persistent_data::Middleware>()
300                 .systemUuid;
301         Node::json["Model"] = "OpenBmc"; // TODO(ed), get model
302         Node::json["EthernetInterfaces"] = {
303             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}};
304 
305         entityPrivileges = {
306             {boost::beast::http::verb::get, {{"Login"}}},
307             {boost::beast::http::verb::head, {{"Login"}}},
308             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
309             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
310             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
311             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
312 
313         // default oem data
314         nlohmann::json& oem = Node::json["Oem"];
315         nlohmann::json& oemOpenbmc = oem["OpenBmc"];
316         oem["@odata.type"] = "#OemManager.Oem";
317         oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem";
318         oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem";
319         oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc";
320         oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc";
321         oemOpenbmc["@odata.context"] =
322             "/redfish/v1/$metadata#OemManager.OpenBmc";
323     }
324 
325   private:
326     void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
327     {
328         crow::connections::systemBus->async_method_call(
329             [asyncResp](const boost::system::error_code ec,
330                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
331                 if (ec)
332                 {
333                     BMCWEB_LOG_ERROR << ec;
334                     asyncResp->res.result(
335                         boost::beast::http::status::internal_server_error);
336                     return;
337                 }
338 
339                 // create map of <connection, path to objMgr>>
340                 boost::container::flat_map<std::string, std::string>
341                     objectMgrPaths;
342                 for (const auto& pathGroup : subtree)
343                 {
344                     for (const auto& connectionGroup : pathGroup.second)
345                     {
346                         for (const std::string& interface :
347                              connectionGroup.second)
348                         {
349                             if (interface == objectManagerIface)
350                             {
351                                 objectMgrPaths[connectionGroup.first] =
352                                     pathGroup.first;
353                             }
354                             // this list is alphabetical, so we
355                             // should have found the objMgr by now
356                             if (interface == pidConfigurationIface ||
357                                 interface == pidZoneConfigurationIface)
358                             {
359                                 auto findObjMgr =
360                                     objectMgrPaths.find(connectionGroup.first);
361                                 if (findObjMgr == objectMgrPaths.end())
362                                 {
363                                     BMCWEB_LOG_DEBUG << connectionGroup.first
364                                                      << "Has no Object Manager";
365                                     continue;
366                                 }
367                                 asyncPopulatePid(findObjMgr->first,
368                                                  findObjMgr->second, asyncResp);
369                                 break;
370                             }
371                         }
372                     }
373                 }
374             },
375             "xyz.openbmc_project.ObjectMapper",
376             "/xyz/openbmc_project/object_mapper",
377             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
378             std::array<const char*, 3>{pidConfigurationIface,
379                                        pidZoneConfigurationIface,
380                                        objectManagerIface});
381     }
382 
383     void doGet(crow::Response& res, const crow::Request& req,
384                const std::vector<std::string>& params) override
385     {
386         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
387         asyncResp->res.jsonValue = Node::json;
388 
389         Node::json["DateTime"] = getDateTime();
390         res.jsonValue = Node::json;
391 
392         crow::connections::systemBus->async_method_call(
393             [asyncResp](const boost::system::error_code ec,
394                         const dbus::utility::ManagedObjectType& resp) {
395                 if (ec)
396                 {
397                     BMCWEB_LOG_ERROR << "Error while getting Software Version";
398                     asyncResp->res.result(
399                         boost::beast::http::status::internal_server_error);
400                     return;
401                 }
402 
403                 for (auto& objpath : resp)
404                 {
405                     for (auto& interface : objpath.second)
406                     {
407                         // If interface is xyz.openbmc_project.Software.Version,
408                         // this is what we're looking for.
409                         if (interface.first ==
410                             "xyz.openbmc_project.Software.Version")
411                         {
412                             // Cut out everyting until last "/", ...
413                             const std::string& iface_id = objpath.first;
414                             for (auto& property : interface.second)
415                             {
416                                 if (property.first == "Version")
417                                 {
418                                     const std::string* value =
419                                         mapbox::getPtr<const std::string>(
420                                             property.second);
421                                     if (value == nullptr)
422                                     {
423                                         continue;
424                                     }
425                                     asyncResp->res
426                                         .jsonValue["FirmwareVersion"] = *value;
427                                 }
428                             }
429                         }
430                     }
431                 }
432             },
433             "xyz.openbmc_project.Software.BMC.Updater",
434             "/xyz/openbmc_project/software",
435             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
436         getPidValues(asyncResp);
437     }
438 
439     void doPatch(crow::Response& res, const crow::Request& req,
440                  const std::vector<std::string>& params) override
441     {
442     }
443 
444     std::string getDateTime() const
445     {
446         std::array<char, 128> dateTime;
447         std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
448         std::time_t time = std::time(nullptr);
449 
450         if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
451                           std::localtime(&time)))
452         {
453             // insert the colon required by the ISO 8601 standard
454             redfishDateTime = std::string(dateTime.data());
455             redfishDateTime.insert(redfishDateTime.end() - 2, ':');
456         }
457 
458         return redfishDateTime;
459     }
460 };
461 
462 class ManagerCollection : public Node
463 {
464   public:
465     ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/")
466     {
467         Node::json["@odata.id"] = "/redfish/v1/Managers";
468         Node::json["@odata.type"] = "#ManagerCollection.ManagerCollection";
469         Node::json["@odata.context"] =
470             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
471         Node::json["Name"] = "Manager Collection";
472         Node::json["Members@odata.count"] = 1;
473         Node::json["Members"] = {{{"@odata.id", "/redfish/v1/Managers/bmc"}}};
474 
475         entityPrivileges = {
476             {boost::beast::http::verb::get, {{"Login"}}},
477             {boost::beast::http::verb::head, {{"Login"}}},
478             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
479             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
480             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
481             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
482     }
483 
484   private:
485     void doGet(crow::Response& res, const crow::Request& req,
486                const std::vector<std::string>& params) override
487     {
488         // Collections don't include the static data added by SubRoute because
489         // it has a duplicate entry for members
490         res.jsonValue["@odata.id"] = "/redfish/v1/Managers";
491         res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection";
492         res.jsonValue["@odata.context"] =
493             "/redfish/v1/$metadata#ManagerCollection.ManagerCollection";
494         res.jsonValue["Name"] = "Manager Collection";
495         res.jsonValue["Members@odata.count"] = 1;
496         res.jsonValue["Members"] = {
497             {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
498         res.end();
499     }
500 };
501 } // namespace redfish
502