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