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