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