xref: /openbmc/bmcweb/redfish-core/lib/chassis.hpp (revision 340d74c8)
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, //
683             "IndicatorLED", indicatorLed, //
684             "LocationIndicatorActive", locationIndicatorActive //
685             ))
686     {
687         return;
688     }
689 
690     // TODO (Gunnar): Remove IndicatorLED after enough time has passed
691     if (!locationIndicatorActive && !indicatorLed)
692     {
693         return; // delete this when we support more patch properties
694     }
695     if (indicatorLed)
696     {
697         asyncResp->res.addHeader(
698             boost::beast::http::field::warning,
699             "299 - \"IndicatorLED is deprecated. Use LocationIndicatorActive instead.\"");
700     }
701 
702     constexpr std::array<std::string_view, 2> interfaces = {
703         "xyz.openbmc_project.Inventory.Item.Board",
704         "xyz.openbmc_project.Inventory.Item.Chassis"};
705 
706     const std::string& chassisId = param;
707 
708     dbus::utility::getSubTree(
709         "/xyz/openbmc_project/inventory", 0, interfaces,
710         [asyncResp, chassisId, locationIndicatorActive,
711          indicatorLed](const boost::system::error_code& ec,
712                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
713             if (ec)
714             {
715                 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
716                 messages::internalError(asyncResp->res);
717                 return;
718             }
719 
720             // Iterate over all retrieved ObjectPaths.
721             for (const std::pair<std::string,
722                                  std::vector<std::pair<
723                                      std::string, std::vector<std::string>>>>&
724                      object : subtree)
725             {
726                 const std::string& path = object.first;
727                 const std::vector<
728                     std::pair<std::string, std::vector<std::string>>>&
729                     connectionNames = object.second;
730 
731                 sdbusplus::message::object_path objPath(path);
732                 if (objPath.filename() != chassisId)
733                 {
734                     continue;
735                 }
736 
737                 if (connectionNames.empty())
738                 {
739                     BMCWEB_LOG_ERROR("Got 0 Connection names");
740                     continue;
741                 }
742 
743                 const std::vector<std::string>& interfaces3 =
744                     connectionNames[0].second;
745 
746                 const std::array<const char*, 3> hasIndicatorLed = {
747                     "xyz.openbmc_project.Inventory.Item.Chassis",
748                     "xyz.openbmc_project.Inventory.Item.Panel",
749                     "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
750                 bool indicatorChassis = false;
751                 for (const char* interface : hasIndicatorLed)
752                 {
753                     if (std::ranges::find(interfaces3, interface) !=
754                         interfaces3.end())
755                     {
756                         indicatorChassis = true;
757                         break;
758                     }
759                 }
760                 if (locationIndicatorActive)
761                 {
762                     if (indicatorChassis)
763                     {
764                         setSystemLocationIndicatorActive(
765                             asyncResp, *locationIndicatorActive);
766                     }
767                     else
768                     {
769                         messages::propertyUnknown(asyncResp->res,
770                                                   "LocationIndicatorActive");
771                     }
772                 }
773                 if (indicatorLed)
774                 {
775                     if (indicatorChassis)
776                     {
777                         setIndicatorLedState(asyncResp, *indicatorLed);
778                     }
779                     else
780                     {
781                         messages::propertyUnknown(asyncResp->res,
782                                                   "IndicatorLED");
783                     }
784                 }
785                 return;
786             }
787 
788             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
789         });
790 }
791 
792 /**
793  * Chassis override class for delivering Chassis Schema
794  * Functions triggers appropriate requests on DBus
795  */
796 inline void requestRoutesChassis(App& app)
797 {
798     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
799         .privileges(redfish::privileges::getChassis)
800         .methods(boost::beast::http::verb::get)(
801             std::bind_front(handleChassisGet, std::ref(app)));
802 
803     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
804         .privileges(redfish::privileges::patchChassis)
805         .methods(boost::beast::http::verb::patch)(
806             std::bind_front(handleChassisPatch, std::ref(app)));
807 }
808 
809 inline void
810     doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
811 {
812     constexpr std::array<std::string_view, 1> interfaces = {
813         "xyz.openbmc_project.State.Chassis"};
814 
815     // Use mapper to get subtree paths.
816     dbus::utility::getSubTreePaths(
817         "/", 0, interfaces,
818         [asyncResp](
819             const boost::system::error_code& ec,
820             const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
821             if (ec)
822             {
823                 BMCWEB_LOG_ERROR("[mapper] Bad D-Bus request error: {}", ec);
824                 messages::internalError(asyncResp->res);
825                 return;
826             }
827 
828             const char* processName = "xyz.openbmc_project.State.Chassis";
829             const char* interfaceName = "xyz.openbmc_project.State.Chassis";
830             const char* destProperty = "RequestedPowerTransition";
831             const std::string propertyValue =
832                 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
833             std::string objectPath =
834                 "/xyz/openbmc_project/state/chassis_system0";
835 
836             /* Look for system reset chassis path */
837             if ((std::ranges::find(chassisList, objectPath)) ==
838                 chassisList.end())
839             {
840                 /* We prefer to reset the full chassis_system, but if it doesn't
841                  * exist on some platforms, fall back to a host-only power reset
842                  */
843                 objectPath = "/xyz/openbmc_project/state/chassis0";
844             }
845 
846             setDbusProperty(asyncResp, "ResetType", processName, objectPath,
847                             interfaceName, destProperty, propertyValue);
848         });
849 }
850 
851 inline void handleChassisResetActionInfoPost(
852     App& app, const crow::Request& req,
853     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
854     const std::string& /*chassisId*/)
855 {
856     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
857     {
858         return;
859     }
860     BMCWEB_LOG_DEBUG("Post Chassis Reset.");
861 
862     std::string resetType;
863 
864     if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType))
865     {
866         return;
867     }
868 
869     if (resetType != "PowerCycle")
870     {
871         BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType);
872         messages::actionParameterNotSupported(asyncResp->res, resetType,
873                                               "ResetType");
874 
875         return;
876     }
877     doChassisPowerCycle(asyncResp);
878 }
879 
880 /**
881  * ChassisResetAction class supports the POST method for the Reset
882  * action.
883  * Function handles POST method request.
884  * Analyzes POST body before sending Reset request data to D-Bus.
885  */
886 
887 inline void requestRoutesChassisResetAction(App& app)
888 {
889     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/")
890         .privileges(redfish::privileges::postChassis)
891         .methods(boost::beast::http::verb::post)(
892             std::bind_front(handleChassisResetActionInfoPost, std::ref(app)));
893 }
894 
895 inline void handleChassisResetActionInfoGet(
896     App& app, const crow::Request& req,
897     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
898     const std::string& chassisId)
899 {
900     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
901     {
902         return;
903     }
904     asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo";
905     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
906         "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId);
907     asyncResp->res.jsonValue["Name"] = "Reset Action Info";
908 
909     asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
910     nlohmann::json::array_t parameters;
911     nlohmann::json::object_t parameter;
912     parameter["Name"] = "ResetType";
913     parameter["Required"] = true;
914     parameter["DataType"] = action_info::ParameterTypes::String;
915     nlohmann::json::array_t allowed;
916     allowed.emplace_back("PowerCycle");
917     parameter["AllowableValues"] = std::move(allowed);
918     parameters.emplace_back(std::move(parameter));
919 
920     asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
921 }
922 
923 /**
924  * ChassisResetActionInfo derived class for delivering Chassis
925  * ResetType AllowableValues using ResetInfo schema.
926  */
927 inline void requestRoutesChassisResetActionInfo(App& app)
928 {
929     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/")
930         .privileges(redfish::privileges::getActionInfo)
931         .methods(boost::beast::http::verb::get)(
932             std::bind_front(handleChassisResetActionInfoGet, std::ref(app)));
933 }
934 
935 } // namespace redfish
936