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