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