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