xref: /openbmc/bmcweb/redfish-core/lib/power.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 // SPDX-FileCopyrightText: Copyright 2018 Ampere Computing LLC
5 
6 #pragma once
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_utility.hpp"
11 #include "error_messages.hpp"
12 #include "generated/enums/power.hpp"
13 #include "http_request.hpp"
14 #include "logging.hpp"
15 #include "query.hpp"
16 #include "registries/privilege_registry.hpp"
17 #include "sensors.hpp"
18 #include "utils/chassis_utils.hpp"
19 #include "utils/dbus_utils.hpp"
20 #include "utils/json_utils.hpp"
21 #include "utils/sensor_utils.hpp"
22 
23 #include <boost/beast/http/verb.hpp>
24 #include <nlohmann/json.hpp>
25 #include <sdbusplus/message/native_types.hpp>
26 
27 #include <array>
28 #include <cmath>
29 #include <cstddef>
30 #include <cstdint>
31 #include <functional>
32 #include <memory>
33 #include <optional>
34 #include <string>
35 #include <string_view>
36 #include <unordered_map>
37 #include <utility>
38 #include <variant>
39 #include <vector>
40 
41 namespace redfish
42 {
43 
afterGetPowerCapEnable(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,uint32_t valueToSet,const boost::system::error_code & ec,bool powerCapEnable)44 inline void afterGetPowerCapEnable(
45     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
46     uint32_t valueToSet, const boost::system::error_code& ec,
47     bool powerCapEnable)
48 {
49     if (ec)
50     {
51         messages::internalError(sensorsAsyncResp->asyncResp->res);
52         BMCWEB_LOG_ERROR("powerCapEnable Get handler: Dbus error {}", ec);
53         return;
54     }
55     if (!powerCapEnable)
56     {
57         messages::actionNotSupported(
58             sensorsAsyncResp->asyncResp->res,
59             "Setting LimitInWatts when PowerLimit feature is disabled");
60         BMCWEB_LOG_ERROR("PowerLimit feature is disabled ");
61         return;
62     }
63 
64     setDbusProperty(sensorsAsyncResp->asyncResp, "PowerControl",
65                     "xyz.openbmc_project.Settings",
66                     sdbusplus::message::object_path(
67                         "/xyz/openbmc_project/control/host0/power_cap"),
68                     "xyz.openbmc_project.Control.Power.Cap", "PowerCap",
69                     valueToSet);
70 }
71 
afterGetChassisPath(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,std::vector<nlohmann::json::object_t> & powerControlCollections,const std::optional<std::string> & chassisPath)72 inline void afterGetChassisPath(
73     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
74     std::vector<nlohmann::json::object_t>& powerControlCollections,
75     const std::optional<std::string>& chassisPath)
76 {
77     if (!chassisPath)
78     {
79         BMCWEB_LOG_WARNING("Don't find valid chassis path ");
80         messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Chassis",
81                                    sensorsAsyncResp->chassisId);
82         return;
83     }
84 
85     if (powerControlCollections.size() != 1)
86     {
87         BMCWEB_LOG_WARNING("Don't support multiple hosts at present ");
88         messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Power",
89                                    "PowerControl");
90         return;
91     }
92 
93     auto& item = powerControlCollections[0];
94 
95     std::optional<uint32_t> value;
96     if (!json_util::readJsonObject(                 //
97             item, sensorsAsyncResp->asyncResp->res, //
98             "PowerLimit/LimitInWatts", value        //
99             ))
100     {
101         return;
102     }
103     if (!value)
104     {
105         return;
106     }
107     dbus::utility::getProperty<bool>(
108         "xyz.openbmc_project.Settings",
109         "/xyz/openbmc_project/control/host0/power_cap",
110         "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable",
111         std::bind_front(afterGetPowerCapEnable, sensorsAsyncResp, *value));
112 }
113 
afterPowerCapSettingGet(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & properties)114 inline void afterPowerCapSettingGet(
115     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
116     const boost::system::error_code& ec,
117     const dbus::utility::DBusPropertiesMap& properties)
118 {
119     if (ec)
120     {
121         messages::internalError(sensorAsyncResp->asyncResp->res);
122         BMCWEB_LOG_ERROR("Power Limit GetAll handler: Dbus error {}", ec);
123         return;
124     }
125 
126     nlohmann::json& tempArray =
127         sensorAsyncResp->asyncResp->res.jsonValue["PowerControl"];
128 
129     // Put multiple "sensors" into a single PowerControl, 0,
130     // so only create the first one
131     if (tempArray.empty())
132     {
133         // Mandatory properties odata.id and MemberId
134         // A warning without a odata.type
135         nlohmann::json::object_t powerControl;
136         powerControl["@odata.type"] = "#Power.v1_0_0.PowerControl";
137         powerControl["@odata.id"] =
138             "/redfish/v1/Chassis/" + sensorAsyncResp->chassisId +
139             "/Power#/PowerControl/0";
140         powerControl["Name"] = "Chassis Power Control";
141         powerControl["MemberId"] = "0";
142         tempArray.emplace_back(std::move(powerControl));
143     }
144 
145     nlohmann::json& sensorJson = tempArray.back();
146     bool enabled = false;
147     double powerCap = 0.0;
148     int64_t scale = 0;
149 
150     for (const std::pair<std::string, dbus::utility::DbusVariantType>&
151              property : properties)
152     {
153         if (property.first == "Scale")
154         {
155             const int64_t* i = std::get_if<int64_t>(&property.second);
156 
157             if (i != nullptr)
158             {
159                 scale = *i;
160             }
161         }
162         else if (property.first == "PowerCap")
163         {
164             const double* d = std::get_if<double>(&property.second);
165             const int64_t* i = std::get_if<int64_t>(&property.second);
166             const uint32_t* u = std::get_if<uint32_t>(&property.second);
167 
168             if (d != nullptr)
169             {
170                 powerCap = *d;
171             }
172             else if (i != nullptr)
173             {
174                 powerCap = static_cast<double>(*i);
175             }
176             else if (u != nullptr)
177             {
178                 powerCap = *u;
179             }
180         }
181         else if (property.first == "PowerCapEnable")
182         {
183             const bool* b = std::get_if<bool>(&property.second);
184 
185             if (b != nullptr)
186             {
187                 enabled = *b;
188             }
189         }
190     }
191 
192     // LimitException is Mandatory attribute as per OCP
193     // Baseline Profile - v1.0.0, so currently making it
194     // "NoAction" as default value to make it OCP Compliant.
195     sensorJson["PowerLimit"]["LimitException"] =
196         power::PowerLimitException::NoAction;
197 
198     if (enabled)
199     {
200         // Redfish specification indicates PowerLimit should
201         // be null if the limit is not enabled.
202         sensorJson["PowerLimit"]["LimitInWatts"] =
203             powerCap * std::pow(10, scale);
204     }
205 }
206 
207 using Mapper = dbus::utility::MapperGetSubTreePathsResponse;
afterGetChassis(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,const boost::system::error_code & ec2,const Mapper & chassisPaths)208 inline void afterGetChassis(
209     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
210     const boost::system::error_code& ec2, const Mapper& chassisPaths)
211 {
212     if (ec2)
213     {
214         BMCWEB_LOG_ERROR("Power Limit GetSubTreePaths handler Dbus error {}",
215                          ec2);
216         return;
217     }
218 
219     bool found = false;
220     for (const std::string& chassis : chassisPaths)
221     {
222         size_t len = std::string::npos;
223         size_t lastPos = chassis.rfind('/');
224         if (lastPos == std::string::npos)
225         {
226             continue;
227         }
228 
229         if (lastPos == chassis.size() - 1)
230         {
231             size_t end = lastPos;
232             lastPos = chassis.rfind('/', lastPos - 1);
233             if (lastPos == std::string::npos)
234             {
235                 continue;
236             }
237 
238             len = end - (lastPos + 1);
239         }
240 
241         std::string interfaceChassisName = chassis.substr(lastPos + 1, len);
242         if (interfaceChassisName == sensorAsyncResp->chassisId)
243         {
244             found = true;
245             break;
246         }
247     }
248 
249     if (!found)
250     {
251         BMCWEB_LOG_DEBUG("Power Limit not present for {}",
252                          sensorAsyncResp->chassisId);
253         return;
254     }
255 
256     dbus::utility::getAllProperties(
257         "xyz.openbmc_project.Settings",
258         "/xyz/openbmc_project/control/host0/power_cap",
259         "xyz.openbmc_project.Control.Power.Cap",
260         [sensorAsyncResp](const boost::system::error_code& ec,
261                           const dbus::utility::DBusPropertiesMap& properties
262 
263         ) { afterPowerCapSettingGet(sensorAsyncResp, ec, properties); });
264 }
265 
handleChassisPowerGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisName)266 inline void handleChassisPowerGet(
267     App& app, const crow::Request& req,
268     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
269     const std::string& chassisName)
270 {
271     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
272     {
273         return;
274     }
275     asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array();
276 
277     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
278         asyncResp, chassisName, sensors::dbus::powerPaths,
279         sensor_utils::chassisSubNodeToString(
280             sensor_utils::ChassisSubNode::powerNode));
281 
282     getChassisData(sensorAsyncResp);
283 
284     // This callback verifies that the power limit is only provided
285     // for the chassis that implements the Chassis inventory item.
286     // This prevents things like power supplies providing the
287     // chassis power limit
288 
289     constexpr std::array<std::string_view, 2> interfaces = {
290         "xyz.openbmc_project.Inventory.Item.Board",
291         "xyz.openbmc_project.Inventory.Item.Chassis"};
292 
293     dbus::utility::getSubTreePaths(
294         "/xyz/openbmc_project/inventory", 0, interfaces,
295         std::bind_front(afterGetChassis, sensorAsyncResp));
296 }
297 
handleChassisPowerPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisName)298 inline void handleChassisPowerPatch(
299     App& app, const crow::Request& req,
300     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
301     const std::string& chassisName)
302 {
303     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
304     {
305         return;
306     }
307     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
308         asyncResp, chassisName, sensors::dbus::powerPaths,
309         sensor_utils::chassisSubNodeToString(
310             sensor_utils::ChassisSubNode::powerNode));
311 
312     std::optional<std::vector<nlohmann::json::object_t>> voltageCollections;
313     std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections;
314 
315     if (!json_util::readJsonPatch(                //
316             req, sensorAsyncResp->asyncResp->res, //
317             "PowerControl", powerCtlCollections,  //
318             "Voltages", voltageCollections        //
319             ))
320     {
321         return;
322     }
323 
324     if (powerCtlCollections)
325     {
326         redfish::chassis_utils::getValidChassisPath(
327             sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
328             std::bind_front(afterGetChassisPath, sensorAsyncResp,
329                             *powerCtlCollections));
330     }
331     if (voltageCollections)
332     {
333         std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>
334             allCollections;
335         allCollections.emplace("Voltages", std::move(*voltageCollections));
336         setSensorsOverride(sensorAsyncResp, allCollections);
337     }
338 }
339 
requestRoutesPower(App & app)340 inline void requestRoutesPower(App& app)
341 {
342     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
343         .privileges(redfish::privileges::getPower)
344         .methods(boost::beast::http::verb::get)(
345             std::bind_front(handleChassisPowerGet, std::ref(app)));
346 
347     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
348         .privileges(redfish::privileges::patchPower)
349         .methods(boost::beast::http::verb::patch)(
350             std::bind_front(handleChassisPowerPatch, std::ref(app)));
351 }
352 
353 } // namespace redfish
354