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