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