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