1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 20 #include <boost/algorithm/string/replace.hpp> 21 #include <dbus_utility.hpp> 22 23 namespace redfish 24 { 25 static constexpr const char* objectManagerIface = 26 "org.freedesktop.DBus.ObjectManager"; 27 static constexpr const char* pidConfigurationIface = 28 "xyz.openbmc_project.Configuration.Pid"; 29 static constexpr const char* pidZoneConfigurationIface = 30 "xyz.openbmc_project.Configuration.Pid.Zone"; 31 32 static void asyncPopulatePid(const std::string& connection, 33 const std::string& path, 34 std::shared_ptr<AsyncResp> asyncResp) 35 { 36 37 crow::connections::systemBus->async_method_call( 38 [asyncResp](const boost::system::error_code ec, 39 const dbus::utility::ManagedObjectType& managedObj) { 40 if (ec) 41 { 42 BMCWEB_LOG_ERROR << ec; 43 asyncResp->res.result( 44 boost::beast::http::status::internal_server_error); 45 asyncResp->res.jsonValue.clear(); 46 return; 47 } 48 nlohmann::json& configRoot = 49 asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; 50 nlohmann::json& fans = configRoot["FanControllers"]; 51 fans["@odata.type"] = "#OemManager.FanControllers"; 52 fans["@odata.context"] = 53 "/redfish/v1/$metadata#OemManager.FanControllers"; 54 fans["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc/" 55 "Fan/FanControllers"; 56 57 nlohmann::json& pids = configRoot["PidControllers"]; 58 pids["@odata.type"] = "#OemManager.PidControllers"; 59 pids["@odata.context"] = 60 "/redfish/v1/$metadata#OemManager.PidControllers"; 61 pids["@odata.id"] = 62 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/PidControllers"; 63 64 nlohmann::json& zones = configRoot["FanZones"]; 65 zones["@odata.id"] = 66 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan/FanZones"; 67 zones["@odata.type"] = "#OemManager.FanZones"; 68 zones["@odata.context"] = 69 "/redfish/v1/$metadata#OemManager.FanZones"; 70 configRoot["@odata.id"] = 71 "/redfish/v1/Managers/bmc#/Oem/OpenBmc/Fan"; 72 configRoot["@odata.type"] = "#OemManager.Fan"; 73 configRoot["@odata.context"] = 74 "/redfish/v1/$metadata#OemManager.Fan"; 75 76 bool propertyError = false; 77 for (const auto& pathPair : managedObj) 78 { 79 for (const auto& intfPair : pathPair.second) 80 { 81 if (intfPair.first != pidConfigurationIface && 82 intfPair.first != pidZoneConfigurationIface) 83 { 84 continue; 85 } 86 auto findName = intfPair.second.find("Name"); 87 if (findName == intfPair.second.end()) 88 { 89 BMCWEB_LOG_ERROR << "Pid Field missing Name"; 90 asyncResp->res.result( 91 boost::beast::http::status::internal_server_error); 92 return; 93 } 94 const std::string* namePtr = 95 mapbox::getPtr<const std::string>(findName->second); 96 if (namePtr == nullptr) 97 { 98 BMCWEB_LOG_ERROR << "Pid Name Field illegal"; 99 return; 100 } 101 102 std::string name = *namePtr; 103 dbus::utility::escapePathForDbus(name); 104 if (intfPair.first == pidZoneConfigurationIface) 105 { 106 std::string chassis; 107 if (!dbus::utility::getNthStringFromPath( 108 pathPair.first.str, 5, chassis)) 109 { 110 chassis = "#IllegalValue"; 111 } 112 nlohmann::json& zone = zones[name]; 113 zone["Chassis"] = { 114 {"@odata.id", "/redfish/v1/Chassis/" + chassis}}; 115 zone["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/" 116 "OpenBmc/Fan/FanZones/" + 117 name; 118 zone["@odata.type"] = "#OemManager.FanZone"; 119 zone["@odata.context"] = 120 "/redfish/v1/$metadata#OemManager.FanZone"; 121 } 122 123 for (const auto& propertyPair : intfPair.second) 124 { 125 if (propertyPair.first == "Type" || 126 propertyPair.first == "Class" || 127 propertyPair.first == "Name") 128 { 129 continue; 130 } 131 132 // zones 133 if (intfPair.first == pidZoneConfigurationIface) 134 { 135 const double* ptr = mapbox::getPtr<const double>( 136 propertyPair.second); 137 if (ptr == nullptr) 138 { 139 BMCWEB_LOG_ERROR << "Field Illegal " 140 << propertyPair.first; 141 asyncResp->res.result( 142 boost::beast::http::status:: 143 internal_server_error); 144 return; 145 } 146 zones[name][propertyPair.first] = *ptr; 147 } 148 149 // pid and fans are off the same configuration 150 if (intfPair.first == pidConfigurationIface) 151 { 152 const std::string* classPtr = nullptr; 153 auto findClass = intfPair.second.find("Class"); 154 if (findClass != intfPair.second.end()) 155 { 156 classPtr = mapbox::getPtr<const std::string>( 157 findClass->second); 158 } 159 if (classPtr == nullptr) 160 { 161 BMCWEB_LOG_ERROR << "Pid Class Field illegal"; 162 asyncResp->res.result( 163 boost::beast::http::status:: 164 internal_server_error); 165 return; 166 } 167 bool isFan = *classPtr == "fan"; 168 nlohmann::json& element = 169 isFan ? fans[name] : pids[name]; 170 if (isFan) 171 { 172 element["@odata.id"] = 173 "/redfish/v1/Managers/bmc#/Oem/" 174 "OpenBmc/Fan/FanControllers/" + 175 std::string(name); 176 element["@odata.type"] = 177 "#OemManager.FanController"; 178 179 element["@odata.context"] = 180 "/redfish/v1/" 181 "$metadata#OemManager.FanController"; 182 } 183 else 184 { 185 element["@odata.id"] = 186 "/redfish/v1/Managers/bmc#/Oem/" 187 "OpenBmc/Fan/PidControllers/" + 188 std::string(name); 189 element["@odata.type"] = 190 "#OemManager.PidController"; 191 element["@odata.context"] = 192 "/redfish/v1/$metadata" 193 "#OemManager.PidController"; 194 } 195 196 if (propertyPair.first == "Zones") 197 { 198 const std::vector<std::string>* inputs = 199 mapbox::getPtr< 200 const std::vector<std::string>>( 201 propertyPair.second); 202 203 if (inputs == nullptr) 204 { 205 BMCWEB_LOG_ERROR 206 << "Zones Pid Field Illegal"; 207 asyncResp->res.result( 208 boost::beast::http::status:: 209 internal_server_error); 210 return; 211 } 212 auto& data = element[propertyPair.first]; 213 data = nlohmann::json::array(); 214 for (std::string itemCopy : *inputs) 215 { 216 dbus::utility::escapePathForDbus(itemCopy); 217 data.push_back( 218 {{"@odata.id", 219 "/redfish/v1/Managers/bmc#/Oem/" 220 "OpenBmc/Fan/FanZones/" + 221 itemCopy}}); 222 } 223 } 224 // todo(james): may never happen, but this 225 // assumes configuration data referenced in the 226 // PID config is provided by the same daemon, we 227 // could add another loop to cover all cases, 228 // but I'm okay kicking this can down the road a 229 // bit 230 231 else if (propertyPair.first == "Inputs" || 232 propertyPair.first == "Outputs") 233 { 234 auto& data = element[propertyPair.first]; 235 const std::vector<std::string>* inputs = 236 mapbox::getPtr< 237 const std::vector<std::string>>( 238 propertyPair.second); 239 240 if (inputs == nullptr) 241 { 242 BMCWEB_LOG_ERROR << "Field Illegal " 243 << propertyPair.first; 244 asyncResp->res.result( 245 boost::beast::http::status:: 246 internal_server_error); 247 return; 248 } 249 data = *inputs; 250 } // doubles 251 else if (propertyPair.first == 252 "FFGainCoefficient" || 253 propertyPair.first == "FFOffCoefficient" || 254 propertyPair.first == "ICoefficient" || 255 propertyPair.first == "ILimitMax" || 256 propertyPair.first == "ILimitMin" || 257 propertyPair.first == "OutLimitMax" || 258 propertyPair.first == "OutLimitMin" || 259 propertyPair.first == "PCoefficient" || 260 propertyPair.first == "SlewNeg" || 261 propertyPair.first == "SlewPos") 262 { 263 const double* ptr = 264 mapbox::getPtr<const double>( 265 propertyPair.second); 266 if (ptr == nullptr) 267 { 268 BMCWEB_LOG_ERROR << "Field Illegal " 269 << propertyPair.first; 270 asyncResp->res.result( 271 boost::beast::http::status:: 272 internal_server_error); 273 return; 274 } 275 element[propertyPair.first] = *ptr; 276 } 277 } 278 } 279 } 280 } 281 }, 282 connection, path, objectManagerIface, "GetManagedObjects"); 283 } 284 285 class Manager : public Node 286 { 287 public: 288 Manager(CrowApp& app) : Node(app, "/redfish/v1/Managers/bmc/") 289 { 290 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc"; 291 Node::json["@odata.type"] = "#Manager.v1_3_0.Manager"; 292 Node::json["@odata.context"] = "/redfish/v1/$metadata#Manager.Manager"; 293 Node::json["Id"] = "bmc"; 294 Node::json["Name"] = "OpenBmc Manager"; 295 Node::json["Description"] = "Baseboard Management Controller"; 296 Node::json["PowerState"] = "On"; 297 Node::json["ManagerType"] = "BMC"; 298 Node::json["UUID"] = 299 app.template getMiddleware<crow::persistent_data::Middleware>() 300 .systemUuid; 301 Node::json["Model"] = "OpenBmc"; // TODO(ed), get model 302 Node::json["EthernetInterfaces"] = { 303 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces"}}; 304 305 entityPrivileges = { 306 {boost::beast::http::verb::get, {{"Login"}}}, 307 {boost::beast::http::verb::head, {{"Login"}}}, 308 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 309 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 310 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 311 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 312 313 // default oem data 314 nlohmann::json& oem = Node::json["Oem"]; 315 nlohmann::json& oemOpenbmc = oem["OpenBmc"]; 316 oem["@odata.type"] = "#OemManager.Oem"; 317 oem["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem"; 318 oem["@odata.context"] = "/redfish/v1/$metadata#OemManager.Oem"; 319 oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; 320 oemOpenbmc["@odata.id"] = "/redfish/v1/Managers/bmc#/Oem/OpenBmc"; 321 oemOpenbmc["@odata.context"] = 322 "/redfish/v1/$metadata#OemManager.OpenBmc"; 323 } 324 325 private: 326 void getPidValues(std::shared_ptr<AsyncResp> asyncResp) 327 { 328 crow::connections::systemBus->async_method_call( 329 [asyncResp](const boost::system::error_code ec, 330 const crow::openbmc_mapper::GetSubTreeType& subtree) { 331 if (ec) 332 { 333 BMCWEB_LOG_ERROR << ec; 334 asyncResp->res.result( 335 boost::beast::http::status::internal_server_error); 336 return; 337 } 338 339 // create map of <connection, path to objMgr>> 340 boost::container::flat_map<std::string, std::string> 341 objectMgrPaths; 342 for (const auto& pathGroup : subtree) 343 { 344 for (const auto& connectionGroup : pathGroup.second) 345 { 346 for (const std::string& interface : 347 connectionGroup.second) 348 { 349 if (interface == objectManagerIface) 350 { 351 objectMgrPaths[connectionGroup.first] = 352 pathGroup.first; 353 } 354 // this list is alphabetical, so we 355 // should have found the objMgr by now 356 if (interface == pidConfigurationIface || 357 interface == pidZoneConfigurationIface) 358 { 359 auto findObjMgr = 360 objectMgrPaths.find(connectionGroup.first); 361 if (findObjMgr == objectMgrPaths.end()) 362 { 363 BMCWEB_LOG_DEBUG << connectionGroup.first 364 << "Has no Object Manager"; 365 continue; 366 } 367 asyncPopulatePid(findObjMgr->first, 368 findObjMgr->second, asyncResp); 369 break; 370 } 371 } 372 } 373 } 374 }, 375 "xyz.openbmc_project.ObjectMapper", 376 "/xyz/openbmc_project/object_mapper", 377 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 378 std::array<const char*, 3>{pidConfigurationIface, 379 pidZoneConfigurationIface, 380 objectManagerIface}); 381 } 382 383 void doGet(crow::Response& res, const crow::Request& req, 384 const std::vector<std::string>& params) override 385 { 386 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 387 asyncResp->res.jsonValue = Node::json; 388 389 Node::json["DateTime"] = getDateTime(); 390 res.jsonValue = Node::json; 391 392 crow::connections::systemBus->async_method_call( 393 [asyncResp](const boost::system::error_code ec, 394 const dbus::utility::ManagedObjectType& resp) { 395 if (ec) 396 { 397 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 398 asyncResp->res.result( 399 boost::beast::http::status::internal_server_error); 400 return; 401 } 402 403 for (auto& objpath : resp) 404 { 405 for (auto& interface : objpath.second) 406 { 407 // If interface is xyz.openbmc_project.Software.Version, 408 // this is what we're looking for. 409 if (interface.first == 410 "xyz.openbmc_project.Software.Version") 411 { 412 // Cut out everyting until last "/", ... 413 const std::string& iface_id = objpath.first; 414 for (auto& property : interface.second) 415 { 416 if (property.first == "Version") 417 { 418 const std::string* value = 419 mapbox::getPtr<const std::string>( 420 property.second); 421 if (value == nullptr) 422 { 423 continue; 424 } 425 asyncResp->res 426 .jsonValue["FirmwareVersion"] = *value; 427 } 428 } 429 } 430 } 431 } 432 }, 433 "xyz.openbmc_project.Software.BMC.Updater", 434 "/xyz/openbmc_project/software", 435 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 436 getPidValues(asyncResp); 437 } 438 439 void doPatch(crow::Response& res, const crow::Request& req, 440 const std::vector<std::string>& params) override 441 { 442 } 443 444 std::string getDateTime() const 445 { 446 std::array<char, 128> dateTime; 447 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 448 std::time_t time = std::time(nullptr); 449 450 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 451 std::localtime(&time))) 452 { 453 // insert the colon required by the ISO 8601 standard 454 redfishDateTime = std::string(dateTime.data()); 455 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 456 } 457 458 return redfishDateTime; 459 } 460 }; 461 462 class ManagerCollection : public Node 463 { 464 public: 465 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 466 { 467 Node::json["@odata.id"] = "/redfish/v1/Managers"; 468 Node::json["@odata.type"] = "#ManagerCollection.ManagerCollection"; 469 Node::json["@odata.context"] = 470 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 471 Node::json["Name"] = "Manager Collection"; 472 Node::json["Members@odata.count"] = 1; 473 Node::json["Members"] = {{{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 474 475 entityPrivileges = { 476 {boost::beast::http::verb::get, {{"Login"}}}, 477 {boost::beast::http::verb::head, {{"Login"}}}, 478 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 479 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 480 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 481 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 482 } 483 484 private: 485 void doGet(crow::Response& res, const crow::Request& req, 486 const std::vector<std::string>& params) override 487 { 488 // Collections don't include the static data added by SubRoute because 489 // it has a duplicate entry for members 490 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 491 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 492 res.jsonValue["@odata.context"] = 493 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 494 res.jsonValue["Name"] = "Manager Collection"; 495 res.jsonValue["Members@odata.count"] = 1; 496 res.jsonValue["Members"] = { 497 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 498 res.end(); 499 } 500 }; 501 } // namespace redfish 502