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