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