xref: /openbmc/bmcweb/features/redfish/lib/task.hpp (revision 363c23022eb3fb0cde577405e8a084a2e819b642)
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->messages.emplace_back(messages::internalError());
162                 self->callback(ec, msg, self);
163             });
164     }
165 
166     std::function<bool(boost::system::error_code, sdbusplus::message::message &,
167                        const std::shared_ptr<TaskData> &)>
168         callback;
169     std::string matchStr;
170     size_t index;
171     time_t startTime;
172     std::string status;
173     std::string state;
174     nlohmann::json messages;
175     boost::asio::steady_timer timer;
176     std::unique_ptr<sdbusplus::bus::match::match> match;
177     std::optional<time_t> endTime;
178     bool gave204 = false;
179 };
180 
181 } // namespace task
182 
183 class TaskMonitor : public Node
184 {
185   public:
186     TaskMonitor(CrowApp &app) :
187         Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor",
188              std::string())
189     {
190         entityPrivileges = {
191             {boost::beast::http::verb::get, {{"Login"}}},
192             {boost::beast::http::verb::head, {{"Login"}}},
193             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
194             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
195             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
196             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
197     }
198 
199   private:
200     void doGet(crow::Response &res, const crow::Request &req,
201                const std::vector<std::string> &params) override
202     {
203         auto asyncResp = std::make_shared<AsyncResp>(res);
204         if (params.size() != 1)
205         {
206             messages::internalError(asyncResp->res);
207             return;
208         }
209 
210         const std::string &strParam = params[0];
211         auto find = std::find_if(
212             task::tasks.begin(), task::tasks.end(),
213             [&strParam](const std::shared_ptr<task::TaskData> &task) {
214                 if (!task)
215                 {
216                     return false;
217                 }
218 
219                 // we compare against the string version as on failure strtoul
220                 // returns 0
221                 return std::to_string(task->index) == strParam;
222             });
223 
224         if (find == task::tasks.end())
225         {
226             messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
227             return;
228         }
229         std::shared_ptr<task::TaskData> &ptr = *find;
230         // monitor expires after 204
231         if (ptr->gave204)
232         {
233             messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
234             return;
235         }
236         ptr->populateResp(asyncResp->res);
237     }
238 };
239 
240 class Task : public Node
241 {
242   public:
243     Task(CrowApp &app) :
244         Node((app), "/redfish/v1/TaskService/Tasks/<str>", std::string())
245     {
246         entityPrivileges = {
247             {boost::beast::http::verb::get, {{"Login"}}},
248             {boost::beast::http::verb::head, {{"Login"}}},
249             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
250             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
251             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
252             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
253     }
254 
255   private:
256     void doGet(crow::Response &res, const crow::Request &req,
257                const std::vector<std::string> &params) override
258     {
259         auto asyncResp = std::make_shared<AsyncResp>(res);
260         if (params.size() != 1)
261         {
262             messages::internalError(asyncResp->res);
263             return;
264         }
265 
266         const std::string &strParam = params[0];
267         auto find = std::find_if(
268             task::tasks.begin(), task::tasks.end(),
269             [&strParam](const std::shared_ptr<task::TaskData> &task) {
270                 if (!task)
271                 {
272                     return false;
273                 }
274 
275                 // we compare against the string version as on failure strtoul
276                 // returns 0
277                 return std::to_string(task->index) == strParam;
278             });
279 
280         if (find == task::tasks.end())
281         {
282             messages::resourceNotFound(asyncResp->res, "Tasks", strParam);
283             return;
284         }
285 
286         std::shared_ptr<task::TaskData> &ptr = *find;
287 
288         asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
289         asyncResp->res.jsonValue["Id"] = strParam;
290         asyncResp->res.jsonValue["Name"] = "Task " + strParam;
291         asyncResp->res.jsonValue["TaskState"] = ptr->state;
292         asyncResp->res.jsonValue["StartTime"] =
293             crow::utility::getDateTime(ptr->startTime);
294         if (ptr->endTime)
295         {
296             asyncResp->res.jsonValue["EndTime"] =
297                 crow::utility::getDateTime(*(ptr->endTime));
298         }
299         asyncResp->res.jsonValue["TaskStatus"] = ptr->status;
300         asyncResp->res.jsonValue["Messages"] = ptr->messages;
301         asyncResp->res.jsonValue["@odata.id"] =
302             "/redfish/v1/TaskService/Tasks/" + strParam;
303         if (!ptr->gave204)
304         {
305             asyncResp->res.jsonValue["TaskMonitor"] =
306                 "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
307         }
308     }
309 };
310 
311 class TaskCollection : public Node
312 {
313   public:
314     TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks")
315     {
316         entityPrivileges = {
317             {boost::beast::http::verb::get, {{"Login"}}},
318             {boost::beast::http::verb::head, {{"Login"}}},
319             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
320             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
321             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
322             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
323     }
324 
325   private:
326     void doGet(crow::Response &res, const crow::Request &req,
327                const std::vector<std::string> &params) override
328     {
329         auto asyncResp = std::make_shared<AsyncResp>(res);
330         asyncResp->res.jsonValue["@odata.type"] =
331             "#TaskCollection.TaskCollection";
332         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks";
333         asyncResp->res.jsonValue["Name"] = "Task Collection";
334         asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size();
335         nlohmann::json &members = asyncResp->res.jsonValue["Members"];
336         members = nlohmann::json::array();
337 
338         for (const std::shared_ptr<task::TaskData> &task : task::tasks)
339         {
340             if (task == nullptr)
341             {
342                 continue; // shouldn't be possible
343             }
344             members.emplace_back(
345                 nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" +
346                                                  std::to_string(task->index)}});
347         }
348     }
349 };
350 
351 class TaskService : public Node
352 {
353   public:
354     TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService")
355     {
356         entityPrivileges = {
357             {boost::beast::http::verb::get, {{"Login"}}},
358             {boost::beast::http::verb::head, {{"Login"}}},
359             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
360             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
361             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
362             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
363     }
364 
365   private:
366     void doGet(crow::Response &res, const crow::Request &req,
367                const std::vector<std::string> &params) override
368     {
369         auto asyncResp = std::make_shared<AsyncResp>(res);
370         asyncResp->res.jsonValue["@odata.type"] =
371             "#TaskService.v1_1_4.TaskService";
372         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService";
373         asyncResp->res.jsonValue["Name"] = "Task Service";
374         asyncResp->res.jsonValue["Id"] = "TaskService";
375         asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
376         asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest";
377 
378         // todo: if we enable events, change this to true
379         asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false;
380 
381         auto health = std::make_shared<HealthPopulate>(asyncResp);
382         health->populate();
383         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
384         asyncResp->res.jsonValue["ServiceEnabled"] = true;
385         asyncResp->res.jsonValue["Tasks"] = {
386             {"@odata.id", "/redfish/v1/TaskService/Tasks"}};
387     }
388 };
389 
390 } // namespace redfish
391