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 == 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.empty()) 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.macAddress = *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 (const 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 (const 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.defaultGateway = *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}}, 321 callback{std::forward<CallbackFunc>(callback)}]( 322 const boost::system::error_code error, 323 const dbus::utility::ManagedObjectType& resp) { 324 EthernetInterfaceData ethData{}; 325 boost::container::flat_set<IPv4AddressData> ipv4Data; 326 if (error) 327 { 328 callback(false, ethData, ipv4Data); 329 return; 330 } 331 332 bool found = extractHypervisorInterfaceData(ethIfaceId, resp, 333 ethData, ipv4Data); 334 if (!found) 335 { 336 BMCWEB_LOG_INFO << "Hypervisor Interface not found"; 337 } 338 callback(found, ethData, ipv4Data); 339 }, 340 "xyz.openbmc_project.Settings", "/", 341 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 342 } 343 344 /** 345 * @brief Sets the Hypervisor Interface IPAddress DBUS 346 * 347 * @param[in] aResp Shared pointer for generating response message. 348 * @param[in] ipv4Address Address from the incoming request 349 * @param[in] ethIfaceId Hypervisor Interface Id 350 * 351 * @return None. 352 */ 353 inline void 354 setHypervisorIPv4Address(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 355 const std::string& ethIfaceId, 356 const std::string& ipv4Address) 357 { 358 BMCWEB_LOG_DEBUG << "Setting the Hypervisor IPaddress : " << ipv4Address 359 << " on Iface: " << ethIfaceId; 360 crow::connections::systemBus->async_method_call( 361 [aResp](const boost::system::error_code ec) { 362 if (ec) 363 { 364 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 365 return; 366 } 367 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress is Set"; 368 }, 369 "xyz.openbmc_project.Settings", 370 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 371 "org.freedesktop.DBus.Properties", "Set", 372 "xyz.openbmc_project.Network.IP", "Address", 373 dbus::utility::DbusVariantType(ipv4Address)); 374 } 375 376 /** 377 * @brief Sets the Hypervisor Interface SubnetMask DBUS 378 * 379 * @param[in] aResp Shared pointer for generating response message. 380 * @param[in] subnet SubnetMask from the incoming request 381 * @param[in] ethIfaceId Hypervisor Interface Id 382 * 383 * @return None. 384 */ 385 inline void 386 setHypervisorIPv4Subnet(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 387 const std::string& ethIfaceId, const uint8_t subnet) 388 { 389 BMCWEB_LOG_DEBUG << "Setting the Hypervisor subnet : " << subnet 390 << " on Iface: " << ethIfaceId; 391 392 crow::connections::systemBus->async_method_call( 393 [aResp](const boost::system::error_code ec) { 394 if (ec) 395 { 396 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 397 return; 398 } 399 BMCWEB_LOG_DEBUG << "SubnetMask is Set"; 400 }, 401 "xyz.openbmc_project.Settings", 402 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 403 "org.freedesktop.DBus.Properties", "Set", 404 "xyz.openbmc_project.Network.IP", "PrefixLength", 405 dbus::utility::DbusVariantType(subnet)); 406 } 407 408 /** 409 * @brief Sets the Hypervisor Interface Gateway DBUS 410 * 411 * @param[in] aResp Shared pointer for generating response message. 412 * @param[in] gateway Gateway from the incoming request 413 * @param[in] ethIfaceId Hypervisor Interface Id 414 * 415 * @return None. 416 */ 417 inline void 418 setHypervisorIPv4Gateway(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 419 const std::string& gateway) 420 { 421 BMCWEB_LOG_DEBUG 422 << "Setting the DefaultGateway to the last configured gateway"; 423 424 crow::connections::systemBus->async_method_call( 425 [aResp](const boost::system::error_code ec) { 426 if (ec) 427 { 428 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 429 return; 430 } 431 BMCWEB_LOG_DEBUG << "Default Gateway is Set"; 432 }, 433 "xyz.openbmc_project.Settings", 434 "/xyz/openbmc_project/network/hypervisor", 435 "org.freedesktop.DBus.Properties", "Set", 436 "xyz.openbmc_project.Network.SystemConfiguration", "DefaultGateway", 437 dbus::utility::DbusVariantType(gateway)); 438 } 439 440 /** 441 * @brief Creates a static IPv4 entry 442 * 443 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 444 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 445 * @param[in] gateway IPv4 address of this interfaces gateway 446 * @param[in] address IPv4 address to assign to this interface 447 * @param[io] asyncResp Response object that will be returned to client 448 * 449 * @return None 450 */ 451 inline void 452 createHypervisorIPv4(const std::string& ifaceId, uint8_t prefixLength, 453 const std::string& gateway, const std::string& address, 454 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 455 { 456 setHypervisorIPv4Address(asyncResp, ifaceId, address); 457 setHypervisorIPv4Gateway(asyncResp, gateway); 458 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 459 } 460 461 /** 462 * @brief Deletes given IPv4 interface 463 * 464 * @param[in] ifaceId Id of interface whose IP should be deleted 465 * @param[io] asyncResp Response object that will be returned to client 466 * 467 * @return None 468 */ 469 inline void 470 deleteHypervisorIPv4(const std::string& ifaceId, 471 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 472 { 473 std::string address = "0.0.0.0"; 474 std::string gateway = "0.0.0.0"; 475 const uint8_t prefixLength = 0; 476 setHypervisorIPv4Address(asyncResp, ifaceId, address); 477 setHypervisorIPv4Gateway(asyncResp, gateway); 478 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 479 } 480 481 inline void parseInterfaceData( 482 nlohmann::json& jsonResponse, const std::string& ifaceId, 483 const EthernetInterfaceData& ethData, 484 const boost::container::flat_set<IPv4AddressData>& ipv4Data) 485 { 486 jsonResponse["Id"] = ifaceId; 487 jsonResponse["@odata.id"] = 488 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + ifaceId; 489 jsonResponse["InterfaceEnabled"] = true; 490 jsonResponse["MACAddress"] = ethData.macAddress; 491 492 jsonResponse["HostName"] = ethData.hostName; 493 jsonResponse["DHCPv4"]["DHCPEnabled"] = 494 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 495 496 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 497 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 498 ipv4Array = nlohmann::json::array(); 499 ipv4StaticArray = nlohmann::json::array(); 500 for (const auto& ipv4Config : ipv4Data) 501 { 502 if (ipv4Config.isActive) 503 { 504 nlohmann::json::object_t ipv4; 505 ipv4["AddressOrigin"] = ipv4Config.origin; 506 ipv4["SubnetMask"] = ipv4Config.netmask; 507 ipv4["Address"] = ipv4Config.address; 508 ipv4["Gateway"] = ethData.defaultGateway; 509 510 if (ipv4Config.origin == "Static") 511 { 512 ipv4StaticArray.push_back(ipv4); 513 } 514 ipv4Array.push_back(std::move(ipv4)); 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) 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(boost::beast::http::verb::get)( 731 [&app](const crow::Request& req, 732 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 733 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 734 { 735 return; 736 } 737 sdbusplus::asio::getProperty<std::string>( 738 *crow::connections::systemBus, 739 "xyz.openbmc_project.Settings", 740 "/xyz/openbmc_project/network/hypervisor", 741 "xyz.openbmc_project.Network.SystemConfiguration", 742 "HostName", 743 [asyncResp](const boost::system::error_code ec, 744 const std::string& /*hostName*/) { 745 if (ec) 746 { 747 messages::resourceNotFound(asyncResp->res, "System", 748 "hypervisor"); 749 return; 750 } 751 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 752 753 asyncResp->res.jsonValue["@odata.type"] = 754 "#ComputerSystem.v1_6_0.ComputerSystem"; 755 asyncResp->res.jsonValue["@odata.id"] = 756 "/redfish/v1/Systems/hypervisor"; 757 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 758 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 759 asyncResp->res.jsonValue["Id"] = "hypervisor"; 760 asyncResp->res.jsonValue["SystemType"] = "OS"; 761 nlohmann::json::array_t managedBy; 762 nlohmann::json::object_t manager; 763 manager["@odata.id"] = "/redfish/v1/Managers/bmc"; 764 managedBy.push_back(std::move(manager)); 765 asyncResp->res.jsonValue["Links"]["ManagedBy"] = 766 std::move(managedBy); 767 asyncResp->res 768 .jsonValue["EthernetInterfaces"]["@odata.id"] = 769 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 770 getHypervisorState(asyncResp); 771 getHypervisorActions(asyncResp); 772 // TODO: Add "SystemType" : "hypervisor" 773 }); 774 }); 775 776 /** 777 * HypervisorInterfaceCollection class to handle the GET and PATCH on 778 * Hypervisor Interface 779 */ 780 781 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 782 .privileges(redfish::privileges::getEthernetInterfaceCollection) 783 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req, 784 const std::shared_ptr< 785 bmcweb::AsyncResp>& 786 asyncResp) { 787 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 788 { 789 return; 790 } 791 const std::array<const char*, 1> interfaces = { 792 "xyz.openbmc_project.Network.EthernetInterface"}; 793 794 crow::connections::systemBus->async_method_call( 795 [asyncResp](const boost::system::error_code error, 796 const dbus::utility::MapperGetSubTreePathsResponse& 797 ifaceList) { 798 if (error) 799 { 800 messages::resourceNotFound(asyncResp->res, "System", 801 "hypervisor"); 802 return; 803 } 804 asyncResp->res.jsonValue["@odata.type"] = 805 "#EthernetInterfaceCollection." 806 "EthernetInterfaceCollection"; 807 asyncResp->res.jsonValue["@odata.id"] = 808 "/redfish/v1/Systems/hypervisor/EthernetInterfaces"; 809 asyncResp->res.jsonValue["Name"] = "Hypervisor Ethernet " 810 "Interface Collection"; 811 asyncResp->res.jsonValue["Description"] = 812 "Collection of Virtual Management " 813 "Interfaces for the hypervisor"; 814 815 nlohmann::json& ifaceArray = 816 asyncResp->res.jsonValue["Members"]; 817 ifaceArray = nlohmann::json::array(); 818 for (const std::string& iface : ifaceList) 819 { 820 sdbusplus::message::object_path path(iface); 821 std::string name = path.filename(); 822 if (name.empty()) 823 { 824 continue; 825 } 826 nlohmann::json::object_t ethIface; 827 ethIface["@odata.id"] = 828 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + 829 name; 830 ifaceArray.push_back(std::move(ethIface)); 831 } 832 asyncResp->res.jsonValue["Members@odata.count"] = 833 ifaceArray.size(); 834 }, 835 "xyz.openbmc_project.ObjectMapper", 836 "/xyz/openbmc_project/object_mapper", 837 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 838 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 839 }); 840 841 BMCWEB_ROUTE(app, 842 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 843 .privileges(redfish::privileges::getEthernetInterface) 844 .methods(boost::beast::http::verb::get)( 845 [&app](const crow::Request& req, 846 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 847 const std::string& id) { 848 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 849 { 850 return; 851 } 852 getHypervisorIfaceData(id, [asyncResp, 853 ifaceId{std::string(id)}]( 854 const bool& success, 855 const EthernetInterfaceData& 856 ethData, 857 const boost::container::flat_set< 858 IPv4AddressData>& ipv4Data) { 859 if (!success) 860 { 861 messages::resourceNotFound( 862 asyncResp->res, "EthernetInterface", ifaceId); 863 return; 864 } 865 asyncResp->res.jsonValue["@odata.type"] = 866 "#EthernetInterface.v1_5_1.EthernetInterface"; 867 asyncResp->res.jsonValue["Name"] = 868 "Hypervisor Ethernet Interface"; 869 asyncResp->res.jsonValue["Description"] = 870 "Hypervisor's Virtual Management Ethernet Interface"; 871 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, 872 ethData, ipv4Data); 873 }); 874 }); 875 876 BMCWEB_ROUTE(app, 877 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 878 .privileges(redfish::privileges::patchEthernetInterface) 879 .methods( 880 boost::beast::http::verb:: 881 patch)([&app]( 882 const crow::Request& req, 883 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 884 const std::string& ifaceId) { 885 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 886 { 887 return; 888 } 889 std::optional<std::string> hostName; 890 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 891 std::optional<nlohmann::json> ipv4Addresses; 892 std::optional<nlohmann::json> dhcpv4; 893 std::optional<bool> ipv4DHCPEnabled; 894 895 if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", 896 hostName, "IPv4StaticAddresses", 897 ipv4StaticAddresses, "IPv4Addresses", 898 ipv4Addresses, "DHCPv4", dhcpv4)) 899 { 900 return; 901 } 902 903 if (ipv4Addresses) 904 { 905 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 906 return; 907 } 908 909 if (dhcpv4) 910 { 911 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 912 ipv4DHCPEnabled)) 913 { 914 return; 915 } 916 } 917 918 getHypervisorIfaceData( 919 ifaceId, 920 [asyncResp, ifaceId, hostName = std::move(hostName), 921 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 922 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 923 const bool& success, const EthernetInterfaceData& ethData, 924 const boost::container::flat_set<IPv4AddressData>&) { 925 if (!success) 926 { 927 messages::resourceNotFound( 928 asyncResp->res, "EthernetInterface", ifaceId); 929 return; 930 } 931 932 if (ipv4StaticAddresses) 933 { 934 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 935 if (ipv4Static.begin() == ipv4Static.end()) 936 { 937 messages::propertyValueTypeError( 938 asyncResp->res, 939 ipv4Static.dump( 940 2, ' ', true, 941 nlohmann::json::error_handler_t::replace), 942 "IPv4StaticAddresses"); 943 return; 944 } 945 946 // One and only one hypervisor instance supported 947 if (ipv4Static.size() != 1) 948 { 949 messages::propertyValueFormatError( 950 asyncResp->res, 951 ipv4Static.dump( 952 2, ' ', true, 953 nlohmann::json::error_handler_t::replace), 954 "IPv4StaticAddresses"); 955 return; 956 } 957 958 const nlohmann::json& ipv4Json = ipv4Static[0]; 959 // Check if the param is 'null'. If its null, it means 960 // that user wants to delete the IP address. Deleting 961 // the IP address is allowed only if its statically 962 // configured. Deleting the address originated from DHCP 963 // is not allowed. 964 if ((ipv4Json.is_null()) && 965 (translateDhcpEnabledToBool(ethData.dhcpEnabled, 966 true))) 967 { 968 BMCWEB_LOG_INFO 969 << "Ignoring the delete on ipv4StaticAddresses " 970 "as the interface is DHCP enabled"; 971 } 972 else 973 { 974 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 975 asyncResp); 976 } 977 } 978 979 if (hostName) 980 { 981 handleHostnamePatch(*hostName, asyncResp); 982 } 983 984 if (dhcpv4) 985 { 986 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 987 } 988 989 // Set this interface to disabled/inactive. This will be set 990 // to enabled/active by the pldm once the hypervisor 991 // consumes the updated settings from the user. 992 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 993 }); 994 asyncResp->res.result(boost::beast::http::status::accepted); 995 }); 996 997 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 998 .privileges(redfish::privileges::getActionInfo) 999 .methods(boost::beast::http::verb::get)( 1000 [&app](const crow::Request& req, 1001 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1002 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1003 { 1004 return; 1005 } 1006 // Only return action info if hypervisor D-Bus object present 1007 crow::connections::systemBus->async_method_call( 1008 [asyncResp]( 1009 const boost::system::error_code ec, 1010 const std::vector<std::pair< 1011 std::string, std::vector<std::string>>>& objInfo) { 1012 if (ec) 1013 { 1014 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1015 1016 // No hypervisor objects found by mapper 1017 if (ec.value() == boost::system::errc::io_error) 1018 { 1019 messages::resourceNotFound(asyncResp->res, 1020 "hypervisor", 1021 "ResetActionInfo"); 1022 return; 1023 } 1024 1025 messages::internalError(asyncResp->res); 1026 return; 1027 } 1028 1029 // One and only one hypervisor instance supported 1030 if (objInfo.size() != 1) 1031 { 1032 messages::internalError(asyncResp->res); 1033 return; 1034 } 1035 1036 // The hypervisor object only support the ability to 1037 // turn On The system object Action should be utilized 1038 // for other operations 1039 1040 asyncResp->res.jsonValue["@odata.type"] = 1041 "#ActionInfo.v1_1_2.ActionInfo"; 1042 asyncResp->res.jsonValue["@odata.id"] = 1043 "/redfish/v1/Systems/hypervisor/ResetActionInfo"; 1044 asyncResp->res.jsonValue["Name"] = "Reset Action Info"; 1045 asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; 1046 nlohmann::json::array_t parameters; 1047 nlohmann::json::object_t parameter; 1048 parameter["Name"] = "ResetType"; 1049 parameter["Required"] = true; 1050 parameter["DataType"] = "String"; 1051 nlohmann::json::array_t allowed; 1052 allowed.push_back("On"); 1053 parameter["AllowableValues"] = std::move(allowed); 1054 parameters.push_back(std::move(parameter)); 1055 asyncResp->res.jsonValue["Parameters"] = 1056 std::move(parameters); 1057 }, 1058 "xyz.openbmc_project.ObjectMapper", 1059 "/xyz/openbmc_project/object_mapper", 1060 "xyz.openbmc_project.ObjectMapper", "GetObject", 1061 "/xyz/openbmc_project/state/hypervisor0", 1062 std::array<const char*, 1>{ 1063 "xyz.openbmc_project.State.Host"}); 1064 }); 1065 1066 BMCWEB_ROUTE(app, 1067 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1068 .privileges(redfish::privileges::postComputerSystem) 1069 .methods(boost::beast::http::verb::post)( 1070 [&app](const crow::Request& req, 1071 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1072 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1073 { 1074 return; 1075 } 1076 std::optional<std::string> resetType; 1077 if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", 1078 resetType)) 1079 { 1080 // readJson adds appropriate error to response 1081 return; 1082 } 1083 1084 if (!resetType) 1085 { 1086 messages::actionParameterMissing( 1087 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1088 return; 1089 } 1090 1091 // Hypervisor object only support On operation 1092 if (resetType != "On") 1093 { 1094 messages::propertyValueNotInList(asyncResp->res, *resetType, 1095 "ResetType"); 1096 return; 1097 } 1098 1099 std::string command = 1100 "xyz.openbmc_project.State.Host.Transition.On"; 1101 1102 crow::connections::systemBus->async_method_call( 1103 [asyncResp, resetType](const boost::system::error_code ec) { 1104 if (ec) 1105 { 1106 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1107 if (ec.value() == 1108 boost::asio::error::invalid_argument) 1109 { 1110 messages::actionParameterNotSupported( 1111 asyncResp->res, *resetType, "Reset"); 1112 return; 1113 } 1114 1115 if (ec.value() == 1116 boost::asio::error::host_unreachable) 1117 { 1118 messages::resourceNotFound(asyncResp->res, 1119 "Actions", "Reset"); 1120 return; 1121 } 1122 1123 messages::internalError(asyncResp->res); 1124 return; 1125 } 1126 messages::success(asyncResp->res); 1127 }, 1128 "xyz.openbmc_project.State.Hypervisor", 1129 "/xyz/openbmc_project/state/hypervisor0", 1130 "org.freedesktop.DBus.Properties", "Set", 1131 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1132 dbus::utility::DbusVariantType{std::move(command)}); 1133 }); 1134 } 1135 } // namespace redfish::hypervisor 1136