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