xref: /openbmc/bmcweb/redfish-core/include/utils/sw_utils.hpp (revision 5e7e2dc585dc7a7e62d2b648b541e7f50c39ea5d)
1 #pragma once
2 #include "async_resp.hpp"
3 #include "dbus_utility.hpp"
4 #include "error_messages.hpp"
5 #include "generated/enums/resource.hpp"
6 #include "http/utility.hpp"
7 #include "utils/dbus_utils.hpp"
8 
9 #include <boost/system/error_code.hpp>
10 #include <sdbusplus/asio/property.hpp>
11 #include <sdbusplus/unpack_properties.hpp>
12 
13 #include <algorithm>
14 #include <array>
15 #include <string>
16 #include <string_view>
17 #include <vector>
18 
19 namespace redfish
20 {
21 namespace sw_util
22 {
23 /* @brief String that indicates a bios software instance */
24 constexpr const char* biosPurpose =
25     "xyz.openbmc_project.Software.Version.VersionPurpose.Host";
26 
27 /* @brief String that indicates a BMC software instance */
28 constexpr const char* bmcPurpose =
29     "xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
30 
31 /**
32  * @brief Populate the running software version and image links
33  *
34  * @param[i,o] aResp             Async response object
35  * @param[i]   swVersionPurpose  Indicates what target to look for
36  * @param[i]   activeVersionPropName  Index in aResp->res.jsonValue to write
37  * the running software version to
38  * @param[i]   populateLinkToImages  Populate aResp->res "Links"
39  * "ActiveSoftwareImage" with a link to the running software image and
40  * "SoftwareImages" with a link to the all its software images
41  *
42  * @return void
43  */
44 inline void
45     populateSoftwareInformation(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
46                                 const std::string& swVersionPurpose,
47                                 const std::string& activeVersionPropName,
48                                 const bool populateLinkToImages)
49 {
50     // Used later to determine running (known on Redfish as active) Sw images
51     sdbusplus::asio::getProperty<std::vector<std::string>>(
52         *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper",
53         "/xyz/openbmc_project/software/functional",
54         "xyz.openbmc_project.Association", "endpoints",
55         [aResp, swVersionPurpose, activeVersionPropName,
56          populateLinkToImages](const boost::system::error_code& ec,
57                                const std::vector<std::string>& functionalSw) {
58         BMCWEB_LOG_DEBUG << "populateSoftwareInformation enter";
59         if (ec)
60         {
61             BMCWEB_LOG_ERROR << "error_code = " << ec;
62             BMCWEB_LOG_ERROR << "error msg = " << ec.message();
63             messages::internalError(aResp->res);
64             return;
65         }
66 
67         if (functionalSw.empty())
68         {
69             // Could keep going and try to populate SoftwareImages but
70             // something is seriously wrong, so just fail
71             BMCWEB_LOG_ERROR << "Zero functional software in system";
72             messages::internalError(aResp->res);
73             return;
74         }
75 
76         std::vector<std::string> functionalSwIds;
77         // example functionalSw:
78         // v as 2 "/xyz/openbmc_project/software/ace821ef"
79         //        "/xyz/openbmc_project/software/230fb078"
80         for (const auto& sw : functionalSw)
81         {
82             sdbusplus::message::object_path path(sw);
83             std::string leaf = path.filename();
84             if (leaf.empty())
85             {
86                 continue;
87             }
88 
89             functionalSwIds.push_back(leaf);
90         }
91 
92         constexpr std::array<std::string_view, 1> interfaces = {
93             "xyz.openbmc_project.Software.Version"};
94         dbus::utility::getSubTree(
95             "/xyz/openbmc_project/software", 0, interfaces,
96             [aResp, swVersionPurpose, activeVersionPropName,
97              populateLinkToImages, functionalSwIds](
98                 const boost::system::error_code& ec2,
99                 const dbus::utility::MapperGetSubTreeResponse& subtree) {
100             if (ec2)
101             {
102                 BMCWEB_LOG_ERROR << "error_code = " << ec2;
103                 BMCWEB_LOG_ERROR << "error msg = " << ec2.message();
104                 messages::internalError(aResp->res);
105                 return;
106             }
107 
108             BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " images";
109 
110             for (const std::pair<std::string,
111                                  std::vector<std::pair<
112                                      std::string, std::vector<std::string>>>>&
113                      obj : subtree)
114             {
115 
116                 sdbusplus::message::object_path path(obj.first);
117                 std::string swId = path.filename();
118                 if (swId.empty())
119                 {
120                     messages::internalError(aResp->res);
121                     BMCWEB_LOG_ERROR << "Invalid software ID";
122 
123                     return;
124                 }
125 
126                 bool runningImage = false;
127                 // Look at Ids from
128                 // /xyz/openbmc_project/software/functional
129                 // to determine if this is a running image
130                 if (std::find(functionalSwIds.begin(), functionalSwIds.end(),
131                               swId) != functionalSwIds.end())
132                 {
133                     runningImage = true;
134                 }
135 
136                 // Now grab its version info
137                 sdbusplus::asio::getAllProperties(
138                     *crow::connections::systemBus, obj.second[0].first,
139                     obj.first, "xyz.openbmc_project.Software.Version",
140                     [aResp, swId, runningImage, swVersionPurpose,
141                      activeVersionPropName, populateLinkToImages](
142                         const boost::system::error_code& ec3,
143                         const dbus::utility::DBusPropertiesMap&
144                             propertiesList) {
145                     if (ec3)
146                     {
147                         BMCWEB_LOG_ERROR << "error_code = " << ec3;
148                         BMCWEB_LOG_ERROR << "error msg = " << ec3.message();
149                         // Have seen the code update app delete the D-Bus
150                         // object, during code update, between the call to
151                         // mapper and here. Just leave these properties off if
152                         // resource not found.
153                         if (ec3.value() == EBADR)
154                         {
155                             return;
156                         }
157                         messages::internalError(aResp->res);
158                         return;
159                     }
160                     // example propertiesList
161                     // a{sv} 2 "Version" s
162                     // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose"
163                     // s
164                     // "xyz.openbmc_project.Software.Version.VersionPurpose.Host"
165                     const std::string* version = nullptr;
166                     const std::string* swInvPurpose = nullptr;
167 
168                     const bool success = sdbusplus::unpackPropertiesNoThrow(
169                         dbus_utils::UnpackErrorPrinter(), propertiesList,
170                         "Purpose", swInvPurpose, "Version", version);
171 
172                     if (!success)
173                     {
174                         messages::internalError(aResp->res);
175                         return;
176                     }
177 
178                     if (version == nullptr || version->empty())
179                     {
180                         messages::internalError(aResp->res);
181                         return;
182                     }
183                     if (swInvPurpose == nullptr ||
184                         *swInvPurpose != swVersionPurpose)
185                     {
186                         // Not purpose we're looking for
187                         return;
188                     }
189 
190                     BMCWEB_LOG_DEBUG << "Image ID: " << swId;
191                     BMCWEB_LOG_DEBUG << "Running image: " << runningImage;
192                     BMCWEB_LOG_DEBUG << "Image purpose: " << *swInvPurpose;
193 
194                     if (populateLinkToImages)
195                     {
196                         nlohmann::json& softwareImageMembers =
197                             aResp->res.jsonValue["Links"]["SoftwareImages"];
198                         // Firmware images are at
199                         // /redfish/v1/UpdateService/FirmwareInventory/<Id>
200                         // e.g. .../FirmwareInventory/82d3ec86
201                         nlohmann::json::object_t member;
202                         member["@odata.id"] = crow::utility::urlFromPieces(
203                             "redfish", "v1", "UpdateService",
204                             "FirmwareInventory", swId);
205                         softwareImageMembers.push_back(std::move(member));
206                         aResp->res
207                             .jsonValue["Links"]["SoftwareImages@odata.count"] =
208                             softwareImageMembers.size();
209 
210                         if (runningImage)
211                         {
212                             nlohmann::json::object_t runningMember;
213                             runningMember["@odata.id"] =
214                                 crow::utility::urlFromPieces(
215                                     "redfish", "v1", "UpdateService",
216                                     "FirmwareInventory", swId);
217                             // Create the link to the running image
218                             aResp->res
219                                 .jsonValue["Links"]["ActiveSoftwareImage"] =
220                                 std::move(runningMember);
221                         }
222                     }
223                     if (!activeVersionPropName.empty() && runningImage)
224                     {
225                         aResp->res.jsonValue[activeVersionPropName] = *version;
226                     }
227                     });
228             }
229             });
230         });
231 }
232 
233 /**
234  * @brief Translate input swState to Redfish state
235  *
236  * This function will return the corresponding Redfish state
237  *
238  * @param[i]   swState  The OpenBMC software state
239  *
240  * @return The corresponding Redfish state
241  */
242 inline resource::State getRedfishSwState(const std::string& swState)
243 {
244     if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active")
245     {
246         return resource::State::Enabled;
247     }
248     if (swState == "xyz.openbmc_project.Software.Activation."
249                    "Activations.Activating")
250     {
251         return resource::State::Updating;
252     }
253     if (swState == "xyz.openbmc_project.Software.Activation."
254                    "Activations.StandbySpare")
255     {
256         return resource::State::StandbySpare;
257     }
258     BMCWEB_LOG_DEBUG << "Default sw state " << swState << " to Disabled";
259     return resource::State::Disabled;
260 }
261 
262 /**
263  * @brief Translate input swState to Redfish health state
264  *
265  * This function will return the corresponding Redfish health state
266  *
267  * @param[i]   swState  The OpenBMC software state
268  *
269  * @return The corresponding Redfish health state
270  */
271 inline std::string getRedfishSwHealth(const std::string& swState)
272 {
273     if ((swState ==
274          "xyz.openbmc_project.Software.Activation.Activations.Active") ||
275         (swState == "xyz.openbmc_project.Software.Activation.Activations."
276                     "Activating") ||
277         (swState ==
278          "xyz.openbmc_project.Software.Activation.Activations.Ready"))
279     {
280         return "OK";
281     }
282     BMCWEB_LOG_DEBUG << "Sw state " << swState << " to Warning";
283     return "Warning";
284 }
285 
286 /**
287  * @brief Put status of input swId into json response
288  *
289  * This function will put the appropriate Redfish state of the input
290  * software id to ["Status"]["State"] within the json response
291  *
292  * @param[i,o] aResp    Async response object
293  * @param[i]   swId     The software ID to get status for
294  * @param[i]   dbusSvc  The dbus service implementing the software object
295  *
296  * @return void
297  */
298 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
299                         const std::shared_ptr<std::string>& swId,
300                         const std::string& dbusSvc)
301 {
302     BMCWEB_LOG_DEBUG << "getSwStatus: swId " << *swId << " svc " << dbusSvc;
303 
304     sdbusplus::asio::getAllProperties(
305         *crow::connections::systemBus, dbusSvc,
306         "/xyz/openbmc_project/software/" + *swId,
307         "xyz.openbmc_project.Software.Activation",
308         [asyncResp,
309          swId](const boost::system::error_code& errorCode,
310                const dbus::utility::DBusPropertiesMap& propertiesList) {
311         if (errorCode)
312         {
313             // not all swtypes are updateable, this is ok
314             asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
315             return;
316         }
317 
318         const std::string* swInvActivation = nullptr;
319 
320         const bool success = sdbusplus::unpackPropertiesNoThrow(
321             dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation",
322             swInvActivation);
323 
324         if (!success)
325         {
326             messages::internalError(asyncResp->res);
327             return;
328         }
329 
330         if (swInvActivation == nullptr)
331         {
332             messages::internalError(asyncResp->res);
333             return;
334         }
335 
336         BMCWEB_LOG_DEBUG << "getSwStatus: Activation " << *swInvActivation;
337         asyncResp->res.jsonValue["Status"]["State"] =
338             getRedfishSwState(*swInvActivation);
339         asyncResp->res.jsonValue["Status"]["Health"] =
340             getRedfishSwHealth(*swInvActivation);
341         });
342 }
343 
344 /**
345  * @brief Updates programmable status of input swId into json response
346  *
347  * This function checks whether software inventory component
348  * can be programmable or not and fill's the "Updatable"
349  * Property.
350  *
351  * @param[i,o] asyncResp  Async response object
352  * @param[i]   swId       The software ID
353  */
354 inline void
355     getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
356                          const std::shared_ptr<std::string>& swId)
357 {
358     sdbusplus::asio::getProperty<std::vector<std::string>>(
359         *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper",
360         "/xyz/openbmc_project/software/updateable",
361         "xyz.openbmc_project.Association", "endpoints",
362         [asyncResp, swId](const boost::system::error_code& ec,
363                           const std::vector<std::string>& objPaths) {
364         if (ec)
365         {
366             BMCWEB_LOG_DEBUG << " error_code = " << ec
367                              << " error msg =  " << ec.message();
368             // System can exist with no updateable software,
369             // so don't throw error here.
370             return;
371         }
372         std::string reqSwObjPath = "/xyz/openbmc_project/software/" + *swId;
373 
374         if (std::find(objPaths.begin(), objPaths.end(), reqSwObjPath) !=
375             objPaths.end())
376         {
377             asyncResp->res.jsonValue["Updateable"] = true;
378             return;
379         }
380         });
381 }
382 
383 } // namespace sw_util
384 } // namespace redfish
385