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