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