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