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