xref: /openbmc/bmcweb/redfish-core/lib/chassis.hpp (revision c8ccb774)
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 "health.hpp"
19 #include "led.hpp"
20 #include "node.hpp"
21 
22 #include <boost/container/flat_map.hpp>
23 
24 #include <variant>
25 
26 namespace redfish
27 {
28 
29 /**
30  * @brief Retrieves chassis state properties over dbus
31  *
32  * @param[in] aResp - Shared pointer for completing asynchronous calls.
33  *
34  * @return None.
35  */
36 inline void getChassisState(std::shared_ptr<AsyncResp> aResp)
37 {
38     crow::connections::systemBus->async_method_call(
39         [aResp{std::move(aResp)}](
40             const boost::system::error_code ec,
41             const std::variant<std::string>& chassisState) {
42             if (ec)
43             {
44                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
45                 messages::internalError(aResp->res);
46                 return;
47             }
48 
49             const std::string* s = std::get_if<std::string>(&chassisState);
50             BMCWEB_LOG_DEBUG << "Chassis state: " << *s;
51             if (s != nullptr)
52             {
53                 // Verify Chassis State
54                 if (*s == "xyz.openbmc_project.State.Chassis.PowerState.On")
55                 {
56                     aResp->res.jsonValue["PowerState"] = "On";
57                     aResp->res.jsonValue["Status"]["State"] = "Enabled";
58                 }
59                 else if (*s ==
60                          "xyz.openbmc_project.State.Chassis.PowerState.Off")
61                 {
62                     aResp->res.jsonValue["PowerState"] = "Off";
63                     aResp->res.jsonValue["Status"]["State"] = "StandbyOffline";
64                 }
65             }
66         },
67         "xyz.openbmc_project.State.Chassis",
68         "/xyz/openbmc_project/state/chassis0",
69         "org.freedesktop.DBus.Properties", "Get",
70         "xyz.openbmc_project.State.Chassis", "CurrentPowerState");
71 }
72 
73 /**
74  * DBus types primitives for several generic DBus interfaces
75  * TODO(Pawel) consider move this to separate file into boost::dbus
76  */
77 // Note, this is not a very useful Variant, but because it isn't used to get
78 // values, it should be as simple as possible
79 // TODO(ed) invent a nullvariant type
80 using VariantType = std::variant<bool, std::string, uint64_t, uint32_t>;
81 using ManagedObjectsType = std::vector<std::pair<
82     sdbusplus::message::object_path,
83     std::vector<std::pair<std::string,
84                           std::vector<std::pair<std::string, VariantType>>>>>>;
85 
86 using PropertiesType = boost::container::flat_map<std::string, VariantType>;
87 
88 inline void getIntrusionByService(std::shared_ptr<AsyncResp> aResp,
89                                   const std::string& service,
90                                   const std::string& objPath)
91 {
92     BMCWEB_LOG_DEBUG << "Get intrusion status by service \n";
93 
94     crow::connections::systemBus->async_method_call(
95         [aResp{std::move(aResp)}](const boost::system::error_code ec,
96                                   const std::variant<std::string>& value) {
97             if (ec)
98             {
99                 // do not add err msg in redfish response, because this is not
100                 //     mandatory property
101                 BMCWEB_LOG_ERROR << "DBUS response error " << ec << "\n";
102                 return;
103             }
104 
105             const std::string* status = std::get_if<std::string>(&value);
106 
107             if (status == nullptr)
108             {
109                 BMCWEB_LOG_ERROR << "intrusion status read error \n";
110                 return;
111             }
112 
113             aResp->res.jsonValue["PhysicalSecurity"] = {
114                 {"IntrusionSensorNumber", 1}, {"IntrusionSensor", *status}};
115         },
116         service, objPath, "org.freedesktop.DBus.Properties", "Get",
117         "xyz.openbmc_project.Chassis.Intrusion", "Status");
118 }
119 
120 /**
121  * Retrieves physical security properties over dbus
122  */
123 inline void getPhysicalSecurityData(std::shared_ptr<AsyncResp> aResp)
124 {
125     crow::connections::systemBus->async_method_call(
126         [aResp{std::move(aResp)}](
127             const boost::system::error_code ec,
128             const std::vector<std::pair<
129                 std::string,
130                 std::vector<std::pair<std::string, std::vector<std::string>>>>>&
131                 subtree) {
132             if (ec)
133             {
134                 // do not add err msg in redfish response, because this is not
135                 //     mandatory property
136                 BMCWEB_LOG_ERROR << "DBUS error: no matched iface " << ec
137                                  << "\n";
138                 return;
139             }
140             // Iterate over all retrieved ObjectPaths.
141             for (const auto& object : subtree)
142             {
143                 for (const auto& service : object.second)
144                 {
145                     getIntrusionByService(aResp, service.first, object.first);
146                     return;
147                 }
148             }
149         },
150         "xyz.openbmc_project.ObjectMapper",
151         "/xyz/openbmc_project/object_mapper",
152         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
153         "/xyz/openbmc_project/Intrusion", 1,
154         std::array<const char*, 1>{"xyz.openbmc_project.Chassis.Intrusion"});
155 }
156 
157 /**
158  * ChassisCollection derived class for delivering Chassis Collection Schema
159  */
160 class ChassisCollection : public Node
161 {
162   public:
163     ChassisCollection(App& app) : Node(app, "/redfish/v1/Chassis/")
164     {
165         entityPrivileges = {
166             {boost::beast::http::verb::get, {{"Login"}}},
167             {boost::beast::http::verb::head, {{"Login"}}},
168             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
169             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
170             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
171             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
172     }
173 
174   private:
175     /**
176      * Functions triggers appropriate requests on DBus
177      */
178     void doGet(crow::Response& res, const crow::Request&,
179                const std::vector<std::string>&) override
180     {
181         res.jsonValue["@odata.type"] = "#ChassisCollection.ChassisCollection";
182         res.jsonValue["@odata.id"] = "/redfish/v1/Chassis";
183         res.jsonValue["Name"] = "Chassis Collection";
184 
185         const std::array<const char*, 2> interfaces = {
186             "xyz.openbmc_project.Inventory.Item.Board",
187             "xyz.openbmc_project.Inventory.Item.Chassis"};
188 
189         auto asyncResp = std::make_shared<AsyncResp>(res);
190         crow::connections::systemBus->async_method_call(
191             [asyncResp](const boost::system::error_code ec,
192                         const std::vector<std::string>& chassisList) {
193                 if (ec)
194                 {
195                     messages::internalError(asyncResp->res);
196                     return;
197                 }
198                 nlohmann::json& chassisArray =
199                     asyncResp->res.jsonValue["Members"];
200                 chassisArray = nlohmann::json::array();
201                 for (const std::string& objpath : chassisList)
202                 {
203                     std::size_t lastPos = objpath.rfind("/");
204                     if (lastPos == std::string::npos)
205                     {
206                         BMCWEB_LOG_ERROR << "Failed to find '/' in " << objpath;
207                         messages::internalError(asyncResp->res);
208                         return;
209                     }
210                     if ((lastPos + 1) >= objpath.size())
211                     {
212                         BMCWEB_LOG_ERROR << "Failed to parse path " << objpath;
213                         messages::internalError(asyncResp->res);
214                         return;
215                     }
216                     chassisArray.push_back(
217                         {{"@odata.id", "/redfish/v1/Chassis/" +
218                                            objpath.substr(lastPos + 1)}});
219                 }
220 
221                 asyncResp->res.jsonValue["Members@odata.count"] =
222                     chassisArray.size();
223             },
224             "xyz.openbmc_project.ObjectMapper",
225             "/xyz/openbmc_project/object_mapper",
226             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
227             "/xyz/openbmc_project/inventory", 0, interfaces);
228     }
229 };
230 
231 /**
232  * Chassis override class for delivering Chassis Schema
233  */
234 class Chassis : public Node
235 {
236   public:
237     Chassis(App& app) : Node(app, "/redfish/v1/Chassis/<str>/", std::string())
238     {
239         entityPrivileges = {
240             {boost::beast::http::verb::get, {{"Login"}}},
241             {boost::beast::http::verb::head, {{"Login"}}},
242             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
243             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
244             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
245             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
246     }
247 
248   private:
249     /**
250      * Functions triggers appropriate requests on DBus
251      */
252     void doGet(crow::Response& res, const crow::Request&,
253                const std::vector<std::string>& params) override
254     {
255         const std::array<const char*, 2> interfaces = {
256             "xyz.openbmc_project.Inventory.Item.Board",
257             "xyz.openbmc_project.Inventory.Item.Chassis"};
258 
259         // Check if there is required param, truly entering this shall be
260         // impossible.
261         if (params.size() != 1)
262         {
263             messages::internalError(res);
264             res.end();
265             return;
266         }
267         const std::string& chassisId = params[0];
268 
269         auto asyncResp = std::make_shared<AsyncResp>(res);
270         crow::connections::systemBus->async_method_call(
271             [asyncResp, chassisId(std::string(chassisId))](
272                 const boost::system::error_code ec,
273                 const crow::openbmc_mapper::GetSubTreeType& subtree) {
274                 if (ec)
275                 {
276                     messages::internalError(asyncResp->res);
277                     return;
278                 }
279                 // Iterate over all retrieved ObjectPaths.
280                 for (const std::pair<
281                          std::string,
282                          std::vector<
283                              std::pair<std::string, std::vector<std::string>>>>&
284                          object : subtree)
285                 {
286                     const std::string& path = object.first;
287                     const std::vector<
288                         std::pair<std::string, std::vector<std::string>>>&
289                         connectionNames = object.second;
290 
291                     if (!boost::ends_with(path, chassisId))
292                     {
293                         continue;
294                     }
295 
296                     auto health = std::make_shared<HealthPopulate>(asyncResp);
297 
298                     crow::connections::systemBus->async_method_call(
299                         [health](const boost::system::error_code ec2,
300                                  std::variant<std::vector<std::string>>& resp) {
301                             if (ec2)
302                             {
303                                 return; // no sensors = no failures
304                             }
305                             std::vector<std::string>* data =
306                                 std::get_if<std::vector<std::string>>(&resp);
307                             if (data == nullptr)
308                             {
309                                 return;
310                             }
311                             health->inventory = std::move(*data);
312                         },
313                         "xyz.openbmc_project.ObjectMapper",
314                         path + "/all_sensors",
315                         "org.freedesktop.DBus.Properties", "Get",
316                         "xyz.openbmc_project.Association", "endpoints");
317 
318                     health->populate();
319 
320                     if (connectionNames.size() < 1)
321                     {
322                         BMCWEB_LOG_ERROR << "Got 0 Connection names";
323                         continue;
324                     }
325 
326                     asyncResp->res.jsonValue["@odata.type"] =
327                         "#Chassis.v1_10_0.Chassis";
328                     asyncResp->res.jsonValue["@odata.id"] =
329                         "/redfish/v1/Chassis/" + chassisId;
330                     asyncResp->res.jsonValue["Name"] = "Chassis Collection";
331                     asyncResp->res.jsonValue["ChassisType"] = "RackMount";
332                     asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"] = {
333                         {"target", "/redfish/v1/Chassis/" + chassisId +
334                                        "/Actions/Chassis.Reset"},
335                         {"@Redfish.ActionInfo", "/redfish/v1/Chassis/" +
336                                                     chassisId +
337                                                     "/ResetActionInfo"}};
338                     asyncResp->res.jsonValue["PCIeDevices"] = {
339                         {"@odata.id",
340                          "/redfish/v1/Systems/system/PCIeDevices"}};
341 
342                     const std::string& connectionName =
343                         connectionNames[0].first;
344 
345                     const std::vector<std::string>& interfaces2 =
346                         connectionNames[0].second;
347                     const std::array<const char*, 2> hasIndicatorLed = {
348                         "xyz.openbmc_project.Inventory.Item.Panel",
349                         "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
350 
351                     for (const char* interface : hasIndicatorLed)
352                     {
353                         if (std::find(interfaces2.begin(), interfaces2.end(),
354                                       interface) != interfaces2.end())
355                         {
356                             getIndicatorLedState(asyncResp);
357                             break;
358                         }
359                     }
360 
361                     crow::connections::systemBus->async_method_call(
362                         [asyncResp, chassisId(std::string(chassisId))](
363                             const boost::system::error_code /*ec2*/,
364                             const std::vector<std::pair<
365                                 std::string, VariantType>>& propertiesList) {
366                             for (const std::pair<std::string, VariantType>&
367                                      property : propertiesList)
368                             {
369                                 // Store DBus properties that are also Redfish
370                                 // properties with same name and a string value
371                                 const std::string& propertyName =
372                                     property.first;
373                                 if ((propertyName == "PartNumber") ||
374                                     (propertyName == "SerialNumber") ||
375                                     (propertyName == "Manufacturer") ||
376                                     (propertyName == "Model"))
377                                 {
378                                     const std::string* value =
379                                         std::get_if<std::string>(
380                                             &property.second);
381                                     if (value != nullptr)
382                                     {
383                                         asyncResp->res.jsonValue[propertyName] =
384                                             *value;
385                                     }
386                                 }
387                             }
388                             asyncResp->res.jsonValue["Name"] = chassisId;
389                             asyncResp->res.jsonValue["Id"] = chassisId;
390                             asyncResp->res.jsonValue["Thermal"] = {
391                                 {"@odata.id", "/redfish/v1/Chassis/" +
392                                                   chassisId + "/Thermal"}};
393                             // Power object
394                             asyncResp->res.jsonValue["Power"] = {
395                                 {"@odata.id", "/redfish/v1/Chassis/" +
396                                                   chassisId + "/Power"}};
397                             // SensorCollection
398                             asyncResp->res.jsonValue["Sensors"] = {
399                                 {"@odata.id", "/redfish/v1/Chassis/" +
400                                                   chassisId + "/Sensors"}};
401                             asyncResp->res.jsonValue["Status"] = {
402                                 {"State", "Enabled"},
403                             };
404 
405                             asyncResp->res
406                                 .jsonValue["Links"]["ComputerSystems"] = {
407                                 {{"@odata.id", "/redfish/v1/Systems/system"}}};
408                             asyncResp->res.jsonValue["Links"]["ManagedBy"] = {
409                                 {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
410                             getChassisState(asyncResp);
411                         },
412                         connectionName, path, "org.freedesktop.DBus.Properties",
413                         "GetAll",
414                         "xyz.openbmc_project.Inventory.Decorator.Asset");
415                     return;
416                 }
417 
418                 // Couldn't find an object with that name.  return an error
419                 messages::resourceNotFound(
420                     asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId);
421             },
422             "xyz.openbmc_project.ObjectMapper",
423             "/xyz/openbmc_project/object_mapper",
424             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
425             "/xyz/openbmc_project/inventory", 0, interfaces);
426 
427         getPhysicalSecurityData(asyncResp);
428     }
429 
430     void doPatch(crow::Response& res, const crow::Request& req,
431                  const std::vector<std::string>& params) override
432     {
433         std::optional<std::string> indicatorLed;
434         auto asyncResp = std::make_shared<AsyncResp>(res);
435 
436         if (params.size() != 1)
437         {
438             return;
439         }
440 
441         if (!json_util::readJson(req, res, "IndicatorLED", indicatorLed))
442         {
443             return;
444         }
445 
446         if (!indicatorLed)
447         {
448             return; // delete this when we support more patch properties
449         }
450 
451         const std::array<const char*, 2> interfaces = {
452             "xyz.openbmc_project.Inventory.Item.Board",
453             "xyz.openbmc_project.Inventory.Item.Chassis"};
454 
455         const std::string& chassisId = params[0];
456 
457         crow::connections::systemBus->async_method_call(
458             [asyncResp, chassisId, indicatorLed](
459                 const boost::system::error_code ec,
460                 const crow::openbmc_mapper::GetSubTreeType& subtree) {
461                 if (ec)
462                 {
463                     messages::internalError(asyncResp->res);
464                     return;
465                 }
466 
467                 // Iterate over all retrieved ObjectPaths.
468                 for (const std::pair<
469                          std::string,
470                          std::vector<
471                              std::pair<std::string, std::vector<std::string>>>>&
472                          object : subtree)
473                 {
474                     const std::string& path = object.first;
475                     const std::vector<
476                         std::pair<std::string, std::vector<std::string>>>&
477                         connectionNames = object.second;
478 
479                     if (!boost::ends_with(path, chassisId))
480                     {
481                         continue;
482                     }
483 
484                     if (connectionNames.size() < 1)
485                     {
486                         BMCWEB_LOG_ERROR << "Got 0 Connection names";
487                         continue;
488                     }
489 
490                     const std::vector<std::string>& interfaces3 =
491                         connectionNames[0].second;
492 
493                     if (indicatorLed)
494                     {
495                         const std::array<const char*, 2> hasIndicatorLed = {
496                             "xyz.openbmc_project.Inventory.Item.Panel",
497                             "xyz.openbmc_project.Inventory.Item.Board."
498                             "Motherboard"};
499                         bool indicatorChassis = false;
500                         for (const char* interface : hasIndicatorLed)
501                         {
502                             if (std::find(interfaces3.begin(),
503                                           interfaces3.end(),
504                                           interface) != interfaces3.end())
505                             {
506                                 indicatorChassis = true;
507                                 break;
508                             }
509                         }
510                         if (indicatorChassis)
511                         {
512                             setIndicatorLedState(asyncResp,
513                                                  std::move(*indicatorLed));
514                         }
515                         else
516                         {
517                             messages::propertyUnknown(asyncResp->res,
518                                                       "IndicatorLED");
519                         }
520                     }
521                     return;
522                 }
523 
524                 messages::resourceNotFound(
525                     asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId);
526             },
527             "xyz.openbmc_project.ObjectMapper",
528             "/xyz/openbmc_project/object_mapper",
529             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
530             "/xyz/openbmc_project/inventory", 0, interfaces);
531     }
532 };
533 
534 inline void doChassisPowerCycle(std::shared_ptr<AsyncResp> asyncResp)
535 {
536     const char* processName = "xyz.openbmc_project.State.Chassis";
537     const char* objectPath = "/xyz/openbmc_project/state/chassis0";
538     const char* interfaceName = "xyz.openbmc_project.State.Chassis";
539     const char* destProperty = "RequestedPowerTransition";
540     const std::string propertyValue =
541         "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
542 
543     crow::connections::systemBus->async_method_call(
544         [asyncResp](const boost::system::error_code ec) {
545             // Use "Set" method to set the property value.
546             if (ec)
547             {
548                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
549                 messages::internalError(asyncResp->res);
550                 return;
551             }
552 
553             messages::success(asyncResp->res);
554         },
555         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
556         interfaceName, destProperty, std::variant<std::string>{propertyValue});
557 }
558 
559 /**
560  * ChassisResetAction class supports the POST method for the Reset
561  * action.
562  */
563 class ChassisResetAction : public Node
564 {
565   public:
566     ChassisResetAction(App& app) :
567         Node(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/",
568              std::string())
569     {
570         entityPrivileges = {
571             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
572     }
573 
574   private:
575     /**
576      * Function handles POST method request.
577      * Analyzes POST body before sending Reset request data to D-Bus.
578      */
579     void doPost(crow::Response& res, const crow::Request& req,
580                 const std::vector<std::string>&) override
581     {
582         BMCWEB_LOG_DEBUG << "Post Chassis Reset.";
583 
584         std::string resetType;
585         auto asyncResp = std::make_shared<AsyncResp>(res);
586 
587         if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType))
588         {
589             return;
590         }
591 
592         if (resetType != "PowerCycle")
593         {
594             BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
595                              << resetType;
596             messages::actionParameterNotSupported(asyncResp->res, resetType,
597                                                   "ResetType");
598 
599             return;
600         }
601         doChassisPowerCycle(asyncResp);
602     }
603 };
604 
605 /**
606  * ChassisResetActionInfo derived class for delivering Chassis
607  * ResetType AllowableValues using ResetInfo schema.
608  */
609 class ChassisResetActionInfo : public Node
610 {
611   public:
612     /*
613      * Default Constructor
614      */
615     ChassisResetActionInfo(App& app) :
616         Node(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/", std::string())
617     {
618         entityPrivileges = {
619             {boost::beast::http::verb::get, {{"Login"}}},
620             {boost::beast::http::verb::head, {{"Login"}}},
621             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
622             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
623             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
624             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
625     }
626 
627   private:
628     /**
629      * Functions triggers appropriate requests on DBus
630      */
631     void doGet(crow::Response& res, const crow::Request&,
632                const std::vector<std::string>& params) override
633     {
634         if (params.size() != 1)
635         {
636             messages::internalError(res);
637             res.end();
638             return;
639         }
640         const std::string& chassisId = params[0];
641 
642         res.jsonValue = {{"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"},
643                          {"@odata.id", "/redfish/v1/Chassis/" + chassisId +
644                                            "/ResetActionInfo"},
645                          {"Name", "Reset Action Info"},
646                          {"Id", "ResetActionInfo"},
647                          {"Parameters",
648                           {{{"Name", "ResetType"},
649                             {"Required", true},
650                             {"DataType", "String"},
651                             {"AllowableValues", {"PowerCycle"}}}}}};
652         res.end();
653     }
654 };
655 
656 } // namespace redfish
657