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