Coverage Report

Created: 2026-09-14 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bmcweb/redfish-core/lib/fan.hpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3
#pragma once
4
5
#include "app.hpp"
6
#include "async_resp.hpp"
7
#include "dbus_utility.hpp"
8
#include "error_messages.hpp"
9
#include "generated/enums/resource.hpp"
10
#include "generated/enums/sensor.hpp"
11
#include "http_request.hpp"
12
#include "led.hpp"
13
#include "logging.hpp"
14
#include "query.hpp"
15
#include "registries/privilege_registry.hpp"
16
#include "utils/asset_utils.hpp"
17
#include "utils/chassis_utils.hpp"
18
#include "utils/fan_utils.hpp"
19
#include "utils/json_utils.hpp"
20
#include "utils/resource_utils.hpp"
21
#include "utils/sensor_utils.hpp"
22
23
#include <asm-generic/errno.h>
24
25
#include <boost/beast/http/field.hpp>
26
#include <boost/beast/http/verb.hpp>
27
#include <boost/system/error_code.hpp>
28
#include <boost/url/format.hpp>
29
#include <nlohmann/json.hpp>
30
#include <sdbusplus/message/native_types.hpp>
31
#include <sdbusplus/unpack_properties.hpp>
32
33
#include <cstddef>
34
#include <functional>
35
#include <memory>
36
#include <optional>
37
#include <string>
38
#include <string_view>
39
#include <utility>
40
#include <vector>
41
42
namespace redfish
43
{
44
inline void updateFanList(
45
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
46
    const std::string& chassisId,
47
    const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
48
0
{
49
0
    nlohmann::json& fanList = asyncResp->res.jsonValue["Members"];
50
0
    for (const std::string& fanPath : fanPaths)
51
0
    {
52
0
        std::string fanName = sdbusplus::object_path(fanPath).filename();
53
0
        if (fanName.empty())
54
0
        {
55
0
            continue;
56
0
        }
57
58
0
        nlohmann::json item = nlohmann::json::object();
59
0
        item["@odata.id"] = boost::urls::format(
60
0
            "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId,
61
0
            fanName);
62
63
0
        fanList.emplace_back(std::move(item));
64
0
    }
65
0
    asyncResp->res.jsonValue["Members@odata.count"] = fanList.size();
66
0
}
67
68
inline void doFanCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69
                            const std::string& chassisId,
70
                            const std::optional<std::string>& validChassisPath)
71
0
{
72
0
    if (!validChassisPath)
73
0
    {
74
0
        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
75
0
        return;
76
0
    }
77
78
0
    asyncResp->res.addHeader(
79
0
        boost::beast::http::field::link,
80
0
        "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
81
0
    asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection";
82
0
    asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
83
0
        "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans", chassisId);
84
0
    asyncResp->res.jsonValue["Name"] = "Fan Collection";
85
0
    asyncResp->res.jsonValue["Description"] =
86
0
        "The collection of Fan resource instances " + chassisId;
87
0
    asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
88
0
    asyncResp->res.jsonValue["Members@odata.count"] = 0;
89
90
0
    fan_utils::getFanPaths(
91
0
        asyncResp, *validChassisPath,
92
0
        std::bind_front(updateFanList, asyncResp, chassisId));
93
0
}
94
95
inline void handleFanCollectionHead(
96
    App& app, const crow::Request& req,
97
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
98
    const std::string& chassisId)
99
0
{
100
0
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
101
0
    {
102
0
        return;
103
0
    }
104
105
0
    redfish::chassis_utils::getValidChassisPath(
106
0
        asyncResp, chassisId,
107
        // ast-grep-ignore: long-lambda
108
0
        [asyncResp,
109
0
         chassisId](const std::optional<std::string>& validChassisPath) {
110
0
            if (!validChassisPath)
111
0
            {
112
0
                messages::resourceNotFound(asyncResp->res, "Chassis",
113
0
                                           chassisId);
114
0
                return;
115
0
            }
116
0
            asyncResp->res.addHeader(
117
0
                boost::beast::http::field::link,
118
0
                "</redfish/v1/JsonSchemas/FanCollection/FanCollection.json>; rel=describedby");
119
0
        });
120
0
}
121
122
inline void handleFanCollectionGet(
123
    App& app, const crow::Request& req,
124
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
125
    const std::string& chassisId)
126
0
{
127
0
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
128
0
    {
129
0
        return;
130
0
    }
131
132
0
    redfish::chassis_utils::getValidChassisPath(
133
0
        asyncResp, chassisId,
134
0
        std::bind_front(doFanCollection, asyncResp, chassisId));
135
0
}
136
137
inline bool checkFanId(const std::string& fanPath, const std::string& fanId)
138
0
{
139
0
    std::string fanName = sdbusplus::object_path(fanPath).filename();
140
141
0
    return !(fanName.empty() || fanName != fanId);
142
0
}
143
144
inline void handleFanPath(
145
    const std::string& fanId,
146
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
147
    const dbus::utility::MapperGetSubTreePathsResponse& fanPaths,
148
    const std::function<void(const std::string& fanPath,
149
                             const std::string& service)>& callback)
150
0
{
151
0
    for (const auto& fanPath : fanPaths)
152
0
    {
153
0
        if (!checkFanId(fanPath, fanId))
154
0
        {
155
0
            continue;
156
0
        }
157
0
        dbus::utility::getDbusObject(
158
0
            fanPath, fanInterface,
159
            // ast-grep-ignore: long-lambda
160
0
            [fanPath, asyncResp,
161
0
             callback](const boost::system::error_code& ec,
162
0
                       const dbus::utility::MapperGetObject& object) {
163
0
                if (ec || object.empty())
164
0
                {
165
0
                    BMCWEB_LOG_ERROR("DBUS response error on getDbusObject {}",
166
0
                                     ec.value());
167
0
                    messages::internalError(asyncResp->res);
168
0
                    return;
169
0
                }
170
0
                callback(fanPath, object.begin()->first);
171
0
            });
172
173
0
        return;
174
0
    }
175
0
    BMCWEB_LOG_WARNING("Fan not found {}", fanId);
176
0
    messages::resourceNotFound(asyncResp->res, "Fan", fanId);
177
0
}
178
179
inline void getValidFanObject(
180
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
181
    const std::string& validChassisPath, const std::string& fanId,
182
    const std::function<void(const std::string& fanPath,
183
                             const std::string& service)>& callback)
184
0
{
185
0
    fan_utils::getFanPaths(
186
0
        asyncResp, validChassisPath,
187
0
        [fanId, asyncResp, callback](
188
0
            const dbus::utility::MapperGetSubTreePathsResponse& fanPaths) {
189
0
            handleFanPath(fanId, asyncResp, fanPaths, callback);
190
0
        });
191
0
}
192
193
inline void addFanCommonProperties(crow::Response& resp,
194
                                   const std::string& chassisId,
195
                                   const std::string& fanId)
196
0
{
197
0
    resp.addHeader(boost::beast::http::field::link,
198
0
                   "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
199
0
    resp.jsonValue["@odata.type"] = "#Fan.v1_6_0.Fan";
200
0
    resp.jsonValue["Name"] = "Fan";
201
0
    resp.jsonValue["Id"] = fanId;
202
0
    resp.jsonValue["@odata.id"] = boost::urls::format(
203
0
        "/redfish/v1/Chassis/{}/ThermalSubsystem/Fans/{}", chassisId, fanId);
204
0
}
205
206
inline void getFanLocation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
207
                           const std::string& fanPath,
208
                           const std::string& service)
209
0
{
210
0
    dbus::utility::getProperty<std::string>(
211
0
        service, fanPath,
212
0
        "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode",
213
        // ast-grep-ignore: long-lambda
214
0
        [asyncResp](const boost::system::error_code& ec,
215
0
                    const std::string& property) {
216
0
            if (ec)
217
0
            {
218
0
                if (ec.value() != EBADR)
219
0
                {
220
0
                    BMCWEB_LOG_ERROR("DBUS response error for Location{}",
221
0
                                     ec.value());
222
0
                    messages::internalError(asyncResp->res);
223
0
                }
224
0
                return;
225
0
            }
226
0
            asyncResp->res
227
0
                .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
228
0
                property;
229
0
        });
230
0
}
231
232
inline void handleGetFanSensorsProperties(
233
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
234
    const std::string& chassisId, const std::string& fanSensorPath,
235
    const size_t nSensors, const boost::system::error_code& ec,
236
    const dbus::utility::DBusPropertiesMap& propertiesList)
237
0
{
238
0
    if (ec)
239
0
    {
240
0
        if (ec.value() != boost::system::errc::io_error && ec.value() != EBADR)
241
0
        {
242
0
            BMCWEB_LOG_ERROR("DBUS response error {}", ec);
243
0
            messages::internalError(asyncResp->res);
244
0
        }
245
0
        return;
246
0
    }
247
248
0
    std::optional<size_t> priority;
249
0
    const bool success = sdbusplus::unpackPropertiesNoThrow(
250
0
        dbus_utils::UnpackErrorPrinter(), propertiesList, "Priority", priority);
251
252
0
    if (!success)
253
0
    {
254
0
        BMCWEB_LOG_ERROR("Failed to unpack properties");
255
0
        messages::internalError(asyncResp->res);
256
0
        return;
257
0
    }
258
259
    // Guard against bad priority combinations
260
0
    if (priority.has_value())
261
0
    {
262
0
        if (*priority > 1)
263
0
        {
264
0
            BMCWEB_LOG_DEBUG("Bad priority {} for {}, skipping", *priority,
265
0
                             fanSensorPath);
266
0
            return;
267
0
        }
268
0
    }
269
0
    else
270
0
    {
271
0
        if (nSensors > 1)
272
0
        {
273
0
            BMCWEB_LOG_DEBUG("No priority for multiple sensors, skipping");
274
0
            return;
275
0
        }
276
        /* No priority for single sensor, use as primary sensor for response */
277
0
        priority = 0;
278
0
    }
279
280
0
    nlohmann::json item = nlohmann::json::object();
281
282
    /* Don't return an error for a failure to fill in properties from this
283
     * sensor. Just skip adding it.
284
     */
285
0
    if (sensor_utils::objectExcerptToJson(
286
0
            fanSensorPath, chassisId, sensor_utils::ChassisSubNode::fansNode,
287
0
            sensor::ReadingType::Percent, propertiesList, item))
288
0
    {
289
0
        if (*priority == 0)
290
0
        {
291
0
            if (asyncResp->res.jsonValue.contains("SpeedPercent"))
292
0
            {
293
0
                BMCWEB_LOG_WARNING(
294
0
                    "Duplicate fan sensor with priority 0, skipping {}",
295
0
                    fanSensorPath);
296
0
                return;
297
0
            }
298
0
            asyncResp->res.jsonValue["SpeedPercent"] = std::move(item);
299
0
        }
300
0
        else if (*priority == 1)
301
0
        {
302
0
            if (asyncResp->res.jsonValue.contains("SecondarySpeedPercent"))
303
0
            {
304
0
                BMCWEB_LOG_WARNING(
305
0
                    "Duplicate fan sensor with priority 1, skipping {}",
306
0
                    fanSensorPath);
307
0
                return;
308
0
            }
309
0
            asyncResp->res.jsonValue["SecondarySpeedPercent"] = std::move(item);
310
0
        }
311
0
    }
312
0
}
313
314
inline void getFanSensorsProperties(
315
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
316
    const std::string& chassisId,
317
    const std::vector<std::pair<std::string, std::string>>&
318
        sensorsPathAndService)
319
0
{
320
0
    const size_t nSensors = sensorsPathAndService.size();
321
322
0
    for (const auto& [service, sensorPath] : sensorsPathAndService)
323
0
    {
324
0
        dbus::utility::getAllProperties(
325
0
            service, sensorPath, "",
326
0
            [asyncResp, chassisId, sensorPath,
327
0
             nSensors](const boost::system::error_code& ec,
328
0
                       const dbus::utility::DBusPropertiesMap& propertiesList) {
329
0
                handleGetFanSensorsProperties(asyncResp, chassisId, sensorPath,
330
0
                                              nSensors, ec, propertiesList);
331
0
            });
332
0
    }
333
0
}
334
335
inline void afterGetValidFanObject(
336
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
337
    const std::string& chassisId, const std::string& fanId,
338
    const std::string& fanPath, const std::string& service)
339
0
{
340
0
    addFanCommonProperties(asyncResp->res, chassisId, fanId);
341
0
    resource_utils::getResourceState(asyncResp, service, fanPath,
342
0
                                     ""_json_pointer);
343
0
    resource_utils::getResourceHealth(asyncResp, service, fanPath,
344
0
                                      ""_json_pointer);
345
0
    asset_utils::getAssetInfo(asyncResp, service, fanPath, ""_json_pointer,
346
0
                              true);
347
0
    getFanLocation(asyncResp, fanPath, service);
348
0
    getLocationIndicatorActive(asyncResp, fanPath);
349
0
    fan_utils::getFanSensorObjects(
350
0
        asyncResp, fanPath,
351
0
        std::bind_front(getFanSensorsProperties, asyncResp, chassisId));
352
0
}
353
354
inline void doFanGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
355
                     const std::string& chassisId, const std::string& fanId,
356
                     const std::optional<std::string>& validChassisPath)
