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