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