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