xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision 0e7de46f9b6365bad4e79a3933112750c5bf7853)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/container/flat_map.hpp>
21 #include <utils/fw_utils.hpp>
22 #include <variant>
23 
24 namespace redfish
25 {
26 
27 // Match signals added on software path
28 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
29 // Only allow one update at a time
30 static bool fwUpdateInProgress = false;
31 
32 class UpdateService : public Node
33 {
34   public:
35     UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/")
36     {
37         entityPrivileges = {
38             {boost::beast::http::verb::get, {{"Login"}}},
39             {boost::beast::http::verb::head, {{"Login"}}},
40             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
41             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
42             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
43             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
44     }
45 
46   private:
47     void doGet(crow::Response &res, const crow::Request &req,
48                const std::vector<std::string> &params) override
49     {
50         res.jsonValue["@odata.type"] = "#UpdateService.v1_2_0.UpdateService";
51         res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
52         res.jsonValue["@odata.context"] =
53             "/redfish/v1/$metadata#UpdateService.UpdateService";
54         res.jsonValue["Id"] = "UpdateService";
55         res.jsonValue["Description"] = "Service for Software Update";
56         res.jsonValue["Name"] = "Update Service";
57         res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService";
58         // UpdateService cannot be disabled
59         res.jsonValue["ServiceEnabled"] = true;
60         res.jsonValue["FirmwareInventory"] = {
61             {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
62         res.end();
63     }
64     static void cleanUp()
65     {
66         fwUpdateInProgress = false;
67         fwUpdateMatcher = nullptr;
68     }
69     static void activateImage(const std::string &objPath,
70                               const std::string &service)
71     {
72         BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
73         crow::connections::systemBus->async_method_call(
74             [](const boost::system::error_code error_code) {
75                 if (error_code)
76                 {
77                     BMCWEB_LOG_DEBUG << "error_code = " << error_code;
78                     BMCWEB_LOG_DEBUG << "error msg = " << error_code.message();
79                 }
80             },
81             service, objPath, "org.freedesktop.DBus.Properties", "Set",
82             "xyz.openbmc_project.Software.Activation", "RequestedActivation",
83             std::variant<std::string>(
84                 "xyz.openbmc_project.Software.Activation.RequestedActivations."
85                 "Active"));
86     }
87     static void softwareInterfaceAdded(std::shared_ptr<AsyncResp> asyncResp,
88                                        boost::asio::deadline_timer &timeout,
89                                        sdbusplus::message::message &m)
90     {
91         std::vector<std::pair<
92             std::string,
93             std::vector<std::pair<std::string, std::variant<std::string>>>>>
94             interfacesProperties;
95 
96         sdbusplus::message::object_path objPath;
97 
98         m.read(objPath, interfacesProperties);
99 
100         BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
101         for (auto &interface : interfacesProperties)
102         {
103             BMCWEB_LOG_DEBUG << "interface = " << interface.first;
104 
105             if (interface.first == "xyz.openbmc_project.Software.Activation")
106             {
107                 // Found our interface, disable callbacks
108                 fwUpdateMatcher = nullptr;
109 
110                 // Retrieve service and activate
111                 crow::connections::systemBus->async_method_call(
112                     [objPath, asyncResp, &timeout](
113                         const boost::system::error_code error_code,
114                         const std::vector<std::pair<
115                             std::string, std::vector<std::string>>> &objInfo) {
116                         if (error_code)
117                         {
118                             BMCWEB_LOG_DEBUG << "error_code = " << error_code;
119                             BMCWEB_LOG_DEBUG << "error msg = "
120                                              << error_code.message();
121                             messages::internalError(asyncResp->res);
122                             cleanUp();
123                             return;
124                         }
125                         // Ensure we only got one service back
126                         if (objInfo.size() != 1)
127                         {
128                             BMCWEB_LOG_ERROR << "Invalid Object Size "
129                                              << objInfo.size();
130                             messages::internalError(asyncResp->res);
131                             cleanUp();
132                             return;
133                         }
134                         // cancel timer only when
135                         // xyz.openbmc_project.Software.Activation interface
136                         // is added
137                         boost::system::error_code ec;
138                         timeout.cancel(ec);
139                         if (ec)
140                         {
141                             BMCWEB_LOG_ERROR << "error canceling timer " << ec;
142                         }
143                         UpdateService::activateImage(objPath.str,
144                                                      objInfo[0].first);
145                         redfish::messages::success(asyncResp->res);
146                         fwUpdateInProgress = false;
147                     },
148                     "xyz.openbmc_project.ObjectMapper",
149                     "/xyz/openbmc_project/object_mapper",
150                     "xyz.openbmc_project.ObjectMapper", "GetObject",
151                     objPath.str,
152                     std::array<const char *, 1>{
153                         "xyz.openbmc_project.Software.Activation"});
154             }
155         }
156     }
157     void doPost(crow::Response &res, const crow::Request &req,
158                 const std::vector<std::string> &params) override
159     {
160         BMCWEB_LOG_DEBUG << "doPost...";
161 
162         // Only allow one FW update at a time
163         if (fwUpdateInProgress != false)
164         {
165             res.addHeader("Retry-After", "30");
166             messages::serviceTemporarilyUnavailable(res, "30");
167             res.end();
168             return;
169         }
170 
171         // Make this const static so it survives outside this method
172         static boost::asio::deadline_timer timeout(
173             *req.ioService, boost::posix_time::seconds(5));
174 
175         timeout.expires_from_now(boost::posix_time::seconds(5));
176 
177         timeout.async_wait([&res](const boost::system::error_code &ec) {
178             cleanUp();
179             if (ec == boost::asio::error::operation_aborted)
180             {
181                 // expected, we were canceled before the timer completed.
182                 return;
183             }
184             BMCWEB_LOG_ERROR
185                 << "Timed out waiting for firmware object being created";
186             BMCWEB_LOG_ERROR
187                 << "FW image may has already been uploaded to server";
188             if (ec)
189             {
190                 BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
191                 return;
192             }
193 
194             redfish::messages::internalError(res);
195             res.end();
196         });
197 
198         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
199         auto callback = [asyncResp](sdbusplus::message::message &m) {
200             BMCWEB_LOG_DEBUG << "Match fired";
201             softwareInterfaceAdded(asyncResp, timeout, m);
202         };
203 
204         fwUpdateInProgress = true;
205 
206         fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
207             *crow::connections::systemBus,
208             "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
209             "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
210             callback);
211 
212         std::string filepath(
213             "/tmp/images/" +
214             boost::uuids::to_string(boost::uuids::random_generator()()));
215         BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
216         std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
217                                         std::ofstream::trunc);
218         out << req.body;
219         out.close();
220         BMCWEB_LOG_DEBUG << "file upload complete!!";
221     }
222 };
223 
224 class SoftwareInventoryCollection : public Node
225 {
226   public:
227     template <typename CrowApp>
228     SoftwareInventoryCollection(CrowApp &app) :
229         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/")
230     {
231         entityPrivileges = {
232             {boost::beast::http::verb::get, {{"Login"}}},
233             {boost::beast::http::verb::head, {{"Login"}}},
234             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
235             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
236             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
237             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
238     }
239 
240   private:
241     void doGet(crow::Response &res, const crow::Request &req,
242                const std::vector<std::string> &params) override
243     {
244         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
245         res.jsonValue["@odata.type"] =
246             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
247         res.jsonValue["@odata.id"] =
248             "/redfish/v1/UpdateService/FirmwareInventory";
249         res.jsonValue["@odata.context"] =
250             "/redfish/v1/"
251             "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection";
252         res.jsonValue["Name"] = "Software Inventory Collection";
253 
254         crow::connections::systemBus->async_method_call(
255             [asyncResp](
256                 const boost::system::error_code ec,
257                 const std::vector<std::pair<
258                     std::string, std::vector<std::pair<
259                                      std::string, std::vector<std::string>>>>>
260                     &subtree) {
261                 if (ec)
262                 {
263                     messages::internalError(asyncResp->res);
264                     return;
265                 }
266                 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
267                 asyncResp->res.jsonValue["Members@odata.count"] = 0;
268 
269                 for (auto &obj : subtree)
270                 {
271                     const std::vector<
272                         std::pair<std::string, std::vector<std::string>>>
273                         &connections = obj.second;
274 
275                     // if can't parse fw id then return
276                     std::size_t idPos;
277                     if ((idPos = obj.first.rfind("/")) == std::string::npos)
278                     {
279                         messages::internalError(asyncResp->res);
280                         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
281                         return;
282                     }
283                     std::string swId = obj.first.substr(idPos + 1);
284 
285                     for (auto &conn : connections)
286                     {
287                         const std::string &connectionName = conn.first;
288                         BMCWEB_LOG_DEBUG << "connectionName = "
289                                          << connectionName;
290                         BMCWEB_LOG_DEBUG << "obj.first = " << obj.first;
291 
292                         crow::connections::systemBus->async_method_call(
293                             [asyncResp,
294                              swId](const boost::system::error_code error_code,
295                                    const VariantType &activation) {
296                                 BMCWEB_LOG_DEBUG
297                                     << "safe returned in lambda function";
298                                 if (error_code)
299                                 {
300                                     messages::internalError(asyncResp->res);
301                                     return;
302                                 }
303 
304                                 const std::string *swActivationStatus =
305                                     std::get_if<std::string>(&activation);
306                                 if (swActivationStatus == nullptr)
307                                 {
308                                     messages::internalError(asyncResp->res);
309                                     return;
310                                 }
311                                 if (swActivationStatus != nullptr &&
312                                     *swActivationStatus !=
313                                         "xyz.openbmc_project.Software."
314                                         "Activation."
315                                         "Activations.Active")
316                                 {
317                                     // The activation status of this software is
318                                     // not currently active, so does not need to
319                                     // be listed in the response
320                                     return;
321                                 }
322                                 nlohmann::json &members =
323                                     asyncResp->res.jsonValue["Members"];
324                                 members.push_back(
325                                     {{"@odata.id", "/redfish/v1/UpdateService/"
326                                                    "FirmwareInventory/" +
327                                                        swId}});
328                                 asyncResp->res
329                                     .jsonValue["Members@odata.count"] =
330                                     members.size();
331                             },
332                             connectionName, obj.first,
333                             "org.freedesktop.DBus.Properties", "Get",
334                             "xyz.openbmc_project.Software.Activation",
335                             "Activation");
336                     }
337                 }
338             },
339             "xyz.openbmc_project.ObjectMapper",
340             "/xyz/openbmc_project/object_mapper",
341             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
342             "/xyz/openbmc_project/software", int32_t(1),
343             std::array<const char *, 1>{
344                 "xyz.openbmc_project.Software.Version"});
345     }
346 };
347 
348 class SoftwareInventory : public Node
349 {
350   public:
351     template <typename CrowApp>
352     SoftwareInventory(CrowApp &app) :
353         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/",
354              std::string())
355     {
356         entityPrivileges = {
357             {boost::beast::http::verb::get, {{"Login"}}},
358             {boost::beast::http::verb::head, {{"Login"}}},
359             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
360             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
361             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
362             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
363     }
364 
365   private:
366     /* Fill related item links (i.e. bmc, bios) in for inventory */
367     static void getRelatedItems(std::shared_ptr<AsyncResp> aResp,
368                                 const std::string &purpose)
369     {
370         if (purpose == fw_util::bmcPurpose)
371         {
372             nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
373             members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}});
374             aResp->res.jsonValue["Members@odata.count"] = members.size();
375         }
376         else if (purpose == fw_util::biosPurpose)
377         {
378             // TODO(geissonator) Need BIOS schema support added for this
379             //                   to be valid
380             // nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
381             // members.push_back(
382             //    {{"@odata.id", "/redfish/v1/Systems/system/BIOS"}});
383             // aResp->res.jsonValue["Members@odata.count"] = members.size();
384         }
385         else
386         {
387             BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
388         }
389     }
390 
391     void doGet(crow::Response &res, const crow::Request &req,
392                const std::vector<std::string> &params) override
393     {
394         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
395         res.jsonValue["@odata.type"] =
396             "#SoftwareInventory.v1_1_0.SoftwareInventory";
397         res.jsonValue["@odata.context"] =
398             "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory";
399         res.jsonValue["Name"] = "Software Inventory";
400         res.jsonValue["Updateable"] = false;
401         res.jsonValue["Status"]["Health"] = "OK";
402         res.jsonValue["Status"]["HealthRollup"] = "OK";
403         res.jsonValue["Status"]["State"] = "Enabled";
404 
405         if (params.size() != 1)
406         {
407             messages::internalError(res);
408             res.end();
409             return;
410         }
411 
412         std::shared_ptr<std::string> swId =
413             std::make_shared<std::string>(params[0]);
414 
415         res.jsonValue["@odata.id"] =
416             "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
417 
418         crow::connections::systemBus->async_method_call(
419             [asyncResp, swId](
420                 const boost::system::error_code ec,
421                 const std::vector<std::pair<
422                     std::string, std::vector<std::pair<
423                                      std::string, std::vector<std::string>>>>>
424                     &subtree) {
425                 BMCWEB_LOG_DEBUG << "doGet callback...";
426                 if (ec)
427                 {
428                     messages::internalError(asyncResp->res);
429                     return;
430                 }
431 
432                 for (const std::pair<
433                          std::string,
434                          std::vector<
435                              std::pair<std::string, std::vector<std::string>>>>
436                          &obj : subtree)
437                 {
438                     if (boost::ends_with(obj.first, *swId) != true)
439                     {
440                         continue;
441                     }
442 
443                     if (obj.second.size() < 1)
444                     {
445                         continue;
446                     }
447 
448                     crow::connections::systemBus->async_method_call(
449                         [asyncResp,
450                          swId](const boost::system::error_code error_code,
451                                const boost::container::flat_map<
452                                    std::string, VariantType> &propertiesList) {
453                             if (error_code)
454                             {
455                                 messages::internalError(asyncResp->res);
456                                 return;
457                             }
458                             boost::container::flat_map<
459                                 std::string, VariantType>::const_iterator it =
460                                 propertiesList.find("Purpose");
461                             if (it == propertiesList.end())
462                             {
463                                 BMCWEB_LOG_DEBUG
464                                     << "Can't find property \"Purpose\"!";
465                                 messages::propertyMissing(asyncResp->res,
466                                                           "Purpose");
467                                 return;
468                             }
469                             const std::string *swInvPurpose =
470                                 std::get_if<std::string>(&it->second);
471                             if (swInvPurpose == nullptr)
472                             {
473                                 BMCWEB_LOG_DEBUG
474                                     << "wrong types for property\"Purpose\"!";
475                                 messages::propertyValueTypeError(asyncResp->res,
476                                                                  "", "Purpose");
477                                 return;
478                             }
479 
480                             BMCWEB_LOG_DEBUG << "swInvPurpose = "
481                                              << *swInvPurpose;
482                             it = propertiesList.find("Version");
483                             if (it == propertiesList.end())
484                             {
485                                 BMCWEB_LOG_DEBUG
486                                     << "Can't find property \"Version\"!";
487                                 messages::propertyMissing(asyncResp->res,
488                                                           "Version");
489                                 return;
490                             }
491 
492                             BMCWEB_LOG_DEBUG << "Version found!";
493 
494                             const std::string *version =
495                                 std::get_if<std::string>(&it->second);
496 
497                             if (version == nullptr)
498                             {
499                                 BMCWEB_LOG_DEBUG
500                                     << "Can't find property \"Version\"!";
501 
502                                 messages::propertyValueTypeError(asyncResp->res,
503                                                                  "", "Version");
504                                 return;
505                             }
506                             asyncResp->res.jsonValue["Version"] = *version;
507                             asyncResp->res.jsonValue["Id"] = *swId;
508 
509                             // swInvPurpose is of format:
510                             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
511                             // Translate this to "ABC update"
512                             size_t endDesc = swInvPurpose->rfind(".");
513                             if (endDesc == std::string::npos)
514                             {
515                                 messages::internalError(asyncResp->res);
516                                 return;
517                             }
518                             endDesc++;
519                             if (endDesc >= swInvPurpose->size())
520                             {
521                                 messages::internalError(asyncResp->res);
522                                 return;
523                             }
524 
525                             std::string formatDesc =
526                                 swInvPurpose->substr(endDesc);
527                             asyncResp->res.jsonValue["Description"] =
528                                 formatDesc + " update";
529                             getRelatedItems(asyncResp, *swInvPurpose);
530                         },
531                         obj.second[0].first, obj.first,
532                         "org.freedesktop.DBus.Properties", "GetAll",
533                         "xyz.openbmc_project.Software.Version");
534                 }
535             },
536             "xyz.openbmc_project.ObjectMapper",
537             "/xyz/openbmc_project/object_mapper",
538             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
539             "/xyz/openbmc_project/software", int32_t(1),
540             std::array<const char *, 1>{
541                 "xyz.openbmc_project.Software.Version"});
542     }
543 };
544 
545 } // namespace redfish
546