357
0
{
358
0
    if (!validChassisPath)
359
0
    {
360
0
        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
361
0
        return;
362
0
    }
363
364
0
    getValidFanObject(
365
0
        asyncResp, *validChassisPath, fanId,
366
0
        std::bind_front(afterGetValidFanObject, asyncResp, chassisId, fanId));
367
0
}
368
369
inline void handleFanHead(App& app, const crow::Request& req,
370
                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
371
                          const std::string& chassisId,
372
                          const std::string& fanId)
373
0
{
374
0
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
375
0
    {
376
0
        return;
377
0
    }
378
379
0
    redfish::chassis_utils::getValidChassisPath(
380
0
        asyncResp, chassisId,
381
        // ast-grep-ignore: long-lambda
382
0
        [asyncResp, chassisId,
383
0
         fanId](const std::optional<std::string>& validChassisPath) {
384
0
            if (!validChassisPath)
385
0
            {
386
0
                messages::resourceNotFound(asyncResp->res, "Chassis",
387
0
                                           chassisId);
388
0
                return;
389
0
            }
390
0
            getValidFanObject(
391
0
                asyncResp, *validChassisPath, fanId,
392
0
                [asyncResp](const std::string&, const std::string&) {
393
0
                    asyncResp->res.addHeader(
394
0
                        boost::beast::http::field::link,
395
0
                        "</redfish/v1/JsonSchemas/Fan/Fan.json>; rel=describedby");
396
0
                });
397
0
        });
398
0
}
399
400
inline void handleFanGet(App& app, const crow::Request& req,
401
                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
402
                         const std::string& chassisId, const std::string& fanId)
