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