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 // 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." 62 "Quiesced") 63 { 64 aResp->res.jsonValue["PowerState"] = "On"; 65 aResp->res.jsonValue["Status"]["State"] = "Quiesced"; 66 } 67 else if (*s == "xyz.openbmc_project.State.Host.HostState." 68 "Standby") 69 { 70 aResp->res.jsonValue["PowerState"] = "On"; 71 aResp->res.jsonValue["Status"]["State"] = "StandbyOffline"; 72 } 73 else if (*s == "xyz.openbmc_project.State.Host.HostState." 74 "TransitioningToRunning") 75 { 76 aResp->res.jsonValue["PowerState"] = "PoweringOn"; 77 aResp->res.jsonValue["Status"]["State"] = "Starting"; 78 } 79 else if (*s == "xyz.openbmc_project.State.Host.HostState." 80 "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 265 { 266 BMCWEB_LOG_ERROR 267 << "Got extra property: " << property.first 268 << " on the " << objpath.first.str << " object"; 269 } 270 } 271 } 272 } 273 if (objpath.first == "/xyz/openbmc_project/network/hypervisor") 274 { 275 // System configuration shows up in the global namespace, so no 276 // need to check eth number 277 if (ifacePair.first == 278 "xyz.openbmc_project.Network.SystemConfiguration") 279 { 280 for (const auto& propertyPair : ifacePair.second) 281 { 282 if (propertyPair.first == "HostName") 283 { 284 const std::string* hostName = 285 std::get_if<std::string>(&propertyPair.second); 286 if (hostName != nullptr) 287 { 288 ethData.hostname = *hostName; 289 } 290 } 291 else if (propertyPair.first == "DefaultGateway") 292 { 293 const std::string* defaultGateway = 294 std::get_if<std::string>(&propertyPair.second); 295 if (defaultGateway != nullptr) 296 { 297 ethData.default_gateway = *defaultGateway; 298 } 299 } 300 } 301 } 302 } 303 } 304 } 305 return idFound; 306 } 307 /** 308 * Function that retrieves all properties for given Hypervisor Ethernet 309 * Interface Object from Settings Manager 310 * @param ethIfaceId Hypervisor ethernet interface id to query on DBus 311 * @param callback a function that shall be called to convert Dbus output 312 * into JSON 313 */ 314 template <typename CallbackFunc> 315 void getHypervisorIfaceData(const std::string& ethIfaceId, 316 CallbackFunc&& callback) 317 { 318 crow::connections::systemBus->async_method_call( 319 [ethIfaceId{std::string{ethIfaceId}}, 320 callback{std::move(callback)}](const boost::system::error_code error, 321 const GetManagedObjects& resp) { 322 EthernetInterfaceData ethData{}; 323 boost::container::flat_set<IPv4AddressData> ipv4Data; 324 if (error) 325 { 326 callback(false, ethData, ipv4Data); 327 return; 328 } 329 330 bool found = extractHypervisorInterfaceData(ethIfaceId, resp, 331 ethData, ipv4Data); 332 if (!found) 333 { 334 BMCWEB_LOG_INFO << "Hypervisor Interface not found"; 335 } 336 callback(found, ethData, ipv4Data); 337 }, 338 "xyz.openbmc_project.Settings", "/", 339 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 340 } 341 342 /** 343 * @brief Sets the Hypervisor Interface IPAddress DBUS 344 * 345 * @param[in] aResp Shared pointer for generating response message. 346 * @param[in] ipv4Address Address from the incoming request 347 * @param[in] ethIfaceId Hypervisor Interface Id 348 * 349 * @return None. 350 */ 351 inline void 352 setHypervisorIPv4Address(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 353 const std::string& ethIfaceId, 354 const std::string& ipv4Address) 355 { 356 BMCWEB_LOG_DEBUG << "Setting the Hypervisor IPaddress : " << ipv4Address 357 << " on Iface: " << ethIfaceId; 358 crow::connections::systemBus->async_method_call( 359 [aResp](const boost::system::error_code ec) { 360 if (ec) 361 { 362 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 363 return; 364 } 365 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress is Set"; 366 }, 367 "xyz.openbmc_project.Settings", 368 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 369 "org.freedesktop.DBus.Properties", "Set", 370 "xyz.openbmc_project.Network.IP", "Address", 371 std::variant<std::string>(ipv4Address)); 372 } 373 374 /** 375 * @brief Sets the Hypervisor Interface SubnetMask DBUS 376 * 377 * @param[in] aResp Shared pointer for generating response message. 378 * @param[in] subnet SubnetMask from the incoming request 379 * @param[in] ethIfaceId Hypervisor Interface Id 380 * 381 * @return None. 382 */ 383 inline void 384 setHypervisorIPv4Subnet(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 385 const std::string& ethIfaceId, const uint8_t subnet) 386 { 387 BMCWEB_LOG_DEBUG << "Setting the Hypervisor subnet : " << subnet 388 << " on Iface: " << ethIfaceId; 389 390 crow::connections::systemBus->async_method_call( 391 [aResp](const boost::system::error_code ec) { 392 if (ec) 393 { 394 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 395 return; 396 } 397 BMCWEB_LOG_DEBUG << "SubnetMask is Set"; 398 }, 399 "xyz.openbmc_project.Settings", 400 "/xyz/openbmc_project/network/hypervisor/" + ethIfaceId + "/ipv4/addr0", 401 "org.freedesktop.DBus.Properties", "Set", 402 "xyz.openbmc_project.Network.IP", "PrefixLength", 403 std::variant<uint8_t>(subnet)); 404 } 405 406 /** 407 * @brief Sets the Hypervisor Interface Gateway DBUS 408 * 409 * @param[in] aResp Shared pointer for generating response message. 410 * @param[in] gateway Gateway from the incoming request 411 * @param[in] ethIfaceId Hypervisor Interface Id 412 * 413 * @return None. 414 */ 415 inline void 416 setHypervisorIPv4Gateway(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 417 const std::string& gateway) 418 { 419 BMCWEB_LOG_DEBUG 420 << "Setting the DefaultGateway to the last configured gateway"; 421 422 crow::connections::systemBus->async_method_call( 423 [aResp](const boost::system::error_code ec) { 424 if (ec) 425 { 426 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 427 return; 428 } 429 BMCWEB_LOG_DEBUG << "Default Gateway is Set"; 430 }, 431 "xyz.openbmc_project.Settings", 432 "/xyz/openbmc_project/network/hypervisor", 433 "org.freedesktop.DBus.Properties", "Set", 434 "xyz.openbmc_project.Network.SystemConfiguration", "DefaultGateway", 435 std::variant<std::string>(gateway)); 436 } 437 438 /** 439 * @brief Creates a static IPv4 entry 440 * 441 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 442 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 443 * @param[in] gateway IPv4 address of this interfaces gateway 444 * @param[in] address IPv4 address to assign to this interface 445 * @param[io] asyncResp Response object that will be returned to client 446 * 447 * @return None 448 */ 449 inline void 450 createHypervisorIPv4(const std::string& ifaceId, uint8_t prefixLength, 451 const std::string& gateway, const std::string& address, 452 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 453 { 454 setHypervisorIPv4Address(asyncResp, ifaceId, address); 455 setHypervisorIPv4Gateway(asyncResp, gateway); 456 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 457 } 458 459 /** 460 * @brief Deletes given IPv4 interface 461 * 462 * @param[in] ifaceId Id of interface whose IP should be deleted 463 * @param[io] asyncResp Response object that will be returned to client 464 * 465 * @return None 466 */ 467 inline void 468 deleteHypervisorIPv4(const std::string& ifaceId, 469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 470 { 471 std::string address = "0.0.0.0"; 472 std::string gateway = "0.0.0.0"; 473 const uint8_t prefixLength = 0; 474 setHypervisorIPv4Address(asyncResp, ifaceId, address); 475 setHypervisorIPv4Gateway(asyncResp, gateway); 476 setHypervisorIPv4Subnet(asyncResp, ifaceId, prefixLength); 477 } 478 479 inline void parseInterfaceData( 480 nlohmann::json& jsonResponse, const std::string& ifaceId, 481 const EthernetInterfaceData& ethData, 482 const boost::container::flat_set<IPv4AddressData>& ipv4Data) 483 { 484 jsonResponse["Id"] = ifaceId; 485 jsonResponse["@odata.id"] = 486 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/" + ifaceId; 487 jsonResponse["InterfaceEnabled"] = true; 488 jsonResponse["MACAddress"] = ethData.mac_address; 489 490 jsonResponse["HostName"] = ethData.hostname; 491 jsonResponse["DHCPv4"]["DHCPEnabled"] = 492 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 493 494 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 495 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 496 ipv4Array = nlohmann::json::array(); 497 ipv4StaticArray = nlohmann::json::array(); 498 for (auto& ipv4Config : ipv4Data) 499 { 500 if (ipv4Config.isActive) 501 { 502 503 ipv4Array.push_back({{"AddressOrigin", ipv4Config.origin}, 504 {"SubnetMask", ipv4Config.netmask}, 505 {"Address", ipv4Config.address}, 506 {"Gateway", ethData.default_gateway}}); 507 if (ipv4Config.origin == "Static") 508 { 509 ipv4StaticArray.push_back( 510 {{"AddressOrigin", ipv4Config.origin}, 511 {"SubnetMask", ipv4Config.netmask}, 512 {"Address", ipv4Config.address}, 513 {"Gateway", ethData.default_gateway}}); 514 } 515 } 516 } 517 } 518 519 inline void setDHCPEnabled(const std::string& ifaceId, 520 const bool& ipv4DHCPEnabled, 521 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 522 { 523 const std::string dhcp = getDhcpEnabledEnumeration(ipv4DHCPEnabled, false); 524 crow::connections::systemBus->async_method_call( 525 [asyncResp](const boost::system::error_code ec) { 526 if (ec) 527 { 528 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 529 messages::internalError(asyncResp->res); 530 return; 531 } 532 }, 533 "xyz.openbmc_project.Settings", 534 "/xyz/openbmc_project/network/hypervisor/" + ifaceId, 535 "org.freedesktop.DBus.Properties", "Set", 536 "xyz.openbmc_project.Network.EthernetInterface", "DHCPEnabled", 537 std::variant<std::string>{dhcp}); 538 539 // Set the IPv4 address origin to the DHCP / Static as per the new value 540 // of the DHCPEnabled property 541 std::string origin; 542 if (ipv4DHCPEnabled == false) 543 { 544 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 545 } 546 else 547 { 548 // DHCPEnabled is set to true. Delete the current IPv4 settings 549 // to receive the new values from DHCP server. 550 deleteHypervisorIPv4(ifaceId, asyncResp); 551 origin = "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 552 } 553 crow::connections::systemBus->async_method_call( 554 [asyncResp](const boost::system::error_code ec) { 555 if (ec) 556 { 557 BMCWEB_LOG_ERROR << "DBUS response error " << ec; 558 messages::internalError(asyncResp->res); 559 return; 560 } 561 BMCWEB_LOG_DEBUG << "Hypervisor IPaddress Origin is Set"; 562 }, 563 "xyz.openbmc_project.Settings", 564 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 565 "org.freedesktop.DBus.Properties", "Set", 566 "xyz.openbmc_project.Network.IP", "Origin", 567 std::variant<std::string>(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 bool isHostnameValid(const std::string& hostName) 677 { 678 // As per RFC 1123 679 // Allow up to 255 characters 680 if (hostName.length() > 255) 681 { 682 return false; 683 } 684 // Validate the regex 685 const std::regex pattern( 686 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 687 688 return std::regex_match(hostName, pattern); 689 } 690 691 inline void 692 handleHostnamePatch(const std::string& hostName, 693 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 694 { 695 if (!isHostnameValid(hostName)) 696 { 697 messages::propertyValueFormatError(asyncResp->res, hostName, 698 "HostName"); 699 return; 700 } 701 702 asyncResp->res.jsonValue["HostName"] = hostName; 703 crow::connections::systemBus->async_method_call( 704 [asyncResp](const boost::system::error_code ec) { 705 if (ec) 706 { 707 messages::internalError(asyncResp->res); 708 } 709 }, 710 "xyz.openbmc_project.Settings", 711 "/xyz/openbmc_project/network/hypervisor", 712 "org.freedesktop.DBus.Properties", "Set", 713 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 714 std::variant<std::string>(hostName)); 715 } 716 717 inline void 718 setIPv4InterfaceEnabled(const std::string& ifaceId, const bool& isActive, 719 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 720 { 721 crow::connections::systemBus->async_method_call( 722 [asyncResp](const boost::system::error_code ec) { 723 if (ec) 724 { 725 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 726 messages::internalError(asyncResp->res); 727 return; 728 } 729 }, 730 "xyz.openbmc_project.Settings", 731 "/xyz/openbmc_project/network/hypervisor/" + ifaceId + "/ipv4/addr0", 732 "org.freedesktop.DBus.Properties", "Set", 733 "xyz.openbmc_project.Object.Enable", "Enabled", 734 std::variant<bool>(isActive)); 735 } 736 737 inline void requestRoutesHypervisorSystems(App& app) 738 { 739 /** 740 * Hypervisor Systems derived class for delivering Computer Systems Schema. 741 */ 742 743 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/") 744 .privileges({"Login"}) 745 .methods(boost::beast::http::verb::get)( 746 [](const crow::Request&, 747 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 748 crow::connections::systemBus->async_method_call( 749 [asyncResp](const boost::system::error_code ec, 750 const std::variant<std::string>& /*hostName*/) { 751 if (ec) 752 { 753 messages::resourceNotFound(asyncResp->res, "System", 754 "hypervisor"); 755 return; 756 } 757 BMCWEB_LOG_DEBUG << "Hypervisor is available"; 758 759 asyncResp->res.jsonValue["@odata.type"] = 760 "#ComputerSystem.v1_6_0.ComputerSystem"; 761 asyncResp->res.jsonValue["@odata.id"] = 762 "/redfish/v1/Systems/hypervisor"; 763 asyncResp->res.jsonValue["Description"] = "Hypervisor"; 764 asyncResp->res.jsonValue["Name"] = "Hypervisor"; 765 asyncResp->res.jsonValue["Id"] = "hypervisor"; 766 asyncResp->res.jsonValue["Links"]["ManagedBy"] = { 767 {{"@odata.id", "/redfish/v1/Managers/bmc"}}}; 768 asyncResp->res.jsonValue["EthernetInterfaces"] = { 769 {"@odata.id", "/redfish/v1/Systems/hypervisor/" 770 "EthernetInterfaces"}}; 771 getHypervisorState(asyncResp); 772 getHypervisorActions(asyncResp); 773 // TODO: Add "SystemType" : "hypervisor" 774 }, 775 "xyz.openbmc_project.Settings", 776 "/xyz/openbmc_project/network/hypervisor", 777 "org.freedesktop.DBus.Properties", "Get", 778 "xyz.openbmc_project.Network.SystemConfiguration", 779 "HostName"); 780 }); 781 782 /** 783 * HypervisorInterfaceCollection class to handle the GET and PATCH on 784 * Hypervisor Interface 785 */ 786 787 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/EthernetInterfaces/") 788 .privileges({"Login"}) 789 .methods(boost::beast::http::verb::get)( 790 [](const crow::Request&, 791 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 792 const std::array<const char*, 1> interfaces = { 793 "xyz.openbmc_project.Network.EthernetInterface"}; 794 795 crow::connections::systemBus->async_method_call( 796 [asyncResp](const boost::system::error_code error, 797 const std::vector<std::string>& 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"] = 810 "Hypervisor Ethernet " 811 "Interface Collection"; 812 asyncResp->res.jsonValue["Description"] = 813 "Collection of Virtual Management " 814 "Interfaces for the hypervisor"; 815 816 nlohmann::json& ifaceArray = 817 asyncResp->res.jsonValue["Members"]; 818 ifaceArray = nlohmann::json::array(); 819 for (const std::string& iface : ifaceList) 820 { 821 sdbusplus::message::object_path path(iface); 822 std::string name = path.filename(); 823 if (name.empty()) 824 { 825 continue; 826 } 827 828 ifaceArray.push_back( 829 {{"@odata.id", "/redfish/v1/Systems/hypervisor/" 830 "EthernetInterfaces/" + 831 name}}); 832 } 833 asyncResp->res.jsonValue["Members@odata.count"] = 834 ifaceArray.size(); 835 }, 836 "xyz.openbmc_project.ObjectMapper", 837 "/xyz/openbmc_project/object_mapper", 838 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 839 "/xyz/openbmc_project/network/hypervisor", 0, interfaces); 840 }); 841 842 BMCWEB_ROUTE(app, 843 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 844 .privileges({"Login"}) 845 .methods( 846 boost::beast::http::verb::get)([](const crow::Request&, 847 const std::shared_ptr< 848 bmcweb::AsyncResp>& asyncResp, 849 const std::string& id) { 850 getHypervisorIfaceData( 851 id, 852 [asyncResp, ifaceId{std::string(id)}]( 853 const bool& success, const EthernetInterfaceData& ethData, 854 const boost::container::flat_set<IPv4AddressData>& 855 ipv4Data) { 856 if (!success) 857 { 858 messages::resourceNotFound( 859 asyncResp->res, "EthernetInterface", ifaceId); 860 return; 861 } 862 asyncResp->res.jsonValue["@odata.type"] = 863 "#EthernetInterface.v1_5_1.EthernetInterface"; 864 asyncResp->res.jsonValue["Name"] = 865 "Hypervisor Ethernet Interface"; 866 asyncResp->res.jsonValue["Description"] = 867 "Hypervisor's Virtual Management Ethernet Interface"; 868 parseInterfaceData(asyncResp->res.jsonValue, ifaceId, 869 ethData, ipv4Data); 870 }); 871 }); 872 873 BMCWEB_ROUTE(app, 874 "/redfish/v1/Systems/hypervisor/EthernetInterfaces/<str>/") 875 .privileges({"ConfigureComponents"}) 876 .methods( 877 boost::beast::http::verb:: 878 patch)([](const crow::Request& req, 879 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 880 const std::string& ifaceId) { 881 std::optional<std::string> hostName; 882 std::optional<std::vector<nlohmann::json>> ipv4StaticAddresses; 883 std::optional<nlohmann::json> ipv4Addresses; 884 std::optional<nlohmann::json> dhcpv4; 885 std::optional<bool> ipv4DHCPEnabled; 886 887 if (!json_util::readJson(req, asyncResp->res, "HostName", hostName, 888 "IPv4StaticAddresses", ipv4StaticAddresses, 889 "IPv4Addresses", ipv4Addresses, "DHCPv4", 890 dhcpv4)) 891 { 892 return; 893 } 894 895 if (ipv4Addresses) 896 { 897 messages::propertyNotWritable(asyncResp->res, "IPv4Addresses"); 898 } 899 900 if (dhcpv4) 901 { 902 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 903 ipv4DHCPEnabled)) 904 { 905 return; 906 } 907 } 908 909 getHypervisorIfaceData( 910 ifaceId, 911 [asyncResp, ifaceId, hostName = std::move(hostName), 912 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 913 ipv4DHCPEnabled, dhcpv4 = std::move(dhcpv4)]( 914 const bool& success, const EthernetInterfaceData& ethData, 915 const boost::container::flat_set<IPv4AddressData>&) { 916 if (!success) 917 { 918 messages::resourceNotFound( 919 asyncResp->res, "EthernetInterface", ifaceId); 920 return; 921 } 922 923 if (ipv4StaticAddresses) 924 { 925 const nlohmann::json& ipv4Static = *ipv4StaticAddresses; 926 if (ipv4Static.begin() == ipv4Static.end()) 927 { 928 messages::propertyValueTypeError( 929 asyncResp->res, 930 ipv4Static.dump( 931 2, ' ', true, 932 nlohmann::json::error_handler_t::replace), 933 "IPv4StaticAddresses"); 934 return; 935 } 936 937 // One and only one hypervisor instance supported 938 if (ipv4Static.size() != 1) 939 { 940 messages::propertyValueFormatError( 941 asyncResp->res, 942 ipv4Static.dump( 943 2, ' ', true, 944 nlohmann::json::error_handler_t::replace), 945 "IPv4StaticAddresses"); 946 return; 947 } 948 949 const nlohmann::json& ipv4Json = ipv4Static[0]; 950 // Check if the param is 'null'. If its null, it means 951 // that user wants to delete the IP address. Deleting 952 // the IP address is allowed only if its statically 953 // configured. Deleting the address originated from DHCP 954 // is not allowed. 955 if ((ipv4Json.is_null()) && 956 (translateDHCPEnabledToBool(ethData.DHCPEnabled, 957 true))) 958 { 959 BMCWEB_LOG_INFO 960 << "Ignoring the delete on ipv4StaticAddresses " 961 "as the interface is DHCP enabled"; 962 } 963 else 964 { 965 handleHypervisorIPv4StaticPatch(ifaceId, ipv4Static, 966 asyncResp); 967 } 968 } 969 970 if (hostName) 971 { 972 handleHostnamePatch(*hostName, asyncResp); 973 } 974 975 if (dhcpv4) 976 { 977 setDHCPEnabled(ifaceId, *ipv4DHCPEnabled, asyncResp); 978 } 979 980 // Set this interface to disabled/inactive. This will be set 981 // to enabled/active by the pldm once the hypervisor 982 // consumes the updated settings from the user. 983 setIPv4InterfaceEnabled(ifaceId, false, asyncResp); 984 }); 985 asyncResp->res.result(boost::beast::http::status::accepted); 986 }); 987 988 BMCWEB_ROUTE(app, "/redfish/v1/Systems/hypervisor/ResetActionInfo/") 989 .privileges({"Login"}) 990 .methods(boost::beast::http::verb::get)( 991 [](const crow::Request&, 992 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 993 // Only return action info if hypervisor D-Bus object present 994 crow::connections::systemBus->async_method_call( 995 [asyncResp]( 996 const boost::system::error_code ec, 997 const std::vector<std::pair< 998 std::string, std::vector<std::string>>>& objInfo) { 999 if (ec) 1000 { 1001 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1002 1003 // No hypervisor objects found by mapper 1004 if (ec.value() == boost::system::errc::io_error) 1005 { 1006 messages::resourceNotFound(asyncResp->res, 1007 "hypervisor", 1008 "ResetActionInfo"); 1009 return; 1010 } 1011 1012 messages::internalError(asyncResp->res); 1013 return; 1014 } 1015 1016 // One and only one hypervisor instance supported 1017 if (objInfo.size() != 1) 1018 { 1019 messages::internalError(asyncResp->res); 1020 return; 1021 } 1022 1023 // The hypervisor object only support the ability to 1024 // turn On The system object Action should be utilized 1025 // for other operations 1026 asyncResp->res.jsonValue = { 1027 {"@odata.type", "#ActionInfo.v1_1_2.ActionInfo"}, 1028 {"@odata.id", 1029 "/redfish/v1/Systems/hypervisor/ResetActionInfo"}, 1030 {"Name", "Reset Action Info"}, 1031 {"Id", "ResetActionInfo"}, 1032 {"Parameters", 1033 {{{"Name", "ResetType"}, 1034 {"Required", true}, 1035 {"DataType", "String"}, 1036 {"AllowableValues", {"On"}}}}}}; 1037 }, 1038 "xyz.openbmc_project.ObjectMapper", 1039 "/xyz/openbmc_project/object_mapper", 1040 "xyz.openbmc_project.ObjectMapper", "GetObject", 1041 "/xyz/openbmc_project/state/hypervisor0", 1042 std::array<const char*, 1>{ 1043 "xyz.openbmc_project.State.Host"}); 1044 }); 1045 1046 BMCWEB_ROUTE(app, 1047 "/redfish/v1/Systems/hypervisor/Actions/ComputerSystem.Reset/") 1048 .privileges({"ConfigureComponents"}) 1049 .methods(boost::beast::http::verb::post)( 1050 [](const crow::Request& req, 1051 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1052 std::optional<std::string> resetType; 1053 if (!json_util::readJson(req, asyncResp->res, "ResetType", 1054 resetType)) 1055 { 1056 // readJson adds appropriate error to response 1057 return; 1058 } 1059 1060 if (!resetType) 1061 { 1062 messages::actionParameterMissing( 1063 asyncResp->res, "ComputerSystem.Reset", "ResetType"); 1064 return; 1065 } 1066 1067 // Hypervisor object only support On operation 1068 if (resetType != "On") 1069 { 1070 messages::propertyValueNotInList(asyncResp->res, *resetType, 1071 "ResetType"); 1072 return; 1073 } 1074 1075 std::string command = 1076 "xyz.openbmc_project.State.Host.Transition.On"; 1077 1078 crow::connections::systemBus->async_method_call( 1079 [asyncResp, resetType](const boost::system::error_code ec) { 1080 if (ec) 1081 { 1082 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1083 if (ec.value() == 1084 boost::asio::error::invalid_argument) 1085 { 1086 messages::actionParameterNotSupported( 1087 asyncResp->res, *resetType, "Reset"); 1088 return; 1089 } 1090 1091 if (ec.value() == 1092 boost::asio::error::host_unreachable) 1093 { 1094 messages::resourceNotFound(asyncResp->res, 1095 "Actions", "Reset"); 1096 return; 1097 } 1098 1099 messages::internalError(asyncResp->res); 1100 return; 1101 } 1102 messages::success(asyncResp->res); 1103 }, 1104 "xyz.openbmc_project.State.Hypervisor", 1105 "/xyz/openbmc_project/state/hypervisor0", 1106 "org.freedesktop.DBus.Properties", "Set", 1107 "xyz.openbmc_project.State.Host", "RequestedHostTransition", 1108 std::variant<std::string>{std::move(command)}); 1109 }); 1110 } 1111 } // namespace redfish::hypervisor 1112