1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/container/flat_set.hpp> 5 #include <dbus_singleton.hpp> 6 #include <dbus_utility.hpp> 7 #include <error_messages.hpp> 8 #include <query.hpp> 9 #include <registries/privilege_registry.hpp> 10 #include <sdbusplus/asio/property.hpp> 11 #include <utils/json_utils.hpp> 12 13 #include <optional> 14 #include <utility> 15 16 // TODO(ed) requestRoutesHypervisorSystems seems to have copy-pasted a 17 // lot of code, and has a number of methods that have name conflicts with the 18 // normal ethernet internfaces in ethernet.hpp. For the moment, we'll put 19 // hypervisor in a namespace to isolate it, but these methods eventually need 20 // deduplicated 21 namespace redfish::hypervisor 22 { 23 24 /** 25 * @brief Retrieves hypervisor state properties over dbus 26 * 27 * The hypervisor state object is optional so this function will only set the 28 * state variables if the object is found 29 * 30 * @param[in] aResp Shared pointer for completing asynchronous calls. 31 * 32 * @return None. 33 */ 34 inline void getHypervisorState(const std::shared_ptr<bmcweb::AsyncResp>& aResp) 35 { 36 BMCWEB_LOG_DEBUG << "Get hypervisor state information."; 37 sdbusplus::asio::getProperty<std::string>( 38 *crow::connections::systemBus, "xyz.openbmc_project.State.Hypervisor", 39 "/xyz/openbmc_project/state/hypervisor0", 40 "xyz.openbmc_project.State.Host", "CurrentHostState", 41 [aResp](const boost::system::error_code ec, 42 const std::string& hostState) { 43 if (ec) 44 { 45 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 46 // This is an optional D-Bus object so just return if 47 // error occurs 48 return; 49 } 50 51 BMCWEB_LOG_DEBUG << "Hypervisor state: " << hostState; 52 // Verify Host State 53 if (hostState == "xyz.openbmc_project.State.Host.HostState.Running") 54 { 55 aResp->res.jsonValue["PowerState"] = "On"; 56 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 57 } 58 else if (hostState == "xyz.openbmc_project.State.Host.HostState." 59 "Quiesced") 60 { 61 aResp->res.jsonValue["PowerState"] = "On"; 62 aResp->res.jsonValue["Status"]["State"] = "Quiesced"; 63 } 64 else if (hostState == "xyz.openbmc_project.State.Host.HostState." 65 "Standby") 66 { 67 aResp->res.jsonValue["PowerState"] = "On"; 68 aResp->res.jsonValue["Status"]["State"] = "StandbyOffline"; 69 } 70 else if (hostState == "xyz.openbmc_project.State.Host.HostState." 71 "TransitioningToRunning") 72 { 73 aResp->res.jsonValue["PowerState"] = "PoweringOn"; 74 aResp->res.jsonValue["Status"]["State"] = "Starting"; 75 } 76 else if (hostState == "xyz.openbmc_project.State.Host.HostState." 77 "TransitioningToOff") 78 { 79 aResp->res.jsonValue["PowerState"] = "PoweringOff"; 80 aResp->res.jsonValue["Status"]["State"] = "Enabled"; 81 } 82 else if (hostState == 83 "xyz.openbmc_project.State.Host.HostState.Off") 84 { 85 aResp->res.jsonValue["PowerState"] = "Off"; 86 aResp->res.jsonValue["Status"]["State"] = "Disabled"; 87 } 88 else 89 { 90 messages::internalError(aResp->res); 91 return; 92 } 93 }); 94 } 95 96 /** 97 * @brief Populate Actions if any are valid for hypervisor object 98 * 99 * The hypervisor state object is optional so this function will only set the 100 * Action if the object is found 101 * 102 * @param[in] aResp Shared pointer for completing asynchronous calls. 103 * 104 * @return None. 105 */ 106 inline void 107 getHypervisorActions(const std::shared_ptr<bmcweb::AsyncResp>& aResp) 108 { 109 BMCWEB_LOG_DEBUG << "Get hypervisor actions."; 110 crow::connections::systemBus->async_method_call( 111 [aResp]( 112 const boost::system::error_code ec, 113 const std::vector<std::pair<std::string, std::vector<std::string>>>& 114 objInfo) { 115 if (ec) 116 { 117 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 118 // This is an optional D-Bus object so just return if 119 // error occurs 120 return; 121 } 122 123 if (objInfo.empty()) 124 { 125 // As noted above, this is an optional interface so just return 126 // if there is no instance found 127 return; 128 } 129 130 if (objInfo.size() > 1) 131 { 132 // More then one hypervisor object is not supported and is an 133 // error 134 messages::internalError(aResp->res); 135 return; 136 } 137 138 // Object present so system support limited ComputerSystem Action 139 aResp->res.jsonValue["Actions"]["#ComputerSystem.Reset"] = { 140 {"target", 141 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset"}, 142 {"@Redfish.ActionInfo", 143 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}}; 144 }, 145 "xyz.openbmc_project.ObjectMapper", 146 "/xyz/openbmc_project/object_mapper", 147 "xyz.openbmc_project.ObjectMapper", "GetObject", 148 "/xyz/openbmc_project/state/hypervisor0", 149 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 150 } 151 152 inline bool extractHypervisorInterfaceData( 153 const std::string& ethIfaceId, 154 const dbus::utility::ManagedObjectType& dbusData, 155 EthernetInterfaceData& ethData, 156 boost::container::flat_set<IPv4AddressData>& ipv4Config) 157 { 158 bool idFound = false; 159 for (const auto& objpath : dbusData) 160 { 161 for (const auto& ifacePair : objpath.second) 162 { 163 if (objpath.first == 164 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId) 165 { 166 idFound = true; 167 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 168 { 169 for (const auto& propertyPair : ifacePair.second) 170 { 171 if (propertyPair.first == "MACAddress") 172 { 173 const std::string* mac = 174 std::get_if<std::string>(&propertyPair.second); 175 if (mac != nullptr) 176 { 177 ethData.mac_address = *mac; 178 } 179 } 180 } 181 } 182 else if (ifacePair.first == 183 "xyz.openbmc_project.Network.EthernetInterface") 184 { 185 for (const auto& propertyPair : ifacePair.second) 186 { 187 if (propertyPair.first == "DHCPEnabled") 188 { 189 const std::string* dhcp = 190 std::get_if<std::string>(&propertyPair.second); 191 if (dhcp != nullptr) 192 { 193 ethData.DHCPEnabled = *dhcp; 194 break; // Interested on only "DHCPEnabled". 195 // Stop parsing since we got the 196 // "DHCPEnabled" value. 197 } 198 } 199 } 200 } 201 } 202 if (objpath.first == "/xyz/openbmc_project/network/hypervisor/" + 203 ethIfaceId + "/ipv4/addr0") 204 { 205 std::pair<boost::container::flat_set<IPv4AddressData>::iterator, 206 bool> 207 it = ipv4Config.insert(IPv4AddressData{}); 208 IPv4AddressData& ipv4Address = *it.first; 209 if (ifacePair.first == "xyz.openbmc_project.Object.Enable") 210 { 211 for (const auto& property : ifacePair.second) 212 { 213 if (property.first == "Enabled") 214 { 215 const bool* intfEnable = 216 std::get_if<bool>(&property.second); 217 if (intfEnable != nullptr) 218 { 219 ipv4Address.isActive = *intfEnable; 220 break; 221 } 222 } 223 } 224 } 225 if (ifacePair.first == "xyz.openbmc_project.Network.IP") 226 { 227 for (const auto& property : ifacePair.second) 228 { 229 if (property.first == "Address") 230 { 231 const std::string* address = 232 std::get_if<std::string>(&property.second); 233 if (address != nullptr) 234 { 235 ipv4Address.address = *address; 236 } 237 } 238 else if (property.first == "Origin") 239 { 240 const std::string* origin = 241 std::get_if<std::string>(&property.second); 242 if (origin != nullptr) 243 { 244 ipv4Address.origin = 245 translateAddressOriginDbusToRedfish(*origin, 246 true); 247 } 248 } 249 else if (property.first == "PrefixLength") 250 { 251 const uint8_t* mask = 252 std::get_if<uint8_t>(&property.second); 253 if (mask != nullptr) 254 { 255 // convert it to the string 256 ipv4Address.netmask = getNetmask(*mask); 257 } 258 } 259 else if (property.first == "Type" || 260 property.first == "Gateway") 261 { 262 // Type & Gateway is not used 263 continue; 264 } 265 else 266 { 267 BMCWEB_LOG_ERROR 268 << "Got extra property: " << property.first 269 << " on the " << objpath.first.str << " object"; 270 } 271 } 272 } 273 } 274 if (objpath.first == "/xyz/openbmc_project/network/hypervisor") 275 { 276 // System configuration shows up in the global namespace, so no 277 // need to check eth number 278 if (ifacePair.first == 279 "xyz.openbmc_project.Network.SystemConfiguration") 280 { 281 for (const auto& propertyPair : ifacePair.second) 282 { 283 if (propertyPair.first == "HostName") 284 { 285 const std::string* hostName = 286 std::get_if<std::string>(&propertyPair.second); 287 if (hostName != nullptr) 288 { 289 ethData.hostname = *hostName; 290 } 291 } 292 else if (propertyPair.first == "DefaultGateway") 293 { 294 const std::string* defaultGateway = 295 std::get_if<std::string>(&propertyPair.second); 296 if (defaultGateway != nullptr) 297 { 298 ethData.default_gateway = *defaultGateway; 299 } 300 } 301 } 302 } 303 } 304 } 305 } 306 return idFound; 307 } 308 /** 309 * Function that retrieves all properties for given Hypervisor Ethernet 310 * Interface Object from Settings Manager 311 * @param ethIfaceId Hypervisor ethernet interface id to query on DBus 312 * @param callback a function that shall be called to convert Dbus output 313 * into JSON 314 */ 315 template <typename CallbackFunc> 316 void getHypervisorIfaceData(const std::string& ethIfaceId, 317 CallbackFunc&& callback) 318 { 319 crow::connections::systemBus->async_method_call( 320 [ethIfaceId{std::string{ethIfaceId}}, 321 callback{std::forward<CallbackFunc>(callback)}]( 322 const boost::system::error_code error, 323 const dbus::utility::ManagedObjectType& resp) { 324 EthernetInterfaceData ethData{}; 325 boost::container::flat_set<IPv4AddressData> ipv4Data; 326 if (error) 327 { 328 callback(false, ethData, ipv4Data); 329 return; 330 } 331 332 bool found = extractHypervisorInterfaceData(ethIfaceId, resp, 333 ethData, ipv4Data); 334 if (!found) 335 { 336 BMCWEB_LOG_INFO << "Hypervisor Interface not found"; 337 } 338 callback(found, ethData, ipv4Data); 339 }, 340 "xyz.openbmc_project.Settings", "/", 341 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 342 } 343 344 /** 345 * @brief Sets the Hypervisor Interface IPAddress DBUS 346 * 347 * @param[in] aResp Shared pointer for generating response message. 348 * @param[in] ipv4Address Address from the incoming request 349 * @param[in] ethIfaceId Hypervisor Interface Id 350 * 351 * @return None. 352 */ 353 inline void 354 setHypervisorIPv4Address(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 355 const std::string& ethIfaceId, 356 const std::string& ipv4Address) 357 { 358 BMCWEB_LOG_DEBUG << "Setting the Hypervisor IPaddress : " << ipv4Address 359 << " on Iface: " << ethIfaceId; 360 crow::connections::systemBus->async_method_call( 361 [aResp](const boost::system::error_code ec) { 362 if (ec) 363 { 364 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 365 return; 366 } 367 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress is Set"; 368 }, 369 "xyz.openbmc_project.Settings", 370 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 371 "org.freedesktop.DBus.Properties", "Set", 372 "xyz.openbmc_project.Network.IP", "Address", 373 dbus::utility::DbusVariantType(ipv4Address)); 374 } 375 376 /** 377 * @brief Sets the Hypervisor Interface SubnetMask DBUS 378 * 379 * @param[in] aResp Shared pointer for generating response message. 380 * @param[in] subnet SubnetMask from the incoming request 381 * @param[in] ethIfaceId Hypervisor Interface Id 382 * 383 * @return None. 384 */ 385 inline void 386 setHypervisorIPv4Subnet(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 387 const std::string& ethIfaceId, const uint8_t subnet) 388 { 389 BMCWEB_LOG_DEBUG << "Setting the Hypervisor subnet : " << subnet 390 << " on Iface: " << ethIfaceId; 391 392 crow::connections::systemBus->async_method_call( 393 [aResp](const boost::system::error_code ec) { 394 if (ec) 395 { 396 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 397 return; 398 } 399 BMCWEB_LOG_DEBUG << "SubnetMask is Set"; 400 }, 401 "xyz.openbmc_project.Settings", 402 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 403 "org.freedesktop.DBus.Properties", "Set", 404 "xyz.openbmc_project.Network.IP", "PrefixLength", 405 dbus::utility::DbusVariantType(subnet)); 406 } 407 408 /** 409 * @brief Sets the Hypervisor Interface Gateway DBUS 410 * 411 * @param[in] aResp Shared pointer for generating response message. 412 * @param[in] gateway Gateway from the incoming request 413 * @param[in] ethIfaceId Hypervisor Interface Id 414 * 415 * @return None. 416 */ 417 inline void 418 setHypervisorIPv4Gateway(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 419 const std::string& gateway) 420 { 421 BMCWEB_LOG_DEBUG 422 << "Setting the DefaultGateway to the last configured gateway"; 423 424 crow::connections::systemBus->async_method_call( 425 [aResp](const boost::system::error_code ec) { 426 if (ec) 427 { 428 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 429 return; 430 } 431 BMCWEB_LOG_DEBUG << "Default Gateway is Set"; 432 }, 433 "xyz.openbmc_project.Settings", 434 "/xyz/openbmc_project/network/hypervisor", 435 "org.freedesktop.DBus.Properties", "Set", 436 "xyz.openbmc_project.Network.SystemConfiguration", "DefaultGateway", 437 dbus::utility::DbusVariantType(gateway)); 438 } 439 440 /** 441 * @brief Creates a static IPv4 entry 442 * 443 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 444 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 445 * @param[in] gateway IPv4 address of this interfaces gateway 446 * @param[in] address IPv4 address to assign to this interface 447 * @param[io] asyncResp Response object that will be returned to client 448 * 449 * @return None 450 */ 451 inline void 452 createHypervisorIPv4(const std::string& ifaceId, uint8_t prefixLength, 453 const std::string& gateway, const std::string& address, 454 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 455 { 456 setHypervisorIPv4Address(asyncResp, ifaceId, address); 457 setHypervisorIPv4Gateway(asyncResp, gateway); 458 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 459 } 460 461 /** 462 * @brief Deletes given IPv4 interface 463 * 464 * @param[in] ifaceId Id of interface whose IP should be deleted 465 * @param[io] asyncResp Response object that will be returned to client 466 * 467 * @return None 468 */ 469 inline void 470 deleteHypervisorIPv4(const std::string& ifaceId, 471 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 472 { 473 std::string address = "0.0.0.0"; 474 std::string gateway = "0.0.0.0"; 475 const uint8_t prefixLength = 0; 476 setHypervisorIPv4Address(asyncResp, ifaceId, address); 477 setHypervisorIPv4Gateway(asyncResp, gateway); 478 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 479 } 480 481 inline void parseInterfaceData( 482 nlohmann::json& jsonResponse, const std::string& ifaceId, 483 const EthernetInterfaceData& ethData, 484 const boost::container::flat_set<IPv4AddressData>& ipv4Data) 485 { 486 jsonResponse["Id"] = ifaceId; 487 jsonResponse["@odata.id"] = 488 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + ifaceId; 489 jsonResponse["InterfaceEnabled"] = true; 490 jsonResponse["MACAddress"] = ethData.mac_address; 491 492 jsonResponse["HostName"] = ethData.hostname; 493 jsonResponse["DHCPv4"]["DHCPEnabled"] = 494 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 495 496 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 497 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 498 ipv4Array = nlohmann::json::array(); 499 ipv4StaticArray = nlohmann::json::array(); 500 for (const auto& ipv4Config : ipv4Data) 501 { 502 if (ipv4Config.isActive) 503 { 504 505 ipv4Array.push_back({{"AddressOrigin", ipv4Config.origin}, 506 {"SubnetMask", ipv4Config.netmask}, 507 {"Address", ipv4Config.address}, 508 {"Gateway", ethData.default_gateway}}); 509 if (ipv4Config.origin == "Static") 510 { 511 ipv4StaticArray.push_back( 512 {{"AddressOrigin", ipv4Config.origin}, 513 {"SubnetMask", ipv4Config.netmask}, 514 {"Address", ipv4Config.address}, 515 {"Gateway", ethData.default_gateway}}); 516 } 517 } 518 } 519 } 520 521 inline void setDHCPEnabled(const std::string& ifaceId, 522 const bool& ipv4DHCPEnabled, 523 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 524 { 525 const std::string dhcp = getDhcpEnabledEnumeration(ipv4DHCPEnabled, false); 526 crow::connections::systemBus->async_method_call( 527 [asyncResp](const boost::system::error_code ec) { 528 if (ec) 529 { 530 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 531 messages::internalError(asyncResp->res); 532 return; 533 } 534 }, 535 "xyz.openbmc_project.Settings", 536 "/xyz/openbmc_project/network/hypervisor/" + ifaceId, 537 "org.freedesktop.DBus.Properties", "Set", 538 "xyz.openbmc_project.Network.EthernetInterface", "DHCPEnabled", 539 dbus::utility::DbusVariantType{dhcp}); 540 541 // Set the IPv4 address origin to the DHCP / Static as per the new value 542 // of the DHCPEnabled property 543 std::string origin; 544 if (!ipv4DHCPEnabled) 545 { 546 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 547 } 548 else 549 { 550 // DHCPEnabled is set to true. Delete the current IPv4 settings 551 // to receive the new values from DHCP server. 552 deleteHypervisorIPv4(ifaceId, asyncResp); 553 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 554 } 555 crow::connections::systemBus->async_method_call( 556 [asyncResp](const boost::system::error_code ec) { 557 if (ec) 558 { 559 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 560 messages::internalError(asyncResp->res); 561 return; 562 } 563 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress Origin is Set"; 564 }, 565 "xyz.openbmc_project.Settings", 566 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 567 "org.freedesktop.DBus.Properties", "Set", 568 "xyz.openbmc_project.Network.IP", "Origin", 569 dbus::utility::DbusVariantType(origin)); 570 } 571 572 inline void handleHypervisorIPv4StaticPatch( 573 const std::string& ifaceId, const nlohmann::json& input, 574 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 575 { 576 if ((!input.is_array()) || input.empty()) 577 { 578 messages::propertyValueTypeError(asyncResp->res, input.dump(), 579 "IPv4StaticAddresses"); 580 return; 581 } 582 583 // Hypervisor considers the first IP address in the array list 584 // as the Hypervisor's virtual management interface supports single IPv4 585 // address 586 const nlohmann::json& thisJson = input[0]; 587 588 // For the error string 589 std::string pathString = "IPv4StaticAddresses/1"; 590 591 if (!thisJson.is_null() && !thisJson.empty()) 592 { 593 std::optional<std::string> address; 594 std::optional<std::string> subnetMask; 595 std::optional<std::string> gateway; 596 nlohmann::json thisJsonCopy = thisJson; 597 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 598 address, "SubnetMask", subnetMask, "Gateway", 599 gateway)) 600 { 601 messages::propertyValueFormatError( 602 asyncResp->res, 603 thisJson.dump(2, ' ', true, 604 nlohmann::json::error_handler_t::replace), 605 pathString); 606 return; 607 } 608 609 uint8_t prefixLength = 0; 610 bool errorInEntry = false; 611 if (address) 612 { 613 if (!ipv4VerifyIpAndGetBitcount(*address)) 614 { 615 messages::propertyValueFormatError(asyncResp->res, *address, 616 pathString + "/Address"); 617 errorInEntry = true; 618 } 619 } 620 else 621 { 622 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 623 errorInEntry = true; 624 } 625 626 if (subnetMask) 627 { 628 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 629 { 630 messages::propertyValueFormatError(asyncResp->res, *subnetMask, 631 pathString + "/SubnetMask"); 632 errorInEntry = true; 633 } 634 } 635 else 636 { 637 messages::propertyMissing(asyncResp->res, 638 pathString + "/SubnetMask"); 639 errorInEntry = true; 640 } 641 642 if (gateway) 643 { 644 if (!ipv4VerifyIpAndGetBitcount(*gateway)) 645 { 646 messages::propertyValueFormatError(asyncResp->res, *gateway, 647 pathString + "/Gateway"); 648 errorInEntry = true; 649 } 650 } 651 else 652 { 653 messages::propertyMissing(asyncResp->res, pathString + "/Gateway"); 654 errorInEntry = true; 655 } 656 657 if (errorInEntry) 658 { 659 return; 660 } 661 662 BMCWEB_LOG_DEBUG << "Calling createHypervisorIPv4 on : " << ifaceId 663 << "," << *address; 664 createHypervisorIPv4(ifaceId, prefixLength, *gateway, *address, 665 asyncResp); 666 // Set the DHCPEnabled to false since the Static IPv4 is set 667 setDHCPEnabled(ifaceId, false, asyncResp); 668 } 669 else 670 { 671 if (thisJson.is_null()) 672 { 673 deleteHypervisorIPv4(ifaceId, asyncResp); 674 } 675 } 676 } 677 678 inline void 679 handleHostnamePatch(const std::string& hostName, 680 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 681 { 682 if (!isHostnameValid(hostName)) 683 { 684 messages::propertyValueFormatError(asyncResp->res, hostName, 685 "HostName"); 686 return; 687 } 688 689 asyncResp->res.jsonValue["HostName"] = hostName; 690 crow::connections::systemBus->async_method_call( 691 [asyncResp](const boost::system::error_code ec) { 692 if (ec) 693 { 694 messages::internalError(asyncResp->res); 695 } 696 }, 697 "xyz.openbmc_project.Settings", 698 "/xyz/openbmc_project/network/hypervisor", 699 "org.freedesktop.DBus.Properties", "Set", 700 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 701 dbus::utility::DbusVariantType(hostName)); 702 } 703 704 inline void 705 setIPv4InterfaceEnabled(const std::string& ifaceId, const bool& isActive, 706 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 707 { 708 crow::connections::systemBus->async_method_call( 709 [asyncResp](const boost::system::error_code ec) { 710 if (ec) 711 { 712 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 713 messages::internalError(asyncResp->res); 714 return; 715 } 716 }, 717 "xyz.openbmc_project.Settings", 718 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 719 "org.freedesktop.DBus.Properties", "Set", 720 "xyz.openbmc_project.Object.Enable", "Enabled", 721 dbus::utility::DbusVariantType(isActive)); 722 } 723 724 inline void requestRoutesHypervisorSystems(App& app) 725 { 726 /** 727 * Hypervisor Systems derived class for delivering Computer Systems Schema. 728 */ 729 730 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/") 731 .privileges(redfish::privileges::getComputerSystem) 732 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req, 733 const std::shared_ptr< 734 bmcweb::AsyncResp>& 735 asyncResp) { 736 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 737 { 738 return; 739 } 740 sdbusplus::asio::getProperty<std::string>( 741 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 742 "/xyz/openbmc_project/network/hypervisor", 743 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 744 [asyncResp](const boost::system::error_code ec, 745 const std::string& /*hostName*/) { 746 if (ec) 747 { 748 messages::resourceNotFound(asyncResp->res, "System", 749 "hypervisor"); 750 return; 751 } 752 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 753 754 asyncResp->res.jsonValue["@odata.type"] = 755 "#ComputerSystem.v1_6_0.ComputerSystem"; 756 asyncResp->res.jsonValue["@odata.id"] = 757 "/redfish/v1/Systems/hypervisor"; 758 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 759 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 760 asyncResp->res.jsonValue["Id"] = "hypervisor"; 761 asyncResp->res.jsonValue["SystemType"] = "OS"; 762 asyncResp->res.jsonValue["Links"]["ManagedBy"] = { 763 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 764 asyncResp->res.jsonValue["EthernetInterfaces"] = { 765 {"@odata.id", 766 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"}}; 767 getHypervisorState(asyncResp); 768 getHypervisorActions(asyncResp); 769 // TODO: Add "SystemType" : "hypervisor" 770 }); 771 }); 772 773 /** 774 * HypervisorInterfaceCollection class to handle the GET and PATCH on 775 * Hypervisor Interface 776 */ 777 778 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 779 .privileges(redfish::privileges::getEthernetInterfaceCollection) 780 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req, 781 const std::shared_ptr< 782 bmcweb::AsyncResp>& 783 asyncResp) { 784 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 785 { 786 return; 787 } 788 const std::array<const char*, 1> interfaces = { 789 "xyz.openbmc_project.Network.EthernetInterface"}; 790 791 crow::connections::systemBus->async_method_call( 792 [asyncResp](const boost::system::error_code error, 793 const dbus::utility::MapperGetSubTreePathsResponse& 794 ifaceList) { 795 if (error) 796 { 797 messages::resourceNotFound(asyncResp->res, "System", 798 "hypervisor"); 799 return; 800 } 801 asyncResp->res.jsonValue["@odata.type"] = 802 "#EthernetInterfaceCollection." 803 "EthernetInterfaceCollection"; 804 asyncResp->res.jsonValue["@odata.id"] = 805 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 806 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 807 "Interface Collection"; 808 asyncResp->res.jsonValue["Description"] = 809 "Collection of Virtual Management " 810 "Interfaces for the hypervisor"; 811 812 nlohmann::json& ifaceArray = 813 asyncResp->res.jsonValue["Members"]; 814 ifaceArray = nlohmann::json::array(); 815 for (const std::string& iface : ifaceList) 816 { 817 sdbusplus::message::object_path path(iface); 818 std::string name = path.filename(); 819 if (name.empty()) 820 { 821 continue; 822 } 823 824 ifaceArray.push_back( 825 {{"@odata.id", 826 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + 827 name}}); 828 } 829 asyncResp->res.jsonValue["Members@odata.count"] = 830 ifaceArray.size(); 831 }, 832 "xyz.openbmc_project.ObjectMapper", 833 "/xyz/openbmc_project/object_mapper", 834 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 835 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 836 }); 837 838 BMCWEB_ROUTE(app, 839 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 840 .privileges(redfish::privileges::getEthernetInterface) 841 .methods(boost::beast::http::verb::get)( 842 [&app](const crow::Request& req, 843 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 844 const std::string& id) { 845 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 846 { 847 return; 848 } 849 getHypervisorIfaceData(id, [asyncResp, 850 ifaceId{std::string(id)}]( 851 const bool& success, 852 const EthernetInterfaceData& 853 ethData, 854 const boost::container::flat_set< 855 IPv4AddressData>& ipv4Data) { 856 if (!success) 857 { 858 messages::resourceNotFound( 859 asyncResp->res, "EthernetInterface", ifaceId); 860 return; 861 } 862 asyncResp->res.jsonValue["@odata.type"] = 863 "#EthernetInterface.v1_5_1.EthernetInterface"; 864 asyncResp->res.jsonValue["Name"] = 865 "Hypervisor Ethernet Interface"; 866 asyncResp->res.jsonValue["Description"] = 867 "Hypervisor's Virtual Management Ethernet Interface"; 868 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, 869 ethData, ipv4Data); 870 }); 871 }); 872 873 BMCWEB_ROUTE(app, 874 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 875 .privileges(redfish::privileges::patchEthernetInterface) 876 .methods( 877 boost::beast::http::verb:: 878 patch)([&app]( 879 const crow::Request& req, 880 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 881 const std::string& ifaceId) { 882 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 883 { 884 return; 885 } 886 std::optional<std::string> hostName; 887 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 888 std::optional<nlohmann::json> ipv4Addresses; 889 std::optional<nlohmann::json> dhcpv4; 890 std::optional<bool> ipv4DHCPEnabled; 891 892 if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", 893 hostName, "IPv4StaticAddresses", 894 ipv4StaticAddresses, "IPv4Addresses", 895 ipv4Addresses, "DHCPv4", dhcpv4)) 896 { 897 return; 898 } 899 900 if (ipv4Addresses) 901 { 902 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 903 return; 904 } 905 906 if (dhcpv4) 907 { 908 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 909 ipv4DHCPEnabled)) 910 { 911 return; 912 } 913 } 914 915 getHypervisorIfaceData( 916 ifaceId, 917 [asyncResp, ifaceId, hostName = std::move(hostName), 918 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 919 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 920 const bool& success, const EthernetInterfaceData& ethData, 921 const boost::container::flat_set<IPv4AddressData>&) { 922 if (!success) 923 { 924 messages::resourceNotFound( 925 asyncResp->res, "EthernetInterface", ifaceId); 926 return; 927 } 928 929 if (ipv4StaticAddresses) 930 { 931 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 932 if (ipv4Static.begin() == ipv4Static.end()) 933 { 934 messages::propertyValueTypeError( 935 asyncResp->res, 936 ipv4Static.dump( 937 2, ' ', true, 938 nlohmann::json::error_handler_t::replace), 939 "IPv4StaticAddresses"); 940 return; 941 } 942 943 // One and only one hypervisor instance supported 944 if (ipv4Static.size() != 1) 945 { 946 messages::propertyValueFormatError( 947 asyncResp->res, 948 ipv4Static.dump( 949 2, ' ', true, 950 nlohmann::json::error_handler_t::replace), 951 "IPv4StaticAddresses"); 952 return; 953 } 954 955 const nlohmann::json& ipv4Json = ipv4Static[0]; 956 // Check if the param is 'null'. If its null, it means 957 // that user wants to delete the IP address. Deleting 958 // the IP address is allowed only if its statically 959 // configured. Deleting the address originated from DHCP 960 // is not allowed. 961 if ((ipv4Json.is_null()) && 962 (translateDHCPEnabledToBool(ethData.DHCPEnabled, 963 true))) 964 { 965 BMCWEB_LOG_INFO 966 << "Ignoring the delete on ipv4StaticAddresses " 967 "as the interface is DHCP enabled"; 968 } 969 else 970 { 971 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 972 asyncResp); 973 } 974 } 975 976 if (hostName) 977 { 978 handleHostnamePatch(*hostName, asyncResp); 979 } 980 981 if (dhcpv4) 982 { 983 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 984 } 985 986 // Set this interface to disabled/inactive. This will be set 987 // to enabled/active by the pldm once the hypervisor 988 // consumes the updated settings from the user. 989 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 990 }); 991 asyncResp->res.result(boost::beast::http::status::accepted); 992 }); 993 994 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 995 .privileges(redfish::privileges::getActionInfo) 996 .methods(boost::beast::http::verb::get)( 997 [&app](const crow::Request& req, 998 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 999 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1000 { 1001 return; 1002 } 1003 // Only return action info if hypervisor D-Bus object present 1004 crow::connections::systemBus->async_method_call( 1005 [asyncResp]( 1006 const boost::system::error_code ec, 1007 const std::vector<std::pair< 1008 std::string, std::vector<std::string>>>& objInfo) { 1009 if (ec) 1010 { 1011 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1012 1013 // No hypervisor objects found by mapper 1014 if (ec.value() == boost::system::errc::io_error) 1015 { 1016 messages::resourceNotFound(asyncResp->res, 1017 "hypervisor", 1018 "ResetActionInfo"); 1019 return; 1020 } 1021 1022 messages::internalError(asyncResp->res); 1023 return; 1024 } 1025 1026 // One and only one hypervisor instance supported 1027 if (objInfo.size() != 1) 1028 { 1029 messages::internalError(asyncResp->res); 1030 return; 1031 } 1032 1033 // The hypervisor object only support the ability to 1034 // turn On The system object Action should be utilized 1035 // for other operations 1036 asyncResp->res.jsonValue = { 1037 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 1038 {"@odata.id", 1039 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}, 1040 {"Name", "Reset Action Info"}, 1041 {"Id", "ResetActionInfo"}, 1042 {"Parameters", 1043 {{{"Name", "ResetType"}, 1044 {"Required", true}, 1045 {"DataType", "String"}, 1046 {"AllowableValues", {"On"}}}}}}; 1047 }, 1048 "xyz.openbmc_project.ObjectMapper", 1049 "/xyz/openbmc_project/object_mapper", 1050 "xyz.openbmc_project.ObjectMapper", "GetObject", 1051 "/xyz/openbmc_project/state/hypervisor0", 1052 std::array<const char*, 1>{ 1053 "xyz.openbmc_project.State.Host"}); 1054 }); 1055 1056 BMCWEB_ROUTE(app, 1057 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1058 .privileges(redfish::privileges::postComputerSystem) 1059 .methods(boost::beast::http::verb::post)( 1060 [&app](const crow::Request& req, 1061 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1062 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1063 { 1064 return; 1065 } 1066 std::optional<std::string> resetType; 1067 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", 1068 resetType)) 1069 { 1070 // readJson adds appropriate error to response 1071 return; 1072 } 1073 1074 if (!resetType) 1075 { 1076 messages::actionParameterMissing( 1077 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1078 return; 1079 } 1080 1081 // Hypervisor object only support On operation 1082 if (resetType != "On") 1083 { 1084 messages::propertyValueNotInList(asyncResp->res, *resetType, 1085 "ResetType"); 1086 return; 1087 } 1088 1089 std::string command = 1090 "xyz.openbmc_project.State.Host.Transition.On"; 1091 1092 crow::connections::systemBus->async_method_call( 1093 [asyncResp, resetType](const boost::system::error_code ec) { 1094 if (ec) 1095 { 1096 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1097 if (ec.value() == 1098 boost::asio::error::invalid_argument) 1099 { 1100 messages::actionParameterNotSupported( 1101 asyncResp->res, *resetType, "Reset"); 1102 return; 1103 } 1104 1105 if (ec.value() == 1106 boost::asio::error::host_unreachable) 1107 { 1108 messages::resourceNotFound(asyncResp->res, 1109 "Actions", "Reset"); 1110 return; 1111 } 1112 1113 messages::internalError(asyncResp->res); 1114 return; 1115 } 1116 messages::success(asyncResp->res); 1117 }, 1118 "xyz.openbmc_project.State.Hypervisor", 1119 "/xyz/openbmc_project/state/hypervisor0", 1120 "org.freedesktop.DBus.Properties", "Set", 1121 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1122 dbus::utility::DbusVariantType{std::move(command)}); 1123 }); 1124 } 1125 } // namespace redfish::hypervisor 1126