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