xref: /openbmc/bmcweb/features/redfish/lib/task.hpp (revision 462295771281bbd9901c688b8684b6c6930322c3)
1 /*
2 // Copyright (c) 2020 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 #include <chrono>
22 #include <variant>
23 
24 namespace redfish
25 {
26 
27 namespace task
28 {
29 constexpr size_t maxTaskCount = 100; // arbitrary limit
30 
31 static std::deque<std::shared_ptr<struct TaskData>> tasks;
32 
33 struct TaskData : std::enable_shared_from_this<TaskData>
34 {
35   private:
36     TaskData(std::function<bool(boost::system::error_code,
37                                 sdbusplus::message::message &,
38                                 const std::shared_ptr<TaskData> &)> &&handler,
39              const std::string &match, size_t idx) :
40         callback(std::move(handler)),
41         matchStr(match), index(idx),
42         startTime(std::chrono::system_clock::to_time_t(
43             std::chrono::system_clock::now())),
44         status("OK"), state("Running"), messages(nlohmann::json::array()),
45         timer(crow::connections::systemBus->get_io_context())
46 
47     {
48     }
49     TaskData() = delete;
50 
51   public:
52     static std::shared_ptr<TaskData> &createTask(
53         std::function<bool(boost::system::error_code,
54                            sdbusplus::message::message &,
55                            const std::shared_ptr<TaskData> &)> &&handler,
56         const std::string &match)
57     {
58         static size_t lastTask = 0;
59         struct MakeSharedHelper : public TaskData
60         {
61             MakeSharedHelper(
62                 std::function<bool(
63                     boost::system::error_code, sdbusplus::message::message &,
64                     const std::shared_ptr<TaskData> &)> &&handler,
65                 const std::string &match, size_t idx) :
66                 TaskData(std::move(handler), match, idx)
67             {
68             }
69         };
70 
71         if (tasks.size() >= maxTaskCount)
72         {
73             auto &last = tasks.front();
74 
75             // destroy all references
76             last->timer.cancel();
77             last->match.reset();
78             tasks.pop_front();
79         }
80 
81         return tasks.emplace_back(std::make_shared<MakeSharedHelper>(
82             std::move(handler), match, lastTask++));
83     }
84 
85     void populateResp(crow::Response &res, size_t retryAfterSeconds = 30)
86     {
87         if (!endTime)
88         {
89             res.result(boost::beast::http::status::accepted);
90             std::string strIdx = std::to_string(index);
91             std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx;
92             res.jsonValue = {{"@odata.id", uri},
93                              {"@odata.type", "#Task.v1_4_3.Task"},
94                              {"Id", strIdx},
95                              {"TaskState", state},
96                              {"TaskStatus", status}};
97             res.addHeader(boost::beast::http::field::location,
98                           uri + "/Monitor");
99             res.addHeader(boost::beast::http::field::retry_after,
100                           std::to_string(retryAfterSeconds));
101         }
102         else if (!gave204)
103         {
104             res.result(boost::beast::http::status::no_content);
105             gave204 = true;
106         }
107     }
108 
109     void finishTask(void)
110     {
111         endTime = std::chrono::system_clock::to_time_t(
112             std::chrono::system_clock::now());
113     }
114 
115     void startTimer(const std::chrono::seconds &timeout)
116     {
117         match = std::make_unique<sdbusplus::bus::match::match>(
118             static_cast<sdbusplus::bus::bus &>(*crow::connections::systemBus),
119             matchStr,
120             [self = shared_from_this()](sdbusplus::message::message &message) {
121                 boost::system::error_code ec;
122 
123                 // set to complete before callback incase user wants a different
124                 // status
125                 self->state = "Completed";
126 
127                 // callback to return True if callback is done, callback needs
128                 // to update status itself if needed
129                 if (self->callback(ec, message, self))
130                 {
131                     self->timer.cancel();
132                     self->finishTask();
133 
134                     // reset the match after the callback was successful
135                     crow::connections::systemBus->get_io_context().post(
136                         [self] { self->match.reset(); });
137                     return;
138                 }
139 
140                 // set back to running if callback returns false to keep
141                 // callback alive
142                 self->state = "Running";
143             });
144         timer.expires_after(timeout);
145         timer.async_wait(
146             [self = shared_from_this()](boost::system::error_code ec) {
147                 if (ec == boost::asio::error::operation_aborted)
148                 {
149                     return; // completed succesfully
150                 }
151                 if (!ec)
152                 {
153                     // change ec to error as timer expired
154                     ec = boost::asio::error::operation_aborted;
155                 }
156                 self->match.reset();
157                 sdbusplus::message::message msg;
158                 self->finishTask();
159                 self->state = "Cancelled";
160                 self->status = "Warning";
161                 self->callback(ec, msg, self);
162             });
163     }
164 
165     std::function<bool(boost::system::error_code, sdbusplus::message::message &,
166                        const std::shared_ptr<TaskData> &)>
167         callback;
168     std::string matchStr;
169     size_t index;
170     time_t startTime;
171     std::string status;
172     std::string state;
173     nlohmann::json messages;
174     boost::asio::steady_timer timer;
175     std::unique_ptr<sdbusplus::bus::match::match> match;
176     std::optional<time_t> endTime;
177     bool gave204 = false;
178 };
179 
180 } // namespace task
181 
182 class TaskMonitor : public Node
183 {
184   public:
185     TaskMonitor(CrowApp &app) :
186         Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor",
187              std::string())
188     {
189         entityPrivileges = {
190             {boost::beast::http::verb::get, {{"Login"}}},
191             {boost::beast::http::verb::head, {{"Login"}}},
192             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
193             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
194             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
195             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
196     }
197 
198   private:
199     void doGet(crow::Response &res, const crow::Request &req,
200                const std::vector<std::string> &params) override
201     {
202         auto asyncResp = std::make_shared<AsyncResp>(res);
203         if (params.size() != 1)
204         {
205             messages::internalError(asyncResp->res);
206             return;
207         }
208 
209         const std::string &strParam = params[0];
210         auto find = std::find_if(
211             task::tasks.begin(), task::tasks.end(),
212             [&strParam](const std::shared_ptr<task::TaskData> &task) {
213                 if (!task)
214                 {
215                     return false;
216                 }
217 
218                 // we compare against the string version as on failure strtoul
219                 // returns 0
220                 return std::to_string(task->index) == strParam;
221             });
222 
223         if (find == task::tasks.end())
224         {
225             messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
226             return;
227         }
228         std::shared_ptr<task::TaskData> &ptr = *find;
229         // monitor expires after 204
230         if (ptr->gave204)
231         {
232             messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
233             return;
234         }
235         ptr->populateResp(asyncResp->res);
236     }
237 };
238 
239 class Task : public Node
240 {
241   public:
242     Task(CrowApp &app) :
243         Node((app), "/redfish/v1/TaskService/Tasks/<str>", std::string())
244     {
245         entityPrivileges = {
246             {boost::beast::http::verb::get, {{"Login"}}},
247             {boost::beast::http::verb::head, {{"Login"}}},
248             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
249             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
250             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
251             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
252     }
253 
254   private:
255     void doGet(crow::Response &res, const crow::Request &req,
256                const std::vector<std::string> &params) override
257     {
258         auto asyncResp = std::make_shared<AsyncResp>(res);
259         if (params.size() != 1)
260         {
261             messages::internalError(asyncResp->res);
262             return;
263         }
264 
265         const std::string &strParam = params[0];
266         auto find = std::find_if(
267             task::tasks.begin(), task::tasks.end(),
268             [&strParam](const std::shared_ptr<task::TaskData> &task) {
269                 if (!task)
270                 {
271                     return false;
272                 }
273 
274                 // we compare against the string version as on failure strtoul
275                 // returns 0
276                 return std::to_string(task->index) == strParam;
277             });
278 
279         if (find == task::tasks.end())
280         {
281             messages::resourceNotFound(asyncResp->res, "Tasks", strParam);
282             return;
283         }
284 
285         std::shared_ptr<task::TaskData> &ptr = *find;
286 
287         asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
288         asyncResp->res.jsonValue["Id"] = strParam;
289         asyncResp->res.jsonValue["Name"] = "Task " + strParam;
290         asyncResp->res.jsonValue["TaskState"] = ptr->state;
291         asyncResp->res.jsonValue["StartTime"] =
292             crow::utility::getDateTime(ptr->startTime);
293         if (ptr->endTime)
294         {
295             asyncResp->res.jsonValue["EndTime"] =
296                 crow::utility::getDateTime(*(ptr->endTime));
297         }
298         asyncResp->res.jsonValue["TaskStatus"] = ptr->status;
299         asyncResp->res.jsonValue["Messages"] = ptr->messages;
300         asyncResp->res.jsonValue["@odata.id"] =
301             "/redfish/v1/TaskService/Tasks/" + strParam;
302         if (!ptr->gave204)
303         {
304             asyncResp->res.jsonValue["TaskMonitor"] =
305                 "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
306         }
307     }
308 };
309 
310 class TaskCollection : public Node
311 {
312   public:
313     TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks")
314     {
315         entityPrivileges = {
316             {boost::beast::http::verb::get, {{"Login"}}},
317             {boost::beast::http::verb::head, {{"Login"}}},
318             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
319             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
320             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
321             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
322     }
323 
324   private:
325     void doGet(crow::Response &res, const crow::Request &req,
326                const std::vector<std::string> &params) override
327     {
328         auto asyncResp = std::make_shared<AsyncResp>(res);
329         asyncResp->res.jsonValue["@odata.type"] =
330             "#TaskCollection.TaskCollection";
331         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks";
332         asyncResp->res.jsonValue["Name"] = "Task Collection";
333         asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size();
334         nlohmann::json &members = asyncResp->res.jsonValue["Members"];
335         members = nlohmann::json::array();
336 
337         for (const std::shared_ptr<task::TaskData> &task : task::tasks)
338         {
339             if (task == nullptr)
340             {
341                 continue; // shouldn't be possible
342             }
343             members.emplace_back(
344                 nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" +
345                                                  std::to_string(task->index)}});
346         }
347     }
348 };
349 
350 class TaskService : public Node
351 {
352   public:
353     TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService")
354     {
355         entityPrivileges = {
356             {boost::beast::http::verb::get, {{"Login"}}},
357             {boost::beast::http::verb::head, {{"Login"}}},
358             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
359             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
360             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
361             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
362     }
363 
364   private:
365     void doGet(crow::Response &res, const crow::Request &req,
366                const std::vector<std::string> &params) override
367     {
368         auto asyncResp = std::make_shared<AsyncResp>(res);
369         asyncResp->res.jsonValue["@odata.type"] =
370             "#TaskService.v1_1_4.TaskService";
371         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService";
372         asyncResp->res.jsonValue["Name"] = "Task Service";
373         asyncResp->res.jsonValue["Id"] = "TaskService";
374         asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
375         asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest";
376 
377         // todo: if we enable events, change this to true
378         asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false;
379 
380         auto health = std::make_shared<HealthPopulate>(asyncResp);
381         health->populate();
382         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
383         asyncResp->res.jsonValue["ServiceEnabled"] = true;
384         asyncResp->res.jsonValue["Tasks"] = {
385             {"@odata.id", "/redfish/v1/TaskService/Tasks"}};
386     }
387 };
388 
389 } // namespace redfish
390