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