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 <boost/container/flat_map.hpp> 19 #include <node.hpp> 20 #include <utils/json_utils.hpp> 21 // for GetObjectType and ManagedObjectType 22 #include <account_service.hpp> 23 24 namespace redfish 25 26 { 27 28 /** 29 * @brief Read all known properties from VM object interfaces 30 */ 31 static void vmParseInterfaceObject(const DbusInterfaceType &interface, 32 std::shared_ptr<AsyncResp> aResp) 33 { 34 const auto mountPointIface = 35 interface.find("xyz.openbmc_project.VirtualMedia.MountPoint"); 36 if (mountPointIface == interface.cend()) 37 { 38 BMCWEB_LOG_DEBUG << "Interface MountPoint not found"; 39 return; 40 } 41 42 const auto processIface = 43 interface.find("xyz.openbmc_project.VirtualMedia.Process"); 44 if (processIface == interface.cend()) 45 { 46 BMCWEB_LOG_DEBUG << "Interface Process not found"; 47 return; 48 } 49 50 const auto endpointIdProperty = mountPointIface->second.find("EndpointId"); 51 if (endpointIdProperty == mountPointIface->second.cend()) 52 { 53 BMCWEB_LOG_DEBUG << "Property EndpointId not found"; 54 return; 55 } 56 57 const auto activeProperty = processIface->second.find("Active"); 58 if (activeProperty == processIface->second.cend()) 59 { 60 BMCWEB_LOG_DEBUG << "Property Active not found"; 61 return; 62 } 63 64 const bool *activeValue = std::get_if<bool>(&activeProperty->second); 65 if (!activeValue) 66 { 67 BMCWEB_LOG_DEBUG << "Value Active not found"; 68 return; 69 } 70 71 const std::string *endpointIdValue = 72 std::get_if<std::string>(&endpointIdProperty->second); 73 if (endpointIdValue) 74 { 75 if (!endpointIdValue->empty()) 76 { 77 // Proxy mode 78 aResp->res.jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = 79 *endpointIdValue; 80 aResp->res.jsonValue["TransferProtocolType"] = "OEM"; 81 aResp->res.jsonValue["Inserted"] = *activeValue; 82 if (*activeValue == true) 83 { 84 aResp->res.jsonValue["ConnectedVia"] = "Applet"; 85 } 86 } 87 else 88 { 89 // Legacy mode 90 const auto imageUrlProperty = 91 mountPointIface->second.find("ImageURL"); 92 if (imageUrlProperty != processIface->second.cend()) 93 { 94 const std::string *imageUrlValue = 95 std::get_if<std::string>(&imageUrlProperty->second); 96 if (imageUrlValue && !imageUrlValue->empty()) 97 { 98 aResp->res.jsonValue["ImageName"] = *imageUrlValue; 99 aResp->res.jsonValue["Inserted"] = *activeValue; 100 if (*activeValue == true) 101 { 102 aResp->res.jsonValue["ConnectedVia"] = "URI"; 103 } 104 } 105 } 106 } 107 } 108 } 109 110 /** 111 * @brief Fill template for Virtual Media Item. 112 */ 113 static nlohmann::json vmItemTemplate(const std::string &name, 114 const std::string &resName) 115 { 116 nlohmann::json item; 117 item["@odata.id"] = 118 "/redfish/v1/Managers/" + name + "/VirtualMedia/" + resName; 119 item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; 120 item["@odata.context"] = "/redfish/v1/$metadata#VirtualMedia.VirtualMedia"; 121 item["Name"] = "Virtual Removable Media"; 122 item["Id"] = resName; 123 item["Image"] = nullptr; 124 item["Inserted"] = nullptr; 125 item["ImageName"] = nullptr; 126 item["WriteProtected"] = true; 127 item["ConnectedVia"] = "NotConnected"; 128 item["MediaTypes"] = {"CD", "USBStick"}; 129 item["TransferMethod"] = "Stream"; 130 item["TransferProtocolType"] = nullptr; 131 item["Oem"]["OpenBmc"]["WebSocketEndpoint"] = nullptr; 132 item["Oem"]["OpenBMC"]["@odata.type"] = 133 "#OemVirtualMedia.v1_0_0.VirtualMedia"; 134 135 return item; 136 } 137 138 /** 139 * @brief Fills collection data 140 */ 141 static void getVmResourceList(std::shared_ptr<AsyncResp> aResp, 142 const std::string &service, 143 const std::string &name) 144 { 145 BMCWEB_LOG_DEBUG << "Get available Virtual Media resources."; 146 crow::connections::systemBus->async_method_call( 147 [name, aResp{std::move(aResp)}](const boost::system::error_code ec, 148 ManagedObjectType &subtree) { 149 if (ec) 150 { 151 BMCWEB_LOG_DEBUG << "DBUS response error"; 152 return; 153 } 154 nlohmann::json &members = aResp->res.jsonValue["Members"]; 155 members = nlohmann::json::array(); 156 157 for (const auto &object : subtree) 158 { 159 nlohmann::json item; 160 const std::string &path = 161 static_cast<const std::string &>(object.first); 162 std::size_t lastIndex = path.rfind("/"); 163 if (lastIndex == std::string::npos) 164 { 165 continue; 166 } 167 168 lastIndex += 1; 169 170 item["@odata.id"] = "/redfish/v1/Managers/" + name + 171 "/VirtualMedia/" + path.substr(lastIndex); 172 173 members.emplace_back(std::move(item)); 174 } 175 aResp->res.jsonValue["Members@odata.count"] = members.size(); 176 }, 177 service, "/xyz/openbmc_project/VirtualMedia", 178 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 179 } 180 181 /** 182 * @brief Fills data for specific resource 183 */ 184 static void getVmData(std::shared_ptr<AsyncResp> aResp, 185 const std::string &service, const std::string &name, 186 const std::string &resName) 187 { 188 BMCWEB_LOG_DEBUG << "Get Virtual Media resource data."; 189 190 crow::connections::systemBus->async_method_call( 191 [resName, name, aResp](const boost::system::error_code ec, 192 ManagedObjectType &subtree) { 193 if (ec) 194 { 195 BMCWEB_LOG_DEBUG << "DBUS response error"; 196 197 return; 198 } 199 200 for (auto &item : subtree) 201 { 202 const std::string &path = 203 static_cast<const std::string &>(item.first); 204 205 std::size_t lastItem = path.rfind("/"); 206 if (lastItem == std::string::npos) 207 { 208 continue; 209 } 210 211 if (path.substr(lastItem + 1) != resName) 212 { 213 continue; 214 } 215 216 aResp->res.jsonValue = vmItemTemplate(name, resName); 217 218 // Check if dbus path is Legacy type 219 if (path.find("VirtualMedia/Legacy") != std::string::npos) 220 { 221 aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] 222 ["target"] = 223 "/redfish/v1/Managers/" + name + "/VirtualMedia/" + 224 resName + "/Actions/VirtualMedia.InsertMedia"; 225 } 226 227 vmParseInterfaceObject(item.second, aResp); 228 229 aResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"] 230 ["target"] = 231 "/redfish/v1/Managers/" + name + "/VirtualMedia/" + 232 resName + "/Actions/VirtualMedia.EjectMedia"; 233 234 return; 235 } 236 237 messages::resourceNotFound( 238 aResp->res, "#VirtualMedia.v1_3_0.VirtualMedia", resName); 239 }, 240 service, "/xyz/openbmc_project/VirtualMedia", 241 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 242 } 243 244 /** 245 @brief InsertMedia action class 246 */ 247 class VirtualMediaActionInsertMedia : public Node 248 { 249 public: 250 VirtualMediaActionInsertMedia(CrowApp &app) : 251 Node(app, 252 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/" 253 "VirtualMedia.InsertMedia", 254 std::string(), std::string()) 255 { 256 entityPrivileges = { 257 {boost::beast::http::verb::get, {{"Login"}}}, 258 {boost::beast::http::verb::head, {{"Login"}}}, 259 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 260 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 261 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 262 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 263 } 264 265 private: 266 /** 267 * @brief Function handles POST method request. 268 * 269 * Analyzes POST body message before sends Reset request data to dbus. 270 */ 271 void doPost(crow::Response &res, const crow::Request &req, 272 const std::vector<std::string> ¶ms) override 273 { 274 auto aResp = std::make_shared<AsyncResp>(res); 275 276 if (params.size() != 2) 277 { 278 messages::internalError(res); 279 return; 280 } 281 282 // take resource name from URL 283 const std::string &resName = params[1]; 284 285 if (params[0] != "bmc") 286 { 287 messages::resourceNotFound(res, "VirtualMedia.Insert", resName); 288 289 return; 290 } 291 292 crow::connections::systemBus->async_method_call( 293 [this, aResp{std::move(aResp)}, req, 294 resName](const boost::system::error_code ec, 295 const GetObjectType &getObjectType) { 296 if (ec) 297 { 298 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 299 << ec; 300 messages::internalError(aResp->res); 301 302 return; 303 } 304 std::string service = getObjectType.begin()->first; 305 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 306 307 crow::connections::systemBus->async_method_call( 308 [this, service, resName, req, aResp{std::move(aResp)}]( 309 const boost::system::error_code ec, 310 ManagedObjectType &subtree) { 311 if (ec) 312 { 313 BMCWEB_LOG_DEBUG << "DBUS response error"; 314 315 return; 316 } 317 318 for (const auto &object : subtree) 319 { 320 const std::string &path = 321 static_cast<const std::string &>(object.first); 322 323 std::size_t lastIndex = path.rfind("/"); 324 if (lastIndex == std::string::npos) 325 { 326 continue; 327 } 328 329 lastIndex += 1; 330 331 if (path.substr(lastIndex) == resName) 332 { 333 lastIndex = path.rfind("Proxy"); 334 if (lastIndex != std::string::npos) 335 { 336 // Not possible in proxy mode 337 BMCWEB_LOG_DEBUG << "InsertMedia not " 338 "allowed in proxy mode"; 339 messages::resourceNotFound( 340 aResp->res, "VirtualMedia.InsertMedia", 341 resName); 342 343 return; 344 } 345 346 lastIndex = path.rfind("Legacy"); 347 if (lastIndex != std::string::npos) 348 { 349 // Legacy mode 350 std::string imageUrl; 351 352 // Read obligatory paramters (url of image) 353 if (!json_util::readJson(req, aResp->res, 354 "Image", imageUrl)) 355 { 356 BMCWEB_LOG_DEBUG 357 << "Image is not provided"; 358 return; 359 } 360 361 // must not be empty 362 if (imageUrl.size() == 0) 363 { 364 BMCWEB_LOG_ERROR 365 << "Request action parameter " 366 "Image is empty."; 367 messages::propertyValueFormatError( 368 aResp->res, "<empty>", "Image"); 369 370 return; 371 } 372 373 // manager is irrelevant for VirtualMedia 374 // dbus calls 375 doVmAction(std::move(aResp), service, 376 resName, true, imageUrl); 377 378 return; 379 } 380 } 381 } 382 BMCWEB_LOG_DEBUG << "Parent item not found"; 383 messages::resourceNotFound(aResp->res, "VirtualMedia", 384 resName); 385 }, 386 service, "/xyz/openbmc_project/VirtualMedia", 387 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 388 }, 389 "xyz.openbmc_project.ObjectMapper", 390 "/xyz/openbmc_project/object_mapper", 391 "xyz.openbmc_project.ObjectMapper", "GetObject", 392 "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>()); 393 } 394 395 /** 396 * @brief Function transceives data with dbus directly. 397 * 398 * All BMC state properties will be retrieved before sending reset request. 399 */ 400 void doVmAction(std::shared_ptr<AsyncResp> asyncResp, 401 const std::string &service, const std::string &name, 402 bool legacy, const std::string &imageUrl) 403 { 404 405 // Legacy mount requires parameter with image 406 if (legacy) 407 { 408 crow::connections::systemBus->async_method_call( 409 [asyncResp](const boost::system::error_code ec) { 410 if (ec) 411 { 412 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 413 messages::internalError(asyncResp->res); 414 415 return; 416 } 417 }, 418 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 419 "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl); 420 } 421 else // proxy 422 { 423 crow::connections::systemBus->async_method_call( 424 [asyncResp](const boost::system::error_code ec) { 425 if (ec) 426 { 427 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 428 messages::internalError(asyncResp->res); 429 430 return; 431 } 432 }, 433 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 434 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount"); 435 } 436 } 437 }; 438 439 /** 440 @brief EjectMedia action class 441 */ 442 class VirtualMediaActionEjectMedia : public Node 443 { 444 public: 445 VirtualMediaActionEjectMedia(CrowApp &app) : 446 Node(app, 447 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/" 448 "VirtualMedia.EjectMedia", 449 std::string(), std::string()) 450 { 451 entityPrivileges = { 452 {boost::beast::http::verb::get, {{"Login"}}}, 453 {boost::beast::http::verb::head, {{"Login"}}}, 454 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 455 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 456 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 457 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 458 } 459 460 private: 461 /** 462 * @brief Function handles POST method request. 463 * 464 * Analyzes POST body message before sends Reset request data to dbus. 465 */ 466 void doPost(crow::Response &res, const crow::Request &req, 467 const std::vector<std::string> ¶ms) override 468 { 469 auto aResp = std::make_shared<AsyncResp>(res); 470 471 if (params.size() != 2) 472 { 473 messages::internalError(res); 474 return; 475 } 476 477 // take resource name from URL 478 const std::string &resName = params[1]; 479 480 if (params[0] != "bmc") 481 { 482 messages::resourceNotFound(res, "VirtualMedia.Eject", resName); 483 484 return; 485 } 486 487 crow::connections::systemBus->async_method_call( 488 [this, aResp{std::move(aResp)}, req, 489 resName](const boost::system::error_code ec, 490 const GetObjectType &getObjectType) { 491 if (ec) 492 { 493 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 494 << ec; 495 messages::internalError(aResp->res); 496 497 return; 498 } 499 std::string service = getObjectType.begin()->first; 500 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 501 502 crow::connections::systemBus->async_method_call( 503 [this, resName, service, req, aResp{std::move(aResp)}]( 504 const boost::system::error_code ec, 505 ManagedObjectType &subtree) { 506 if (ec) 507 { 508 BMCWEB_LOG_DEBUG << "DBUS response error"; 509 510 return; 511 } 512 513 for (const auto &object : subtree) 514 { 515 const std::string &path = 516 static_cast<const std::string &>(object.first); 517 518 std::size_t lastIndex = path.rfind("/"); 519 if (lastIndex == std::string::npos) 520 { 521 continue; 522 } 523 524 lastIndex += 1; 525 526 if (path.substr(lastIndex) == resName) 527 { 528 lastIndex = path.rfind("Proxy"); 529 if (lastIndex != std::string::npos) 530 { 531 // Proxy mode 532 doVmAction(std::move(aResp), service, 533 resName, false); 534 } 535 536 lastIndex = path.rfind("Legacy"); 537 if (lastIndex != std::string::npos) 538 { 539 // Legacy mode 540 doVmAction(std::move(aResp), service, 541 resName, true); 542 } 543 544 return; 545 } 546 } 547 BMCWEB_LOG_DEBUG << "Parent item not found"; 548 messages::resourceNotFound(aResp->res, "VirtualMedia", 549 resName); 550 }, 551 service, "/xyz/openbmc_project/VirtualMedia", 552 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 553 }, 554 "xyz.openbmc_project.ObjectMapper", 555 "/xyz/openbmc_project/object_mapper", 556 "xyz.openbmc_project.ObjectMapper", "GetObject", 557 "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>()); 558 } 559 560 /** 561 * @brief Function transceives data with dbus directly. 562 * 563 * All BMC state properties will be retrieved before sending reset request. 564 */ 565 void doVmAction(std::shared_ptr<AsyncResp> asyncResp, 566 const std::string &service, const std::string &name, 567 bool legacy) 568 { 569 570 // Legacy mount requires parameter with image 571 if (legacy) 572 { 573 crow::connections::systemBus->async_method_call( 574 [asyncResp](const boost::system::error_code ec) { 575 if (ec) 576 { 577 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 578 579 messages::internalError(asyncResp->res); 580 return; 581 } 582 }, 583 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 584 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); 585 } 586 else // proxy 587 { 588 crow::connections::systemBus->async_method_call( 589 [asyncResp](const boost::system::error_code ec) { 590 if (ec) 591 { 592 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 593 594 messages::internalError(asyncResp->res); 595 return; 596 } 597 }, 598 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 599 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); 600 } 601 } 602 }; 603 604 class VirtualMediaCollection : public Node 605 { 606 public: 607 /* 608 * Default Constructor 609 */ 610 VirtualMediaCollection(CrowApp &app) : 611 Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/", std::string()) 612 { 613 entityPrivileges = { 614 {boost::beast::http::verb::get, {{"Login"}}}, 615 {boost::beast::http::verb::head, {{"Login"}}}, 616 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 617 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 618 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 619 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 620 } 621 622 private: 623 /** 624 * Functions triggers appropriate requests on DBus 625 */ 626 void doGet(crow::Response &res, const crow::Request &req, 627 const std::vector<std::string> ¶ms) override 628 { 629 auto asyncResp = std::make_shared<AsyncResp>(res); 630 631 // Check if there is required param, truly entering this shall be 632 // impossible 633 if (params.size() != 1) 634 { 635 messages::internalError(res); 636 637 return; 638 } 639 640 const std::string &name = params[0]; 641 642 if (name != "bmc") 643 { 644 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 645 646 return; 647 } 648 649 res.jsonValue["@odata.type"] = 650 "#VirtualMediaCollection.VirtualMediaCollection"; 651 res.jsonValue["Name"] = "Virtual Media Services"; 652 res.jsonValue["@odata.context"] = 653 "/redfish/v1/" 654 "$metadata#VirtualMediaCollection.VirtualMediaCollection"; 655 res.jsonValue["@odata.id"] = 656 "/redfish/v1/Managers/" + name + "/VirtualMedia/"; 657 658 crow::connections::systemBus->async_method_call( 659 [asyncResp, name](const boost::system::error_code ec, 660 const GetObjectType &getObjectType) { 661 if (ec) 662 { 663 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 664 << ec; 665 messages::internalError(asyncResp->res); 666 667 return; 668 } 669 std::string service = getObjectType.begin()->first; 670 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 671 672 getVmResourceList(asyncResp, service, name); 673 }, 674 "xyz.openbmc_project.ObjectMapper", 675 "/xyz/openbmc_project/object_mapper", 676 "xyz.openbmc_project.ObjectMapper", "GetObject", 677 "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>()); 678 } 679 }; 680 681 class VirtualMedia : public Node 682 { 683 public: 684 /* 685 * Default Constructor 686 */ 687 VirtualMedia(CrowApp &app) : 688 Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/", 689 std::string(), std::string()) 690 { 691 entityPrivileges = { 692 {boost::beast::http::verb::get, {{"Login"}}}, 693 {boost::beast::http::verb::head, {{"Login"}}}, 694 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 695 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 696 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 697 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 698 } 699 700 private: 701 /** 702 * Functions triggers appropriate requests on DBus 703 */ 704 void doGet(crow::Response &res, const crow::Request &req, 705 const std::vector<std::string> ¶ms) override 706 { 707 // Check if there is required param, truly entering this shall be 708 // impossible 709 if (params.size() != 2) 710 { 711 messages::internalError(res); 712 713 res.end(); 714 return; 715 } 716 const std::string &name = params[0]; 717 const std::string &resName = params[1]; 718 719 auto asyncResp = std::make_shared<AsyncResp>(res); 720 721 if (name != "bmc") 722 { 723 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 724 725 return; 726 } 727 728 crow::connections::systemBus->async_method_call( 729 [asyncResp, name, resName](const boost::system::error_code ec, 730 const GetObjectType &getObjectType) { 731 if (ec) 732 { 733 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 734 << ec; 735 messages::internalError(asyncResp->res); 736 737 return; 738 } 739 std::string service = getObjectType.begin()->first; 740 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 741 742 getVmData(asyncResp, service, name, resName); 743 }, 744 "xyz.openbmc_project.ObjectMapper", 745 "/xyz/openbmc_project/object_mapper", 746 "xyz.openbmc_project.ObjectMapper", "GetObject", 747 "/xyz/openbmc_project/VirtualMedia", std::array<const char *, 0>()); 748 } 749 }; 750 751 } // namespace redfish 752