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