xref: /openbmc/bmcweb/redfish-core/lib/chassis.hpp (revision 56b81992)
1 /*
2 Copyright (c) 2018 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 
18 #include "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "generated/enums/action_info.hpp"
21 #include "generated/enums/chassis.hpp"
22 #include "generated/enums/resource.hpp"
23 #include "led.hpp"
24 #include "query.hpp"
25 #include "redfish_util.hpp"
26 #include "registries/privilege_registry.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30 
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/format.hpp>
33 #include <sdbusplus/asio/property.hpp>
34 #include <sdbusplus/message.hpp>
35 #include <sdbusplus/unpack_properties.hpp>
36 
37 #include <array>
38 #include <memory>
39 #include <ranges>
40 #include <string_view>
41 
42 namespace redfish
43 {
44 
45 inline chassis::ChassisType
46     translateChassisTypeToRedfish(const std::string_view& chassisType)
47 {
48     if (chassisType ==
49         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Blade")
50     {
51         return chassis::ChassisType::Blade;
52     }
53     if (chassisType ==
54         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Component")
55     {
56         return chassis::ChassisType::Component;
57     }
58     if (chassisType ==
59         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Enclosure")
60     {
61         return chassis::ChassisType::Enclosure;
62     }
63     if (chassisType ==
64         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Module")
65     {
66         return chassis::ChassisType::Module;
67     }
68     if (chassisType ==
69         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.RackMount")
70     {
71         return chassis::ChassisType::RackMount;
72     }
73     if (chassisType ==
74         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StandAlone")
75     {
76         return chassis::ChassisType::StandAlone;
77     }
78     if (chassisType ==
79         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StorageEnclosure")
80     {
81         return chassis::ChassisType::StorageEnclosure;
82     }
83     if (chassisType ==
84         "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Zone")
85     {
86         return chassis::ChassisType::Zone;
87     }
88     return chassis::ChassisType::Invalid;
89 }
90 
91 /**
92  * @brief Retrieves resources over dbus to link to the chassis
93  *
94  * @param[in] asyncResp  - Shared pointer for completing asynchronous
95  * calls
96  * @param[in] path       - Chassis dbus path to look for the storage.
97  *
98  * Calls the Association endpoints on the path + "/storage" and add the link of
99  * json["Links"]["Storage@odata.count"] =
100  *    {"@odata.id", "/redfish/v1/Storage/" + resourceId}
101  *
102  * @return None.
103  */
104 inline void getStorageLink(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
105                            const sdbusplus::message::object_path& path)
106 {
107     sdbusplus::asio::getProperty<std::vector<std::string>>(
108         *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper",
109         (path / "storage").str, "xyz.openbmc_project.Association", "endpoints",
110         [asyncResp](const boost::system::error_code& ec,
111                     const std::vector<std::string>& storageList) {
112             if (ec)
113             {
114                 BMCWEB_LOG_DEBUG("getStorageLink got DBUS response error");
115                 return;
116             }
117 
118             nlohmann::json::array_t storages;
119             for (const std::string& storagePath : storageList)
120             {
121                 std::string id =
122                     sdbusplus::message::object_path(storagePath).filename();
123                 if (id.empty())
124                 {
125                     continue;
126                 }
127 
128                 nlohmann::json::object_t storage;
129                 storage["@odata.id"] =
130                     boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
131                                         BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
132                 storages.emplace_back(std::move(storage));
133             }
134             asyncResp->res.jsonValue["Links"]["Storage@odata.count"] =
135                 storages.size();
136             asyncResp->res.jsonValue["Links"]["Storage"] = std::move(storages);
137         });
138 }
139 
140 /**
141  * @brief Retrieves chassis state properties over dbus
142  *
143  * @param[in] asyncResp - Shared pointer for completing asynchronous calls.
144  *
145  * @return None.
146  */
147 inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> asyncResp)
148 {
149     // crow::connections::systemBus->async_method_call(
150     sdbusplus::asio::getProperty<std::string>(
151         *crow::connections::systemBus, "xyz.openbmc_project.State.Chassis",
152         "/xyz/openbmc_project/state/chassis0",
153         "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
154         [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec,
155                                           const std::string& chassisState) {
156             if (ec)
157             {
158                 if (ec == boost::system::errc::host_unreachable)
159                 {
160                     // Service not available, no error, just don't return
161                     // chassis state info
162                     BMCWEB_LOG_DEBUG("Service not available {}", ec);
163                     return;
164                 }
165                 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
166                 messages::internalError(asyncResp->res);
167                 return;
168             }
169 
170             BMCWEB_LOG_DEBUG("Chassis state: {}", chassisState);
171             // Verify Chassis State
172             if (chassisState ==
173                 "xyz.openbmc_project.State.Chassis.PowerState.On")
174             {
175                 asyncResp->res.jsonValue["PowerState"] =
176                     resource::PowerState::On;
177                 asyncResp->res.jsonValue["Status"]["State"] =
178                     resource::State::Enabled;
179             }
180             else if (chassisState ==
181                      "xyz.openbmc_project.State.Chassis.PowerState.Off")
182             {
183                 asyncResp->res.jsonValue["PowerState"] =
184                     resource::PowerState::Off;
185                 asyncResp->res.jsonValue["Status"]["State"] =
186                     resource::State::StandbyOffline;
187             }
188         });
189 }
190 
191 /**
192  * Retrieves physical security properties over dbus
193  */
194 inline void handlePhysicalSecurityGetSubTree(
195     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
196     const boost::system::error_code& ec,
197     const dbus::utility::MapperGetSubTreeResponse& subtree)
198 {
199     if (ec)
200     {
201         // do not add err msg in redfish response, because this is not
202         //     mandatory property
203         BMCWEB_LOG_INFO("DBUS error: no matched iface {}", ec);
204         return;
205     }
206     // Iterate over all retrieved ObjectPaths.
207     for (const auto& object : subtree)
208     {
209         if (!object.second.empty())
210         {
211             const auto& service = object.second.front();
212 
213             BMCWEB_LOG_DEBUG("Get intrusion status by service ");
214 
215             sdbusplus::asio::getProperty<std::string>(
216                 *crow::connections::systemBus, service.first, object.first,
217                 "xyz.openbmc_project.Chassis.Intrusion", "Status",
218                 [asyncResp](const boost::system::error_code& ec1,
219                             const std::string& value) {
220                     if (ec1)
221                     {
222                         // do not add err msg in redfish response, because this
223                         // is not
224                         //     mandatory property
225                         BMCWEB_LOG_ERROR("DBUS response error {}", ec1);
226                         return;
227                     }
228                     asyncResp->res.jsonValue["PhysicalSecurity"]
229                                             ["IntrusionSensorNumber"] = 1;
230                     asyncResp->res
231                         .jsonValue["PhysicalSecurity"]["IntrusionSensor"] =
232                         value;
233                 });
234 
235             return;
236         }
237     }
238 }
239 
240 inline void handleChassisCollectionGet(
241     App& app, const crow::Request& req,
242     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
243 {
244     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
245     {
246         return;
247     }
248     asyncResp->res.jsonValue["@odata.type"] =
249         "#ChassisCollection.ChassisCollection";
250     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis";
251     asyncResp->res.jsonValue["Name"] = "Chassis Collection";
252 
253     constexpr std::array<std::string_view, 2> interfaces{
254         "xyz.openbmc_project.Inventory.Item.Board",
255         "xyz.openbmc_project.Inventory.Item.Chassis"};
256     collection_util::getCollectionMembers(
257         asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces,
258         "/xyz/openbmc_project/inventory");
259 }
260 
261 inline void getChassisContainedBy(
262     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
263     const std::string& chassisId, const boost::system::error_code& ec,
264     const dbus::utility::MapperGetSubTreePathsResponse& upstreamChassisPaths)
265 {
266     if (ec)
267     {
268         if (ec.value() != EBADR)
269         {
270             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
271             messages::internalError(asyncResp->res);
272         }
273         return;
274     }
275     if (upstreamChassisPaths.empty())
276     {
277         return;
278     }
279     if (upstreamChassisPaths.size() > 1)
280     {
281         BMCWEB_LOG_ERROR("{} is contained by multiple chassis", chassisId);
282         messages::internalError(asyncResp->res);
283         return;
284     }
285 
286     sdbusplus::message::object_path upstreamChassisPath(
287         upstreamChassisPaths[0]);
288     std::string upstreamChassis = upstreamChassisPath.filename();
289     if (upstreamChassis.empty())
290     {
291         BMCWEB_LOG_WARNING("Malformed upstream Chassis path {} on {}",
292                            upstreamChassisPath.str, chassisId);
293         return;
294     }
295 
296     asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] =
297         boost::urls::format("/redfish/v1/Chassis/{}", upstreamChassis);
298 }
299 
300 inline void getChassisContains(
301     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302     const std::string& chassisId, const boost::system::error_code& ec,
303     const dbus::utility::MapperGetSubTreePathsResponse& downstreamChassisPaths)
304 {
305     if (ec)
306     {
307         if (ec.value() != EBADR)
308         {
309             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
310             messages::internalError(asyncResp->res);
311         }
312         return;
313     }
314     if (downstreamChassisPaths.empty())
315     {
316         return;
317     }
318     nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"];
319     if (!jValue.is_array())
320     {
321         // Create the array if it was empty
322         jValue = nlohmann::json::array();
323     }
324     for (const auto& p : downstreamChassisPaths)
325     {
326         sdbusplus::message::object_path downstreamChassisPath(p);
327         std::string downstreamChassis = downstreamChassisPath.filename();
328         if (downstreamChassis.empty())
329         {
330             BMCWEB_LOG_WARNING("Malformed downstream Chassis path {} on {}",
331                                downstreamChassisPath.str, chassisId);
332             continue;
333         }
334         nlohmann::json link;
335         link["@odata.id"] =
336             boost::urls::format("/redfish/v1/Chassis/{}", downstreamChassis);
337         jValue.push_back(std::move(link));
338     }
339     asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size();
340 }
341 
342 inline void getChassisConnectivity(
343     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
344     const std::string& chassisId, const std::string& chassisPath)
345 {
346     BMCWEB_LOG_DEBUG("Get chassis connectivity");
347 
348     constexpr std::array<std::string_view, 2> interfaces{
349         "xyz.openbmc_project.Inventory.Item.Board",
350         "xyz.openbmc_project.Inventory.Item.Chassis"};
351 
352     dbus::utility::getAssociatedSubTreePaths(
353         chassisPath + "/contained_by",
354         sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
355         interfaces,
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         interfaces, 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     sdbusplus::asio::getProperty<std::string>(
381         *crow::connections::systemBus, 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     sdbusplus::asio::getProperty<std::string>(
403         *crow::connections::systemBus, connectionName, path,
404         "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     if (type != nullptr)
529     {
530         auto chassisType = translateChassisTypeToRedfish(*type);
531         if (chassisType != chassis::ChassisType::Invalid)
532         {
533             asyncResp->res.jsonValue["ChassisType"] = chassisType;
534         }
535     }
536     else
537     {
538         asyncResp->res.jsonValue["ChassisType"] =
539             chassis::ChassisType::RackMount;
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                 sdbusplus::asio::getProperty<std::string>(
623                     *crow::connections::systemBus, connectionName, path,
624                     assetTagInterface, "AssetTag",
625                     [asyncResp, chassisId](const boost::system::error_code& ec2,
626                                            const std::string& property) {
627                         if (ec2)
628                         {
629                             BMCWEB_LOG_ERROR(
630                                 "DBus response error for AssetTag: {}", ec2);
631                             messages::internalError(asyncResp->res);
632                             return;
633                         }
634                         asyncResp->res.jsonValue["AssetTag"] = property;
635                     });
636             }
637             else if (interface == replaceableInterface)
638             {
639                 sdbusplus::asio::getProperty<bool>(
640                     *crow::connections::systemBus, connectionName, path,
641                     replaceableInterface, "HotPluggable",
642                     [asyncResp, chassisId](const boost::system::error_code& ec2,
643                                            const bool property) {
644                         if (ec2)
645                         {
646                             BMCWEB_LOG_ERROR(
647                                 "DBus response error for HotPluggable: {}",
648                                 ec2);
649                             messages::internalError(asyncResp->res);
650                             return;
651                         }
652                         asyncResp->res.jsonValue["HotPluggable"] = property;
653                     });
654             }
655             else if (interface == revisionInterface)
656             {
657                 sdbusplus::asio::getProperty<std::string>(
658                     *crow::connections::systemBus, connectionName, path,
659                     revisionInterface, "Version",
660                     [asyncResp, chassisId](const boost::system::error_code& ec2,
661                                            const std::string& property) {
662                         if (ec2)
663                         {
664                             BMCWEB_LOG_ERROR(
665                                 "DBus response error for Version: {}", ec2);
666                             messages::internalError(asyncResp->res);
667                             return;
668                         }
669                         asyncResp->res.jsonValue["Version"] = property;
670                     });
671             }
672         }
673 
674         for (const char* interface : hasIndicatorLed)
675         {
676             if (std::ranges::find(interfaces2, interface) != interfaces2.end())
677             {
678                 getIndicatorLedState(asyncResp);
679                 getSystemLocationIndicatorActive(asyncResp);
680                 break;
681             }
682         }
683 
684         sdbusplus::asio::getAllProperties(
685             *crow::connections::systemBus, connectionName, path,
686             "xyz.openbmc_project.Inventory.Decorator.Asset",
687             [asyncResp, chassisId,
688              path](const boost::system::error_code&,
689                    const dbus::utility::DBusPropertiesMap& propertiesList) {
690                 handleDecoratorAssetProperties(asyncResp, chassisId, path,
691                                                propertiesList);
692             });
693 
694         sdbusplus::asio::getAllProperties(
695             *crow::connections::systemBus, connectionName, path,
696             "xyz.openbmc_project.Inventory.Item.Chassis",
697             [asyncResp](
698                 const boost::system::error_code&,
699                 const dbus::utility::DBusPropertiesMap& propertiesList) {
700                 handleChassisProperties(asyncResp, propertiesList);
701             });
702 
703         for (const auto& interface : interfaces2)
704         {
705             if (interface == "xyz.openbmc_project.Common.UUID")
706             {
707                 getChassisUUID(asyncResp, connectionName, path);
708             }
709             else if (interface ==
710                      "xyz.openbmc_project.Inventory.Decorator.LocationCode")
711             {
712                 getChassisLocationCode(asyncResp, connectionName, path);
713             }
714         }
715 
716         return;
717     }
718 
719     // Couldn't find an object with that name.  return an error
720     messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
721 }
722 
723 inline void
724     handleChassisGet(App& app, const crow::Request& req,
725                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
726                      const std::string& chassisId)
727 {
728     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
729     {
730         return;
731     }
732     constexpr std::array<std::string_view, 2> interfaces = {
733         "xyz.openbmc_project.Inventory.Item.Board",
734         "xyz.openbmc_project.Inventory.Item.Chassis"};
735 
736     dbus::utility::getSubTree(
737         "/xyz/openbmc_project/inventory", 0, interfaces,
738         std::bind_front(handleChassisGetSubTree, asyncResp, chassisId));
739 
740     constexpr std::array<std::string_view, 1> interfaces2 = {
741         "xyz.openbmc_project.Chassis.Intrusion"};
742 
743     dbus::utility::getSubTree(
744         "/xyz/openbmc_project", 0, interfaces2,
745         std::bind_front(handlePhysicalSecurityGetSubTree, asyncResp));
746 }
747 
748 inline void
749     handleChassisPatch(App& app, const crow::Request& req,
750                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
751                        const std::string& param)
752 {
753     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
754     {
755         return;
756     }
757     std::optional<bool> locationIndicatorActive;
758     std::optional<std::string> indicatorLed;
759 
760     if (param.empty())
761     {
762         return;
763     }
764 
765     if (!json_util::readJsonPatch( //
766             req, asyncResp->res, //
767             "IndicatorLED", indicatorLed, //
768             "LocationIndicatorActive", locationIndicatorActive //
769             ))
770     {
771         return;
772     }
773 
774     // TODO (Gunnar): Remove IndicatorLED after enough time has passed
775     if (!locationIndicatorActive && !indicatorLed)
776     {
777         return; // delete this when we support more patch properties
778     }
779     if (indicatorLed)
780     {
781         asyncResp->res.addHeader(
782             boost::beast::http::field::warning,
783             "299 - \"IndicatorLED is deprecated. Use LocationIndicatorActive instead.\"");
784     }
785 
786     constexpr std::array<std::string_view, 2> interfaces = {
787         "xyz.openbmc_project.Inventory.Item.Board",
788         "xyz.openbmc_project.Inventory.Item.Chassis"};
789 
790     const std::string& chassisId = param;
791 
792     dbus::utility::getSubTree(
793         "/xyz/openbmc_project/inventory", 0, interfaces,
794         [asyncResp, chassisId, locationIndicatorActive,
795          indicatorLed](const boost::system::error_code& ec,
796                        const dbus::utility::MapperGetSubTreeResponse& subtree) {
797             if (ec)
798             {
799                 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
800                 messages::internalError(asyncResp->res);
801                 return;
802             }
803 
804             // Iterate over all retrieved ObjectPaths.
805             for (const std::pair<std::string,
806                                  std::vector<std::pair<
807                                      std::string, std::vector<std::string>>>>&
808                      object : subtree)
809             {
810                 const std::string& path = object.first;
811                 const std::vector<
812                     std::pair<std::string, std::vector<std::string>>>&
813                     connectionNames = object.second;
814 
815                 sdbusplus::message::object_path objPath(path);
816                 if (objPath.filename() != chassisId)
817                 {
818                     continue;
819                 }
820 
821                 if (connectionNames.empty())
822                 {
823                     BMCWEB_LOG_ERROR("Got 0 Connection names");
824                     continue;
825                 }
826 
827                 const std::vector<std::string>& interfaces3 =
828                     connectionNames[0].second;
829 
830                 const std::array<const char*, 3> hasIndicatorLed = {
831                     "xyz.openbmc_project.Inventory.Item.Chassis",
832                     "xyz.openbmc_project.Inventory.Item.Panel",
833                     "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
834                 bool indicatorChassis = false;
835                 for (const char* interface : hasIndicatorLed)
836                 {
837                     if (std::ranges::find(interfaces3, interface) !=
838                         interfaces3.end())
839                     {
840                         indicatorChassis = true;
841                         break;
842                     }
843                 }
844                 if (locationIndicatorActive)
845                 {
846                     if (indicatorChassis)
847                     {
848                         setSystemLocationIndicatorActive(
849                             asyncResp, *locationIndicatorActive);
850                     }
851                     else
852                     {
853                         messages::propertyUnknown(asyncResp->res,
854                                                   "LocationIndicatorActive");
855                     }
856                 }
857                 if (indicatorLed)
858                 {
859                     if (indicatorChassis)
860                     {
861                         setIndicatorLedState(asyncResp, *indicatorLed);
862                     }
863                     else
864                     {
865                         messages::propertyUnknown(asyncResp->res,
866                                                   "IndicatorLED");
867                     }
868                 }
869                 return;
870             }
871 
872             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
873         });
874 }
875 
876 /**
877  * Chassis override class for delivering Chassis Schema
878  * Functions triggers appropriate requests on DBus
879  */
880 inline void requestRoutesChassis(App& app)
881 {
882     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
883         .privileges(redfish::privileges::getChassis)
884         .methods(boost::beast::http::verb::get)(
885             std::bind_front(handleChassisGet, std::ref(app)));
886 
887     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
888         .privileges(redfish::privileges::patchChassis)
889         .methods(boost::beast::http::verb::patch)(
890             std::bind_front(handleChassisPatch, std::ref(app)));
891 }
892 
893 inline void
894     doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
895 {
896     constexpr std::array<std::string_view, 1> interfaces = {
897         "xyz.openbmc_project.State.Chassis"};
898 
899     // Use mapper to get subtree paths.
900     dbus::utility::getSubTreePaths(
901         "/", 0, interfaces,
902         [asyncResp](
903             const boost::system::error_code& ec,
904             const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
905             if (ec)
906             {
907                 BMCWEB_LOG_ERROR("[mapper] Bad D-Bus request error: {}", ec);
908                 messages::internalError(asyncResp->res);
909                 return;
910             }
911 
912             const char* processName = "xyz.openbmc_project.State.Chassis";
913             const char* interfaceName = "xyz.openbmc_project.State.Chassis";
914             const char* destProperty = "RequestedPowerTransition";
915             const std::string propertyValue =
916                 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
917             std::string objectPath =
918                 "/xyz/openbmc_project/state/chassis_system0";
919 
920             /* Look for system reset chassis path */
921             if ((std::ranges::find(chassisList, objectPath)) ==
922                 chassisList.end())
923             {
924                 /* We prefer to reset the full chassis_system, but if it doesn't
925                  * exist on some platforms, fall back to a host-only power reset
926                  */
927                 objectPath = "/xyz/openbmc_project/state/chassis0";
928             }
929 
930             setDbusProperty(asyncResp, "ResetType", processName, objectPath,
931                             interfaceName, destProperty, propertyValue);
932         });
933 }
934 
935 inline void handleChassisResetActionInfoPost(
936     App& app, const crow::Request& req,
937     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
938     const std::string& /*chassisId*/)
939 {
940     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
941     {
942         return;
943     }
944     BMCWEB_LOG_DEBUG("Post Chassis Reset.");
945 
946     std::string resetType;
947 
948     if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType))
949     {
950         return;
951     }
952 
953     if (resetType != "PowerCycle")
954     {
955         BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType);
956         messages::actionParameterNotSupported(asyncResp->res, resetType,
957                                               "ResetType");
958 
959         return;
960     }
961     doChassisPowerCycle(asyncResp);
962 }
963 
964 /**
965  * ChassisResetAction class supports the POST method for the Reset
966  * action.
967  * Function handles POST method request.
968  * Analyzes POST body before sending Reset request data to D-Bus.
969  */
970 
971 inline void requestRoutesChassisResetAction(App& app)
972 {
973     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/")
974         .privileges(redfish::privileges::postChassis)
975         .methods(boost::beast::http::verb::post)(
976             std::bind_front(handleChassisResetActionInfoPost, std::ref(app)));
977 }
978 
979 inline void handleChassisResetActionInfoGet(
980     App& app, const crow::Request& req,
981     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
982     const std::string& chassisId)
983 {
984     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
985     {
986         return;
987     }
988     asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo";
989     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
990         "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId);
991     asyncResp->res.jsonValue["Name"] = "Reset Action Info";
992 
993     asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
994     nlohmann::json::array_t parameters;
995     nlohmann::json::object_t parameter;
996     parameter["Name"] = "ResetType";
997     parameter["Required"] = true;
998     parameter["DataType"] = action_info::ParameterTypes::String;
999     nlohmann::json::array_t allowed;
1000     allowed.emplace_back("PowerCycle");
1001     parameter["AllowableValues"] = std::move(allowed);
1002     parameters.emplace_back(std::move(parameter));
1003 
1004     asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
1005 }
1006 
1007 /**
1008  * ChassisResetActionInfo derived class for delivering Chassis
1009  * ResetType AllowableValues using ResetInfo schema.
1010  */
1011 inline void requestRoutesChassisResetActionInfo(App& app)
1012 {
1013     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/")
1014         .privileges(redfish::privileges::getActionInfo)
1015         .methods(boost::beast::http::verb::get)(
1016             std::bind_front(handleChassisResetActionInfoGet, std::ref(app)));
1017 }
1018 
1019 } // namespace redfish
1020