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