blob: a746239a35f9c675722bef8564dd9a798db80d4f [file] [log] [blame]
James Feist46229572020-02-19 15:11:58 -08001/*
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
Zhenfei Taidecde9e2020-05-05 13:39:07 -070020#include <boost/asio.hpp>
James Feist46229572020-02-19 15:11:58 -080021#include <boost/container/flat_map.hpp>
22#include <chrono>
James Feiste5d50062020-05-11 17:29:00 -070023#include <task_messages.hpp>
James Feist46229572020-02-19 15:11:58 -080024#include <variant>
25
26namespace redfish
27{
28
29namespace task
30{
31constexpr size_t maxTaskCount = 100; // arbitrary limit
32
33static std::deque<std::shared_ptr<struct TaskData>> tasks;
34
James Feist32898ce2020-03-10 16:16:52 -070035constexpr bool completed = true;
36
James Feistfe306722020-03-12 16:32:08 -070037struct Payload
38{
39 Payload(const crow::Request &req) :
40 targetUri(req.url), httpOperation(req.methodString()),
41 httpHeaders(nlohmann::json::array())
42
43 {
44 using field_ns = boost::beast::http::field;
45 constexpr const std::array<boost::beast::http::field, 7>
46 headerWhitelist = {field_ns::accept, field_ns::accept_encoding,
47 field_ns::user_agent, field_ns::host,
48 field_ns::connection, field_ns::content_length,
49 field_ns::upgrade};
50
51 jsonBody = nlohmann::json::parse(req.body, nullptr, false);
52 if (jsonBody.is_discarded())
53 {
54 jsonBody = nullptr;
55 }
56
57 for (const auto &field : req.fields)
58 {
59 if (std::find(headerWhitelist.begin(), headerWhitelist.end(),
60 field.name()) == headerWhitelist.end())
61 {
62 continue;
63 }
64 std::string header;
65 header.reserve(field.name_string().size() + 2 +
66 field.value().size());
67 header += field.name_string();
68 header += ": ";
69 header += field.value();
70 httpHeaders.emplace_back(std::move(header));
71 }
72 }
73 Payload() = delete;
74
75 std::string targetUri;
76 std::string httpOperation;
77 nlohmann::json httpHeaders;
78 nlohmann::json jsonBody;
79};
80
81inline void to_json(nlohmann::json &j, const Payload &p)
82{
83 j = {{"TargetUri", p.targetUri},
84 {"HttpOperation", p.httpOperation},
85 {"HttpHeaders", p.httpHeaders},
86 {"JsonBody", p.jsonBody.dump()}};
87}
88
James Feist46229572020-02-19 15:11:58 -080089struct TaskData : std::enable_shared_from_this<TaskData>
90{
91 private:
92 TaskData(std::function<bool(boost::system::error_code,
93 sdbusplus::message::message &,
94 const std::shared_ptr<TaskData> &)> &&handler,
95 const std::string &match, size_t idx) :
96 callback(std::move(handler)),
97 matchStr(match), index(idx),
98 startTime(std::chrono::system_clock::to_time_t(
99 std::chrono::system_clock::now())),
100 status("OK"), state("Running"), messages(nlohmann::json::array()),
101 timer(crow::connections::systemBus->get_io_context())
102
103 {
104 }
105 TaskData() = delete;
106
107 public:
108 static std::shared_ptr<TaskData> &createTask(
109 std::function<bool(boost::system::error_code,
110 sdbusplus::message::message &,
111 const std::shared_ptr<TaskData> &)> &&handler,
112 const std::string &match)
113 {
114 static size_t lastTask = 0;
115 struct MakeSharedHelper : public TaskData
116 {
117 MakeSharedHelper(
118 std::function<bool(
119 boost::system::error_code, sdbusplus::message::message &,
120 const std::shared_ptr<TaskData> &)> &&handler,
121 const std::string &match, size_t idx) :
122 TaskData(std::move(handler), match, idx)
123 {
124 }
125 };
126
127 if (tasks.size() >= maxTaskCount)
128 {
129 auto &last = tasks.front();
130
131 // destroy all references
132 last->timer.cancel();
133 last->match.reset();
134 tasks.pop_front();
135 }
136
137 return tasks.emplace_back(std::make_shared<MakeSharedHelper>(
138 std::move(handler), match, lastTask++));
139 }
140
141 void populateResp(crow::Response &res, size_t retryAfterSeconds = 30)
142 {
143 if (!endTime)
144 {
145 res.result(boost::beast::http::status::accepted);
146 std::string strIdx = std::to_string(index);
147 std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx;
148 res.jsonValue = {{"@odata.id", uri},
149 {"@odata.type", "#Task.v1_4_3.Task"},
150 {"Id", strIdx},
151 {"TaskState", state},
152 {"TaskStatus", status}};
153 res.addHeader(boost::beast::http::field::location,
154 uri + "/Monitor");
155 res.addHeader(boost::beast::http::field::retry_after,
156 std::to_string(retryAfterSeconds));
157 }
158 else if (!gave204)
159 {
160 res.result(boost::beast::http::status::no_content);
161 gave204 = true;
162 }
163 }
164
165 void finishTask(void)
166 {
167 endTime = std::chrono::system_clock::to_time_t(
168 std::chrono::system_clock::now());
169 }
170
James Feistfd9ab9e2020-05-19 13:48:07 -0700171 void extendTimer(const std::chrono::seconds &timeout)
James Feist46229572020-02-19 15:11:58 -0800172 {
James Feist46229572020-02-19 15:11:58 -0800173 timer.expires_after(timeout);
174 timer.async_wait(
175 [self = shared_from_this()](boost::system::error_code ec) {
176 if (ec == boost::asio::error::operation_aborted)
177 {
178 return; // completed succesfully
179 }
180 if (!ec)
181 {
182 // change ec to error as timer expired
183 ec = boost::asio::error::operation_aborted;
184 }
185 self->match.reset();
186 sdbusplus::message::message msg;
187 self->finishTask();
188 self->state = "Cancelled";
189 self->status = "Warning";
James Feiste5d50062020-05-11 17:29:00 -0700190 self->messages.emplace_back(
191 messages::taskAborted(std::to_string(self->index)));
James Feist46229572020-02-19 15:11:58 -0800192 self->callback(ec, msg, self);
193 });
James Feistfd9ab9e2020-05-19 13:48:07 -0700194 }
195
196 void startTimer(const std::chrono::seconds &timeout)
197 {
198 if (match)
199 {
200 return;
201 }
202 match = std::make_unique<sdbusplus::bus::match::match>(
203 static_cast<sdbusplus::bus::bus &>(*crow::connections::systemBus),
204 matchStr,
205 [self = shared_from_this()](sdbusplus::message::message &message) {
206 boost::system::error_code ec;
207
208 // callback to return True if callback is done, callback needs
209 // to update status itself if needed
210 if (self->callback(ec, message, self) == task::completed)
211 {
212 self->timer.cancel();
213 self->finishTask();
214
215 // reset the match after the callback was successful
216 boost::asio::post(
217 crow::connections::systemBus->get_io_context(),
218 [self] { self->match.reset(); });
219 return;
220 }
221 });
222
223 extendTimer(timeout);
James Feiste5d50062020-05-11 17:29:00 -0700224 messages.emplace_back(messages::taskStarted(std::to_string(index)));
James Feist46229572020-02-19 15:11:58 -0800225 }
226
227 std::function<bool(boost::system::error_code, sdbusplus::message::message &,
228 const std::shared_ptr<TaskData> &)>
229 callback;
230 std::string matchStr;
231 size_t index;
232 time_t startTime;
233 std::string status;
234 std::string state;
235 nlohmann::json messages;
236 boost::asio::steady_timer timer;
237 std::unique_ptr<sdbusplus::bus::match::match> match;
238 std::optional<time_t> endTime;
James Feistfe306722020-03-12 16:32:08 -0700239 std::optional<Payload> payload;
James Feist46229572020-02-19 15:11:58 -0800240 bool gave204 = false;
241};
242
243} // namespace task
244
245class TaskMonitor : public Node
246{
247 public:
248 TaskMonitor(CrowApp &app) :
Gunnar Mills7af91512020-04-14 22:16:57 -0500249 Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor/",
James Feist46229572020-02-19 15:11:58 -0800250 std::string())
251 {
252 entityPrivileges = {
253 {boost::beast::http::verb::get, {{"Login"}}},
254 {boost::beast::http::verb::head, {{"Login"}}},
255 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
256 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
257 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
258 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
259 }
260
261 private:
262 void doGet(crow::Response &res, const crow::Request &req,
263 const std::vector<std::string> &params) override
264 {
265 auto asyncResp = std::make_shared<AsyncResp>(res);
266 if (params.size() != 1)
267 {
268 messages::internalError(asyncResp->res);
269 return;
270 }
271
272 const std::string &strParam = params[0];
273 auto find = std::find_if(
274 task::tasks.begin(), task::tasks.end(),
275 [&strParam](const std::shared_ptr<task::TaskData> &task) {
276 if (!task)
277 {
278 return false;
279 }
280
281 // we compare against the string version as on failure strtoul
282 // returns 0
283 return std::to_string(task->index) == strParam;
284 });
285
286 if (find == task::tasks.end())
287 {
288 messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
289 return;
290 }
291 std::shared_ptr<task::TaskData> &ptr = *find;
292 // monitor expires after 204
293 if (ptr->gave204)
294 {
295 messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
296 return;
297 }
298 ptr->populateResp(asyncResp->res);
299 }
300};
301
302class Task : public Node
303{
304 public:
305 Task(CrowApp &app) :
Gunnar Mills7af91512020-04-14 22:16:57 -0500306 Node((app), "/redfish/v1/TaskService/Tasks/<str>/", std::string())
James Feist46229572020-02-19 15:11:58 -0800307 {
308 entityPrivileges = {
309 {boost::beast::http::verb::get, {{"Login"}}},
310 {boost::beast::http::verb::head, {{"Login"}}},
311 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
312 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
313 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
314 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
315 }
316
317 private:
318 void doGet(crow::Response &res, const crow::Request &req,
319 const std::vector<std::string> &params) override
320 {
321 auto asyncResp = std::make_shared<AsyncResp>(res);
322 if (params.size() != 1)
323 {
324 messages::internalError(asyncResp->res);
325 return;
326 }
327
328 const std::string &strParam = params[0];
329 auto find = std::find_if(
330 task::tasks.begin(), task::tasks.end(),
331 [&strParam](const std::shared_ptr<task::TaskData> &task) {
332 if (!task)
333 {
334 return false;
335 }
336
337 // we compare against the string version as on failure strtoul
338 // returns 0
339 return std::to_string(task->index) == strParam;
340 });
341
342 if (find == task::tasks.end())
343 {
344 messages::resourceNotFound(asyncResp->res, "Tasks", strParam);
345 return;
346 }
347
348 std::shared_ptr<task::TaskData> &ptr = *find;
349
350 asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
351 asyncResp->res.jsonValue["Id"] = strParam;
352 asyncResp->res.jsonValue["Name"] = "Task " + strParam;
353 asyncResp->res.jsonValue["TaskState"] = ptr->state;
354 asyncResp->res.jsonValue["StartTime"] =
355 crow::utility::getDateTime(ptr->startTime);
356 if (ptr->endTime)
357 {
358 asyncResp->res.jsonValue["EndTime"] =
359 crow::utility::getDateTime(*(ptr->endTime));
360 }
361 asyncResp->res.jsonValue["TaskStatus"] = ptr->status;
362 asyncResp->res.jsonValue["Messages"] = ptr->messages;
363 asyncResp->res.jsonValue["@odata.id"] =
364 "/redfish/v1/TaskService/Tasks/" + strParam;
365 if (!ptr->gave204)
366 {
367 asyncResp->res.jsonValue["TaskMonitor"] =
368 "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
369 }
James Feistfe306722020-03-12 16:32:08 -0700370 if (ptr->payload)
371 {
372 asyncResp->res.jsonValue["Payload"] = *(ptr->payload);
373 }
James Feist46229572020-02-19 15:11:58 -0800374 }
375};
376
377class TaskCollection : public Node
378{
379 public:
Gunnar Mills7af91512020-04-14 22:16:57 -0500380 TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks/")
James Feist46229572020-02-19 15:11:58 -0800381 {
382 entityPrivileges = {
383 {boost::beast::http::verb::get, {{"Login"}}},
384 {boost::beast::http::verb::head, {{"Login"}}},
385 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
386 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
387 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
388 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
389 }
390
391 private:
392 void doGet(crow::Response &res, const crow::Request &req,
393 const std::vector<std::string> &params) override
394 {
395 auto asyncResp = std::make_shared<AsyncResp>(res);
396 asyncResp->res.jsonValue["@odata.type"] =
397 "#TaskCollection.TaskCollection";
398 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks";
399 asyncResp->res.jsonValue["Name"] = "Task Collection";
400 asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size();
401 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
402 members = nlohmann::json::array();
403
404 for (const std::shared_ptr<task::TaskData> &task : task::tasks)
405 {
406 if (task == nullptr)
407 {
408 continue; // shouldn't be possible
409 }
410 members.emplace_back(
411 nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" +
412 std::to_string(task->index)}});
413 }
414 }
415};
416
417class TaskService : public Node
418{
419 public:
Gunnar Mills7af91512020-04-14 22:16:57 -0500420 TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService/")
James Feist46229572020-02-19 15:11:58 -0800421 {
422 entityPrivileges = {
423 {boost::beast::http::verb::get, {{"Login"}}},
424 {boost::beast::http::verb::head, {{"Login"}}},
425 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
426 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
427 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
428 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
429 }
430
431 private:
432 void doGet(crow::Response &res, const crow::Request &req,
433 const std::vector<std::string> &params) override
434 {
435 auto asyncResp = std::make_shared<AsyncResp>(res);
436 asyncResp->res.jsonValue["@odata.type"] =
437 "#TaskService.v1_1_4.TaskService";
438 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService";
439 asyncResp->res.jsonValue["Name"] = "Task Service";
440 asyncResp->res.jsonValue["Id"] = "TaskService";
441 asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
442 asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest";
443
444 // todo: if we enable events, change this to true
445 asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false;
446
447 auto health = std::make_shared<HealthPopulate>(asyncResp);
448 health->populate();
449 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
450 asyncResp->res.jsonValue["ServiceEnabled"] = true;
451 asyncResp->res.jsonValue["Tasks"] = {
452 {"@odata.id", "/redfish/v1/TaskService/Tasks"}};
453 }
454};
455
456} // namespace redfish