xref: /openbmc/bmcweb/features/redfish/lib/chassis.hpp (revision 1cb1a9e651c8a38ce3c60028a36c5d415d4afd79)
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 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 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 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(CrowApp& 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& req,
179                const std::vector<std::string>& params) 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                         continue;
208                     }
209                     chassisArray.push_back(
210                         {{"@odata.id", "/redfish/v1/Chassis/" +
211                                            objpath.substr(lastPos + 1)}});
212                 }
213 
214                 asyncResp->res.jsonValue["Members@odata.count"] =
215                     chassisArray.size();
216             },
217             "xyz.openbmc_project.ObjectMapper",
218             "/xyz/openbmc_project/object_mapper",
219             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
220             "/xyz/openbmc_project/inventory", 0, interfaces);
221     }
222 };
223 
224 /**
225  * Chassis override class for delivering Chassis Schema
226  */
227 class Chassis : public Node
228 {
229   public:
230     Chassis(CrowApp& app) :
231         Node(app, "/redfish/v1/Chassis/<str>/", std::string())
232     {
233         entityPrivileges = {
234             {boost::beast::http::verb::get, {{"Login"}}},
235             {boost::beast::http::verb::head, {{"Login"}}},
236             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
237             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
238             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
239             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
240     }
241 
242   private:
243     /**
244      * Functions triggers appropriate requests on DBus
245      */
246     void doGet(crow::Response& res, const crow::Request& req,
247                const std::vector<std::string>& params) override
248     {
249         const std::array<const char*, 2> interfaces = {
250             "xyz.openbmc_project.Inventory.Item.Board",
251             "xyz.openbmc_project.Inventory.Item.Chassis"};
252 
253         // Check if there is required param, truly entering this shall be
254         // impossible.
255         if (params.size() != 1)
256         {
257             messages::internalError(res);
258             res.end();
259             return;
260         }
261         const std::string& chassisId = params[0];
262 
263         auto asyncResp = std::make_shared<AsyncResp>(res);
264         crow::connections::systemBus->async_method_call(
265             [asyncResp, chassisId(std::string(chassisId))](
266                 const boost::system::error_code ec,
267                 const crow::openbmc_mapper::GetSubTreeType& subtree) {
268                 if (ec)
269                 {
270                     messages::internalError(asyncResp->res);
271                     return;
272                 }
273                 // Iterate over all retrieved ObjectPaths.
274                 for (const std::pair<
275                          std::string,
276                          std::vector<
277                              std::pair<std::string, std::vector<std::string>>>>&
278                          object : subtree)
279                 {
280                     const std::string& path = object.first;
281                     const std::vector<
282                         std::pair<std::string, std::vector<std::string>>>&
283                         connectionNames = object.second;
284 
285                     if (!boost::ends_with(path, chassisId))
286                     {
287                         continue;
288                     }
289 
290                     auto health = std::make_shared<HealthPopulate>(asyncResp);
291 
292                     crow::connections::systemBus->async_method_call(
293                         [health](const boost::system::error_code ec,
294                                  std::variant<std::vector<std::string>>& resp) {
295                             if (ec)
296                             {
297                                 return; // no sensors = no failures
298                             }
299                             std::vector<std::string>* data =
300                                 std::get_if<std::vector<std::string>>(&resp);
301                             if (data == nullptr)
302                             {
303                                 return;
304                             }
305                             health->inventory = std::move(*data);
306                         },
307                         "xyz.openbmc_project.ObjectMapper",
308                         path + "/all_sensors",
309                         "org.freedesktop.DBus.Properties", "Get",
310                         "xyz.openbmc_project.Association", "endpoints");
311 
312                     health->populate();
313 
314                     if (connectionNames.size() < 1)
315                     {
316                         BMCWEB_LOG_ERROR << "Got 0 Connection names";
317                         continue;
318                     }
319 
320                     asyncResp->res.jsonValue["@odata.type"] =
321                         "#Chassis.v1_10_0.Chassis";
322                     asyncResp->res.jsonValue["@odata.id"] =
323                         "/redfish/v1/Chassis/" + chassisId;
324                     asyncResp->res.jsonValue["Name"] = "Chassis Collection";
325                     asyncResp->res.jsonValue["ChassisType"] = "RackMount";
326                     asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"] = {
327                         {"target", "/redfish/v1/Chassis/" + chassisId +
328                                        "/Actions/Chassis.Reset"},
329                         {"@Redfish.ActionInfo", "/redfish/v1/Chassis/" +
330                                                     chassisId +
331                                                     "/ResetActionInfo"}};
332                     asyncResp->res.jsonValue["PCIeDevices"] = {
333                         {"@odata.id",
334                          "/redfish/v1/Systems/system/PCIeDevices"}};
335 
336                     const std::string& connectionName =
337                         connectionNames[0].first;
338 
339                     const std::vector<std::string>& interfaces =
340                         connectionNames[0].second;
341                     const std::array<const char*, 2> hasIndicatorLed = {
342                         "xyz.openbmc_project.Inventory.Item.Panel",
343                         "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
344 
345                     for (const char* interface : hasIndicatorLed)
346                     {
347                         if (std::find(interfaces.begin(), interfaces.end(),
348                                       interface) != interfaces.end())
349                         {
350                             getIndicatorLedState(asyncResp);
351                             break;
352                         }
353                     }
354 
355                     crow::connections::systemBus->async_method_call(
356                         [asyncResp, chassisId(std::string(chassisId))](
357                             const boost::system::error_code ec,
358                             const std::vector<std::pair<
359                                 std::string, VariantType>>& propertiesList) {
360                             for (const std::pair<std::string, VariantType>&
361                                      property : propertiesList)
362                             {
363                                 // Store DBus properties that are also Redfish
364                                 // properties with same name and a string value
365                                 const std::string& propertyName =
366                                     property.first;
367                                 if ((propertyName == "PartNumber") ||
368                                     (propertyName == "SerialNumber") ||
369                                     (propertyName == "Manufacturer") ||
370                                     (propertyName == "Model"))
371                                 {
372                                     const std::string* value =
373                                         std::get_if<std::string>(
374                                             &property.second);
375                                     if (value != nullptr)
376                                     {
377                                         asyncResp->res.jsonValue[propertyName] =
378                                             *value;
379                                     }
380                                 }
381                             }
382                             asyncResp->res.jsonValue["Name"] = chassisId;
383                             asyncResp->res.jsonValue["Id"] = chassisId;
384                             asyncResp->res.jsonValue["Thermal"] = {
385                                 {"@odata.id", "/redfish/v1/Chassis/" +
386                                                   chassisId + "/Thermal"}};
387                             // Power object
388                             asyncResp->res.jsonValue["Power"] = {
389                                 {"@odata.id", "/redfish/v1/Chassis/" +
390                                                   chassisId + "/Power"}};
391                             // SensorCollection
392                             asyncResp->res.jsonValue["Sensors"] = {
393                                 {"@odata.id", "/redfish/v1/Chassis/" +
394                                                   chassisId + "/Sensors"}};
395                             asyncResp->res.jsonValue["Status"] = {
396                                 {"State", "Enabled"},
397                             };
398 
399                             asyncResp->res
400                                 .jsonValue["Links"]["ComputerSystems"] = {
401                                 {{"@odata.id", "/redfish/v1/Systems/system"}}};
402                             asyncResp->res.jsonValue["Links"]["ManagedBy"] = {
403                                 {{"@odata.id", "/redfish/v1/Managers/bmc"}}};
404                             getChassisState(asyncResp);
405                         },
406                         connectionName, path, "org.freedesktop.DBus.Properties",
407                         "GetAll",
408                         "xyz.openbmc_project.Inventory.Decorator.Asset");
409                     return;
410                 }
411 
412                 // Couldn't find an object with that name.  return an error
413                 messages::resourceNotFound(
414                     asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId);
415             },
416             "xyz.openbmc_project.ObjectMapper",
417             "/xyz/openbmc_project/object_mapper",
418             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
419             "/xyz/openbmc_project/inventory", 0, interfaces);
420 
421         getPhysicalSecurityData(asyncResp);
422     }
423 
424     void doPatch(crow::Response& res, const crow::Request& req,
425                  const std::vector<std::string>& params) override
426     {
427         std::optional<std::string> indicatorLed;
428         auto asyncResp = std::make_shared<AsyncResp>(res);
429 
430         if (params.size() != 1)
431         {
432             return;
433         }
434 
435         if (!json_util::readJson(req, res, "IndicatorLED", indicatorLed))
436         {
437             return;
438         }
439 
440         if (!indicatorLed)
441         {
442             return; // delete this when we support more patch properties
443         }
444 
445         const std::array<const char*, 2> interfaces = {
446             "xyz.openbmc_project.Inventory.Item.Board",
447             "xyz.openbmc_project.Inventory.Item.Chassis"};
448 
449         const std::string& chassisId = params[0];
450 
451         crow::connections::systemBus->async_method_call(
452             [asyncResp, chassisId, indicatorLed](
453                 const boost::system::error_code ec,
454                 const crow::openbmc_mapper::GetSubTreeType& subtree) {
455                 if (ec)
456                 {
457                     messages::internalError(asyncResp->res);
458                     return;
459                 }
460 
461                 // Iterate over all retrieved ObjectPaths.
462                 for (const std::pair<
463                          std::string,
464                          std::vector<
465                              std::pair<std::string, std::vector<std::string>>>>&
466                          object : subtree)
467                 {
468                     const std::string& path = object.first;
469                     const std::vector<
470                         std::pair<std::string, std::vector<std::string>>>&
471                         connectionNames = object.second;
472 
473                     if (!boost::ends_with(path, chassisId))
474                     {
475                         continue;
476                     }
477 
478                     if (connectionNames.size() < 1)
479                     {
480                         BMCWEB_LOG_ERROR << "Got 0 Connection names";
481                         continue;
482                     }
483 
484                     const std::vector<std::string>& interfaces =
485                         connectionNames[0].second;
486 
487                     if (indicatorLed)
488                     {
489                         const std::array<const char*, 2> hasIndicatorLed = {
490                             "xyz.openbmc_project.Inventory.Item.Panel",
491                             "xyz.openbmc_project.Inventory.Item.Board."
492                             "Motherboard"};
493                         bool indicatorChassis = false;
494                         for (const char* interface : hasIndicatorLed)
495                         {
496                             if (std::find(interfaces.begin(), interfaces.end(),
497                                           interface) != interfaces.end())
498                             {
499                                 indicatorChassis = true;
500                                 break;
501                             }
502                         }
503                         if (indicatorChassis)
504                         {
505                             setIndicatorLedState(asyncResp,
506                                                  std::move(*indicatorLed));
507                         }
508                         else
509                         {
510                             messages::propertyUnknown(asyncResp->res,
511                                                       "IndicatorLED");
512                         }
513                     }
514                     return;
515                 }
516 
517                 messages::resourceNotFound(
518                     asyncResp->res, "#Chassis.v1_10_0.Chassis", chassisId);
519             },
520             "xyz.openbmc_project.ObjectMapper",
521             "/xyz/openbmc_project/object_mapper",
522             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
523             "/xyz/openbmc_project/inventory", 0, interfaces);
524     }
525 };
526 
527 void doChassisPowerCycle(std::shared_ptr<AsyncResp> asyncResp)
528 {
529     const char* processName = "xyz.openbmc_project.State.Chassis";
530     const char* objectPath = "/xyz/openbmc_project/state/chassis0";
531     const char* interfaceName = "xyz.openbmc_project.State.Chassis";
532     const char* destProperty = "RequestedPowerTransition";
533     const std::string propertyValue =
534         "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
535 
536     crow::connections::systemBus->async_method_call(
537         [asyncResp](const boost::system::error_code ec) {
538             // Use "Set" method to set the property value.
539             if (ec)
540             {
541                 BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " << ec;
542                 messages::internalError(asyncResp->res);
543                 return;
544             }
545 
546             messages::success(asyncResp->res);
547         },
548         processName, objectPath, "org.freedesktop.DBus.Properties", "Set",
549         interfaceName, destProperty, std::variant<std::string>{propertyValue});
550 }
551 
552 /**
553  * ChassisResetAction class supports the POST method for the Reset
554  * action.
555  */
556 class ChassisResetAction : public Node
557 {
558   public:
559     ChassisResetAction(CrowApp& app) :
560         Node(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/",
561              std::string())
562     {
563         entityPrivileges = {
564             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
565     }
566 
567   private:
568     /**
569      * Function handles POST method request.
570      * Analyzes POST body before sending Reset request data to D-Bus.
571      */
572     void doPost(crow::Response& res, const crow::Request& req,
573                 const std::vector<std::string>& params) override
574     {
575         BMCWEB_LOG_DEBUG << "Post Chassis Reset.";
576 
577         std::string resetType;
578         auto asyncResp = std::make_shared<AsyncResp>(res);
579 
580         if (!json_util::readJson(req, asyncResp->res, "ResetType", resetType))
581         {
582             return;
583         }
584 
585         if (resetType != "PowerCycle")
586         {
587             BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
588                              << resetType;
589             messages::actionParameterNotSupported(asyncResp->res, resetType,
590                                                   "ResetType");
591 
592             return;
593         }
594         doChassisPowerCycle(asyncResp);
595     }
596 };
597 
598 /**
599  * ChassisResetActionInfo derived class for delivering Chassis
600  * ResetType AllowableValues using ResetInfo schema.
601  */
602 class ChassisResetActionInfo : public Node
603 {
604   public:
605     /*
606      * Default Constructor
607      */
608     ChassisResetActionInfo(CrowApp& app) :
609         Node(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/", std::string())
610     {
611         entityPrivileges = {
612             {boost::beast::http::verb::get, {{"Login"}}},
613             {boost::beast::http::verb::head, {{"Login"}}},
614             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
615             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
616             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
617             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
618     }
619 
620   private:
621     /**
622      * Functions triggers appropriate requests on DBus
623      */
624     void doGet(crow::Response& res, const crow::Request& req,
625                const std::vector<std::string>& params) override
626     {
627         if (params.size() != 1)
628         {
629             messages::internalError(res);
630             res.end();
631             return;
632         }
633         const std::string& chassisId = params[0];
634 
635         res.jsonValue = {{"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"},
636                          {"@odata.id", "/redfish/v1/Chassis/" + chassisId +
637                                            "/ResetActionInfo"},
638                          {"Name", "Reset Action Info"},
639                          {"Id", "ResetActionInfo"},
640                          {"Parameters",
641                           {{{"Name", "ResetType"},
642                             {"Required", true},
643                             {"DataType", "String"},
644                             {"AllowableValues", {"PowerCycle"}}}}}};
645         res.end();
646     }
647 };
648 
649 } // namespace redfish
650