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