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