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