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