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