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