1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/container/flat_map.hpp> 5 #include <boost/container/flat_set.hpp> 6 #include <dbus_singleton.hpp> 7 #include <dbus_utility.hpp> 8 #include <error_messages.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( 733 boost::beast::http::verb:: 734 get)([](const crow::Request&, 735 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 736 sdbusplus::asio::getProperty<std::string>( 737 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 738 "/xyz/openbmc_project/network/hypervisor", 739 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 740 [asyncResp](const boost::system::error_code ec, 741 const std::string& /*hostName*/) { 742 if (ec) 743 { 744 messages::resourceNotFound(asyncResp->res, "System", 745 "hypervisor"); 746 return; 747 } 748 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 749 750 asyncResp->res.jsonValue["@odata.type"] = 751 "#ComputerSystem.v1_6_0.ComputerSystem"; 752 asyncResp->res.jsonValue["@odata.id"] = 753 "/redfish/v1/Systems/hypervisor"; 754 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 755 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 756 asyncResp->res.jsonValue["Id"] = "hypervisor"; 757 asyncResp->res.jsonValue["SystemType"] = "OS"; 758 asyncResp->res.jsonValue["Links"]["ManagedBy"] = { 759 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 760 asyncResp->res.jsonValue["EthernetInterfaces"] = { 761 {"@odata.id", 762 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"}}; 763 getHypervisorState(asyncResp); 764 getHypervisorActions(asyncResp); 765 // TODO: Add "SystemType" : "hypervisor" 766 }); 767 }); 768 769 /** 770 * HypervisorInterfaceCollection class to handle the GET and PATCH on 771 * Hypervisor Interface 772 */ 773 774 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 775 .privileges(redfish::privileges::getEthernetInterfaceCollection) 776 .methods( 777 boost::beast::http::verb:: 778 get)([](const crow::Request&, 779 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 780 const std::array<const char*, 1> interfaces = { 781 "xyz.openbmc_project.Network.EthernetInterface"}; 782 783 crow::connections::systemBus->async_method_call( 784 [asyncResp](const boost::system::error_code error, 785 const std::vector<std::string>& ifaceList) { 786 if (error) 787 { 788 messages::resourceNotFound(asyncResp->res, "System", 789 "hypervisor"); 790 return; 791 } 792 asyncResp->res.jsonValue["@odata.type"] = 793 "#EthernetInterfaceCollection." 794 "EthernetInterfaceCollection"; 795 asyncResp->res.jsonValue["@odata.id"] = 796 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 797 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 798 "Interface Collection"; 799 asyncResp->res.jsonValue["Description"] = 800 "Collection of Virtual Management " 801 "Interfaces for the hypervisor"; 802 803 nlohmann::json& ifaceArray = 804 asyncResp->res.jsonValue["Members"]; 805 ifaceArray = nlohmann::json::array(); 806 for (const std::string& iface : ifaceList) 807 { 808 sdbusplus::message::object_path path(iface); 809 std::string name = path.filename(); 810 if (name.empty()) 811 { 812 continue; 813 } 814 815 ifaceArray.push_back( 816 {{"@odata.id", 817 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + 818 name}}); 819 } 820 asyncResp->res.jsonValue["Members@odata.count"] = 821 ifaceArray.size(); 822 }, 823 "xyz.openbmc_project.ObjectMapper", 824 "/xyz/openbmc_project/object_mapper", 825 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 826 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 827 }); 828 829 BMCWEB_ROUTE(app, 830 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 831 .privileges(redfish::privileges::getEthernetInterface) 832 .methods( 833 boost::beast::http::verb::get)([](const crow::Request&, 834 const std::shared_ptr< 835 bmcweb::AsyncResp>& asyncResp, 836 const std::string& id) { 837 getHypervisorIfaceData( 838 id, 839 [asyncResp, ifaceId{std::string(id)}]( 840 const bool& success, const EthernetInterfaceData& ethData, 841 const boost::container::flat_set<IPv4AddressData>& 842 ipv4Data) { 843 if (!success) 844 { 845 messages::resourceNotFound( 846 asyncResp->res, "EthernetInterface", ifaceId); 847 return; 848 } 849 asyncResp->res.jsonValue["@odata.type"] = 850 "#EthernetInterface.v1_5_1.EthernetInterface"; 851 asyncResp->res.jsonValue["Name"] = 852 "Hypervisor Ethernet Interface"; 853 asyncResp->res.jsonValue["Description"] = 854 "Hypervisor's Virtual Management Ethernet Interface"; 855 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, 856 ethData, ipv4Data); 857 }); 858 }); 859 860 BMCWEB_ROUTE(app, 861 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 862 .privileges(redfish::privileges::patchEthernetInterface) 863 .methods( 864 boost::beast::http::verb:: 865 patch)([](const crow::Request& req, 866 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 867 const std::string& ifaceId) { 868 std::optional<std::string> hostName; 869 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 870 std::optional<nlohmann::json> ipv4Addresses; 871 std::optional<nlohmann::json> dhcpv4; 872 std::optional<bool> ipv4DHCPEnabled; 873 874 if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", 875 hostName, "IPv4StaticAddresses", 876 ipv4StaticAddresses, "IPv4Addresses", 877 ipv4Addresses, "DHCPv4", dhcpv4)) 878 { 879 return; 880 } 881 882 if (ipv4Addresses) 883 { 884 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 885 return; 886 } 887 888 if (dhcpv4) 889 { 890 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 891 ipv4DHCPEnabled)) 892 { 893 return; 894 } 895 } 896 897 getHypervisorIfaceData( 898 ifaceId, 899 [asyncResp, ifaceId, hostName = std::move(hostName), 900 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 901 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 902 const bool& success, const EthernetInterfaceData& ethData, 903 const boost::container::flat_set<IPv4AddressData>&) { 904 if (!success) 905 { 906 messages::resourceNotFound( 907 asyncResp->res, "EthernetInterface", ifaceId); 908 return; 909 } 910 911 if (ipv4StaticAddresses) 912 { 913 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 914 if (ipv4Static.begin() == ipv4Static.end()) 915 { 916 messages::propertyValueTypeError( 917 asyncResp->res, 918 ipv4Static.dump( 919 2, ' ', true, 920 nlohmann::json::error_handler_t::replace), 921 "IPv4StaticAddresses"); 922 return; 923 } 924 925 // One and only one hypervisor instance supported 926 if (ipv4Static.size() != 1) 927 { 928 messages::propertyValueFormatError( 929 asyncResp->res, 930 ipv4Static.dump( 931 2, ' ', true, 932 nlohmann::json::error_handler_t::replace), 933 "IPv4StaticAddresses"); 934 return; 935 } 936 937 const nlohmann::json& ipv4Json = ipv4Static[0]; 938 // Check if the param is 'null'. If its null, it means 939 // that user wants to delete the IP address. Deleting 940 // the IP address is allowed only if its statically 941 // configured. Deleting the address originated from DHCP 942 // is not allowed. 943 if ((ipv4Json.is_null()) && 944 (translateDHCPEnabledToBool(ethData.DHCPEnabled, 945 true))) 946 { 947 BMCWEB_LOG_INFO 948 << "Ignoring the delete on ipv4StaticAddresses " 949 "as the interface is DHCP enabled"; 950 } 951 else 952 { 953 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 954 asyncResp); 955 } 956 } 957 958 if (hostName) 959 { 960 handleHostnamePatch(*hostName, asyncResp); 961 } 962 963 if (dhcpv4) 964 { 965 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 966 } 967 968 // Set this interface to disabled/inactive. This will be set 969 // to enabled/active by the pldm once the hypervisor 970 // consumes the updated settings from the user. 971 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 972 }); 973 asyncResp->res.result(boost::beast::http::status::accepted); 974 }); 975 976 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 977 .privileges(redfish::privileges::getActionInfo) 978 .methods(boost::beast::http::verb::get)( 979 [](const crow::Request&, 980 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 981 // Only return action info if hypervisor D-Bus object present 982 crow::connections::systemBus->async_method_call( 983 [asyncResp]( 984 const boost::system::error_code ec, 985 const std::vector<std::pair< 986 std::string, std::vector<std::string>>>& objInfo) { 987 if (ec) 988 { 989 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 990 991 // No hypervisor objects found by mapper 992 if (ec.value() == boost::system::errc::io_error) 993 { 994 messages::resourceNotFound(asyncResp->res, 995 "hypervisor", 996 "ResetActionInfo"); 997 return; 998 } 999 1000 messages::internalError(asyncResp->res); 1001 return; 1002 } 1003 1004 // One and only one hypervisor instance supported 1005 if (objInfo.size() != 1) 1006 { 1007 messages::internalError(asyncResp->res); 1008 return; 1009 } 1010 1011 // The hypervisor object only support the ability to 1012 // turn On The system object Action should be utilized 1013 // for other operations 1014 asyncResp->res.jsonValue = { 1015 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 1016 {"@odata.id", 1017 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}, 1018 {"Name", "Reset Action Info"}, 1019 {"Id", "ResetActionInfo"}, 1020 {"Parameters", 1021 {{{"Name", "ResetType"}, 1022 {"Required", true}, 1023 {"DataType", "String"}, 1024 {"AllowableValues", {"On"}}}}}}; 1025 }, 1026 "xyz.openbmc_project.ObjectMapper", 1027 "/xyz/openbmc_project/object_mapper", 1028 "xyz.openbmc_project.ObjectMapper", "GetObject", 1029 "/xyz/openbmc_project/state/hypervisor0", 1030 std::array<const char*, 1>{ 1031 "xyz.openbmc_project.State.Host"}); 1032 }); 1033 1034 BMCWEB_ROUTE(app, 1035 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1036 .privileges(redfish::privileges::postComputerSystem) 1037 .methods(boost::beast::http::verb::post)( 1038 [](const crow::Request& req, 1039 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1040 std::optional<std::string> resetType; 1041 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", 1042 resetType)) 1043 { 1044 // readJson adds appropriate error to response 1045 return; 1046 } 1047 1048 if (!resetType) 1049 { 1050 messages::actionParameterMissing( 1051 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1052 return; 1053 } 1054 1055 // Hypervisor object only support On operation 1056 if (resetType != "On") 1057 { 1058 messages::propertyValueNotInList(asyncResp->res, *resetType, 1059 "ResetType"); 1060 return; 1061 } 1062 1063 std::string command = 1064 "xyz.openbmc_project.State.Host.Transition.On"; 1065 1066 crow::connections::systemBus->async_method_call( 1067 [asyncResp, resetType](const boost::system::error_code ec) { 1068 if (ec) 1069 { 1070 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1071 if (ec.value() == 1072 boost::asio::error::invalid_argument) 1073 { 1074 messages::actionParameterNotSupported( 1075 asyncResp->res, *resetType, "Reset"); 1076 return; 1077 } 1078 1079 if (ec.value() == 1080 boost::asio::error::host_unreachable) 1081 { 1082 messages::resourceNotFound(asyncResp->res, 1083 "Actions", "Reset"); 1084 return; 1085 } 1086 1087 messages::internalError(asyncResp->res); 1088 return; 1089 } 1090 messages::success(asyncResp->res); 1091 }, 1092 "xyz.openbmc_project.State.Hypervisor", 1093 "/xyz/openbmc_project/state/hypervisor0", 1094 "org.freedesktop.DBus.Properties", "Set", 1095 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1096 dbus::utility::DbusVariantType{std::move(command)}); 1097 }); 1098 } 1099 } // namespace redfish::hypervisor 1100