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