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