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