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