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