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