1 /*
2 Copyright (c) 2018 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17
18 #include "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "generated/enums/action_info.hpp"
21 #include "generated/enums/chassis.hpp"
22 #include "generated/enums/resource.hpp"
23 #include "led.hpp"
24 #include "query.hpp"
25 #include "redfish_util.hpp"
26 #include "registries/privilege_registry.hpp"
27 #include "utils/collection.hpp"
28 #include "utils/dbus_utils.hpp"
29 #include "utils/json_utils.hpp"
30
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/format.hpp>
33 #include <sdbusplus/asio/property.hpp>
34 #include <sdbusplus/message.hpp>
35 #include <sdbusplus/unpack_properties.hpp>
36
37 #include <array>
38 #include <memory>
39 #include <ranges>
40 #include <string_view>
41
42 namespace redfish
43 {
44
45 inline chassis::ChassisType
translateChassisTypeToRedfish(const std::string_view & chassisType)46 translateChassisTypeToRedfish(const std::string_view& chassisType)
47 {
48 if (chassisType ==
49 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Blade")
50 {
51 return chassis::ChassisType::Blade;
52 }
53 if (chassisType ==
54 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Component")
55 {
56 return chassis::ChassisType::Component;
57 }
58 if (chassisType ==
59 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Enclosure")
60 {
61 return chassis::ChassisType::Enclosure;
62 }
63 if (chassisType ==
64 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Module")
65 {
66 return chassis::ChassisType::Module;
67 }
68 if (chassisType ==
69 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.RackMount")
70 {
71 return chassis::ChassisType::RackMount;
72 }
73 if (chassisType ==
74 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StandAlone")
75 {
76 return chassis::ChassisType::StandAlone;
77 }
78 if (chassisType ==
79 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.StorageEnclosure")
80 {
81 return chassis::ChassisType::StorageEnclosure;
82 }
83 if (chassisType ==
84 "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Zone")
85 {
86 return chassis::ChassisType::Zone;
87 }
88 return chassis::ChassisType::Invalid;
89 }
90
91 /**
92 * @brief Retrieves resources over dbus to link to the chassis
93 *
94 * @param[in] asyncResp - Shared pointer for completing asynchronous
95 * calls
96 * @param[in] path - Chassis dbus path to look for the storage.
97 *
98 * Calls the Association endpoints on the path + "/storage" and add the link of
99 * json["Links"]["Storage@odata.count"] =
100 * {"@odata.id", "/redfish/v1/Storage/" + resourceId}
101 *
102 * @return None.
103 */
getStorageLink(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const sdbusplus::message::object_path & path)104 inline void getStorageLink(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
105 const sdbusplus::message::object_path& path)
106 {
107 dbus::utility::getProperty<std::vector<std::string>>(
108 "xyz.openbmc_project.ObjectMapper", (path / "storage").str,
109 "xyz.openbmc_project.Association", "endpoints",
110 [asyncResp](const boost::system::error_code& ec,
111 const std::vector<std::string>& storageList) {
112 if (ec)
113 {
114 BMCWEB_LOG_DEBUG("getStorageLink got DBUS response error");
115 return;
116 }
117
118 nlohmann::json::array_t storages;
119 for (const std::string& storagePath : storageList)
120 {
121 std::string id =
122 sdbusplus::message::object_path(storagePath).filename();
123 if (id.empty())
124 {
125 continue;
126 }
127
128 nlohmann::json::object_t storage;
129 storage["@odata.id"] =
130 boost::urls::format("/redfish/v1/Systems/{}/Storage/{}",
131 BMCWEB_REDFISH_SYSTEM_URI_NAME, id);
132 storages.emplace_back(std::move(storage));
133 }
134 asyncResp->res.jsonValue["Links"]["Storage@odata.count"] =
135 storages.size();
136 asyncResp->res.jsonValue["Links"]["Storage"] = std::move(storages);
137 });
138 }
139
140 /**
141 * @brief Retrieves chassis state properties over dbus
142 *
143 * @param[in] asyncResp - Shared pointer for completing asynchronous calls.
144 *
145 * @return None.
146 */
getChassisState(std::shared_ptr<bmcweb::AsyncResp> asyncResp)147 inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> asyncResp)
148 {
149 // crow::connections::systemBus->async_method_call(
150 dbus::utility::getProperty<std::string>(
151 "xyz.openbmc_project.State.Chassis",
152 "/xyz/openbmc_project/state/chassis0",
153 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
154 [asyncResp{std::move(asyncResp)}](const boost::system::error_code& ec,
155 const std::string& chassisState) {
156 if (ec)
157 {
158 if (ec == boost::system::errc::host_unreachable)
159 {
160 // Service not available, no error, just don't return
161 // chassis state info
162 BMCWEB_LOG_DEBUG("Service not available {}", ec);
163 return;
164 }
165 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
166 messages::internalError(asyncResp->res);
167 return;
168 }
169
170 BMCWEB_LOG_DEBUG("Chassis state: {}", chassisState);
171 // Verify Chassis State
172 if (chassisState ==
173 "xyz.openbmc_project.State.Chassis.PowerState.On")
174 {
175 asyncResp->res.jsonValue["PowerState"] =
176 resource::PowerState::On;
177 asyncResp->res.jsonValue["Status"]["State"] =
178 resource::State::Enabled;
179 }
180 else if (chassisState ==
181 "xyz.openbmc_project.State.Chassis.PowerState.Off")
182 {
183 asyncResp->res.jsonValue["PowerState"] =
184 resource::PowerState::Off;
185 asyncResp->res.jsonValue["Status"]["State"] =
186 resource::State::StandbyOffline;
187 }
188 });
189 }
190
191 /**
192 * Retrieves physical security properties over dbus
193 */
handlePhysicalSecurityGetSubTree(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreeResponse & subtree)194 inline void handlePhysicalSecurityGetSubTree(
195 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
196 const boost::system::error_code& ec,
197 const dbus::utility::MapperGetSubTreeResponse& subtree)
198 {
199 if (ec)
200 {
201 // do not add err msg in redfish response, because this is not
202 // mandatory property
203 BMCWEB_LOG_INFO("DBUS error: no matched iface {}", ec);
204 return;
205 }
206 // Iterate over all retrieved ObjectPaths.
207 for (const auto& object : subtree)
208 {
209 if (!object.second.empty())
210 {
211 const auto& service = object.second.front();
212
213 BMCWEB_LOG_DEBUG("Get intrusion status by service ");
214
215 dbus::utility::getProperty<std::string>(
216 service.first, object.first,
217 "xyz.openbmc_project.Chassis.Intrusion", "Status",
218 [asyncResp](const boost::system::error_code& ec1,
219 const std::string& value) {
220 if (ec1)
221 {
222 // do not add err msg in redfish response, because this
223 // is not
224 // mandatory property
225 BMCWEB_LOG_ERROR("DBUS response error {}", ec1);
226 return;
227 }
228 asyncResp->res.jsonValue["PhysicalSecurity"]
229 ["IntrusionSensorNumber"] = 1;
230 asyncResp->res
231 .jsonValue["PhysicalSecurity"]["IntrusionSensor"] =
232 value;
233 });
234
235 return;
236 }
237 }
238 }
239
handleChassisCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)240 inline void handleChassisCollectionGet(
241 App& app, const crow::Request& req,
242 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
243 {
244 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
245 {
246 return;
247 }
248 asyncResp->res.jsonValue["@odata.type"] =
249 "#ChassisCollection.ChassisCollection";
250 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis";
251 asyncResp->res.jsonValue["Name"] = "Chassis Collection";
252
253 constexpr std::array<std::string_view, 2> interfaces{
254 "xyz.openbmc_project.Inventory.Item.Board",
255 "xyz.openbmc_project.Inventory.Item.Chassis"};
256 collection_util::getCollectionMembers(
257 asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces,
258 "/xyz/openbmc_project/inventory");
259 }
260
getChassisContainedBy(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & upstreamChassisPaths)261 inline void getChassisContainedBy(
262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
263 const std::string& chassisId, const boost::system::error_code& ec,
264 const dbus::utility::MapperGetSubTreePathsResponse& upstreamChassisPaths)
265 {
266 if (ec)
267 {
268 if (ec.value() != EBADR)
269 {
270 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
271 messages::internalError(asyncResp->res);
272 }
273 return;
274 }
275 if (upstreamChassisPaths.empty())
276 {
277 return;
278 }
279 if (upstreamChassisPaths.size() > 1)
280 {
281 BMCWEB_LOG_ERROR("{} is contained by multiple chassis", chassisId);
282 messages::internalError(asyncResp->res);
283 return;
284 }
285
286 sdbusplus::message::object_path upstreamChassisPath(
287 upstreamChassisPaths[0]);
288 std::string upstreamChassis = upstreamChassisPath.filename();
289 if (upstreamChassis.empty())
290 {
291 BMCWEB_LOG_WARNING("Malformed upstream Chassis path {} on {}",
292 upstreamChassisPath.str, chassisId);
293 return;
294 }
295
296 asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] =
297 boost::urls::format("/redfish/v1/Chassis/{}", upstreamChassis);
298 }
299
getChassisContains(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const dbus::utility::MapperGetSubTreePathsResponse & downstreamChassisPaths)300 inline void getChassisContains(
301 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302 const std::string& chassisId, const boost::system::error_code& ec,
303 const dbus::utility::MapperGetSubTreePathsResponse& downstreamChassisPaths)
304 {
305 if (ec)
306 {
307 if (ec.value() != EBADR)
308 {
309 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
310 messages::internalError(asyncResp->res);
311 }
312 return;
313 }
314 if (downstreamChassisPaths.empty())
315 {
316 return;
317 }
318 nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"];
319 if (!jValue.is_array())
320 {
321 // Create the array if it was empty
322 jValue = nlohmann::json::array();
323 }
324 for (const auto& p : downstreamChassisPaths)
325 {
326 sdbusplus::message::object_path downstreamChassisPath(p);
327 std::string downstreamChassis = downstreamChassisPath.filename();
328 if (downstreamChassis.empty())
329 {
330 BMCWEB_LOG_WARNING("Malformed downstream Chassis path {} on {}",
331 downstreamChassisPath.str, chassisId);
332 continue;
333 }
334 nlohmann::json link;
335 link["@odata.id"] =
336 boost::urls::format("/redfish/v1/Chassis/{}", downstreamChassis);
337 jValue.push_back(std::move(link));
338 }
339 asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size();
340 }
341
getChassisConnectivity(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & chassisPath)342 inline void getChassisConnectivity(
343 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
344 const std::string& chassisId, const std::string& chassisPath)
345 {
346 BMCWEB_LOG_DEBUG("Get chassis connectivity");
347
348 constexpr std::array<std::string_view, 2> interfaces{
349 "xyz.openbmc_project.Inventory.Item.Board",
350 "xyz.openbmc_project.Inventory.Item.Chassis"};
351
352 dbus::utility::getAssociatedSubTreePaths(
353 chassisPath + "/contained_by",
354 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
355 interfaces,
356 std::bind_front(getChassisContainedBy, asyncResp, chassisId));
357
358 dbus::utility::getAssociatedSubTreePaths(
359 chassisPath + "/containing",
360 sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
361 interfaces, 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 sdbusplus::asio::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
719 inline void
handleChassisGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)720 handleChassisGet(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 constexpr std::array<std::string_view, 2> interfaces = {
729 "xyz.openbmc_project.Inventory.Item.Board",
730 "xyz.openbmc_project.Inventory.Item.Chassis"};
731
732 dbus::utility::getSubTree(
733 "/xyz/openbmc_project/inventory", 0, interfaces,
734 std::bind_front(handleChassisGetSubTree, asyncResp, chassisId));
735
736 constexpr std::array<std::string_view, 1> interfaces2 = {
737 "xyz.openbmc_project.Chassis.Intrusion"};
738
739 dbus::utility::getSubTree(
740 "/xyz/openbmc_project", 0, interfaces2,
741 std::bind_front(handlePhysicalSecurityGetSubTree, asyncResp));
742 }
743
744 inline void
handleChassisPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param)745 handleChassisPatch(App& app, const crow::Request& req,
746 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
747 const std::string& param)
748 {
749 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
750 {
751 return;
752 }
753 std::optional<bool> locationIndicatorActive;
754 std::optional<std::string> indicatorLed;
755
756 if (param.empty())
757 {
758 return;
759 }
760
761 if (!json_util::readJsonPatch( //
762 req, asyncResp->res, //
763 "IndicatorLED", indicatorLed, //
764 "LocationIndicatorActive", locationIndicatorActive //
765 ))
766 {
767 return;
768 }
769
770 // TODO (Gunnar): Remove IndicatorLED after enough time has passed
771 if (!locationIndicatorActive && !indicatorLed)
772 {
773 return; // delete this when we support more patch properties
774 }
775 if (indicatorLed)
776 {
777 asyncResp->res.addHeader(
778 boost::beast::http::field::warning,
779 "299 - \"IndicatorLED is deprecated. Use LocationIndicatorActive instead.\"");
780 }
781
782 constexpr std::array<std::string_view, 2> interfaces = {
783 "xyz.openbmc_project.Inventory.Item.Board",
784 "xyz.openbmc_project.Inventory.Item.Chassis"};
785
786 const std::string& chassisId = param;
787
788 dbus::utility::getSubTree(
789 "/xyz/openbmc_project/inventory", 0, interfaces,
790 [asyncResp, chassisId, locationIndicatorActive,
791 indicatorLed](const boost::system::error_code& ec,
792 const dbus::utility::MapperGetSubTreeResponse& subtree) {
793 if (ec)
794 {
795 BMCWEB_LOG_ERROR("DBUS response error {}", ec);
796 messages::internalError(asyncResp->res);
797 return;
798 }
799
800 // Iterate over all retrieved ObjectPaths.
801 for (const std::pair<std::string,
802 std::vector<std::pair<
803 std::string, std::vector<std::string>>>>&
804 object : subtree)
805 {
806 const std::string& path = object.first;
807 const std::vector<
808 std::pair<std::string, std::vector<std::string>>>&
809 connectionNames = object.second;
810
811 sdbusplus::message::object_path objPath(path);
812 if (objPath.filename() != chassisId)
813 {
814 continue;
815 }
816
817 if (connectionNames.empty())
818 {
819 BMCWEB_LOG_ERROR("Got 0 Connection names");
820 continue;
821 }
822
823 const std::vector<std::string>& interfaces3 =
824 connectionNames[0].second;
825
826 const std::array<const char*, 3> hasIndicatorLed = {
827 "xyz.openbmc_project.Inventory.Item.Chassis",
828 "xyz.openbmc_project.Inventory.Item.Panel",
829 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
830 bool indicatorChassis = false;
831 for (const char* interface : hasIndicatorLed)
832 {
833 if (std::ranges::find(interfaces3, interface) !=
834 interfaces3.end())
835 {
836 indicatorChassis = true;
837 break;
838 }
839 }
840 if (locationIndicatorActive)
841 {
842 if (indicatorChassis)
843 {
844 setSystemLocationIndicatorActive(
845 asyncResp, *locationIndicatorActive);
846 }
847 else
848 {
849 messages::propertyUnknown(asyncResp->res,
850 "LocationIndicatorActive");
851 }
852 }
853 if (indicatorLed)
854 {
855 if (indicatorChassis)
856 {
857 setIndicatorLedState(asyncResp, *indicatorLed);
858 }
859 else
860 {
861 messages::propertyUnknown(asyncResp->res,
862 "IndicatorLED");
863 }
864 }
865 return;
866 }
867
868 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
869 });
870 }
871
872 /**
873 * Chassis override class for delivering Chassis Schema
874 * Functions triggers appropriate requests on DBus
875 */
requestRoutesChassis(App & app)876 inline void requestRoutesChassis(App& app)
877 {
878 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
879 .privileges(redfish::privileges::getChassis)
880 .methods(boost::beast::http::verb::get)(
881 std::bind_front(handleChassisGet, std::ref(app)));
882
883 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/")
884 .privileges(redfish::privileges::patchChassis)
885 .methods(boost::beast::http::verb::patch)(
886 std::bind_front(handleChassisPatch, std::ref(app)));
887 }
888
889 inline void
doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)890 doChassisPowerCycle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
891 {
892 constexpr std::array<std::string_view, 1> interfaces = {
893 "xyz.openbmc_project.State.Chassis"};
894
895 // Use mapper to get subtree paths.
896 dbus::utility::getSubTreePaths(
897 "/", 0, interfaces,
898 [asyncResp](
899 const boost::system::error_code& ec,
900 const dbus::utility::MapperGetSubTreePathsResponse& chassisList) {
901 if (ec)
902 {
903 BMCWEB_LOG_ERROR("[mapper] Bad D-Bus request error: {}", ec);
904 messages::internalError(asyncResp->res);
905 return;
906 }
907
908 const char* processName = "xyz.openbmc_project.State.Chassis";
909 const char* interfaceName = "xyz.openbmc_project.State.Chassis";
910 const char* destProperty = "RequestedPowerTransition";
911 const std::string propertyValue =
912 "xyz.openbmc_project.State.Chassis.Transition.PowerCycle";
913 std::string objectPath =
914 "/xyz/openbmc_project/state/chassis_system0";
915
916 /* Look for system reset chassis path */
917 if ((std::ranges::find(chassisList, objectPath)) ==
918 chassisList.end())
919 {
920 /* We prefer to reset the full chassis_system, but if it doesn't
921 * exist on some platforms, fall back to a host-only power reset
922 */
923 objectPath = "/xyz/openbmc_project/state/chassis0";
924 }
925
926 setDbusProperty(asyncResp, "ResetType", processName, objectPath,
927 interfaceName, destProperty, propertyValue);
928 });
929 }
930
handleChassisResetActionInfoPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)931 inline void handleChassisResetActionInfoPost(
932 App& app, const crow::Request& req,
933 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
934 const std::string& /*chassisId*/)
935 {
936 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
937 {
938 return;
939 }
940 BMCWEB_LOG_DEBUG("Post Chassis Reset.");
941
942 std::string resetType;
943
944 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType))
945 {
946 return;
947 }
948
949 if (resetType != "PowerCycle")
950 {
951 BMCWEB_LOG_DEBUG("Invalid property value for ResetType: {}", resetType);
952 messages::actionParameterNotSupported(asyncResp->res, resetType,
953 "ResetType");
954
955 return;
956 }
957 doChassisPowerCycle(asyncResp);
958 }
959
960 /**
961 * ChassisResetAction class supports the POST method for the Reset
962 * action.
963 * Function handles POST method request.
964 * Analyzes POST body before sending Reset request data to D-Bus.
965 */
966
requestRoutesChassisResetAction(App & app)967 inline void requestRoutesChassisResetAction(App& app)
968 {
969 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/")
970 .privileges(redfish::privileges::postChassis)
971 .methods(boost::beast::http::verb::post)(
972 std::bind_front(handleChassisResetActionInfoPost, std::ref(app)));
973 }
974
handleChassisResetActionInfoGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)975 inline void handleChassisResetActionInfoGet(
976 App& app, const crow::Request& req,
977 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
978 const std::string& chassisId)
979 {
980 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
981 {
982 return;
983 }
984 asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo";
985 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
986 "/redfish/v1/Chassis/{}/ResetActionInfo", chassisId);
987 asyncResp->res.jsonValue["Name"] = "Reset Action Info";
988
989 asyncResp->res.jsonValue["Id"] = "ResetActionInfo";
990 nlohmann::json::array_t parameters;
991 nlohmann::json::object_t parameter;
992 parameter["Name"] = "ResetType";
993 parameter["Required"] = true;
994 parameter["DataType"] = action_info::ParameterTypes::String;
995 nlohmann::json::array_t allowed;
996 allowed.emplace_back("PowerCycle");
997 parameter["AllowableValues"] = std::move(allowed);
998 parameters.emplace_back(std::move(parameter));
999
1000 asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
1001 }
1002
1003 /**
1004 * ChassisResetActionInfo derived class for delivering Chassis
1005 * ResetType AllowableValues using ResetInfo schema.
1006 */
requestRoutesChassisResetActionInfo(App & app)1007 inline void requestRoutesChassisResetActionInfo(App& app)
1008 {
1009 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/")
1010 .privileges(redfish::privileges::getActionInfo)
1011 .methods(boost::beast::http::verb::get)(
1012 std::bind_front(handleChassisResetActionInfoGet, std::ref(app)));
1013 }
1014
1015 } // namespace redfish
1016