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