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 aResp->res.jsonValue["Actions"]["#ComputerSystem.Reset"] = { 141 {"target", 142 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset"}, 143 {"@Redfish.ActionInfo", 144 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}}; 145 }, 146 "xyz.openbmc_project.ObjectMapper", 147 "/xyz/openbmc_project/object_mapper", 148 "xyz.openbmc_project.ObjectMapper", "GetObject", 149 "/xyz/openbmc_project/state/hypervisor0", 150 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 151 } 152 153 inline bool extractHypervisorInterfaceData( 154 const std::string& ethIfaceId, 155 const dbus::utility::ManagedObjectType& dbusData, 156 EthernetInterfaceData& ethData, 157 boost::container::flat_set<IPv4AddressData>& ipv4Config) 158 { 159 bool idFound = false; 160 for (const auto& objpath : dbusData) 161 { 162 for (const auto& ifacePair : objpath.second) 163 { 164 if (objpath.first == 165 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId) 166 { 167 idFound = true; 168 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 169 { 170 for (const auto& propertyPair : ifacePair.second) 171 { 172 if (propertyPair.first == "MACAddress") 173 { 174 const std::string* mac = 175 std::get_if<std::string>(&propertyPair.second); 176 if (mac != nullptr) 177 { 178 ethData.macAddress = *mac; 179 } 180 } 181 } 182 } 183 else if (ifacePair.first == 184 "xyz.openbmc_project.Network.EthernetInterface") 185 { 186 for (const auto& propertyPair : ifacePair.second) 187 { 188 if (propertyPair.first == "DHCPEnabled") 189 { 190 const std::string* dhcp = 191 std::get_if<std::string>(&propertyPair.second); 192 if (dhcp != nullptr) 193 { 194 ethData.dhcpEnabled = *dhcp; 195 break; // Interested on only "DHCPEnabled". 196 // Stop parsing since we got the 197 // "DHCPEnabled" value. 198 } 199 } 200 } 201 } 202 } 203 if (objpath.first == "/xyz/openbmc_project/network/hypervisor/" + 204 ethIfaceId + "/ipv4/addr0") 205 { 206 std::pair<boost::container::flat_set<IPv4AddressData>::iterator, 207 bool> 208 it = ipv4Config.insert(IPv4AddressData{}); 209 IPv4AddressData& ipv4Address = *it.first; 210 if (ifacePair.first == "xyz.openbmc_project.Object.Enable") 211 { 212 for (const auto& property : ifacePair.second) 213 { 214 if (property.first == "Enabled") 215 { 216 const bool* intfEnable = 217 std::get_if<bool>(&property.second); 218 if (intfEnable != nullptr) 219 { 220 ipv4Address.isActive = *intfEnable; 221 break; 222 } 223 } 224 } 225 } 226 if (ifacePair.first == "xyz.openbmc_project.Network.IP") 227 { 228 for (const auto& property : ifacePair.second) 229 { 230 if (property.first == "Address") 231 { 232 const std::string* address = 233 std::get_if<std::string>(&property.second); 234 if (address != nullptr) 235 { 236 ipv4Address.address = *address; 237 } 238 } 239 else if (property.first == "Origin") 240 { 241 const std::string* origin = 242 std::get_if<std::string>(&property.second); 243 if (origin != nullptr) 244 { 245 ipv4Address.origin = 246 translateAddressOriginDbusToRedfish(*origin, 247 true); 248 } 249 } 250 else if (property.first == "PrefixLength") 251 { 252 const uint8_t* mask = 253 std::get_if<uint8_t>(&property.second); 254 if (mask != nullptr) 255 { 256 // convert it to the string 257 ipv4Address.netmask = getNetmask(*mask); 258 } 259 } 260 else if (property.first == "Type" || 261 property.first == "Gateway") 262 { 263 // Type & Gateway is not used 264 continue; 265 } 266 else 267 { 268 BMCWEB_LOG_ERROR 269 << "Got extra property: " << property.first 270 << " on the " << objpath.first.str << " object"; 271 } 272 } 273 } 274 } 275 if (objpath.first == "/xyz/openbmc_project/network/hypervisor") 276 { 277 // System configuration shows up in the global namespace, so no 278 // need to check eth number 279 if (ifacePair.first == 280 "xyz.openbmc_project.Network.SystemConfiguration") 281 { 282 for (const auto& propertyPair : ifacePair.second) 283 { 284 if (propertyPair.first == "HostName") 285 { 286 const std::string* hostName = 287 std::get_if<std::string>(&propertyPair.second); 288 if (hostName != nullptr) 289 { 290 ethData.hostName = *hostName; 291 } 292 } 293 else if (propertyPair.first == "DefaultGateway") 294 { 295 const std::string* defaultGateway = 296 std::get_if<std::string>(&propertyPair.second); 297 if (defaultGateway != nullptr) 298 { 299 ethData.defaultGateway = *defaultGateway; 300 } 301 } 302 } 303 } 304 } 305 } 306 } 307 return idFound; 308 } 309 /** 310 * Function that retrieves all properties for given Hypervisor Ethernet 311 * Interface Object from Settings Manager 312 * @param ethIfaceId Hypervisor ethernet interface id to query on DBus 313 * @param callback a function that shall be called to convert Dbus output 314 * into JSON 315 */ 316 template <typename CallbackFunc> 317 void getHypervisorIfaceData(const std::string& ethIfaceId, 318 CallbackFunc&& callback) 319 { 320 crow::connections::systemBus->async_method_call( 321 [ethIfaceId{std::string{ethIfaceId}}, 322 callback{std::forward<CallbackFunc>(callback)}]( 323 const boost::system::error_code error, 324 const dbus::utility::ManagedObjectType& resp) { 325 EthernetInterfaceData ethData{}; 326 boost::container::flat_set<IPv4AddressData> ipv4Data; 327 if (error) 328 { 329 callback(false, ethData, ipv4Data); 330 return; 331 } 332 333 bool found = 334 extractHypervisorInterfaceData(ethIfaceId, resp, ethData, ipv4Data); 335 if (!found) 336 { 337 BMCWEB_LOG_INFO << "Hypervisor Interface not found"; 338 } 339 callback(found, ethData, ipv4Data); 340 }, 341 "xyz.openbmc_project.Settings", "/", 342 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 343 } 344 345 /** 346 * @brief Sets the Hypervisor Interface IPAddress DBUS 347 * 348 * @param[in] aResp Shared pointer for generating response message. 349 * @param[in] ipv4Address Address from the incoming request 350 * @param[in] ethIfaceId Hypervisor Interface Id 351 * 352 * @return None. 353 */ 354 inline void 355 setHypervisorIPv4Address(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 356 const std::string& ethIfaceId, 357 const std::string& ipv4Address) 358 { 359 BMCWEB_LOG_DEBUG << "Setting the Hypervisor IPaddress : " << ipv4Address 360 << " on Iface: " << ethIfaceId; 361 crow::connections::systemBus->async_method_call( 362 [aResp](const boost::system::error_code ec) { 363 if (ec) 364 { 365 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 366 return; 367 } 368 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress is Set"; 369 }, 370 "xyz.openbmc_project.Settings", 371 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 372 "org.freedesktop.DBus.Properties", "Set", 373 "xyz.openbmc_project.Network.IP", "Address", 374 dbus::utility::DbusVariantType(ipv4Address)); 375 } 376 377 /** 378 * @brief Sets the Hypervisor Interface SubnetMask DBUS 379 * 380 * @param[in] aResp Shared pointer for generating response message. 381 * @param[in] subnet SubnetMask from the incoming request 382 * @param[in] ethIfaceId Hypervisor Interface Id 383 * 384 * @return None. 385 */ 386 inline void 387 setHypervisorIPv4Subnet(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 388 const std::string& ethIfaceId, const uint8_t subnet) 389 { 390 BMCWEB_LOG_DEBUG << "Setting the Hypervisor subnet : " << subnet 391 << " on Iface: " << ethIfaceId; 392 393 crow::connections::systemBus->async_method_call( 394 [aResp](const boost::system::error_code ec) { 395 if (ec) 396 { 397 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 398 return; 399 } 400 BMCWEB_LOG_DEBUG << "SubnetMask is Set"; 401 }, 402 "xyz.openbmc_project.Settings", 403 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 404 "org.freedesktop.DBus.Properties", "Set", 405 "xyz.openbmc_project.Network.IP", "PrefixLength", 406 dbus::utility::DbusVariantType(subnet)); 407 } 408 409 /** 410 * @brief Sets the Hypervisor Interface Gateway DBUS 411 * 412 * @param[in] aResp Shared pointer for generating response message. 413 * @param[in] gateway Gateway from the incoming request 414 * @param[in] ethIfaceId Hypervisor Interface Id 415 * 416 * @return None. 417 */ 418 inline void 419 setHypervisorIPv4Gateway(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 420 const std::string& gateway) 421 { 422 BMCWEB_LOG_DEBUG 423 << "Setting the DefaultGateway to the last configured gateway"; 424 425 crow::connections::systemBus->async_method_call( 426 [aResp](const boost::system::error_code ec) { 427 if (ec) 428 { 429 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 430 return; 431 } 432 BMCWEB_LOG_DEBUG << "Default Gateway is Set"; 433 }, 434 "xyz.openbmc_project.Settings", 435 "/xyz/openbmc_project/network/hypervisor", 436 "org.freedesktop.DBus.Properties", "Set", 437 "xyz.openbmc_project.Network.SystemConfiguration", "DefaultGateway", 438 dbus::utility::DbusVariantType(gateway)); 439 } 440 441 /** 442 * @brief Creates a static IPv4 entry 443 * 444 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 445 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 446 * @param[in] gateway IPv4 address of this interfaces gateway 447 * @param[in] address IPv4 address to assign to this interface 448 * @param[io] asyncResp Response object that will be returned to client 449 * 450 * @return None 451 */ 452 inline void 453 createHypervisorIPv4(const std::string& ifaceId, uint8_t prefixLength, 454 const std::string& gateway, const std::string& address, 455 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 456 { 457 setHypervisorIPv4Address(asyncResp, ifaceId, address); 458 setHypervisorIPv4Gateway(asyncResp, gateway); 459 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 460 } 461 462 /** 463 * @brief Deletes given IPv4 interface 464 * 465 * @param[in] ifaceId Id of interface whose IP should be deleted 466 * @param[io] asyncResp Response object that will be returned to client 467 * 468 * @return None 469 */ 470 inline void 471 deleteHypervisorIPv4(const std::string& ifaceId, 472 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 473 { 474 std::string address = "0.0.0.0"; 475 std::string gateway = "0.0.0.0"; 476 const uint8_t prefixLength = 0; 477 setHypervisorIPv4Address(asyncResp, ifaceId, address); 478 setHypervisorIPv4Gateway(asyncResp, gateway); 479 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 480 } 481 482 inline void parseInterfaceData( 483 nlohmann::json& jsonResponse, const std::string& ifaceId, 484 const EthernetInterfaceData& ethData, 485 const boost::container::flat_set<IPv4AddressData>& ipv4Data) 486 { 487 jsonResponse["Id"] = ifaceId; 488 jsonResponse["@odata.id"] = 489 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + ifaceId; 490 jsonResponse["InterfaceEnabled"] = true; 491 jsonResponse["MACAddress"] = ethData.macAddress; 492 493 jsonResponse["HostName"] = ethData.hostName; 494 jsonResponse["DHCPv4"]["DHCPEnabled"] = 495 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 496 497 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 498 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 499 ipv4Array = nlohmann::json::array(); 500 ipv4StaticArray = nlohmann::json::array(); 501 for (const auto& ipv4Config : ipv4Data) 502 { 503 if (ipv4Config.isActive) 504 { 505 nlohmann::json::object_t ipv4; 506 ipv4["AddressOrigin"] = ipv4Config.origin; 507 ipv4["SubnetMask"] = ipv4Config.netmask; 508 ipv4["Address"] = ipv4Config.address; 509 ipv4["Gateway"] = ethData.defaultGateway; 510 511 if (ipv4Config.origin == "Static") 512 { 513 ipv4StaticArray.push_back(ipv4); 514 } 515 ipv4Array.push_back(std::move(ipv4)); 516 } 517 } 518 } 519 520 inline void setDHCPEnabled(const std::string& ifaceId, 521 const bool& ipv4DHCPEnabled, 522 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 523 { 524 const std::string dhcp = getDhcpEnabledEnumeration(ipv4DHCPEnabled, false); 525 crow::connections::systemBus->async_method_call( 526 [asyncResp](const boost::system::error_code ec) { 527 if (ec) 528 { 529 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 530 messages::internalError(asyncResp->res); 531 return; 532 } 533 }, 534 "xyz.openbmc_project.Settings", 535 "/xyz/openbmc_project/network/hypervisor/" + ifaceId, 536 "org.freedesktop.DBus.Properties", "Set", 537 "xyz.openbmc_project.Network.EthernetInterface", "DHCPEnabled", 538 dbus::utility::DbusVariantType{dhcp}); 539 540 // Set the IPv4 address origin to the DHCP / Static as per the new value 541 // of the DHCPEnabled property 542 std::string origin; 543 if (!ipv4DHCPEnabled) 544 { 545 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 546 } 547 else 548 { 549 // DHCPEnabled is set to true. Delete the current IPv4 settings 550 // to receive the new values from DHCP server. 551 deleteHypervisorIPv4(ifaceId, asyncResp); 552 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 553 } 554 crow::connections::systemBus->async_method_call( 555 [asyncResp](const boost::system::error_code ec) { 556 if (ec) 557 { 558 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 559 messages::internalError(asyncResp->res); 560 return; 561 } 562 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress Origin is Set"; 563 }, 564 "xyz.openbmc_project.Settings", 565 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 566 "org.freedesktop.DBus.Properties", "Set", 567 "xyz.openbmc_project.Network.IP", "Origin", 568 dbus::utility::DbusVariantType(origin)); 569 } 570 571 inline void handleHypervisorIPv4StaticPatch( 572 const std::string& ifaceId, const nlohmann::json& input, 573 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 574 { 575 if ((!input.is_array()) || input.empty()) 576 { 577 messages::propertyValueTypeError(asyncResp->res, input.dump(), 578 "IPv4StaticAddresses"); 579 return; 580 } 581 582 // Hypervisor considers the first IP address in the array list 583 // as the Hypervisor's virtual management interface supports single IPv4 584 // address 585 const nlohmann::json& thisJson = input[0]; 586 587 // For the error string 588 std::string pathString = "IPv4StaticAddresses/1"; 589 590 if (!thisJson.is_null() && !thisJson.empty()) 591 { 592 std::optional<std::string> address; 593 std::optional<std::string> subnetMask; 594 std::optional<std::string> gateway; 595 nlohmann::json thisJsonCopy = thisJson; 596 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 597 address, "SubnetMask", subnetMask, "Gateway", 598 gateway)) 599 { 600 messages::propertyValueFormatError( 601 asyncResp->res, 602 thisJson.dump(2, ' ', true, 603 nlohmann::json::error_handler_t::replace), 604 pathString); 605 return; 606 } 607 608 uint8_t prefixLength = 0; 609 bool errorInEntry = false; 610 if (address) 611 { 612 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address)) 613 { 614 messages::propertyValueFormatError(asyncResp->res, *address, 615 pathString + "/Address"); 616 errorInEntry = true; 617 } 618 } 619 else 620 { 621 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 622 errorInEntry = true; 623 } 624 625 if (subnetMask) 626 { 627 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 628 &prefixLength)) 629 { 630 messages::propertyValueFormatError(asyncResp->res, *subnetMask, 631 pathString + "/SubnetMask"); 632 errorInEntry = true; 633 } 634 } 635 else 636 { 637 messages::propertyMissing(asyncResp->res, 638 pathString + "/SubnetMask"); 639 errorInEntry = true; 640 } 641 642 if (gateway) 643 { 644 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 645 { 646 messages::propertyValueFormatError(asyncResp->res, *gateway, 647 pathString + "/Gateway"); 648 errorInEntry = true; 649 } 650 } 651 else 652 { 653 messages::propertyMissing(asyncResp->res, pathString + "/Gateway"); 654 errorInEntry = true; 655 } 656 657 if (errorInEntry) 658 { 659 return; 660 } 661 662 BMCWEB_LOG_DEBUG << "Calling createHypervisorIPv4 on : " << ifaceId 663 << "," << *address; 664 createHypervisorIPv4(ifaceId, prefixLength, *gateway, *address, 665 asyncResp); 666 // Set the DHCPEnabled to false since the Static IPv4 is set 667 setDHCPEnabled(ifaceId, false, asyncResp); 668 } 669 else 670 { 671 if (thisJson.is_null()) 672 { 673 deleteHypervisorIPv4(ifaceId, asyncResp); 674 } 675 } 676 } 677 678 inline void 679 handleHostnamePatch(const std::string& hostName, 680 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 681 { 682 if (!isHostnameValid(hostName)) 683 { 684 messages::propertyValueFormatError(asyncResp->res, hostName, 685 "HostName"); 686 return; 687 } 688 689 asyncResp->res.jsonValue["HostName"] = hostName; 690 crow::connections::systemBus->async_method_call( 691 [asyncResp](const boost::system::error_code ec) { 692 if (ec) 693 { 694 messages::internalError(asyncResp->res); 695 } 696 }, 697 "xyz.openbmc_project.Settings", 698 "/xyz/openbmc_project/network/hypervisor", 699 "org.freedesktop.DBus.Properties", "Set", 700 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 701 dbus::utility::DbusVariantType(hostName)); 702 } 703 704 inline void 705 setIPv4InterfaceEnabled(const std::string& ifaceId, const bool& isActive, 706 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 707 { 708 crow::connections::systemBus->async_method_call( 709 [asyncResp](const boost::system::error_code ec) { 710 if (ec) 711 { 712 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 713 messages::internalError(asyncResp->res); 714 return; 715 } 716 }, 717 "xyz.openbmc_project.Settings", 718 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 719 "org.freedesktop.DBus.Properties", "Set", 720 "xyz.openbmc_project.Object.Enable", "Enabled", 721 dbus::utility::DbusVariantType(isActive)); 722 } 723 724 inline void requestRoutesHypervisorSystems(App& app) 725 { 726 /** 727 * Hypervisor Systems derived class for delivering Computer Systems Schema. 728 */ 729 730 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/") 731 .privileges(redfish::privileges::getComputerSystem) 732 .methods(boost::beast::http::verb::get)( 733 [&app](const crow::Request& req, 734 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 735 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 736 { 737 return; 738 } 739 sdbusplus::asio::getProperty<std::string>( 740 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 741 "/xyz/openbmc_project/network/hypervisor", 742 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 743 [asyncResp](const boost::system::error_code ec, 744 const std::string& /*hostName*/) { 745 if (ec) 746 { 747 messages::resourceNotFound(asyncResp->res, "System", 748 "hypervisor"); 749 return; 750 } 751 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 752 753 asyncResp->res.jsonValue["@odata.type"] = 754 "#ComputerSystem.v1_6_0.ComputerSystem"; 755 asyncResp->res.jsonValue["@odata.id"] = 756 "/redfish/v1/Systems/hypervisor"; 757 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 758 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 759 asyncResp->res.jsonValue["Id"] = "hypervisor"; 760 asyncResp->res.jsonValue["SystemType"] = "OS"; 761 nlohmann::json::array_t managedBy; 762 nlohmann::json::object_t manager; 763 manager["@odata.id"] = "/redfish/v1/Managers/bmc"; 764 managedBy.push_back(std::move(manager)); 765 asyncResp->res.jsonValue["Links"]["ManagedBy"] = 766 std::move(managedBy); 767 asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] = 768 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 769 getHypervisorState(asyncResp); 770 getHypervisorActions(asyncResp); 771 // TODO: Add "SystemType" : "hypervisor" 772 }); 773 }); 774 775 /** 776 * HypervisorInterfaceCollection class to handle the GET and PATCH on 777 * Hypervisor Interface 778 */ 779 780 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 781 .privileges(redfish::privileges::getEthernetInterfaceCollection) 782 .methods(boost::beast::http::verb::get)( 783 [&app](const crow::Request& req, 784 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 785 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 786 { 787 return; 788 } 789 const std::array<const char*, 1> interfaces = { 790 "xyz.openbmc_project.Network.EthernetInterface"}; 791 792 crow::connections::systemBus->async_method_call( 793 [asyncResp]( 794 const boost::system::error_code error, 795 const dbus::utility::MapperGetSubTreePathsResponse& ifaceList) { 796 if (error) 797 { 798 messages::resourceNotFound(asyncResp->res, "System", 799 "hypervisor"); 800 return; 801 } 802 asyncResp->res.jsonValue["@odata.type"] = 803 "#EthernetInterfaceCollection." 804 "EthernetInterfaceCollection"; 805 asyncResp->res.jsonValue["@odata.id"] = 806 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 807 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 808 "Interface Collection"; 809 asyncResp->res.jsonValue["Description"] = 810 "Collection of Virtual Management " 811 "Interfaces for the hypervisor"; 812 813 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 814 ifaceArray = nlohmann::json::array(); 815 for (const std::string& iface : ifaceList) 816 { 817 sdbusplus::message::object_path path(iface); 818 std::string name = path.filename(); 819 if (name.empty()) 820 { 821 continue; 822 } 823 nlohmann::json::object_t ethIface; 824 ethIface["@odata.id"] = 825 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + name; 826 ifaceArray.push_back(std::move(ethIface)); 827 } 828 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 829 }, 830 "xyz.openbmc_project.ObjectMapper", 831 "/xyz/openbmc_project/object_mapper", 832 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 833 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 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_5_1.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