blob: cbae67b10951987777cfc3a31de92bafd623ee28 [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
171 void startTimer(const std::chrono::seconds &timeout)
172 {
173 match = std::make_unique<sdbusplus::bus::match::match>(
174 static_cast<sdbusplus::bus::bus &>(*crow::connections::systemBus),
175 matchStr,
176 [self = shared_from_this()](sdbusplus::message::message &message) {
177 boost::system::error_code ec;
178
James Feist46229572020-02-19 15:11:58 -0800179 // callback to return True if callback is done, callback needs
180 // to update status itself if needed
James Feist32898ce2020-03-10 16:16:52 -0700181 if (self->callback(ec, message, self) == task::completed)
James Feist46229572020-02-19 15:11:58 -0800182 {
183 self->timer.cancel();
184 self->finishTask();
185
186 // reset the match after the callback was successful
Zhenfei Taidecde9e2020-05-05 13:39:07 -0700187 boost::asio::post(
188 crow::connections::systemBus->get_io_context(),
James Feist46229572020-02-19 15:11:58 -0800189 [self] { self->match.reset(); });
190 return;
191 }
James Feist46229572020-02-19 15:11:58 -0800192 });
193 timer.expires_after(timeout);
194 timer.async_wait(
195 [self = shared_from_this()](boost::system::error_code ec) {
196 if (ec == boost::asio::error::operation_aborted)
197 {
198 return; // completed succesfully
199 }
200 if (!ec)
201 {
202 // change ec to error as timer expired
203 ec = boost::asio::error::operation_aborted;
204 }
205 self->match.reset();
206 sdbusplus::message::message msg;
207 self->finishTask();
208 self->state = "Cancelled";
209 self->status = "Warning";
James Feiste5d50062020-05-11 17:29:00 -0700210 self->messages.emplace_back(
211 messages::taskAborted(std::to_string(self->index)));
James Feist46229572020-02-19 15:11:58 -0800212 self->callback(ec, msg, self);
213 });
James Feiste5d50062020-05-11 17:29:00 -0700214 messages.emplace_back(messages::taskStarted(std::to_string(index)));
James Feist46229572020-02-19 15:11:58 -0800215 }
216
217 std::function<bool(boost::system::error_code, sdbusplus::message::message &,
218 const std::shared_ptr<TaskData> &)>
219 callback;
220 std::string matchStr;
221 size_t index;
222 time_t startTime;
223 std::string status;
224 std::string state;
225 nlohmann::json messages;
226 boost::asio::steady_timer timer;
227 std::unique_ptr<sdbusplus::bus::match::match> match;
228 std::optional<time_t> endTime;
James Feistfe306722020-03-12 16:32:08 -0700229 std::optional<Payload> payload;
James Feist46229572020-02-19 15:11:58 -0800230 bool gave204 = false;
231};
232
233} // namespace task
234
235class TaskMonitor : public Node
236{
237 public:
238 TaskMonitor(CrowApp &app) :
Gunnar Mills7af91512020-04-14 22:16:57 -0500239 Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor/",
James Feist46229572020-02-19 15:11:58 -0800240 std::string())
241 {
242 entityPrivileges = {
243 {boost::beast::http::verb::get, {{"Login"}}},
244 {boost::beast::http::verb::head, {{"Login"}}},
245 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
246 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
247 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
248 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
249 }
250
251 private:
252 void doGet(crow::Response &res, const crow::Request &req,
253 const std::vector<std::string> &params) override
254 {
255 auto asyncResp = std::make_shared<AsyncResp>(res);
256 if (params.size() != 1)
257 {
258 messages::internalError(asyncResp->res);
259 return;
260 }
261
262 const std::string &strParam = params[0];
263 auto find = std::find_if(
264 task::tasks.begin(), task::tasks.end(),
265 [&strParam](const std::shared_ptr<task::TaskData> &task) {
266 if (!task)
267 {
268 return false;
269 }
270
271 // we compare against the string version as on failure strtoul
272 // returns 0
273 return std::to_string(task->index) == strParam;
274 });
275
276 if (find == task::tasks.end())
277 {
278 messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
279 return;
280 }
281 std::shared_ptr<task::TaskData> &ptr = *find;
282 // monitor expires after 204
283 if (ptr->gave204)
284 {
285 messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
286 return;
287 }
288 ptr->populateResp(asyncResp->res);
289 }
290};
291
292class Task : public Node
293{
294 public:
295 Task(CrowApp &app) :
Gunnar Mills7af91512020-04-14 22:16:57 -0500296 Node((app), "/redfish/v1/TaskService/Tasks/<str>/", std::string())
James Feist46229572020-02-19 15:11:58 -0800297 {
298 entityPrivileges = {
299 {boost::beast::http::verb::get, {{"Login"}}},
300 {boost::beast::http::verb::head, {{"Login"}}},
301 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
302 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
303 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
304 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
305 }
306
307 private:
308 void doGet(crow::Response &res, const crow::Request &req,
309 const std::vector<std::string> &params) override
310 {
311 auto asyncResp = std::make_shared<AsyncResp>(res);
312 if (params.size() != 1)
313 {
314 messages::internalError(asyncResp->res);
315 return;
316 }
317
318 const std::string &strParam = params[0];
319 auto find = std::find_if(
320 task::tasks.begin(), task::tasks.end(),
321 [&strParam](const std::shared_ptr<task::TaskData> &task) {
322 if (!task)
323 {
324 return false;
325 }
326
327 // we compare against the string version as on failure strtoul
328 // returns 0
329 return std::to_string(task->index) == strParam;
330 });
331
332 if (find == task::tasks.end())
333 {
334 messages::resourceNotFound(asyncResp->res, "Tasks", strParam);
335 return;
336 }
337
338 std::shared_ptr<task::TaskData> &ptr = *find;
339
340 asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
341 asyncResp->res.jsonValue["Id"] = strParam;
342 asyncResp->res.jsonValue["Name"] = "Task " + strParam;
343 asyncResp->res.jsonValue["TaskState"] = ptr->state;
344 asyncResp->res.jsonValue["StartTime"] =
345 crow::utility::getDateTime(ptr->startTime);
346 if (ptr->endTime)
347 {
348 asyncResp->res.jsonValue["EndTime"] =
349 crow::utility::getDateTime(*(ptr->endTime));
350 }
351 asyncResp->res.jsonValue["TaskStatus"] = ptr->status;
352 asyncResp->res.jsonValue["Messages"] = ptr->messages;
353 asyncResp->res.jsonValue["@odata.id"] =
354 "/redfish/v1/TaskService/Tasks/" + strParam;
355 if (!ptr->gave204)
356 {
357 asyncResp->res.jsonValue["TaskMonitor"] =
358 "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
359 }
James Feistfe306722020-03-12 16:32:08 -0700360 if (ptr->payload)
361 {
362 asyncResp->res.jsonValue["Payload"] = *(ptr->payload);
363 }
James Feist46229572020-02-19 15:11:58 -0800364 }
365};
366
367class TaskCollection : public Node
368{
369 public:
Gunnar Mills7af91512020-04-14 22:16:57 -0500370 TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks/")
James Feist46229572020-02-19 15:11:58 -0800371 {
372 entityPrivileges = {
373 {boost::beast::http::verb::get, {{"Login"}}},
374 {boost::beast::http::verb::head, {{"Login"}}},
375 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
376 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
377 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
378 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
379 }
380
381 private:
382 void doGet(crow::Response &res, const crow::Request &req,
383 const std::vector<std::string> &params) override
384 {
385 auto asyncResp = std::make_shared<AsyncResp>(res);
386 asyncResp->res.jsonValue["@odata.type"] =
387 "#TaskCollection.TaskCollection";
388 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks";
389 asyncResp->res.jsonValue["Name"] = "Task Collection";
390 asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size();
391 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
392 members = nlohmann::json::array();
393
394 for (const std::shared_ptr<task::TaskData> &task : task::tasks)
395 {
396 if (task == nullptr)
397 {
398 continue; // shouldn't be possible
399 }
400 members.emplace_back(
401 nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" +
402 std::to_string(task->index)}});
403 }
404 }
405};
406
407class TaskService : public Node
408{
409 public:
Gunnar Mills7af91512020-04-14 22:16:57 -0500410 TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService/")
James Feist46229572020-02-19 15:11:58 -0800411 {
412 entityPrivileges = {
413 {boost::beast::http::verb::get, {{"Login"}}},
414 {boost::beast::http::verb::head, {{"Login"}}},
415 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
416 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
417 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
418 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
419 }
420
421 private:
422 void doGet(crow::Response &res, const crow::Request &req,
423 const std::vector<std::string> &params) override
424 {
425 auto asyncResp = std::make_shared<AsyncResp>(res);
426 asyncResp->res.jsonValue["@odata.type"] =
427 "#TaskService.v1_1_4.TaskService";
428 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService";
429 asyncResp->res.jsonValue["Name"] = "Task Service";
430 asyncResp->res.jsonValue["Id"] = "TaskService";
431 asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
432 asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest";
433
434 // todo: if we enable events, change this to true
435 asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false;
436
437 auto health = std::make_shared<HealthPopulate>(asyncResp);
438 health->populate();
439 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
440 asyncResp->res.jsonValue["ServiceEnabled"] = true;
441 asyncResp->res.jsonValue["Tasks"] = {
442 {"@odata.id", "/redfish/v1/TaskService/Tasks"}};
443 }
444};
445
446} // namespace redfish