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