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