403
0
{
404
0
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
405
0
    {
406
0
        return;
407
0
    }
408
409
0
    redfish::chassis_utils::getValidChassisPath(
410
0
        asyncResp, chassisId,
411
0
        std::bind_front(doFanGet, asyncResp, chassisId, fanId));
412
0
}
413
414
inline void handleSetFanPathById(
415
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
416
    const std::string& chassisId, const std::string& fanId,
417
    bool locationIndicatorActive, const boost::system::error_code& ec,
418
    const dbus::utility::MapperGetSubTreePathsResponse& fanPaths)
419
0
{
420
0
    if (ec)
421
0
    {
422
0
        if (ec.value() == boost::system::errc::io_error)
423
0
        {
424
0
            BMCWEB_LOG_WARNING("Chassis {} not found", chassisId);
425
0
            messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
426
0
            return;
427
0
        }
428
429
0
        BMCWEB_LOG_ERROR("DBUS response error {}", ec.value());
430
0
        messages::internalError(asyncResp->res);
431
0
        return;
432
0
    }
433
434
0
    for (const auto& fanPath : fanPaths)
435
0
    {
436
0
        if (checkFanId(fanPath, fanId))
437
0
        {
438
0
            setLocationIndicatorActive(asyncResp, fanPath,
439
0
                                       locationIndicatorActive);
440
0
            return;
441
0
        }
442
0
    }
443
0
    BMCWEB_LOG_WARNING("Fan {} not found", fanId);
444
0
    messages::resourceNotFound(asyncResp->res, "Fan", fanId);
445
0
}
446
447
inline void handleFanPatch(App& app, const crow::Request& req,
448
                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
449
                           const std::string& chassisId,
450
                           const std::string& fanId)
