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