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