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