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->messages.emplace_back(messages::internalError()); 162 self->callback(ec, msg, self); 163 }); 164 } 165 166 std::function<bool(boost::system::error_code, sdbusplus::message::message &, 167 const std::shared_ptr<TaskData> &)> 168 callback; 169 std::string matchStr; 170 size_t index; 171 time_t startTime; 172 std::string status; 173 std::string state; 174 nlohmann::json messages; 175 boost::asio::steady_timer timer; 176 std::unique_ptr<sdbusplus::bus::match::match> match; 177 std::optional<time_t> endTime; 178 bool gave204 = false; 179 }; 180 181 } // namespace task 182 183 class TaskMonitor : public Node 184 { 185 public: 186 TaskMonitor(CrowApp &app) : 187 Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor", 188 std::string()) 189 { 190 entityPrivileges = { 191 {boost::beast::http::verb::get, {{"Login"}}}, 192 {boost::beast::http::verb::head, {{"Login"}}}, 193 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 194 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 195 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 196 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 197 } 198 199 private: 200 void doGet(crow::Response &res, const crow::Request &req, 201 const std::vector<std::string> ¶ms) override 202 { 203 auto asyncResp = std::make_shared<AsyncResp>(res); 204 if (params.size() != 1) 205 { 206 messages::internalError(asyncResp->res); 207 return; 208 } 209 210 const std::string &strParam = params[0]; 211 auto find = std::find_if( 212 task::tasks.begin(), task::tasks.end(), 213 [&strParam](const std::shared_ptr<task::TaskData> &task) { 214 if (!task) 215 { 216 return false; 217 } 218 219 // we compare against the string version as on failure strtoul 220 // returns 0 221 return std::to_string(task->index) == strParam; 222 }); 223 224 if (find == task::tasks.end()) 225 { 226 messages::resourceNotFound(asyncResp->res, "Monitor", strParam); 227 return; 228 } 229 std::shared_ptr<task::TaskData> &ptr = *find; 230 // monitor expires after 204 231 if (ptr->gave204) 232 { 233 messages::resourceNotFound(asyncResp->res, "Monitor", strParam); 234 return; 235 } 236 ptr->populateResp(asyncResp->res); 237 } 238 }; 239 240 class Task : public Node 241 { 242 public: 243 Task(CrowApp &app) : 244 Node((app), "/redfish/v1/TaskService/Tasks/<str>", std::string()) 245 { 246 entityPrivileges = { 247 {boost::beast::http::verb::get, {{"Login"}}}, 248 {boost::beast::http::verb::head, {{"Login"}}}, 249 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 250 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 251 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 252 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 253 } 254 255 private: 256 void doGet(crow::Response &res, const crow::Request &req, 257 const std::vector<std::string> ¶ms) override 258 { 259 auto asyncResp = std::make_shared<AsyncResp>(res); 260 if (params.size() != 1) 261 { 262 messages::internalError(asyncResp->res); 263 return; 264 } 265 266 const std::string &strParam = params[0]; 267 auto find = std::find_if( 268 task::tasks.begin(), task::tasks.end(), 269 [&strParam](const std::shared_ptr<task::TaskData> &task) { 270 if (!task) 271 { 272 return false; 273 } 274 275 // we compare against the string version as on failure strtoul 276 // returns 0 277 return std::to_string(task->index) == strParam; 278 }); 279 280 if (find == task::tasks.end()) 281 { 282 messages::resourceNotFound(asyncResp->res, "Tasks", strParam); 283 return; 284 } 285 286 std::shared_ptr<task::TaskData> &ptr = *find; 287 288 asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task"; 289 asyncResp->res.jsonValue["Id"] = strParam; 290 asyncResp->res.jsonValue["Name"] = "Task " + strParam; 291 asyncResp->res.jsonValue["TaskState"] = ptr->state; 292 asyncResp->res.jsonValue["StartTime"] = 293 crow::utility::getDateTime(ptr->startTime); 294 if (ptr->endTime) 295 { 296 asyncResp->res.jsonValue["EndTime"] = 297 crow::utility::getDateTime(*(ptr->endTime)); 298 } 299 asyncResp->res.jsonValue["TaskStatus"] = ptr->status; 300 asyncResp->res.jsonValue["Messages"] = ptr->messages; 301 asyncResp->res.jsonValue["@odata.id"] = 302 "/redfish/v1/TaskService/Tasks/" + strParam; 303 if (!ptr->gave204) 304 { 305 asyncResp->res.jsonValue["TaskMonitor"] = 306 "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor"; 307 } 308 } 309 }; 310 311 class TaskCollection : public Node 312 { 313 public: 314 TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks") 315 { 316 entityPrivileges = { 317 {boost::beast::http::verb::get, {{"Login"}}}, 318 {boost::beast::http::verb::head, {{"Login"}}}, 319 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 320 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 321 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 322 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 323 } 324 325 private: 326 void doGet(crow::Response &res, const crow::Request &req, 327 const std::vector<std::string> ¶ms) override 328 { 329 auto asyncResp = std::make_shared<AsyncResp>(res); 330 asyncResp->res.jsonValue["@odata.type"] = 331 "#TaskCollection.TaskCollection"; 332 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks"; 333 asyncResp->res.jsonValue["Name"] = "Task Collection"; 334 asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size(); 335 nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 336 members = nlohmann::json::array(); 337 338 for (const std::shared_ptr<task::TaskData> &task : task::tasks) 339 { 340 if (task == nullptr) 341 { 342 continue; // shouldn't be possible 343 } 344 members.emplace_back( 345 nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" + 346 std::to_string(task->index)}}); 347 } 348 } 349 }; 350 351 class TaskService : public Node 352 { 353 public: 354 TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService") 355 { 356 entityPrivileges = { 357 {boost::beast::http::verb::get, {{"Login"}}}, 358 {boost::beast::http::verb::head, {{"Login"}}}, 359 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 360 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 361 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 362 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 363 } 364 365 private: 366 void doGet(crow::Response &res, const crow::Request &req, 367 const std::vector<std::string> ¶ms) override 368 { 369 auto asyncResp = std::make_shared<AsyncResp>(res); 370 asyncResp->res.jsonValue["@odata.type"] = 371 "#TaskService.v1_1_4.TaskService"; 372 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService"; 373 asyncResp->res.jsonValue["Name"] = "Task Service"; 374 asyncResp->res.jsonValue["Id"] = "TaskService"; 375 asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 376 asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest"; 377 378 // todo: if we enable events, change this to true 379 asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false; 380 381 auto health = std::make_shared<HealthPopulate>(asyncResp); 382 health->populate(); 383 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 384 asyncResp->res.jsonValue["ServiceEnabled"] = true; 385 asyncResp->res.jsonValue["Tasks"] = { 386 {"@odata.id", "/redfish/v1/TaskService/Tasks"}}; 387 } 388 }; 389 390 } // namespace redfish 391