1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/container/flat_set.hpp> 5 #include <dbus_singleton.hpp> 6 #include <dbus_utility.hpp> 7 #include <error_messages.hpp> 8 #include <query.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 == "xyz.openbmc_project.State.Host.HostState.Off") 83 { 84 aResp->res.jsonValue["PowerState"] = "Off"; 85 aResp->res.jsonValue["Status"]["State"] = "Disabled"; 86 } 87 else 88 { 89 messages::internalError(aResp->res); 90 return; 91 } 92 }); 93 } 94 95 /** 96 * @brief Populate Actions if any are valid for hypervisor object 97 * 98 * The hypervisor state object is optional so this function will only set the 99 * Action if the object is found 100 * 101 * @param[in] aResp Shared pointer for completing asynchronous calls. 102 * 103 * @return None. 104 */ 105 inline void 106 getHypervisorActions(const std::shared_ptr<bmcweb::AsyncResp>& aResp) 107 { 108 BMCWEB_LOG_DEBUG << "Get hypervisor actions."; 109 crow::connections::systemBus->async_method_call( 110 [aResp]( 111 const boost::system::error_code ec, 112 const std::vector<std::pair<std::string, std::vector<std::string>>>& 113 objInfo) { 114 if (ec) 115 { 116 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 117 // This is an optional D-Bus object so just return if 118 // error occurs 119 return; 120 } 121 122 if (objInfo.empty()) 123 { 124 // As noted above, this is an optional interface so just return 125 // if there is no instance found 126 return; 127 } 128 129 if (objInfo.size() > 1) 130 { 131 // More then one hypervisor object is not supported and is an 132 // error 133 messages::internalError(aResp->res); 134 return; 135 } 136 137 // Object present so system support limited ComputerSystem Action 138 aResp->res.jsonValue["Actions"]["#ComputerSystem.Reset"] = { 139 {"target", 140 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset"}, 141 {"@Redfish.ActionInfo", 142 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}}; 143 }, 144 "xyz.openbmc_project.ObjectMapper", 145 "/xyz/openbmc_project/object_mapper", 146 "xyz.openbmc_project.ObjectMapper", "GetObject", 147 "/xyz/openbmc_project/state/hypervisor0", 148 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 149 } 150 151 inline bool extractHypervisorInterfaceData( 152 const std::string& ethIfaceId, 153 const dbus::utility::ManagedObjectType& 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.macAddress = *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 (const 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 (const 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.defaultGateway = *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::forward<CallbackFunc>(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 = 332 extractHypervisorInterfaceData(ethIfaceId, resp, 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.macAddress; 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 (const auto& ipv4Config : ipv4Data) 500 { 501 if (ipv4Config.isActive) 502 { 503 nlohmann::json::object_t ipv4; 504 ipv4["AddressOrigin"] = ipv4Config.origin; 505 ipv4["SubnetMask"] = ipv4Config.netmask; 506 ipv4["Address"] = ipv4Config.address; 507 ipv4["Gateway"] = ethData.defaultGateway; 508 509 if (ipv4Config.origin == "Static") 510 { 511 ipv4StaticArray.push_back(ipv4); 512 } 513 ipv4Array.push_back(std::move(ipv4)); 514 } 515 } 516 } 517 518 inline void setDHCPEnabled(const std::string& ifaceId, 519 const bool& ipv4DHCPEnabled, 520 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 521 { 522 const std::string dhcp = getDhcpEnabledEnumeration(ipv4DHCPEnabled, false); 523 crow::connections::systemBus->async_method_call( 524 [asyncResp](const boost::system::error_code ec) { 525 if (ec) 526 { 527 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 528 messages::internalError(asyncResp->res); 529 return; 530 } 531 }, 532 "xyz.openbmc_project.Settings", 533 "/xyz/openbmc_project/network/hypervisor/" + ifaceId, 534 "org.freedesktop.DBus.Properties", "Set", 535 "xyz.openbmc_project.Network.EthernetInterface", "DHCPEnabled", 536 dbus::utility::DbusVariantType{dhcp}); 537 538 // Set the IPv4 address origin to the DHCP / Static as per the new value 539 // of the DHCPEnabled property 540 std::string origin; 541 if (!ipv4DHCPEnabled) 542 { 543 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 544 } 545 else 546 { 547 // DHCPEnabled is set to true. Delete the current IPv4 settings 548 // to receive the new values from DHCP server. 549 deleteHypervisorIPv4(ifaceId, asyncResp); 550 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 551 } 552 crow::connections::systemBus->async_method_call( 553 [asyncResp](const boost::system::error_code ec) { 554 if (ec) 555 { 556 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 557 messages::internalError(asyncResp->res); 558 return; 559 } 560 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress Origin is Set"; 561 }, 562 "xyz.openbmc_project.Settings", 563 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 564 "org.freedesktop.DBus.Properties", "Set", 565 "xyz.openbmc_project.Network.IP", "Origin", 566 dbus::utility::DbusVariantType(origin)); 567 } 568 569 inline void handleHypervisorIPv4StaticPatch( 570 const std::string& ifaceId, const nlohmann::json& input, 571 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 572 { 573 if ((!input.is_array()) || input.empty()) 574 { 575 messages::propertyValueTypeError(asyncResp->res, input.dump(), 576 "IPv4StaticAddresses"); 577 return; 578 } 579 580 // Hypervisor considers the first IP address in the array list 581 // as the Hypervisor's virtual management interface supports single IPv4 582 // address 583 const nlohmann::json& thisJson = input[0]; 584 585 // For the error string 586 std::string pathString = "IPv4StaticAddresses/1"; 587 588 if (!thisJson.is_null() && !thisJson.empty()) 589 { 590 std::optional<std::string> address; 591 std::optional<std::string> subnetMask; 592 std::optional<std::string> gateway; 593 nlohmann::json thisJsonCopy = thisJson; 594 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 595 address, "SubnetMask", subnetMask, "Gateway", 596 gateway)) 597 { 598 messages::propertyValueFormatError( 599 asyncResp->res, 600 thisJson.dump(2, ' ', true, 601 nlohmann::json::error_handler_t::replace), 602 pathString); 603 return; 604 } 605 606 uint8_t prefixLength = 0; 607 bool errorInEntry = false; 608 if (address) 609 { 610 if (!ipv4VerifyIpAndGetBitcount(*address)) 611 { 612 messages::propertyValueFormatError(asyncResp->res, *address, 613 pathString + "/Address"); 614 errorInEntry = true; 615 } 616 } 617 else 618 { 619 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 620 errorInEntry = true; 621 } 622 623 if (subnetMask) 624 { 625 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 626 { 627 messages::propertyValueFormatError(asyncResp->res, *subnetMask, 628 pathString + "/SubnetMask"); 629 errorInEntry = true; 630 } 631 } 632 else 633 { 634 messages::propertyMissing(asyncResp->res, 635 pathString + "/SubnetMask"); 636 errorInEntry = true; 637 } 638 639 if (gateway) 640 { 641 if (!ipv4VerifyIpAndGetBitcount(*gateway)) 642 { 643 messages::propertyValueFormatError(asyncResp->res, *gateway, 644 pathString + "/Gateway"); 645 errorInEntry = true; 646 } 647 } 648 else 649 { 650 messages::propertyMissing(asyncResp->res, pathString + "/Gateway"); 651 errorInEntry = true; 652 } 653 654 if (errorInEntry) 655 { 656 return; 657 } 658 659 BMCWEB_LOG_DEBUG << "Calling createHypervisorIPv4 on : " << ifaceId 660 << "," << *address; 661 createHypervisorIPv4(ifaceId, prefixLength, *gateway, *address, 662 asyncResp); 663 // Set the DHCPEnabled to false since the Static IPv4 is set 664 setDHCPEnabled(ifaceId, false, asyncResp); 665 } 666 else 667 { 668 if (thisJson.is_null()) 669 { 670 deleteHypervisorIPv4(ifaceId, asyncResp); 671 } 672 } 673 } 674 675 inline void 676 handleHostnamePatch(const std::string& hostName, 677 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 678 { 679 if (!isHostnameValid(hostName)) 680 { 681 messages::propertyValueFormatError(asyncResp->res, hostName, 682 "HostName"); 683 return; 684 } 685 686 asyncResp->res.jsonValue["HostName"] = hostName; 687 crow::connections::systemBus->async_method_call( 688 [asyncResp](const boost::system::error_code ec) { 689 if (ec) 690 { 691 messages::internalError(asyncResp->res); 692 } 693 }, 694 "xyz.openbmc_project.Settings", 695 "/xyz/openbmc_project/network/hypervisor", 696 "org.freedesktop.DBus.Properties", "Set", 697 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 698 dbus::utility::DbusVariantType(hostName)); 699 } 700 701 inline void 702 setIPv4InterfaceEnabled(const std::string& ifaceId, const bool& isActive, 703 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 704 { 705 crow::connections::systemBus->async_method_call( 706 [asyncResp](const boost::system::error_code ec) { 707 if (ec) 708 { 709 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 710 messages::internalError(asyncResp->res); 711 return; 712 } 713 }, 714 "xyz.openbmc_project.Settings", 715 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 716 "org.freedesktop.DBus.Properties", "Set", 717 "xyz.openbmc_project.Object.Enable", "Enabled", 718 dbus::utility::DbusVariantType(isActive)); 719 } 720 721 inline void requestRoutesHypervisorSystems(App& app) 722 { 723 /** 724 * Hypervisor Systems derived class for delivering Computer Systems Schema. 725 */ 726 727 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/") 728 .privileges(redfish::privileges::getComputerSystem) 729 .methods(boost::beast::http::verb::get)( 730 [&app](const crow::Request& req, 731 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 732 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 733 { 734 return; 735 } 736 sdbusplus::asio::getProperty<std::string>( 737 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 738 "/xyz/openbmc_project/network/hypervisor", 739 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 740 [asyncResp](const boost::system::error_code ec, 741 const std::string& /*hostName*/) { 742 if (ec) 743 { 744 messages::resourceNotFound(asyncResp->res, "System", 745 "hypervisor"); 746 return; 747 } 748 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 749 750 asyncResp->res.jsonValue["@odata.type"] = 751 "#ComputerSystem.v1_6_0.ComputerSystem"; 752 asyncResp->res.jsonValue["@odata.id"] = 753 "/redfish/v1/Systems/hypervisor"; 754 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 755 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 756 asyncResp->res.jsonValue["Id"] = "hypervisor"; 757 asyncResp->res.jsonValue["SystemType"] = "OS"; 758 nlohmann::json::array_t managedBy; 759 nlohmann::json::object_t manager; 760 manager["@odata.id"] = "/redfish/v1/Managers/bmc"; 761 managedBy.push_back(std::move(manager)); 762 asyncResp->res.jsonValue["Links"]["ManagedBy"] = 763 std::move(managedBy); 764 asyncResp->res.jsonValue["EthernetInterfaces"]["@odata.id"] = 765 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 766 getHypervisorState(asyncResp); 767 getHypervisorActions(asyncResp); 768 // TODO: Add "SystemType" : "hypervisor" 769 }); 770 }); 771 772 /** 773 * HypervisorInterfaceCollection class to handle the GET and PATCH on 774 * Hypervisor Interface 775 */ 776 777 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 778 .privileges(redfish::privileges::getEthernetInterfaceCollection) 779 .methods(boost::beast::http::verb::get)( 780 [&app](const crow::Request& req, 781 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 782 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 783 { 784 return; 785 } 786 const std::array<const char*, 1> interfaces = { 787 "xyz.openbmc_project.Network.EthernetInterface"}; 788 789 crow::connections::systemBus->async_method_call( 790 [asyncResp]( 791 const boost::system::error_code error, 792 const dbus::utility::MapperGetSubTreePathsResponse& ifaceList) { 793 if (error) 794 { 795 messages::resourceNotFound(asyncResp->res, "System", 796 "hypervisor"); 797 return; 798 } 799 asyncResp->res.jsonValue["@odata.type"] = 800 "#EthernetInterfaceCollection." 801 "EthernetInterfaceCollection"; 802 asyncResp->res.jsonValue["@odata.id"] = 803 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 804 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 805 "Interface Collection"; 806 asyncResp->res.jsonValue["Description"] = 807 "Collection of Virtual Management " 808 "Interfaces for the hypervisor"; 809 810 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 811 ifaceArray = nlohmann::json::array(); 812 for (const std::string& iface : ifaceList) 813 { 814 sdbusplus::message::object_path path(iface); 815 std::string name = path.filename(); 816 if (name.empty()) 817 { 818 continue; 819 } 820 nlohmann::json::object_t ethIface; 821 ethIface["@odata.id"] = 822 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + name; 823 ifaceArray.push_back(std::move(ethIface)); 824 } 825 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 826 }, 827 "xyz.openbmc_project.ObjectMapper", 828 "/xyz/openbmc_project/object_mapper", 829 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 830 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 831 }); 832 833 BMCWEB_ROUTE(app, 834 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 835 .privileges(redfish::privileges::getEthernetInterface) 836 .methods(boost::beast::http::verb::get)( 837 [&app](const crow::Request& req, 838 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 839 const std::string& id) { 840 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 841 { 842 return; 843 } 844 getHypervisorIfaceData( 845 id, 846 [asyncResp, ifaceId{std::string(id)}]( 847 const bool& success, const EthernetInterfaceData& ethData, 848 const boost::container::flat_set<IPv4AddressData>& ipv4Data) { 849 if (!success) 850 { 851 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 852 ifaceId); 853 return; 854 } 855 asyncResp->res.jsonValue["@odata.type"] = 856 "#EthernetInterface.v1_5_1.EthernetInterface"; 857 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet Interface"; 858 asyncResp->res.jsonValue["Description"] = 859 "Hypervisor's Virtual Management Ethernet Interface"; 860 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, ethData, 861 ipv4Data); 862 }); 863 }); 864 865 BMCWEB_ROUTE(app, 866 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 867 .privileges(redfish::privileges::patchEthernetInterface) 868 .methods(boost::beast::http::verb::patch)( 869 [&app](const crow::Request& req, 870 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 871 const std::string& ifaceId) { 872 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 873 { 874 return; 875 } 876 std::optional<std::string> hostName; 877 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 878 std::optional<nlohmann::json> ipv4Addresses; 879 std::optional<nlohmann::json> dhcpv4; 880 std::optional<bool> ipv4DHCPEnabled; 881 882 if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", hostName, 883 "IPv4StaticAddresses", 884 ipv4StaticAddresses, "IPv4Addresses", 885 ipv4Addresses, "DHCPv4", dhcpv4)) 886 { 887 return; 888 } 889 890 if (ipv4Addresses) 891 { 892 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 893 return; 894 } 895 896 if (dhcpv4) 897 { 898 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 899 ipv4DHCPEnabled)) 900 { 901 return; 902 } 903 } 904 905 getHypervisorIfaceData( 906 ifaceId, 907 [asyncResp, ifaceId, hostName = std::move(hostName), 908 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 909 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 910 const bool& success, const EthernetInterfaceData& ethData, 911 const boost::container::flat_set<IPv4AddressData>&) { 912 if (!success) 913 { 914 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 915 ifaceId); 916 return; 917 } 918 919 if (ipv4StaticAddresses) 920 { 921 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 922 if (ipv4Static.begin() == ipv4Static.end()) 923 { 924 messages::propertyValueTypeError( 925 asyncResp->res, 926 ipv4Static.dump( 927 2, ' ', true, 928 nlohmann::json::error_handler_t::replace), 929 "IPv4StaticAddresses"); 930 return; 931 } 932 933 // One and only one hypervisor instance supported 934 if (ipv4Static.size() != 1) 935 { 936 messages::propertyValueFormatError( 937 asyncResp->res, 938 ipv4Static.dump( 939 2, ' ', true, 940 nlohmann::json::error_handler_t::replace), 941 "IPv4StaticAddresses"); 942 return; 943 } 944 945 const nlohmann::json& ipv4Json = ipv4Static[0]; 946 // Check if the param is 'null'. If its null, it means 947 // that user wants to delete the IP address. Deleting 948 // the IP address is allowed only if its statically 949 // configured. Deleting the address originated from DHCP 950 // is not allowed. 951 if ((ipv4Json.is_null()) && 952 (translateDhcpEnabledToBool(ethData.dhcpEnabled, true))) 953 { 954 BMCWEB_LOG_INFO 955 << "Ignoring the delete on ipv4StaticAddresses " 956 "as the interface is DHCP enabled"; 957 } 958 else 959 { 960 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 961 asyncResp); 962 } 963 } 964 965 if (hostName) 966 { 967 handleHostnamePatch(*hostName, asyncResp); 968 } 969 970 if (dhcpv4) 971 { 972 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 973 } 974 975 // Set this interface to disabled/inactive. This will be set 976 // to enabled/active by the pldm once the hypervisor 977 // consumes the updated settings from the user. 978 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 979 }); 980 asyncResp->res.result(boost::beast::http::status::accepted); 981 }); 982 983 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 984 .privileges(redfish::privileges::getActionInfo) 985 .methods(boost::beast::http::verb::get)( 986 [&app](const crow::Request& req, 987 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 988 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 989 { 990 return; 991 } 992 // Only return action info if hypervisor D-Bus object present 993 crow::connections::systemBus->async_method_call( 994 [asyncResp](const boost::system::error_code ec, 995 const std::vector<std::pair< 996 std::string, std::vector<std::string>>>& objInfo) { 997 if (ec) 998 { 999 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1000 1001 // No hypervisor objects found by mapper 1002 if (ec.value() == boost::system::errc::io_error) 1003 { 1004 messages::resourceNotFound(asyncResp->res, "hypervisor", 1005 "ResetActionInfo"); 1006 return; 1007 } 1008 1009 messages::internalError(asyncResp->res); 1010 return; 1011 } 1012 1013 // One and only one hypervisor instance supported 1014 if (objInfo.size() != 1) 1015 { 1016 messages::internalError(asyncResp->res); 1017 return; 1018 } 1019 1020 // The hypervisor object only support the ability to 1021 // turn On The system object Action should be utilized 1022 // for other operations 1023 1024 asyncResp->res.jsonValue["@odata.type"] = 1025 "#ActionInfo.v1_1_2.ActionInfo"; 1026 asyncResp->res.jsonValue["@odata.id"] = 1027 "/redfish/v1/Systems/hypervisor/ResetActionInfo"; 1028 asyncResp->res.jsonValue["Name"] = "Reset Action Info"; 1029 asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; 1030 nlohmann::json::array_t parameters; 1031 nlohmann::json::object_t parameter; 1032 parameter["Name"] = "ResetType"; 1033 parameter["Required"] = true; 1034 parameter["DataType"] = "String"; 1035 nlohmann::json::array_t allowed; 1036 allowed.push_back("On"); 1037 parameter["AllowableValues"] = std::move(allowed); 1038 parameters.push_back(std::move(parameter)); 1039 asyncResp->res.jsonValue["Parameters"] = std::move(parameters); 1040 }, 1041 "xyz.openbmc_project.ObjectMapper", 1042 "/xyz/openbmc_project/object_mapper", 1043 "xyz.openbmc_project.ObjectMapper", "GetObject", 1044 "/xyz/openbmc_project/state/hypervisor0", 1045 std::array<const char*, 1>{"xyz.openbmc_project.State.Host"}); 1046 }); 1047 1048 BMCWEB_ROUTE(app, 1049 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1050 .privileges(redfish::privileges::postComputerSystem) 1051 .methods(boost::beast::http::verb::post)( 1052 [&app](const crow::Request& req, 1053 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1054 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1055 { 1056 return; 1057 } 1058 std::optional<std::string> resetType; 1059 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", 1060 resetType)) 1061 { 1062 // readJson adds appropriate error to response 1063 return; 1064 } 1065 1066 if (!resetType) 1067 { 1068 messages::actionParameterMissing( 1069 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1070 return; 1071 } 1072 1073 // Hypervisor object only support On operation 1074 if (resetType != "On") 1075 { 1076 messages::propertyValueNotInList(asyncResp->res, *resetType, 1077 "ResetType"); 1078 return; 1079 } 1080 1081 std::string command = "xyz.openbmc_project.State.Host.Transition.On"; 1082 1083 crow::connections::systemBus->async_method_call( 1084 [asyncResp, resetType](const boost::system::error_code ec) { 1085 if (ec) 1086 { 1087 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1088 if (ec.value() == boost::asio::error::invalid_argument) 1089 { 1090 messages::actionParameterNotSupported(asyncResp->res, 1091 *resetType, "Reset"); 1092 return; 1093 } 1094 1095 if (ec.value() == boost::asio::error::host_unreachable) 1096 { 1097 messages::resourceNotFound(asyncResp->res, "Actions", 1098 "Reset"); 1099 return; 1100 } 1101 1102 messages::internalError(asyncResp->res); 1103 return; 1104 } 1105 messages::success(asyncResp->res); 1106 }, 1107 "xyz.openbmc_project.State.Hypervisor", 1108 "/xyz/openbmc_project/state/hypervisor0", 1109 "org.freedesktop.DBus.Properties", "Set", 1110 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1111 dbus::utility::DbusVariantType{std::move(command)}); 1112 }); 1113 } 1114 } // namespace redfish::hypervisor 1115