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