xref: /openbmc/bmcweb/features/redfish/lib/update_service.hpp (revision fa1a5a38551bd1b9f04ad2d4f9fea2e5ade5cc4c)
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 doPatch(crow::Response &res, const crow::Request &req,
206                  const std::vector<std::string> &params) override
207     {
208         BMCWEB_LOG_DEBUG << "doPatch...";
209 
210         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
211         std::string applyTime;
212 
213         if (!json_util::readJson(req, res, "ApplyTime", applyTime))
214         {
215             return;
216         }
217 
218         if ((applyTime == "Immediate") || (applyTime == "OnReset"))
219         {
220             std::string applyTimeNewVal;
221             if (applyTime == "Immediate")
222             {
223                 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime."
224                                   "RequestedApplyTimes.Immediate";
225             }
226             else
227             {
228                 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime."
229                                   "RequestedApplyTimes.OnReset";
230             }
231 
232             // Set the requested image apply time value
233             crow::connections::systemBus->async_method_call(
234                 [asyncResp](const boost::system::error_code ec) {
235                     if (ec)
236                     {
237                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
238                         messages::internalError(asyncResp->res);
239                         return;
240                     }
241                     messages::success(asyncResp->res);
242                 },
243                 "xyz.openbmc_project.Settings",
244                 "/xyz/openbmc_project/software/apply_time",
245                 "org.freedesktop.DBus.Properties", "Set",
246                 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
247                 std::variant<std::string>{applyTimeNewVal});
248         }
249         else
250         {
251             BMCWEB_LOG_INFO << "ApplyTime value is not in the list of "
252                                "acceptable values";
253             messages::propertyValueNotInList(asyncResp->res, applyTime,
254                                              "ApplyTime");
255         }
256     }
257 
258     void doPost(crow::Response &res, const crow::Request &req,
259                 const std::vector<std::string> &params) override
260     {
261         BMCWEB_LOG_DEBUG << "doPost...";
262 
263         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
264 
265         // Setup callback for when new software detected
266         monitorForSoftwareAvailable(asyncResp, req);
267 
268         std::string filepath(
269             "/tmp/images/" +
270             boost::uuids::to_string(boost::uuids::random_generator()()));
271         BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
272         std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
273                                         std::ofstream::trunc);
274         out << req.body;
275         out.close();
276         BMCWEB_LOG_DEBUG << "file upload complete!!";
277     }
278 };
279 
280 class SoftwareInventoryCollection : public Node
281 {
282   public:
283     template <typename CrowApp>
284     SoftwareInventoryCollection(CrowApp &app) :
285         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/")
286     {
287         entityPrivileges = {
288             {boost::beast::http::verb::get, {{"Login"}}},
289             {boost::beast::http::verb::head, {{"Login"}}},
290             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
291             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
292             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
293             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
294     }
295 
296   private:
297     void doGet(crow::Response &res, const crow::Request &req,
298                const std::vector<std::string> &params) override
299     {
300         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
301         res.jsonValue["@odata.type"] =
302             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
303         res.jsonValue["@odata.id"] =
304             "/redfish/v1/UpdateService/FirmwareInventory";
305         res.jsonValue["@odata.context"] =
306             "/redfish/v1/"
307             "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection";
308         res.jsonValue["Name"] = "Software Inventory Collection";
309 
310         crow::connections::systemBus->async_method_call(
311             [asyncResp](
312                 const boost::system::error_code ec,
313                 const std::vector<std::pair<
314                     std::string, std::vector<std::pair<
315                                      std::string, std::vector<std::string>>>>>
316                     &subtree) {
317                 if (ec)
318                 {
319                     messages::internalError(asyncResp->res);
320                     return;
321                 }
322                 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
323                 asyncResp->res.jsonValue["Members@odata.count"] = 0;
324 
325                 for (auto &obj : subtree)
326                 {
327                     const std::vector<
328                         std::pair<std::string, std::vector<std::string>>>
329                         &connections = obj.second;
330 
331                     // if can't parse fw id then return
332                     std::size_t idPos;
333                     if ((idPos = obj.first.rfind("/")) == std::string::npos)
334                     {
335                         messages::internalError(asyncResp->res);
336                         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
337                         return;
338                     }
339                     std::string swId = obj.first.substr(idPos + 1);
340 
341                     for (auto &conn : connections)
342                     {
343                         const std::string &connectionName = conn.first;
344                         BMCWEB_LOG_DEBUG << "connectionName = "
345                                          << connectionName;
346                         BMCWEB_LOG_DEBUG << "obj.first = " << obj.first;
347 
348                         crow::connections::systemBus->async_method_call(
349                             [asyncResp,
350                              swId](const boost::system::error_code error_code,
351                                    const VariantType &activation) {
352                                 BMCWEB_LOG_DEBUG
353                                     << "safe returned in lambda function";
354                                 if (error_code)
355                                 {
356                                     messages::internalError(asyncResp->res);
357                                     return;
358                                 }
359 
360                                 const std::string *swActivationStatus =
361                                     std::get_if<std::string>(&activation);
362                                 if (swActivationStatus == nullptr)
363                                 {
364                                     messages::internalError(asyncResp->res);
365                                     return;
366                                 }
367                                 if (swActivationStatus != nullptr &&
368                                     *swActivationStatus !=
369                                         "xyz.openbmc_project.Software."
370                                         "Activation."
371                                         "Activations.Active")
372                                 {
373                                     // The activation status of this software is
374                                     // not currently active, so does not need to
375                                     // be listed in the response
376                                     return;
377                                 }
378                                 nlohmann::json &members =
379                                     asyncResp->res.jsonValue["Members"];
380                                 members.push_back(
381                                     {{"@odata.id", "/redfish/v1/UpdateService/"
382                                                    "FirmwareInventory/" +
383                                                        swId}});
384                                 asyncResp->res
385                                     .jsonValue["Members@odata.count"] =
386                                     members.size();
387                             },
388                             connectionName, obj.first,
389                             "org.freedesktop.DBus.Properties", "Get",
390                             "xyz.openbmc_project.Software.Activation",
391                             "Activation");
392                     }
393                 }
394             },
395             "xyz.openbmc_project.ObjectMapper",
396             "/xyz/openbmc_project/object_mapper",
397             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
398             "/xyz/openbmc_project/software", int32_t(1),
399             std::array<const char *, 1>{
400                 "xyz.openbmc_project.Software.Version"});
401     }
402 };
403 
404 class SoftwareInventory : public Node
405 {
406   public:
407     template <typename CrowApp>
408     SoftwareInventory(CrowApp &app) :
409         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/",
410              std::string())
411     {
412         entityPrivileges = {
413             {boost::beast::http::verb::get, {{"Login"}}},
414             {boost::beast::http::verb::head, {{"Login"}}},
415             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
416             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
417             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
418             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
419     }
420 
421   private:
422     /* Fill related item links (i.e. bmc, bios) in for inventory */
423     static void getRelatedItems(std::shared_ptr<AsyncResp> aResp,
424                                 const std::string &purpose)
425     {
426         if (purpose == fw_util::bmcPurpose)
427         {
428             nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
429             members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}});
430             aResp->res.jsonValue["Members@odata.count"] = members.size();
431         }
432         else if (purpose == fw_util::biosPurpose)
433         {
434             // TODO(geissonator) Need BIOS schema support added for this
435             //                   to be valid
436             // nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
437             // members.push_back(
438             //    {{"@odata.id", "/redfish/v1/Systems/system/BIOS"}});
439             // aResp->res.jsonValue["Members@odata.count"] = members.size();
440         }
441         else
442         {
443             BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
444         }
445     }
446 
447     void doGet(crow::Response &res, const crow::Request &req,
448                const std::vector<std::string> &params) override
449     {
450         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
451         res.jsonValue["@odata.type"] =
452             "#SoftwareInventory.v1_1_0.SoftwareInventory";
453         res.jsonValue["@odata.context"] =
454             "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory";
455         res.jsonValue["Name"] = "Software Inventory";
456         res.jsonValue["Updateable"] = false;
457         res.jsonValue["Status"]["Health"] = "OK";
458         res.jsonValue["Status"]["HealthRollup"] = "OK";
459         res.jsonValue["Status"]["State"] = "Enabled";
460 
461         if (params.size() != 1)
462         {
463             messages::internalError(res);
464             res.end();
465             return;
466         }
467 
468         std::shared_ptr<std::string> swId =
469             std::make_shared<std::string>(params[0]);
470 
471         res.jsonValue["@odata.id"] =
472             "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
473 
474         crow::connections::systemBus->async_method_call(
475             [asyncResp, swId](
476                 const boost::system::error_code ec,
477                 const std::vector<std::pair<
478                     std::string, std::vector<std::pair<
479                                      std::string, std::vector<std::string>>>>>
480                     &subtree) {
481                 BMCWEB_LOG_DEBUG << "doGet callback...";
482                 if (ec)
483                 {
484                     messages::internalError(asyncResp->res);
485                     return;
486                 }
487 
488                 for (const std::pair<
489                          std::string,
490                          std::vector<
491                              std::pair<std::string, std::vector<std::string>>>>
492                          &obj : subtree)
493                 {
494                     if (boost::ends_with(obj.first, *swId) != true)
495                     {
496                         continue;
497                     }
498 
499                     if (obj.second.size() < 1)
500                     {
501                         continue;
502                     }
503 
504                     crow::connections::systemBus->async_method_call(
505                         [asyncResp,
506                          swId](const boost::system::error_code error_code,
507                                const boost::container::flat_map<
508                                    std::string, VariantType> &propertiesList) {
509                             if (error_code)
510                             {
511                                 messages::internalError(asyncResp->res);
512                                 return;
513                             }
514                             boost::container::flat_map<
515                                 std::string, VariantType>::const_iterator it =
516                                 propertiesList.find("Purpose");
517                             if (it == propertiesList.end())
518                             {
519                                 BMCWEB_LOG_DEBUG
520                                     << "Can't find property \"Purpose\"!";
521                                 messages::propertyMissing(asyncResp->res,
522                                                           "Purpose");
523                                 return;
524                             }
525                             const std::string *swInvPurpose =
526                                 std::get_if<std::string>(&it->second);
527                             if (swInvPurpose == nullptr)
528                             {
529                                 BMCWEB_LOG_DEBUG
530                                     << "wrong types for property\"Purpose\"!";
531                                 messages::propertyValueTypeError(asyncResp->res,
532                                                                  "", "Purpose");
533                                 return;
534                             }
535 
536                             BMCWEB_LOG_DEBUG << "swInvPurpose = "
537                                              << *swInvPurpose;
538                             it = propertiesList.find("Version");
539                             if (it == propertiesList.end())
540                             {
541                                 BMCWEB_LOG_DEBUG
542                                     << "Can't find property \"Version\"!";
543                                 messages::propertyMissing(asyncResp->res,
544                                                           "Version");
545                                 return;
546                             }
547 
548                             BMCWEB_LOG_DEBUG << "Version found!";
549 
550                             const std::string *version =
551                                 std::get_if<std::string>(&it->second);
552 
553                             if (version == nullptr)
554                             {
555                                 BMCWEB_LOG_DEBUG
556                                     << "Can't find property \"Version\"!";
557 
558                                 messages::propertyValueTypeError(asyncResp->res,
559                                                                  "", "Version");
560                                 return;
561                             }
562                             asyncResp->res.jsonValue["Version"] = *version;
563                             asyncResp->res.jsonValue["Id"] = *swId;
564 
565                             // swInvPurpose is of format:
566                             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
567                             // Translate this to "ABC update"
568                             size_t endDesc = swInvPurpose->rfind(".");
569                             if (endDesc == std::string::npos)
570                             {
571                                 messages::internalError(asyncResp->res);
572                                 return;
573                             }
574                             endDesc++;
575                             if (endDesc >= swInvPurpose->size())
576                             {
577                                 messages::internalError(asyncResp->res);
578                                 return;
579                             }
580 
581                             std::string formatDesc =
582                                 swInvPurpose->substr(endDesc);
583                             asyncResp->res.jsonValue["Description"] =
584                                 formatDesc + " update";
585                             getRelatedItems(asyncResp, *swInvPurpose);
586                         },
587                         obj.second[0].first, obj.first,
588                         "org.freedesktop.DBus.Properties", "GetAll",
589                         "xyz.openbmc_project.Software.Version");
590                 }
591             },
592             "xyz.openbmc_project.ObjectMapper",
593             "/xyz/openbmc_project/object_mapper",
594             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
595             "/xyz/openbmc_project/software", int32_t(1),
596             std::array<const char *, 1>{
597                 "xyz.openbmc_project.Software.Version"});
598     }
599 };
600 
601 } // namespace redfish
602