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