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