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