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