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