451
0
{
452
0
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
453
0
    {
454
0
        return;
455
0
    }
456
457
0
    std::optional<bool> locationIndicatorActive;
458
0
    if (!json_util::readJsonPatch(req, asyncResp->res,
459
0
                                  "LocationIndicatorActive",
460
0
                                  locationIndicatorActive))
461
0
    {
462
0
        return;
463
0
    }
464
465
0
    if (locationIndicatorActive)
466
0
    {
467
0
        dbus::utility::getAssociatedSubTreePathsById(
468
0
            chassisId, "/xyz/openbmc_project/inventory", chassisInterfaces,
469
0
            "cooled_by", fanInterface,
470
0
            [asyncResp, chassisId, fanId, locationIndicatorActive](
471
0
                const boost::system::error_code& ec,
472
0
                const dbus::utility::MapperGetSubTreePathsResponse&
473
0
                    subtreePaths) {
474
0
                handleSetFanPathById(asyncResp, chassisId, fanId,
475
0
                                     *locationIndicatorActive, ec,
476
0
                                     subtreePaths);
477
0
            });
478
0
    }
479
0
}
480
481
inline void requestRoutesFan(App& app)
482
6.49k
{
483
6.49k
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
484
6.49k
        .privileges(redfish::privileges::headFanCollection)
485
6.49k
        .methods(boost::beast::http::verb::head)(
486
6.49k
            std::bind_front(handleFanCollectionHead, std::ref(app)));
487
488
6.49k
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
489
6.49k
        .privileges(redfish::privileges::getFanCollection)
490
6.49k
        .methods(boost::beast::http::verb::get)(
491
6.49k
            std::bind_front(handleFanCollectionGet, std::ref(app)));
492
493
6.49k
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
494
6.49k
        .privileges(redfish::privileges::headFan)
495
6.49k
        .methods(boost::beast::http::verb::head)(
496
6.49k
            std::bind_front(handleFanHead, std::ref(app)));
497
498
6.49k
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
499
6.49k
        .privileges(redfish::privileges::getFan)
500
6.49k
        .methods(boost::beast::http::verb::get)(
501
6.49k
            std::bind_front(handleFanGet, std::ref(app)));
502
503
6.49k
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
504
6.49k
        .privileges(redfish::privileges::patchFan)
505
6.49k
        .methods(boost::beast::http::verb::patch)(
506
6.49k
            std::bind_front(handleFanPatch, std::ref(app)));
507
6.49k
}
508
509
} // namespace redfish