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