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