xref: /openbmc/bmcweb/redfish-core/lib/chassis.hpp (revision e01d0c36af115ed46d54b5dbbacfe3ad92226bd3)
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 "bmcweb_config.h"
19 
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "health.hpp"
23 #include "led.hpp"
24 #include "query.hpp"
25 #include "registries/privilege_registry.hpp"
26 #include "utils/collection.hpp"
27 #include "utils/dbus_utils.hpp"
28 #include "utils/json_utils.hpp"
29 
30 #include <boost/system/error_code.hpp>
31 #include <boost/url/format.hpp>
32 #include <sdbusplus/asio/property.hpp>
33 #include <sdbusplus/message.hpp>
34 #include <sdbusplus/unpack_properties.hpp>
35 
36 #include <array>
37 #include <string_view>
38 
39 namespace redfish
40 {
41 
42 /**
43  * @brief Retrieves resources over dbus to link to the chassis
44  *
45  * @param[in] asyncResp  - Shared pointer for completing asynchronous
46  * calls
47  * @param[in] path       - Chassis dbus path to look for the storage.
48  *
49  * Calls the Association endpoints on the path + "/storage" and add the link of
50  * json["Links"]["Storage@odata.count"] =
51  *    {"@odata.id", "/redfish/v1/Storage/" + resourceId}
52  *
53  * @return None.
54  */
55 inline void getStorageLink(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
56                            const sdbusplus::message::object_path& path)
57 {
58     sdbusplus::asio::getProperty<std::vector<std::string>>(
59         *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper",
60         (path / "storage").str, "xyz.openbmc_project.Association", "endpoints",
61         [asyncResp](const boost::system::error_code& ec,
62                     const std::vector<std::string>& storageList) {
63         if (ec)
64         {
65             BMCWEB_LOG_DEBUG("getStorageLink got DBUS response error");
66             return;
67         }
68 
69         nlohmann::json::array_t storages;
70         for (const std::string& storagePath : storageList)
71         {
72             std::string id =
73                 sdbusplus::message::object_path(storagePath).filename();
74             if (id.empty())
75             {
76                 continue;
77             }
78 
79             nlohmann::json::object_t storage;
80             storage["@odata.id"] = boost::urls::format(
81                 "/redfish/v1/Systems/system/Storage/{}", id);
82             storages.emplace_back(std::move(storage));
83         }
84         asyncResp->res.jsonValue["Links"]["Storage@odata.count"] =
85             storages.size();
86         asyncResp->res.jsonValue["Links"]["Storage"] = std::move(storages);
87         });
88 }
89 
90 /**
91  * @brief Retrieves chassis state properties over dbus
92  *
93  * @param[in] asyncResp - Shared pointer for completing asynchronous calls.
94  *
95  * @return None.
96  */
97 inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> asyncResp)
98 {
99     // crow::connections::systemBus->async_method_call(
100     sdbusplus::asio::getProperty<std::string>(
101         *crow::connections::systemBus, "xyz.openbmc_project.State.Chassis",
102         "/xyz/openbmc_project/state/chassis0",
103         "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
104         [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec,
105                                           const std::string& chassisState) {
106         if (ec)
107         {
108             if (ec == boost::system::errc::host_unreachable)
109             {
110                 // Service not available, no error, just don't return
111                 // chassis state info
112                 BMCWEB_LOG_DEBUG("Service not available {}", ec);
113                 return;
114             }
115             BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
116             messages::internalError(asyncResp->res);
117             return;
118         }
119 
120         BMCWEB_LOG_DEBUG("Chassis state: {}", chassisState);
121         // Verify Chassis State
122         if (chassisState == "xyz.openbmc_project.State.Chassis.PowerState.On")
123         {
124             asyncResp->res.jsonValue["PowerState"] = "On";
125             asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
126         }
127         else if (chassisState ==
128                  "xyz.openbmc_project.State.Chassis.PowerState.Off")
129         {
130             asyncResp->res.jsonValue["PowerState"] = "Off";
131             asyncResp->res.jsonValue["Status"]["State"] = "StandbyOffline";
132         }
133         });
134 }
135 
136 inline void getIntrusionByService(std::shared_ptr<bmcweb::AsyncResp> asyncResp,
137                                   const std::string& service,
138                                   const std::string& objPath)
139 {
140     BMCWEB_LOG_DEBUG("Get intrusion status by service ");
141 
142     sdbusplus::asio::getProperty<std::string>(
143         *crow::connections::systemBus, service, objPath,
144         "xyz.openbmc_project.Chassis.Intrusion", "Status",
145         [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec,
146                                           const std::string& value) {
147         if (ec)
148         {
149             // do not add err msg in redfish response, because this is not
150             //     mandatory property
151             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
152             return;
153         }
154 
155         asyncResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensorNumber"] =
156             1;
157         asyncResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensor"] = value;
158         });
159 }
160 
161 /**
162  * Retrieves physical security properties over dbus
163  */
164 inline void
165     getPhysicalSecurityData(std::shared_ptr<bmcweb::AsyncResp> asyncResp)
166 {
167     constexpr std::array<std::string_view, 1> interfaces = {
168         "xyz.openbmc_project.Chassis.Intrusion"};
169     dbus::utility::getSubTree(
170         "/xyz/openbmc_project", 0, interfaces,
171         [asyncResp{std::move(asyncResp)}](
172             const boost::system::error_code& ec,
173             const dbus::utility::MapperGetSubTreeResponse& subtree) {
174         if (ec)
175         {
176             // do not add err msg in redfish response, because this is not
177             //     mandatory property
178             BMCWEB_LOG_INFO("DBUS error: no matched iface {}", ec);
179             return;
180         }
181         // Iterate over all retrieved ObjectPaths.
182         for (const auto& object : subtree)
183         {
184             if (!object.second.empty())
185             {
186                 const auto service = object.second.front();
187                 getIntrusionByService(asyncResp, service.first, object.first);
188                 return;
189             }
190         }
191         });
192 }
193 
194 inline void handleChassisCollectionGet(
195     App& app, const crow::Request& req,
196     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
197 {
198     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
199     {
200         return;
201     }
202     asyncResp->res.jsonValue["@odata.type"] =
203         "#ChassisCollection.ChassisCollection";
204     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis";
205     asyncResp->res.jsonValue["Name"] = "Chassis Collection";
206 
207     constexpr std::array<std::string_view, 2> interfaces{
208         "xyz.openbmc_project.Inventory.Item.Board",
209         "xyz.openbmc_project.Inventory.Item.Chassis"};
210     collection_util::getCollectionMembers(
211         asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces);
212 }
213 
214 inline void getChassisContainedBy(
215     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
216     const std::string& chassisId, const boost::system::error_code& ec,
217     const dbus::utility::MapperEndPoints& upstreamChassisPaths)
218 {
219     if (ec)
220     {
221         if (ec.value() != EBADR)
222         {
223             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
224             messages::internalError(asyncResp->res);
225         }
226         return;
227     }
228     if (upstreamChassisPaths.empty())
229     {
230         return;
231     }
232     if (upstreamChassisPaths.size() > 1)
233     {
234         BMCWEB_LOG_ERROR("{} is contained by mutliple chassis", chassisId);
235         messages::internalError(asyncResp->res);
236         return;
237     }
238 
239     sdbusplus::message::object_path upstreamChassisPath(
240         upstreamChassisPaths[0]);
241     std::string upstreamChassis = upstreamChassisPath.filename();
242     if (upstreamChassis.empty())
243     {
244         BMCWEB_LOG_WARNING("Malformed upstream Chassis path {} on {}",
245                            upstreamChassisPath.str, chassisId);
246         return;
247     }
248 
249     asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] =
250         boost::urls::format("/redfish/v1/Chassis/{}", upstreamChassis);
251 }
252 
253 inline void getChassisContains(
254     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
255     const std::string& chassisId, const boost::system::error_code& ec,
256     const dbus::utility::MapperEndPoints& downstreamChassisPaths)
257 {
258     if (ec)
259     {
260         if (ec.value() != EBADR)
261         {
262             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
263             messages::internalError(asyncResp->res);
264         }
265         return;
266     }
267     if (downstreamChassisPaths.empty())
268     {
269         return;
270     }
271     nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"];
272     if (!jValue.is_array())
273     {
274         // Create the array if it was empty
275         jValue = nlohmann::json::array();
276     }
277     for (const auto& p : downstreamChassisPaths)
278     {
279         sdbusplus::message::object_path downstreamChassisPath(p);
280         std::string downstreamChassis = downstreamChassisPath.filename();
281         if (downstreamChassis.empty())
282         {
283             BMCWEB_LOG_WARNING("Malformed downstream Chassis path {} on {}",
284                                downstreamChassisPath.str, chassisId);
285             continue;
286         }
287         nlohmann::json link;
288         link["@odata.id"] = boost::urls::format("/redfish/v1/Chassis/{}",
289                                                 downstreamChassis);
290         jValue.push_back(std::move(link));
291     }
292     asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size();
293 }
294 
295 inline void
296     getChassisConnectivity(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
297                            const std::string& chassisId,
298                            const std::string& chassisPath)
299 {
300     BMCWEB_LOG_DEBUG("Get chassis connectivity");
301 
302     dbus::utility::getAssociationEndPoints(
303         chassisPath + "/contained_by",
304         std::bind_front(getChassisContainedBy, asyncResp, chassisId));
305 
306     dbus::utility::getAssociationEndPoints(
307         chassisPath + "/containing",
308         std::bind_front(getChassisContains, asyncResp, chassisId));
309 }
310 
311 /**
312  * ChassisCollection derived class for delivering Chassis Collection Schema
313  *  Functions triggers appropriate requests on DBus
314  */
315 inline void requestRoutesChassisCollection(App& app)
316 {
317     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/")
318         .privileges(redfish::privileges::getChassisCollection)
319         .methods(boost::beast::http::verb::get)(
320             std::bind_front(handleChassisCollectionGet, std::ref(app)));
321 }
322 
323 inline void
324     getChassisLocationCode(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
325                            const std::string& connectionName,
326                            const std::string& path)
327 {
328     sdbusplus::asio::getProperty<std::string>(
329         *crow::connections::systemBus, connectionName, path,
330         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
331         [asyncResp](const boost::system::error_code& ec,
332                     const std::string& property) {
333         if (ec)
334         {
335             BMCWEB_LOG_ERROR("DBUS response error for Location");
336             messages::internalError(asyncResp->res);
337             return;
338         }
339 
340         asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
341             property;
342         });
343 }
344 
345 inline void getChassisUUID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
346                            const std::string& connectionName,
347                            const std::string& path)
348 {
349     sdbusplus::asio::getProperty<std::string>(
350         *crow::connections::systemBus, connectionName, path,
351         "xyz.openbmc_project.Common.UUID", "UUID",
352         [asyncResp](const boost::system::error_code& ec,
353                     const std::string& chassisUUID) {
354         if (ec)
355         {
356             BMCWEB_LOG_ERROR("DBUS response error for UUID");
357             messages::internalError(asyncResp->res);
358             return;
359         }
360         asyncResp->res.jsonValue["UUID"] = chassisUUID;
361         });
362 }
363 
364 inline void
365     handleChassisGet(App& app, const crow::Request& req,
366                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
367                      const std::string& chassisId)
368 {
369     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
370     {
371         return;
372     }
373     constexpr std::array<std::string_view, 2> interfaces = {
374         "xyz.openbmc_project.Inventory.Item.Board",
375         "xyz.openbmc_project.Inventory.Item.Chassis"};
376 
377     dbus::utility::getSubTree(
378         "/xyz/openbmc_project/inventory", 0, interfaces,
379         [asyncResp, chassisId(std::string(chassisId))](
380             const boost::system::error_code& ec,
381             const dbus::utility::MapperGetSubTreeResponse& subtree) {
382         if (ec)
383         {
384             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
385             messages::internalError(asyncResp->res);
386             return;
387         }
388         // Iterate over all retrieved ObjectPaths.
389         for (const std::pair<
390                  std::string,
391                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
392                  object : subtree)
393         {
394             const std::string& path = object.first;
395             const std::vector<std::pair<std::string, std::vector<std::string>>>&
396                 connectionNames = object.second;
397 
398             sdbusplus::message::object_path objPath(path);
399             if (objPath.filename() != chassisId)
400             {
401                 continue;
402             }
403 
404             getChassisConnectivity(asyncResp, chassisId, path);
405 
406             auto health = std::make_shared<HealthPopulate>(asyncResp);
407 
408             if constexpr (bmcwebEnableHealthPopulate)
409             {
410                 dbus::utility::getAssociationEndPoints(
411                     path + "/all_sensors",
412                     [health](const boost::system::error_code& ec2,
413                              const dbus::utility::MapperEndPoints& resp) {
414                     if (ec2)
415                     {
416                         return; // no sensors = no failures
417                     }
418                     health->inventory = resp;
419                     });
420 
421                 health->populate();
422             }
423 
424             if (connectionNames.empty())
425             {
426                 BMCWEB_LOG_ERROR("Got 0 Connection names");
427                 continue;
428             }
429 
430             asyncResp->res.jsonValue["@odata.type"] =
431                 "#Chassis.v1_22_0.Chassis";
432             asyncResp->res.jsonValue["@odata.id"] =
433                 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
434             asyncResp->res.jsonValue["Name"] = "Chassis Collection";
435             asyncResp->res.jsonValue["ChassisType"] = "RackMount";
436             asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"]["target"] =
437                 boost::urls::format(
438                     "/redfish/v1/Chassis/{}/Actions/Chassis.Reset", chassisId);
439             asyncResp->res
440                 .jsonValue["Actions"]["#Chassis.Reset"]["@Redfish.ActionInfo"] =
441                 boost::urls::format("/redfish/v1/Chassis/{}/ResetActionInfo",
442                                     chassisId);
443             asyncResp->res.jsonValue["PCIeDevices"]["@odata.id"] =
444                 "/redfish/v1/Systems/system/PCIeDevices";
445 
446             dbus::utility::getAssociationEndPoints(
447                 path + "/drive",
448                 [asyncResp,
449                  chassisId](const boost::system::error_code& ec3,
450                             const dbus::utility::MapperEndPoints& resp) {
451                 if (ec3 || resp.empty())
452                 {
453                     return; // no drives = no failures
454                 }
455 
456                 nlohmann::json reference;
457                 reference["@odata.id"] = boost::urls::format(
458                     "/redfish/v1/Chassis/{}/Drives", chassisId);
459                 asyncResp->res.jsonValue["Drives"] = std::move(reference);
460                 });
461 
462             const std::string& connectionName = connectionNames[0].first;
463 
464             const std::vector<std::string>& interfaces2 =
465                 connectionNames[0].second;
466             const std::array<const char*, 2> hasIndicatorLed = {
467                 "xyz.openbmc_project.Inventory.Item.Panel",
468                 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
469 
470             const std::string assetTagInterface =
471                 "xyz.openbmc_project.Inventory.Decorator.AssetTag";
472             const std::string replaceableInterface =
473                 "xyz.openbmc_project.Inventory.Decorator.Replaceable";
474             for (const auto& interface : interfaces2)
475             {
476                 if (interface == assetTagInterface)
477                 {
478                     sdbusplus::asio::getProperty<std::string>(
479                         *crow::connections::systemBus, connectionName, path,
480                         assetTagInterface, "AssetTag",
481                         [asyncResp,
482                          chassisId](const boost::system::error_code& ec2,
483                                     const std::string& property) {
484                         if (ec2)
485                         {
486                             BMCWEB_LOG_ERROR(
487                                 "DBus response error for AssetTag: {}", ec2);
488                             messages::internalError(asyncResp->res);
489                             return;
490                         }
491                         asyncResp->res.jsonValue["AssetTag"] = property;
492                         });
493                 }
494                 else if (interface == replaceableInterface)
495                 {
496                     sdbusplus::asio::getProperty<bool>(
497                         *crow::connections::systemBus, connectionName, path,
498                         replaceableInterface, "HotPluggable",
499                         [asyncResp,
500                          chassisId](const boost::system::error_code& ec2,
501                                     const bool property) {
502                         if (ec2)
503                         {
504                             BMCWEB_LOG_ERROR(
505                                 "DBus response error for HotPluggable: {}",
506                                 ec2);
507                             messages::internalError(asyncResp->res);
508                             return;
509                         }
510                         asyncResp->res.jsonValue["HotPluggable"] = property;
511                         });
512                 }
513             }
514 
515             for (const char* interface : hasIndicatorLed)
516             {
517                 if (std::find(interfaces2.begin(), interfaces2.end(),
518                               interface) != interfaces2.end())
519                 {
520                     getIndicatorLedState(asyncResp);
521                     getLocationIndicatorActive(asyncResp);
522                     break;
523                 }
524             }
525 
526             sdbusplus::asio::getAllProperties(
527                 *crow::connections::systemBus, connectionName, path,
528                 "xyz.openbmc_project.Inventory.Decorator.Asset",
529                 [asyncResp, chassisId(std::string(chassisId)),
530                  path](const boost::system::error_code& /*ec2*/,
531                        const dbus::utility::DBusPropertiesMap& propertiesList) {
532                 const std::string* partNumber = nullptr;
533                 const std::string* serialNumber = nullptr;
534                 const std::string* manufacturer = nullptr;
535                 const std::string* model = nullptr;
536                 const std::string* sparePartNumber = nullptr;
537 
538                 const bool success = sdbusplus::unpackPropertiesNoThrow(
539                     dbus_utils::UnpackErrorPrinter(), propertiesList,
540                     "PartNumber", partNumber, "SerialNumber", serialNumber,
541                     "Manufacturer", manufacturer, "Model", model,
542                     "SparePartNumber", sparePartNumber);
543 
544                 if (!success)
545                 {
546                     messages::internalError(asyncResp->res);
547                     return;
548                 }
549 
550                 if (partNumber != nullptr)
551                 {
552                     asyncResp->res.jsonValue["PartNumber"] = *partNumber;
553                 }
554 
555                 if (serialNumber != nullptr)
556                 {
557                     asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
558                 }
559 
560                 if (manufacturer != nullptr)
561                 {
562                     asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
563                 }
564 
565                 if (model != nullptr)
566                 {
567                     asyncResp->res.jsonValue["Model"] = *model;
568                 }
569 
570                 // SparePartNumber is optional on D-Bus
571                 // so skip if it is empty
572                 if (sparePartNumber != nullptr && !sparePartNumber->empty())
573                 {
574                     asyncResp->res.jsonValue["SparePartNumber"] =
575                         *sparePartNumber;
576                 }
577 
578                 asyncResp->res.jsonValue["Name"] = chassisId;
579                 asyncResp->res.jsonValue["Id"] = chassisId;
580 #ifdef BMCWEB_ALLOW_DEPRECATED_POWER_THERMAL
581                 asyncResp->res.jsonValue["Thermal"]["@odata.id"] =
582                     boost::urls::format("/redfish/v1/Chassis/{}/Thermal",
583                                         chassisId);
584                 // Power object
585                 asyncResp->res.jsonValue["Power"]["@odata.id"] =
586                     boost::urls::format("/redfish/v1/Chassis/{}/Power",
587                                         chassisId);
588 #endif
589 #ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM
590                 asyncResp->res.jsonValue["ThermalSubsystem"]["@odata.id"] =
591                     boost::urls::format(
592                         "/redfish/v1/Chassis/{}/ThermalSubsystem", chassisId);
593                 asyncResp->res.jsonValue["PowerSubsystem"]["@odata.id"] =
594                     boost::urls::format("/redfish/v1/Chassis/{}/PowerSubsystem",
595                                         chassisId);
596                 asyncResp->res.jsonValue["EnvironmentMetrics"]["@odata.id"] =
597                     boost::urls::format(
598                         "/redfish/v1/Chassis/{}/EnvironmentMetrics", chassisId);
599 #endif
600                 // SensorCollection
601                 asyncResp->res.jsonValue["Sensors"]["@odata.id"] =
602                     boost::urls::format("/redfish/v1/Chassis/{}/Sensors",
603                                         chassisId);
604                 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
605 
606                 nlohmann::json::array_t computerSystems;
607                 nlohmann::json::object_t system;
608                 system["@odata.id"] = "/redfish/v1/Systems/system";
609                 computerSystems.emplace_back(std::move(system));
610                 asyncResp->res.jsonValue["Links"]["ComputerSystems"] =
611                     std::move(computerSystems);
612 
613                 nlohmann::json::array_t managedBy;
614                 nlohmann::json::object_t manager;
615                 manager["@odata.id"] = "/redfish/v1/Managers/bmc";
616                 managedBy.emplace_back(std::move(manager));
617                 asyncResp->res.jsonValue["Links"]["ManagedBy"] =
618                     std::move(managedBy);
619                 getChassisState(asyncResp);
620                 getStorageLink(asyncResp, path);
621                 });
622 
623             for (const auto& interface : interfaces2)
624             {
625                 if (interface == "xyz.openbmc_project.Common.UUID")
626                 {
627                     getChassisUUID(asyncResp, connectionName, path);
628                 }
629                 else if (interface ==
630                          "xyz.openbmc_project.Inventory.Decorator.LocationCode")
631                 {
632                     getChassisLocationCode(asyncResp, connectionName, path);
633                 }
634             }
635 
636             return;
637         }
638 
639         // Couldn't find an object with that name.  return an error
640         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
641         });
642 
643     getPhysicalSecurityData(asyncResp);
644 }
645 
646 inline void
647     handleChassisPatch(App& app, const crow::Request& req,
648                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
649                        const std::string& param)
650 {
651     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
652     {
653         return;
654     }
655     std::optional<bool> locationIndicatorActive;
656     std::optional<std::string> indicatorLed;
657 
658     if (param.empty())
659     {
660         return;
661     }
662 
663     if (!json_util::readJsonPatch(
664             req, asyncResp->res, "LocationIndicatorActive",
665             locationIndicatorActive, "IndicatorLED", indicatorLed))
666     {
667         return;
668     }
669 
670     // TODO (Gunnar): Remove IndicatorLED after enough time has passed
671     if (!locationIndicatorActive && !indicatorLed)
672     {
673         return; // delete this when we support more patch properties
674     }
675     if (indicatorLed)
676     {
677         asyncResp->res.addHeader(
678             boost::beast::http::field::warning,
679             "299 - \"IndicatorLED is deprecated. Use LocationIndicatorActive instead.\"");
680     }
681 
682     constexpr std::array<std::string_view, 2> interfaces = {
683         "xyz.openbmc_project.Inventory.Item.Board",
684         "xyz.openbmc_project.Inventory.Item.Chassis"};
685 
686     const std::string& chassisId = param;
687 
688     dbus::utility::getSubTree(
689         "/xyz/openbmc_project/inventory", 0, interfaces,
690         [asyncResp, chassisId, locationIndicatorActive,
691          indicatorLed](const boost::system::error_code& ec,
692                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
693         if (ec)
694         {
695             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
696             messages::internalError(asyncResp->res);
697             return;
698         }
699 
700         // Iterate over all retrieved ObjectPaths.
701         for (const std::pair<
702                  std::string,
703                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
704                  object : subtree)
705         {
706             const std::string& path = object.first;
707             const std::vector<std::pair<std::string, std::vector<std::string>>>&
708                 connectionNames = object.second;
709 
710             sdbusplus::message::object_path objPath(path);
711             if (objPath.filename() != chassisId)
712             {
713                 continue;
714             }
715 
716             if (connectionNames.empty())
717             {
718                 BMCWEB_LOG_ERROR("Got 0 Connection names");
719                 continue;
720             }
721 
722             const std::vector<std::string>& interfaces3 =
723                 connectionNames[0].second;
724 
725             const std::array<const char*, 2> hasIndicatorLed = {
726                 "xyz.openbmc_project.Inventory.Item.Panel",
727                 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
728             bool indicatorChassis = false;
729             for (const char* interface : hasIndicatorLed)
730             {
731                 if (std::find(interfaces3.begin(), interfaces3.end(),
732                               interface) != interfaces3.end())
733                 {
734                     indicatorChassis = true;
735                     break;
736                 }
737             }
738             if (locationIndicatorActive)
739             {
740                 if (indicatorChassis)
741                 {
742                     setLocationIndicatorActive(asyncResp,
743                                                *locationIndicatorActive);
744                 }
745                 else
746                 {
747                     messages::propertyUnknown(asyncResp->res,
748                                               "LocationIndicatorActive");
749                 }
750             }
751             if (indicatorLed)
752             {
753                 if (indicatorChassis)
754                 {
755                     setIndicatorLedState(asyncResp, *indicatorLed);
756                 }
757                 else
758                 {
759                     messages::propertyUnknown(asyncResp->res, "IndicatorLED");
760                 }
761             }
762             return;
763         }
764 
765         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
766         });
767 }
768 
769 /**
770  * Chassis override class for delivering Chassis Schema
771  * Functions triggers appropriate requests on DBus
772  */
773 inline void requestRoutesChassis(App& app)
774 {
775     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
776         .privileges(redfish::privileges::getChassis)
777         .methods(boost::beast::http::verb::get)(
778             std::bind_front(handleChassisGet, std::ref(app)));
779 
780     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
781         .privileges(redfish::privileges::patchChassis)
782         .methods(boost::beast::http::verb::patch)(
783             std::bind_front(handleChassisPatch, std::ref(app)));
784 }
785 
786 /**
787  * Handle error responses from d-bus for chassis power cycles
788  */
789 inline void handleChassisPowerCycleError(const boost::system::error_code& ec,
790                                          const sdbusplus::message_t& eMsg,
791                                          crow::Response& res)
792 {
793     if (eMsg.get_error() == nullptr)
794     {
795         BMCWEB_LOG_ERROR("D-Bus response error: {}", ec);
796         messages::internalError(res);
797         return;
798     }
799     std::string_view errorMessage = eMsg.get_error()->name;
800 
801     // If operation failed due to BMC not being in Ready state, tell
802     // user to retry in a bit
803     if (errorMessage ==
804         std::string_view("xyz.openbmc_project.State.Chassis.Error.BMCNotReady"))
805     {
806         BMCWEB_LOG_DEBUG("BMC not ready, operation not allowed right now");
807         messages::serviceTemporarilyUnavailable(res, "10");
808         return;
809     }
810 
811     BMCWEB_LOG_ERROR("Chassis Power Cycle fail {} sdbusplus:{}", ec,
812                      errorMessage);
813     messages::internalError(res);
814 }
815 
816 inline void
817     doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
818 {
819     constexpr std::array<std::string_view, 1> interfaces = {
820         "xyz.openbmc_project.State.Chassis"};
821 
822     // Use mapper to get subtree paths.
823     dbus::utility::getSubTreePaths(
824         "/", 0, interfaces,
825         [asyncResp](
826             const boost::system::error_code& ec,
827             const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
828         if (ec)
829         {
830             BMCWEB_LOG_ERROR("[mapper] Bad D-Bus request error: {}", ec);
831             messages::internalError(asyncResp->res);
832             return;
833         }
834 
835         const char* processName = "xyz.openbmc_project.State.Chassis";
836         const char* interfaceName = "xyz.openbmc_project.State.Chassis";
837         const char* destProperty = "RequestedPowerTransition";
838         const std::string propertyValue =
839             "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
840         std::string objectPath = "/xyz/openbmc_project/state/chassis_system0";
841 
842         /* Look for system reset chassis path */
843         if ((std::find(chassisList.begin(), chassisList.end(), objectPath)) ==
844             chassisList.end())
845         {
846             /* We prefer to reset the full chassis_system, but if it doesn't
847              * exist on some platforms, fall back to a host-only power reset
848              */
849             objectPath = "/xyz/openbmc_project/state/chassis0";
850         }
851 
852         sdbusplus::asio::setProperty(
853             *crow::connections::systemBus, processName, objectPath,
854             interfaceName, destProperty, propertyValue,
855             [asyncResp](const boost::system::error_code& ec2,
856                         sdbusplus::message_t& sdbusErrMsg) {
857             // Use "Set" method to set the property value.
858             if (ec2)
859             {
860                 handleChassisPowerCycleError(ec2, sdbusErrMsg, asyncResp->res);
861 
862                 return;
863             }
864 
865             messages::success(asyncResp->res);
866             });
867         });
868 }
869 
870 inline void handleChassisResetActionInfoPost(
871     App& app, const crow::Request& req,
872     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
873     const std::string& /*chassisId*/)
874 {
875     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
876     {
877         return;
878     }
879     BMCWEB_LOG_DEBUG("Post Chassis Reset.");
880 
881     std::string resetType;
882 
883     if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType))
884     {
885         return;
886     }
887 
888     if (resetType != "PowerCycle")
889     {
890         BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType);
891         messages::actionParameterNotSupported(asyncResp->res, resetType,
892                                               "ResetType");
893 
894         return;
895     }
896     doChassisPowerCycle(asyncResp);
897 }
898 
899 /**
900  * ChassisResetAction class supports the POST method for the Reset
901  * action.
902  * Function handles POST method request.
903  * Analyzes POST body before sending Reset request data to D-Bus.
904  */
905 
906 inline void requestRoutesChassisResetAction(App& app)
907 {
908     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/")
909         .privileges(redfish::privileges::postChassis)
910         .methods(boost::beast::http::verb::post)(
911             std::bind_front(handleChassisResetActionInfoPost, std::ref(app)));
912 }
913 
914 inline void handleChassisResetActionInfoGet(
915     App& app, const crow::Request& req,
916     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
917     const std::string& chassisId)
918 {
919     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
920     {
921         return;
922     }
923     asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo";
924     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
925         "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId);
926     asyncResp->res.jsonValue["Name"] = "Reset Action Info";
927 
928     asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
929     nlohmann::json::array_t parameters;
930     nlohmann::json::object_t parameter;
931     parameter["Name"] = "ResetType";
932     parameter["Required"] = true;
933     parameter["DataType"] = "String";
934     nlohmann::json::array_t allowed;
935     allowed.emplace_back("PowerCycle");
936     parameter["AllowableValues"] = std::move(allowed);
937     parameters.emplace_back(std::move(parameter));
938 
939     asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
940 }
941 
942 /**
943  * ChassisResetActionInfo derived class for delivering Chassis
944  * ResetType AllowableValues using ResetInfo schema.
945  */
946 inline void requestRoutesChassisResetActionInfo(App& app)
947 {
948     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/")
949         .privileges(redfish::privileges::getActionInfo)
950         .methods(boost::beast::http::verb::get)(
951             std::bind_front(handleChassisResetActionInfoGet, std::ref(app)));
952 }
953 
954 } // namespace redfish
955