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