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