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 for (const auto& pathGroup : subtree) 332 { 333 for (const auto& connectionGroup : pathGroup.second) 334 { 335 for (const std::string& interface : 336 connectionGroup.second) 337 { 338 if (interface == objectManagerIface) 339 { 340 objectMgrPaths[connectionGroup.first] = 341 pathGroup.first; 342 } 343 // this list is alphabetical, so we 344 // should have found the objMgr by now 345 if (interface == pidConfigurationIface || 346 interface == pidZoneConfigurationIface) 347 { 348 auto findObjMgr = 349 objectMgrPaths.find(connectionGroup.first); 350 if (findObjMgr == objectMgrPaths.end()) 351 { 352 BMCWEB_LOG_DEBUG << connectionGroup.first 353 << "Has no Object Manager"; 354 continue; 355 } 356 asyncPopulatePid(findObjMgr->first, 357 findObjMgr->second, asyncResp); 358 break; 359 } 360 } 361 } 362 } 363 }, 364 "xyz.openbmc_project.ObjectMapper", 365 "/xyz/openbmc_project/object_mapper", 366 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, 367 std::array<const char*, 3>{pidConfigurationIface, 368 pidZoneConfigurationIface, 369 objectManagerIface}); 370 } 371 372 void doGet(crow::Response& res, const crow::Request& req, 373 const std::vector<std::string>& params) override 374 { 375 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 376 asyncResp->res.jsonValue = Node::json; 377 378 Node::json["DateTime"] = getDateTime(); 379 res.jsonValue = Node::json; 380 381 crow::connections::systemBus->async_method_call( 382 [asyncResp](const boost::system::error_code ec, 383 const dbus::utility::ManagedObjectType& resp) { 384 if (ec) 385 { 386 BMCWEB_LOG_ERROR << "Error while getting Software Version"; 387 messages::internalError(asyncResp->res); 388 return; 389 } 390 391 for (auto& objpath : resp) 392 { 393 for (auto& interface : objpath.second) 394 { 395 // If interface is xyz.openbmc_project.Software.Version, 396 // this is what we're looking for. 397 if (interface.first == 398 "xyz.openbmc_project.Software.Version") 399 { 400 // Cut out everyting until last "/", ... 401 const std::string& iface_id = objpath.first; 402 for (auto& property : interface.second) 403 { 404 if (property.first == "Version") 405 { 406 const std::string* value = 407 mapbox::getPtr<const std::string>( 408 property.second); 409 if (value == nullptr) 410 { 411 continue; 412 } 413 asyncResp->res 414 .jsonValue["FirmwareVersion"] = *value; 415 } 416 } 417 } 418 } 419 } 420 }, 421 "xyz.openbmc_project.Software.BMC.Updater", 422 "/xyz/openbmc_project/software", 423 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 424 getPidValues(asyncResp); 425 } 426 427 void doPatch(crow::Response& res, const crow::Request& req, 428 const std::vector<std::string>& params) override 429 { 430 } 431 432 std::string getDateTime() const 433 { 434 std::array<char, 128> dateTime; 435 std::string redfishDateTime("0000-00-00T00:00:00Z00:00"); 436 std::time_t time = std::time(nullptr); 437 438 if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z", 439 std::localtime(&time))) 440 { 441 // insert the colon required by the ISO 8601 standard 442 redfishDateTime = std::string(dateTime.data()); 443 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 444 } 445 446 return redfishDateTime; 447 } 448 }; 449 450 class ManagerCollection : public Node 451 { 452 public: 453 ManagerCollection(CrowApp& app) : Node(app, "/redfish/v1/Managers/") 454 { 455 Node::json["@odata.id"] = "/redfish/v1/Managers"; 456 Node::json["@odata.type"] = "#ManagerCollection.ManagerCollection"; 457 Node::json["@odata.context"] = 458 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 459 Node::json["Name"] = "Manager Collection"; 460 Node::json["Members@odata.count"] = 1; 461 Node::json["Members"] = {{{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 462 463 entityPrivileges = { 464 {boost::beast::http::verb::get, {{"Login"}}}, 465 {boost::beast::http::verb::head, {{"Login"}}}, 466 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 467 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 468 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 469 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 470 } 471 472 private: 473 void doGet(crow::Response& res, const crow::Request& req, 474 const std::vector<std::string>& params) override 475 { 476 // Collections don't include the static data added by SubRoute because 477 // it has a duplicate entry for members 478 res.jsonValue["@odata.id"] = "/redfish/v1/Managers"; 479 res.jsonValue["@odata.type"] = "#ManagerCollection.ManagerCollection"; 480 res.jsonValue["@odata.context"] = 481 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"; 482 res.jsonValue["Name"] = "Manager Collection"; 483 res.jsonValue["Members@odata.count"] = 1; 484 res.jsonValue["Members"] = { 485 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 486 res.end(); 487 } 488 }; 489 } // namespace redfish 490