xref: /openbmc/bmcweb/redfish-core/lib/chassis.hpp (revision 2c6ffdb08b2207ff7c31041f77cc3755508d45c4)
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 "bmcweb_config.h"
19 
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "health.hpp"
23 #include "led.hpp"
24 #include "query.hpp"
25 #include "registries/privilege_registry.hpp"
26 #include "utils/collection.hpp"
27 #include "utils/dbus_utils.hpp"
28 #include "utils/json_utils.hpp"
29 
30 #include <boost/system/error_code.hpp>
31 #include <boost/url/format.hpp>
32 #include <sdbusplus/asio/property.hpp>
33 #include <sdbusplus/message.hpp>
34 #include <sdbusplus/unpack_properties.hpp>
35 
36 #include <array>
37 #include <string_view>
38 
39 namespace redfish
40 {
41 
42 /**
43  * @brief Retrieves resources over dbus to link to the chassis
44  *
45  * @param[in] asyncResp  - Shared pointer for completing asynchronous
46  * calls
47  * @param[in] path       - Chassis dbus path to look for the storage.
48  *
49  * Calls the Association endpoints on the path + "/storage" and add the link of
50  * json["Links"]["Storage@odata.count"] =
51  *    {"@odata.id", "/redfish/v1/Storage/" + resourceId}
52  *
53  * @return None.
54  */
55 inline void getStorageLink(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
56                            const sdbusplus::message::object_path& path)
57 {
58     sdbusplus::asio::getProperty<std::vector<std::string>>(
59         *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper",
60         (path / "storage").str, "xyz.openbmc_project.Association", "endpoints",
61         [asyncResp](const boost::system::error_code& ec,
62                     const std::vector<std::string>& storageList) {
63         if (ec)
64         {
65             BMCWEB_LOG_DEBUG << "getStorageLink got DBUS response error";
66             return;
67         }
68 
69         nlohmann::json::array_t storages;
70         for (const std::string& storagePath : storageList)
71         {
72             std::string id =
73                 sdbusplus::message::object_path(storagePath).filename();
74             if (id.empty())
75             {
76                 continue;
77             }
78 
79             nlohmann::json::object_t storage;
80             storage["@odata.id"] = boost::urls::format(
81                 "/redfish/v1/Systems/system/Storage/{}", 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 inline void getIntrusionByService(std::shared_ptr<bmcweb::AsyncResp> asyncResp,
137                                   const std::string& service,
138                                   const std::string& objPath)
139 {
140     BMCWEB_LOG_DEBUG << "Get intrusion status by service \n";
141 
142     sdbusplus::asio::getProperty<std::string>(
143         *crow::connections::systemBus, service, objPath,
144         "xyz.openbmc_project.Chassis.Intrusion", "Status",
145         [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec,
146                                           const std::string& value) {
147         if (ec)
148         {
149             // do not add err msg in redfish response, because this is not
150             //     mandatory property
151             BMCWEB_LOG_ERROR << "DBUS response error " << ec << "\n";
152             return;
153         }
154 
155         asyncResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensorNumber"] =
156             1;
157         asyncResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensor"] = value;
158         });
159 }
160 
161 /**
162  * Retrieves physical security properties over dbus
163  */
164 inline void
165     getPhysicalSecurityData(std::shared_ptr<bmcweb::AsyncResp> asyncResp)
166 {
167     constexpr std::array<std::string_view, 1> interfaces = {
168         "xyz.openbmc_project.Chassis.Intrusion"};
169     dbus::utility::getSubTree(
170         "/xyz/openbmc_project/Intrusion", 1, interfaces,
171         [asyncResp{std::move(asyncResp)}](
172             const boost::system::error_code& ec,
173             const dbus::utility::MapperGetSubTreeResponse& subtree) {
174         if (ec)
175         {
176             // do not add err msg in redfish response, because this is not
177             //     mandatory property
178             BMCWEB_LOG_INFO << "DBUS error: no matched iface " << ec << "\n";
179             return;
180         }
181         // Iterate over all retrieved ObjectPaths.
182         for (const auto& object : subtree)
183         {
184             if (!object.second.empty())
185             {
186                 const auto service = object.second.front();
187                 getIntrusionByService(asyncResp, service.first, object.first);
188                 return;
189             }
190         }
191         });
192 }
193 
194 inline void handleChassisCollectionGet(
195     App& app, const crow::Request& req,
196     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
197 {
198     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
199     {
200         return;
201     }
202     asyncResp->res.jsonValue["@odata.type"] =
203         "#ChassisCollection.ChassisCollection";
204     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis";
205     asyncResp->res.jsonValue["Name"] = "Chassis Collection";
206 
207     constexpr std::array<std::string_view, 2> interfaces{
208         "xyz.openbmc_project.Inventory.Item.Board",
209         "xyz.openbmc_project.Inventory.Item.Chassis"};
210     collection_util::getCollectionMembers(
211         asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces);
212 }
213 
214 inline void getChassisContainedBy(
215     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
216     const std::string& chassisId, const boost::system::error_code& ec,
217     const dbus::utility::MapperEndPoints& upstreamChassisPaths)
218 {
219     if (ec)
220     {
221         if (ec.value() != EBADR)
222         {
223             BMCWEB_LOG_ERROR << "DBUS response error " << ec;
224             messages::internalError(asyncResp->res);
225         }
226         return;
227     }
228     if (upstreamChassisPaths.empty())
229     {
230         return;
231     }
232     if (upstreamChassisPaths.size() > 1)
233     {
234         BMCWEB_LOG_ERROR << chassisId << " is contained by mutliple chassis";
235         messages::internalError(asyncResp->res);
236         return;
237     }
238 
239     sdbusplus::message::object_path upstreamChassisPath(
240         upstreamChassisPaths[0]);
241     std::string upstreamChassis = upstreamChassisPath.filename();
242     if (upstreamChassis.empty())
243     {
244         BMCWEB_LOG_WARNING << "Malformed upstream Chassis path "
245                            << upstreamChassisPath.str << " on " << chassisId;
246         return;
247     }
248 
249     asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] =
250         boost::urls::format("/redfish/v1/Chassis/{}", upstreamChassis);
251 }
252 
253 inline void getChassisContains(
254     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
255     const std::string& chassisId, const boost::system::error_code& ec,
256     const dbus::utility::MapperEndPoints& downstreamChassisPaths)
257 {
258     if (ec)
259     {
260         if (ec.value() != EBADR)
261         {
262             BMCWEB_LOG_ERROR << "DBUS response error " << ec;
263             messages::internalError(asyncResp->res);
264         }
265         return;
266     }
267     if (downstreamChassisPaths.empty())
268     {
269         return;
270     }
271     nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"];
272     if (!jValue.is_array())
273     {
274         // Create the array if it was empty
275         jValue = nlohmann::json::array();
276     }
277     for (const auto& p : downstreamChassisPaths)
278     {
279         sdbusplus::message::object_path downstreamChassisPath(p);
280         std::string downstreamChassis = downstreamChassisPath.filename();
281         if (downstreamChassis.empty())
282         {
283             BMCWEB_LOG_WARNING << "Malformed downstream Chassis path "
284                                << downstreamChassisPath.str << " on "
285                                << chassisId;
286             continue;
287         }
288         nlohmann::json link;
289         link["@odata.id"] = boost::urls::format("/redfish/v1/Chassis/{}",
290                                                 downstreamChassis);
291         jValue.push_back(std::move(link));
292     }
293     asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size();
294 }
295 
296 inline void
297     getChassisConnectivity(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
298                            const std::string& chassisId,
299                            const std::string& chassisPath)
300 {
301     BMCWEB_LOG_DEBUG << "Get chassis connectivity";
302 
303     dbus::utility::getAssociationEndPoints(
304         chassisPath + "/contained_by",
305         std::bind_front(getChassisContainedBy, asyncResp, chassisId));
306 
307     dbus::utility::getAssociationEndPoints(
308         chassisPath + "/containing",
309         std::bind_front(getChassisContains, asyncResp, chassisId));
310 }
311 
312 /**
313  * ChassisCollection derived class for delivering Chassis Collection Schema
314  *  Functions triggers appropriate requests on DBus
315  */
316 inline void requestRoutesChassisCollection(App& app)
317 {
318     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/")
319         .privileges(redfish::privileges::getChassisCollection)
320         .methods(boost::beast::http::verb::get)(
321             std::bind_front(handleChassisCollectionGet, std::ref(app)));
322 }
323 
324 inline void
325     getChassisLocationCode(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
326                            const std::string& connectionName,
327                            const std::string& path)
328 {
329     sdbusplus::asio::getProperty<std::string>(
330         *crow::connections::systemBus, connectionName, path,
331         "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
332         [asyncResp](const boost::system::error_code& ec,
333                     const std::string& property) {
334         if (ec)
335         {
336             BMCWEB_LOG_ERROR << "DBUS response error for Location";
337             messages::internalError(asyncResp->res);
338             return;
339         }
340 
341         asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
342             property;
343         });
344 }
345 
346 inline void getChassisUUID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
347                            const std::string& connectionName,
348                            const std::string& path)
349 {
350     sdbusplus::asio::getProperty<std::string>(
351         *crow::connections::systemBus, connectionName, path,
352         "xyz.openbmc_project.Common.UUID", "UUID",
353         [asyncResp](const boost::system::error_code& ec,
354                     const std::string& chassisUUID) {
355         if (ec)
356         {
357             BMCWEB_LOG_ERROR << "DBUS response error for UUID";
358             messages::internalError(asyncResp->res);
359             return;
360         }
361         asyncResp->res.jsonValue["UUID"] = chassisUUID;
362         });
363 }
364 
365 inline void
366     handleChassisGet(App& app, const crow::Request& req,
367                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
368                      const std::string& chassisId)
369 {
370     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
371     {
372         return;
373     }
374     constexpr std::array<std::string_view, 2> interfaces = {
375         "xyz.openbmc_project.Inventory.Item.Board",
376         "xyz.openbmc_project.Inventory.Item.Chassis"};
377 
378     dbus::utility::getSubTree(
379         "/xyz/openbmc_project/inventory", 0, interfaces,
380         [asyncResp, chassisId(std::string(chassisId))](
381             const boost::system::error_code& ec,
382             const dbus::utility::MapperGetSubTreeResponse& subtree) {
383         if (ec)
384         {
385             BMCWEB_LOG_ERROR << "DBUS response error " << ec;
386             messages::internalError(asyncResp->res);
387             return;
388         }
389         // Iterate over all retrieved ObjectPaths.
390         for (const std::pair<
391                  std::string,
392                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
393                  object : subtree)
394         {
395             const std::string& path = object.first;
396             const std::vector<std::pair<std::string, std::vector<std::string>>>&
397                 connectionNames = object.second;
398 
399             sdbusplus::message::object_path objPath(path);
400             if (objPath.filename() != chassisId)
401             {
402                 continue;
403             }
404 
405             getChassisConnectivity(asyncResp, chassisId, path);
406 
407             auto health = std::make_shared<HealthPopulate>(asyncResp);
408 
409             if constexpr (bmcwebEnableHealthPopulate)
410             {
411                 dbus::utility::getAssociationEndPoints(
412                     path + "/all_sensors",
413                     [health](const boost::system::error_code& ec2,
414                              const dbus::utility::MapperEndPoints& resp) {
415                     if (ec2)
416                     {
417                         return; // no sensors = no failures
418                     }
419                     health->inventory = resp;
420                     });
421 
422                 health->populate();
423             }
424 
425             if (connectionNames.empty())
426             {
427                 BMCWEB_LOG_ERROR << "Got 0 Connection names";
428                 continue;
429             }
430 
431             asyncResp->res.jsonValue["@odata.type"] =
432                 "#Chassis.v1_22_0.Chassis";
433             asyncResp->res.jsonValue["@odata.id"] =
434                 boost::urls::format("/redfish/v1/Chassis/{}", chassisId);
435             asyncResp->res.jsonValue["Name"] = "Chassis Collection";
436             asyncResp->res.jsonValue["ChassisType"] = "RackMount";
437             asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"]["target"] =
438                 boost::urls::format(
439                     "/redfish/v1/Chassis/{}/Actions/Chassis.Reset", chassisId);
440             asyncResp->res
441                 .jsonValue["Actions"]["#Chassis.Reset"]["@Redfish.ActionInfo"] =
442                 boost::urls::format("/redfish/v1/Chassis/{}/ResetActionInfo",
443                                     chassisId);
444             asyncResp->res.jsonValue["PCIeDevices"]["@odata.id"] =
445                 "/redfish/v1/Systems/system/PCIeDevices";
446 
447             dbus::utility::getAssociationEndPoints(
448                 path + "/drive",
449                 [asyncResp,
450                  chassisId](const boost::system::error_code& ec3,
451                             const dbus::utility::MapperEndPoints& resp) {
452                 if (ec3 || resp.empty())
453                 {
454                     return; // no drives = no failures
455                 }
456 
457                 nlohmann::json reference;
458                 reference["@odata.id"] = boost::urls::format(
459                     "/redfish/v1/Chassis/{}/Drives", chassisId);
460                 asyncResp->res.jsonValue["Drives"] = std::move(reference);
461                 });
462 
463             const std::string& connectionName = connectionNames[0].first;
464 
465             const std::vector<std::string>& interfaces2 =
466                 connectionNames[0].second;
467             const std::array<const char*, 2> hasIndicatorLed = {
468                 "xyz.openbmc_project.Inventory.Item.Panel",
469                 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
470 
471             const std::string assetTagInterface =
472                 "xyz.openbmc_project.Inventory.Decorator.AssetTag";
473             const std::string replaceableInterface =
474                 "xyz.openbmc_project.Inventory.Decorator.Replaceable";
475             for (const auto& interface : interfaces2)
476             {
477                 if (interface == assetTagInterface)
478                 {
479                     sdbusplus::asio::getProperty<std::string>(
480                         *crow::connections::systemBus, connectionName, path,
481                         assetTagInterface, "AssetTag",
482                         [asyncResp,
483                          chassisId](const boost::system::error_code& ec2,
484                                     const std::string& property) {
485                         if (ec2)
486                         {
487                             BMCWEB_LOG_ERROR
488                                 << "DBus response error for AssetTag: " << ec2;
489                             messages::internalError(asyncResp->res);
490                             return;
491                         }
492                         asyncResp->res.jsonValue["AssetTag"] = property;
493                         });
494                 }
495                 else if (interface == replaceableInterface)
496                 {
497                     sdbusplus::asio::getProperty<bool>(
498                         *crow::connections::systemBus, connectionName, path,
499                         replaceableInterface, "HotPluggable",
500                         [asyncResp,
501                          chassisId](const boost::system::error_code& ec2,
502                                     const bool property) {
503                         if (ec2)
504                         {
505                             BMCWEB_LOG_ERROR
506                                 << "DBus response error for HotPluggable: "
507                                 << ec2;
508                             messages::internalError(asyncResp->res);
509                             return;
510                         }
511                         asyncResp->res.jsonValue["HotPluggable"] = property;
512                         });
513                 }
514             }
515 
516             for (const char* interface : hasIndicatorLed)
517             {
518                 if (std::find(interfaces2.begin(), interfaces2.end(),
519                               interface) != interfaces2.end())
520                 {
521                     getIndicatorLedState(asyncResp);
522                     getLocationIndicatorActive(asyncResp);
523                     break;
524                 }
525             }
526 
527             sdbusplus::asio::getAllProperties(
528                 *crow::connections::systemBus, connectionName, path,
529                 "xyz.openbmc_project.Inventory.Decorator.Asset",
530                 [asyncResp, chassisId(std::string(chassisId)),
531                  path](const boost::system::error_code& /*ec2*/,
532                        const dbus::utility::DBusPropertiesMap& propertiesList) {
533                 const std::string* partNumber = nullptr;
534                 const std::string* serialNumber = nullptr;
535                 const std::string* manufacturer = nullptr;
536                 const std::string* model = nullptr;
537                 const std::string* sparePartNumber = nullptr;
538 
539                 const bool success = sdbusplus::unpackPropertiesNoThrow(
540                     dbus_utils::UnpackErrorPrinter(), propertiesList,
541                     "PartNumber", partNumber, "SerialNumber", serialNumber,
542                     "Manufacturer", manufacturer, "Model", model,
543                     "SparePartNumber", sparePartNumber);
544 
545                 if (!success)
546                 {
547                     messages::internalError(asyncResp->res);
548                     return;
549                 }
550 
551                 if (partNumber != nullptr)
552                 {
553                     asyncResp->res.jsonValue["PartNumber"] = *partNumber;
554                 }
555 
556                 if (serialNumber != nullptr)
557                 {
558                     asyncResp->res.jsonValue["SerialNumber"] = *serialNumber;
559                 }
560 
561                 if (manufacturer != nullptr)
562                 {
563                     asyncResp->res.jsonValue["Manufacturer"] = *manufacturer;
564                 }
565 
566                 if (model != nullptr)
567                 {
568                     asyncResp->res.jsonValue["Model"] = *model;
569                 }
570 
571                 // SparePartNumber is optional on D-Bus
572                 // so skip if it is empty
573                 if (sparePartNumber != nullptr && !sparePartNumber->empty())
574                 {
575                     asyncResp->res.jsonValue["SparePartNumber"] =
576                         *sparePartNumber;
577                 }
578 
579                 asyncResp->res.jsonValue["Name"] = chassisId;
580                 asyncResp->res.jsonValue["Id"] = chassisId;
581 #ifdef BMCWEB_ALLOW_DEPRECATED_POWER_THERMAL
582                 asyncResp->res.jsonValue["Thermal"]["@odata.id"] =
583                     boost::urls::format("/redfish/v1/Chassis/{}/Thermal",
584                                         chassisId);
585                 // Power object
586                 asyncResp->res.jsonValue["Power"]["@odata.id"] =
587                     boost::urls::format("/redfish/v1/Chassis/{}/Power",
588                                         chassisId);
589 #endif
590 #ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM
591                 asyncResp->res.jsonValue["ThermalSubsystem"]["@odata.id"] =
592                     boost::urls::format(
593                         "/redfish/v1/Chassis/{}/ThermalSubsystem", chassisId);
594                 asyncResp->res.jsonValue["PowerSubsystem"]["@odata.id"] =
595                     boost::urls::format("/redfish/v1/Chassis/{}/PowerSubsystem",
596                                         chassisId);
597                 asyncResp->res.jsonValue["EnvironmentMetrics"]["@odata.id"] =
598                     boost::urls::format(
599                         "/redfish/v1/Chassis/{}/EnvironmentMetrics", chassisId);
600 #endif
601                 // SensorCollection
602                 asyncResp->res.jsonValue["Sensors"]["@odata.id"] =
603                     boost::urls::format("/redfish/v1/Chassis/{}/Sensors",
604                                         chassisId);
605                 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
606 
607                 nlohmann::json::array_t computerSystems;
608                 nlohmann::json::object_t system;
609                 system["@odata.id"] = "/redfish/v1/Systems/system";
610                 computerSystems.emplace_back(std::move(system));
611                 asyncResp->res.jsonValue["Links"]["ComputerSystems"] =
612                     std::move(computerSystems);
613 
614                 nlohmann::json::array_t managedBy;
615                 nlohmann::json::object_t manager;
616                 manager["@odata.id"] = "/redfish/v1/Managers/bmc";
617                 managedBy.emplace_back(std::move(manager));
618                 asyncResp->res.jsonValue["Links"]["ManagedBy"] =
619                     std::move(managedBy);
620                 getChassisState(asyncResp);
621                 getStorageLink(asyncResp, path);
622                 });
623 
624             for (const auto& interface : interfaces2)
625             {
626                 if (interface == "xyz.openbmc_project.Common.UUID")
627                 {
628                     getChassisUUID(asyncResp, connectionName, path);
629                 }
630                 else if (interface ==
631                          "xyz.openbmc_project.Inventory.Decorator.LocationCode")
632                 {
633                     getChassisLocationCode(asyncResp, connectionName, path);
634                 }
635             }
636 
637             return;
638         }
639 
640         // Couldn't find an object with that name.  return an error
641         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
642         });
643 
644     getPhysicalSecurityData(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*, 2> hasIndicatorLed = {
727                 "xyz.openbmc_project.Inventory.Item.Panel",
728                 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
729             bool indicatorChassis = false;
730             for (const char* interface : hasIndicatorLed)
731             {
732                 if (std::find(interfaces3.begin(), interfaces3.end(),
733                               interface) != interfaces3.end())
734                 {
735                     indicatorChassis = true;
736                     break;
737                 }
738             }
739             if (locationIndicatorActive)
740             {
741                 if (indicatorChassis)
742                 {
743                     setLocationIndicatorActive(asyncResp,
744                                                *locationIndicatorActive);
745                 }
746                 else
747                 {
748                     messages::propertyUnknown(asyncResp->res,
749                                               "LocationIndicatorActive");
750                 }
751             }
752             if (indicatorLed)
753             {
754                 if (indicatorChassis)
755                 {
756                     setIndicatorLedState(asyncResp, *indicatorLed);
757                 }
758                 else
759                 {
760                     messages::propertyUnknown(asyncResp->res, "IndicatorLED");
761                 }
762             }
763             return;
764         }
765 
766         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
767         });
768 }
769 
770 /**
771  * Chassis override class for delivering Chassis Schema
772  * Functions triggers appropriate requests on DBus
773  */
774 inline void requestRoutesChassis(App& app)
775 {
776     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
777         .privileges(redfish::privileges::getChassis)
778         .methods(boost::beast::http::verb::get)(
779             std::bind_front(handleChassisGet, std::ref(app)));
780 
781     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
782         .privileges(redfish::privileges::patchChassis)
783         .methods(boost::beast::http::verb::patch)(
784             std::bind_front(handleChassisPatch, std::ref(app)));
785 }
786 
787 /**
788  * Handle error responses from d-bus for chassis power cycles
789  */
790 inline void handleChassisPowerCycleError(const boost::system::error_code& ec,
791                                          const sdbusplus::message_t& eMsg,
792                                          crow::Response& res)
793 {
794     if (eMsg.get_error() == nullptr)
795     {
796         BMCWEB_LOG_ERROR << "D-Bus response error: " << ec;
797         messages::internalError(res);
798         return;
799     }
800     std::string_view errorMessage = eMsg.get_error()->name;
801 
802     // If operation failed due to BMC not being in Ready state, tell
803     // user to retry in a bit
804     if (errorMessage ==
805         std::string_view("xyz.openbmc_project.State.Chassis.Error.BMCNotReady"))
806     {
807         BMCWEB_LOG_DEBUG << "BMC not ready, operation not allowed right now";
808         messages::serviceTemporarilyUnavailable(res, "10");
809         return;
810     }
811 
812     BMCWEB_LOG_ERROR << "Chassis Power Cycle fail " << ec
813                      << " sdbusplus:" << errorMessage;
814     messages::internalError(res);
815 }
816 
817 inline void
818     doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
819 {
820     constexpr std::array<std::string_view, 1> interfaces = {
821         "xyz.openbmc_project.State.Chassis"};
822 
823     // Use mapper to get subtree paths.
824     dbus::utility::getSubTreePaths(
825         "/", 0, interfaces,
826         [asyncResp](
827             const boost::system::error_code& ec,
828             const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
829         if (ec)
830         {
831             BMCWEB_LOG_ERROR << "[mapper] Bad D-Bus request error: " << ec;
832             messages::internalError(asyncResp->res);
833             return;
834         }
835 
836         const char* processName = "xyz.openbmc_project.State.Chassis";
837         const char* interfaceName = "xyz.openbmc_project.State.Chassis";
838         const char* destProperty = "RequestedPowerTransition";
839         const std::string propertyValue =
840             "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
841         std::string objectPath = "/xyz/openbmc_project/state/chassis_system0";
842 
843         /* Look for system reset chassis path */
844         if ((std::find(chassisList.begin(), chassisList.end(), objectPath)) ==
845             chassisList.end())
846         {
847             /* We prefer to reset the full chassis_system, but if it doesn't
848              * exist on some platforms, fall back to a host-only power reset
849              */
850             objectPath = "/xyz/openbmc_project/state/chassis0";
851         }
852 
853         sdbusplus::asio::setProperty(
854             *crow::connections::systemBus, processName, objectPath,
855             interfaceName, destProperty, propertyValue,
856             [asyncResp](const boost::system::error_code& ec2,
857                         sdbusplus::message_t& sdbusErrMsg) {
858             // Use "Set" method to set the property value.
859             if (ec2)
860             {
861                 handleChassisPowerCycleError(ec2, sdbusErrMsg, asyncResp->res);
862 
863                 return;
864             }
865 
866             messages::success(asyncResp->res);
867             });
868         });
869 }
870 
871 inline void handleChassisResetActionInfoPost(
872     App& app, const crow::Request& req,
873     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
874     const std::string& /*chassisId*/)
875 {
876     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
877     {
878         return;
879     }
880     BMCWEB_LOG_DEBUG << "Post Chassis Reset.";
881 
882     std::string resetType;
883 
884     if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType))
885     {
886         return;
887     }
888 
889     if (resetType != "PowerCycle")
890     {
891         BMCWEB_LOG_DEBUG << "Invalid property value for ResetType: "
892                          << resetType;
893         messages::actionParameterNotSupported(asyncResp->res, resetType,
894                                               "ResetType");
895 
896         return;
897     }
898     doChassisPowerCycle(asyncResp);
899 }
900 
901 /**
902  * ChassisResetAction class supports the POST method for the Reset
903  * action.
904  * Function handles POST method request.
905  * Analyzes POST body before sending Reset request data to D-Bus.
906  */
907 
908 inline void requestRoutesChassisResetAction(App& app)
909 {
910     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/")
911         .privileges(redfish::privileges::postChassis)
912         .methods(boost::beast::http::verb::post)(
913             std::bind_front(handleChassisResetActionInfoPost, std::ref(app)));
914 }
915 
916 inline void handleChassisResetActionInfoGet(
917     App& app, const crow::Request& req,
918     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
919     const std::string& chassisId)
920 {
921     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
922     {
923         return;
924     }
925     asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo";
926     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
927         "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId);
928     asyncResp->res.jsonValue["Name"] = "Reset Action Info";
929 
930     asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
931     nlohmann::json::array_t parameters;
932     nlohmann::json::object_t parameter;
933     parameter["Name"] = "ResetType";
934     parameter["Required"] = true;
935     parameter["DataType"] = "String";
936     nlohmann::json::array_t allowed;
937     allowed.emplace_back("PowerCycle");
938     parameter["AllowableValues"] = std::move(allowed);
939     parameters.emplace_back(std::move(parameter));
940 
941     asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
942 }
943 
944 /**
945  * ChassisResetActionInfo derived class for delivering Chassis
946  * ResetType AllowableValues using ResetInfo schema.
947  */
948 inline void requestRoutesChassisResetActionInfo(App& app)
949 {
950     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/")
951         .privileges(redfish::privileges::getActionInfo)
952         .methods(boost::beast::http::verb::get)(
953             std::bind_front(handleChassisResetActionInfoGet, std::ref(app)));
954 }
955 
956 } // namespace redfish
957