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