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