146229577SJames Feist /* 246229577SJames Feist // Copyright (c) 2020 Intel Corporation 346229577SJames Feist // 446229577SJames Feist // Licensed under the Apache License, Version 2.0 (the "License"); 546229577SJames Feist // you may not use this file except in compliance with the License. 646229577SJames Feist // You may obtain a copy of the License at 746229577SJames Feist // 846229577SJames Feist // http://www.apache.org/licenses/LICENSE-2.0 946229577SJames Feist // 1046229577SJames Feist // Unless required by applicable law or agreed to in writing, software 1146229577SJames Feist // distributed under the License is distributed on an "AS IS" BASIS, 1246229577SJames Feist // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1346229577SJames Feist // See the License for the specific language governing permissions and 1446229577SJames Feist // limitations under the License. 1546229577SJames Feist */ 1646229577SJames Feist #pragma once 1746229577SJames Feist 1846229577SJames Feist #include "node.hpp" 1946229577SJames Feist 2046229577SJames Feist #include <boost/container/flat_map.hpp> 2146229577SJames Feist #include <chrono> 2246229577SJames Feist #include <variant> 2346229577SJames Feist 2446229577SJames Feist namespace redfish 2546229577SJames Feist { 2646229577SJames Feist 2746229577SJames Feist namespace task 2846229577SJames Feist { 2946229577SJames Feist constexpr size_t maxTaskCount = 100; // arbitrary limit 3046229577SJames Feist 3146229577SJames Feist static std::deque<std::shared_ptr<struct TaskData>> tasks; 3246229577SJames Feist 3346229577SJames Feist struct TaskData : std::enable_shared_from_this<TaskData> 3446229577SJames Feist { 3546229577SJames Feist private: 3646229577SJames Feist TaskData(std::function<bool(boost::system::error_code, 3746229577SJames Feist sdbusplus::message::message &, 3846229577SJames Feist const std::shared_ptr<TaskData> &)> &&handler, 3946229577SJames Feist const std::string &match, size_t idx) : 4046229577SJames Feist callback(std::move(handler)), 4146229577SJames Feist matchStr(match), index(idx), 4246229577SJames Feist startTime(std::chrono::system_clock::to_time_t( 4346229577SJames Feist std::chrono::system_clock::now())), 4446229577SJames Feist status("OK"), state("Running"), messages(nlohmann::json::array()), 4546229577SJames Feist timer(crow::connections::systemBus->get_io_context()) 4646229577SJames Feist 4746229577SJames Feist { 4846229577SJames Feist } 4946229577SJames Feist TaskData() = delete; 5046229577SJames Feist 5146229577SJames Feist public: 5246229577SJames Feist static std::shared_ptr<TaskData> &createTask( 5346229577SJames Feist std::function<bool(boost::system::error_code, 5446229577SJames Feist sdbusplus::message::message &, 5546229577SJames Feist const std::shared_ptr<TaskData> &)> &&handler, 5646229577SJames Feist const std::string &match) 5746229577SJames Feist { 5846229577SJames Feist static size_t lastTask = 0; 5946229577SJames Feist struct MakeSharedHelper : public TaskData 6046229577SJames Feist { 6146229577SJames Feist MakeSharedHelper( 6246229577SJames Feist std::function<bool( 6346229577SJames Feist boost::system::error_code, sdbusplus::message::message &, 6446229577SJames Feist const std::shared_ptr<TaskData> &)> &&handler, 6546229577SJames Feist const std::string &match, size_t idx) : 6646229577SJames Feist TaskData(std::move(handler), match, idx) 6746229577SJames Feist { 6846229577SJames Feist } 6946229577SJames Feist }; 7046229577SJames Feist 7146229577SJames Feist if (tasks.size() >= maxTaskCount) 7246229577SJames Feist { 7346229577SJames Feist auto &last = tasks.front(); 7446229577SJames Feist 7546229577SJames Feist // destroy all references 7646229577SJames Feist last->timer.cancel(); 7746229577SJames Feist last->match.reset(); 7846229577SJames Feist tasks.pop_front(); 7946229577SJames Feist } 8046229577SJames Feist 8146229577SJames Feist return tasks.emplace_back(std::make_shared<MakeSharedHelper>( 8246229577SJames Feist std::move(handler), match, lastTask++)); 8346229577SJames Feist } 8446229577SJames Feist 8546229577SJames Feist void populateResp(crow::Response &res, size_t retryAfterSeconds = 30) 8646229577SJames Feist { 8746229577SJames Feist if (!endTime) 8846229577SJames Feist { 8946229577SJames Feist res.result(boost::beast::http::status::accepted); 9046229577SJames Feist std::string strIdx = std::to_string(index); 9146229577SJames Feist std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx; 9246229577SJames Feist res.jsonValue = {{"@odata.id", uri}, 9346229577SJames Feist {"@odata.type", "#Task.v1_4_3.Task"}, 9446229577SJames Feist {"Id", strIdx}, 9546229577SJames Feist {"TaskState", state}, 9646229577SJames Feist {"TaskStatus", status}}; 9746229577SJames Feist res.addHeader(boost::beast::http::field::location, 9846229577SJames Feist uri + "/Monitor"); 9946229577SJames Feist res.addHeader(boost::beast::http::field::retry_after, 10046229577SJames Feist std::to_string(retryAfterSeconds)); 10146229577SJames Feist } 10246229577SJames Feist else if (!gave204) 10346229577SJames Feist { 10446229577SJames Feist res.result(boost::beast::http::status::no_content); 10546229577SJames Feist gave204 = true; 10646229577SJames Feist } 10746229577SJames Feist } 10846229577SJames Feist 10946229577SJames Feist void finishTask(void) 11046229577SJames Feist { 11146229577SJames Feist endTime = std::chrono::system_clock::to_time_t( 11246229577SJames Feist std::chrono::system_clock::now()); 11346229577SJames Feist } 11446229577SJames Feist 11546229577SJames Feist void startTimer(const std::chrono::seconds &timeout) 11646229577SJames Feist { 11746229577SJames Feist match = std::make_unique<sdbusplus::bus::match::match>( 11846229577SJames Feist static_cast<sdbusplus::bus::bus &>(*crow::connections::systemBus), 11946229577SJames Feist matchStr, 12046229577SJames Feist [self = shared_from_this()](sdbusplus::message::message &message) { 12146229577SJames Feist boost::system::error_code ec; 12246229577SJames Feist 12346229577SJames Feist // set to complete before callback incase user wants a different 12446229577SJames Feist // status 12546229577SJames Feist self->state = "Completed"; 12646229577SJames Feist 12746229577SJames Feist // callback to return True if callback is done, callback needs 12846229577SJames Feist // to update status itself if needed 12946229577SJames Feist if (self->callback(ec, message, self)) 13046229577SJames Feist { 13146229577SJames Feist self->timer.cancel(); 13246229577SJames Feist self->finishTask(); 13346229577SJames Feist 13446229577SJames Feist // reset the match after the callback was successful 13546229577SJames Feist crow::connections::systemBus->get_io_context().post( 13646229577SJames Feist [self] { self->match.reset(); }); 13746229577SJames Feist return; 13846229577SJames Feist } 13946229577SJames Feist 14046229577SJames Feist // set back to running if callback returns false to keep 14146229577SJames Feist // callback alive 14246229577SJames Feist self->state = "Running"; 14346229577SJames Feist }); 14446229577SJames Feist timer.expires_after(timeout); 14546229577SJames Feist timer.async_wait( 14646229577SJames Feist [self = shared_from_this()](boost::system::error_code ec) { 14746229577SJames Feist if (ec == boost::asio::error::operation_aborted) 14846229577SJames Feist { 14946229577SJames Feist return; // completed succesfully 15046229577SJames Feist } 15146229577SJames Feist if (!ec) 15246229577SJames Feist { 15346229577SJames Feist // change ec to error as timer expired 15446229577SJames Feist ec = boost::asio::error::operation_aborted; 15546229577SJames Feist } 15646229577SJames Feist self->match.reset(); 15746229577SJames Feist sdbusplus::message::message msg; 15846229577SJames Feist self->finishTask(); 15946229577SJames Feist self->state = "Cancelled"; 16046229577SJames Feist self->status = "Warning"; 161*363c2302SJames Feist self->messages.emplace_back(messages::internalError()); 16246229577SJames Feist self->callback(ec, msg, self); 16346229577SJames Feist }); 16446229577SJames Feist } 16546229577SJames Feist 16646229577SJames Feist std::function<bool(boost::system::error_code, sdbusplus::message::message &, 16746229577SJames Feist const std::shared_ptr<TaskData> &)> 16846229577SJames Feist callback; 16946229577SJames Feist std::string matchStr; 17046229577SJames Feist size_t index; 17146229577SJames Feist time_t startTime; 17246229577SJames Feist std::string status; 17346229577SJames Feist std::string state; 17446229577SJames Feist nlohmann::json messages; 17546229577SJames Feist boost::asio::steady_timer timer; 17646229577SJames Feist std::unique_ptr<sdbusplus::bus::match::match> match; 17746229577SJames Feist std::optional<time_t> endTime; 17846229577SJames Feist bool gave204 = false; 17946229577SJames Feist }; 18046229577SJames Feist 18146229577SJames Feist } // namespace task 18246229577SJames Feist 18346229577SJames Feist class TaskMonitor : public Node 18446229577SJames Feist { 18546229577SJames Feist public: 18646229577SJames Feist TaskMonitor(CrowApp &app) : 18746229577SJames Feist Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor", 18846229577SJames Feist std::string()) 18946229577SJames Feist { 19046229577SJames Feist entityPrivileges = { 19146229577SJames Feist {boost::beast::http::verb::get, {{"Login"}}}, 19246229577SJames Feist {boost::beast::http::verb::head, {{"Login"}}}, 19346229577SJames Feist {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 19446229577SJames Feist {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 19546229577SJames Feist {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 19646229577SJames Feist {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 19746229577SJames Feist } 19846229577SJames Feist 19946229577SJames Feist private: 20046229577SJames Feist void doGet(crow::Response &res, const crow::Request &req, 20146229577SJames Feist const std::vector<std::string> ¶ms) override 20246229577SJames Feist { 20346229577SJames Feist auto asyncResp = std::make_shared<AsyncResp>(res); 20446229577SJames Feist if (params.size() != 1) 20546229577SJames Feist { 20646229577SJames Feist messages::internalError(asyncResp->res); 20746229577SJames Feist return; 20846229577SJames Feist } 20946229577SJames Feist 21046229577SJames Feist const std::string &strParam = params[0]; 21146229577SJames Feist auto find = std::find_if( 21246229577SJames Feist task::tasks.begin(), task::tasks.end(), 21346229577SJames Feist [&strParam](const std::shared_ptr<task::TaskData> &task) { 21446229577SJames Feist if (!task) 21546229577SJames Feist { 21646229577SJames Feist return false; 21746229577SJames Feist } 21846229577SJames Feist 21946229577SJames Feist // we compare against the string version as on failure strtoul 22046229577SJames Feist // returns 0 22146229577SJames Feist return std::to_string(task->index) == strParam; 22246229577SJames Feist }); 22346229577SJames Feist 22446229577SJames Feist if (find == task::tasks.end()) 22546229577SJames Feist { 22646229577SJames Feist messages::resourceNotFound(asyncResp->res, "Monitor", strParam); 22746229577SJames Feist return; 22846229577SJames Feist } 22946229577SJames Feist std::shared_ptr<task::TaskData> &ptr = *find; 23046229577SJames Feist // monitor expires after 204 23146229577SJames Feist if (ptr->gave204) 23246229577SJames Feist { 23346229577SJames Feist messages::resourceNotFound(asyncResp->res, "Monitor", strParam); 23446229577SJames Feist return; 23546229577SJames Feist } 23646229577SJames Feist ptr->populateResp(asyncResp->res); 23746229577SJames Feist } 23846229577SJames Feist }; 23946229577SJames Feist 24046229577SJames Feist class Task : public Node 24146229577SJames Feist { 24246229577SJames Feist public: 24346229577SJames Feist Task(CrowApp &app) : 24446229577SJames Feist Node((app), "/redfish/v1/TaskService/Tasks/<str>", std::string()) 24546229577SJames Feist { 24646229577SJames Feist entityPrivileges = { 24746229577SJames Feist {boost::beast::http::verb::get, {{"Login"}}}, 24846229577SJames Feist {boost::beast::http::verb::head, {{"Login"}}}, 24946229577SJames Feist {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 25046229577SJames Feist {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 25146229577SJames Feist {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 25246229577SJames Feist {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 25346229577SJames Feist } 25446229577SJames Feist 25546229577SJames Feist private: 25646229577SJames Feist void doGet(crow::Response &res, const crow::Request &req, 25746229577SJames Feist const std::vector<std::string> ¶ms) override 25846229577SJames Feist { 25946229577SJames Feist auto asyncResp = std::make_shared<AsyncResp>(res); 26046229577SJames Feist if (params.size() != 1) 26146229577SJames Feist { 26246229577SJames Feist messages::internalError(asyncResp->res); 26346229577SJames Feist return; 26446229577SJames Feist } 26546229577SJames Feist 26646229577SJames Feist const std::string &strParam = params[0]; 26746229577SJames Feist auto find = std::find_if( 26846229577SJames Feist task::tasks.begin(), task::tasks.end(), 26946229577SJames Feist [&strParam](const std::shared_ptr<task::TaskData> &task) { 27046229577SJames Feist if (!task) 27146229577SJames Feist { 27246229577SJames Feist return false; 27346229577SJames Feist } 27446229577SJames Feist 27546229577SJames Feist // we compare against the string version as on failure strtoul 27646229577SJames Feist // returns 0 27746229577SJames Feist return std::to_string(task->index) == strParam; 27846229577SJames Feist }); 27946229577SJames Feist 28046229577SJames Feist if (find == task::tasks.end()) 28146229577SJames Feist { 28246229577SJames Feist messages::resourceNotFound(asyncResp->res, "Tasks", strParam); 28346229577SJames Feist return; 28446229577SJames Feist } 28546229577SJames Feist 28646229577SJames Feist std::shared_ptr<task::TaskData> &ptr = *find; 28746229577SJames Feist 28846229577SJames Feist asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task"; 28946229577SJames Feist asyncResp->res.jsonValue["Id"] = strParam; 29046229577SJames Feist asyncResp->res.jsonValue["Name"] = "Task " + strParam; 29146229577SJames Feist asyncResp->res.jsonValue["TaskState"] = ptr->state; 29246229577SJames Feist asyncResp->res.jsonValue["StartTime"] = 29346229577SJames Feist crow::utility::getDateTime(ptr->startTime); 29446229577SJames Feist if (ptr->endTime) 29546229577SJames Feist { 29646229577SJames Feist asyncResp->res.jsonValue["EndTime"] = 29746229577SJames Feist crow::utility::getDateTime(*(ptr->endTime)); 29846229577SJames Feist } 29946229577SJames Feist asyncResp->res.jsonValue["TaskStatus"] = ptr->status; 30046229577SJames Feist asyncResp->res.jsonValue["Messages"] = ptr->messages; 30146229577SJames Feist asyncResp->res.jsonValue["@odata.id"] = 30246229577SJames Feist "/redfish/v1/TaskService/Tasks/" + strParam; 30346229577SJames Feist if (!ptr->gave204) 30446229577SJames Feist { 30546229577SJames Feist asyncResp->res.jsonValue["TaskMonitor"] = 30646229577SJames Feist "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor"; 30746229577SJames Feist } 30846229577SJames Feist } 30946229577SJames Feist }; 31046229577SJames Feist 31146229577SJames Feist class TaskCollection : public Node 31246229577SJames Feist { 31346229577SJames Feist public: 31446229577SJames Feist TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks") 31546229577SJames Feist { 31646229577SJames Feist entityPrivileges = { 31746229577SJames Feist {boost::beast::http::verb::get, {{"Login"}}}, 31846229577SJames Feist {boost::beast::http::verb::head, {{"Login"}}}, 31946229577SJames Feist {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 32046229577SJames Feist {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 32146229577SJames Feist {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 32246229577SJames Feist {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 32346229577SJames Feist } 32446229577SJames Feist 32546229577SJames Feist private: 32646229577SJames Feist void doGet(crow::Response &res, const crow::Request &req, 32746229577SJames Feist const std::vector<std::string> ¶ms) override 32846229577SJames Feist { 32946229577SJames Feist auto asyncResp = std::make_shared<AsyncResp>(res); 33046229577SJames Feist asyncResp->res.jsonValue["@odata.type"] = 33146229577SJames Feist "#TaskCollection.TaskCollection"; 33246229577SJames Feist asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks"; 33346229577SJames Feist asyncResp->res.jsonValue["Name"] = "Task Collection"; 33446229577SJames Feist asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size(); 33546229577SJames Feist nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 33646229577SJames Feist members = nlohmann::json::array(); 33746229577SJames Feist 33846229577SJames Feist for (const std::shared_ptr<task::TaskData> &task : task::tasks) 33946229577SJames Feist { 34046229577SJames Feist if (task == nullptr) 34146229577SJames Feist { 34246229577SJames Feist continue; // shouldn't be possible 34346229577SJames Feist } 34446229577SJames Feist members.emplace_back( 34546229577SJames Feist nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" + 34646229577SJames Feist std::to_string(task->index)}}); 34746229577SJames Feist } 34846229577SJames Feist } 34946229577SJames Feist }; 35046229577SJames Feist 35146229577SJames Feist class TaskService : public Node 35246229577SJames Feist { 35346229577SJames Feist public: 35446229577SJames Feist TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService") 35546229577SJames Feist { 35646229577SJames Feist entityPrivileges = { 35746229577SJames Feist {boost::beast::http::verb::get, {{"Login"}}}, 35846229577SJames Feist {boost::beast::http::verb::head, {{"Login"}}}, 35946229577SJames Feist {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 36046229577SJames Feist {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 36146229577SJames Feist {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 36246229577SJames Feist {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 36346229577SJames Feist } 36446229577SJames Feist 36546229577SJames Feist private: 36646229577SJames Feist void doGet(crow::Response &res, const crow::Request &req, 36746229577SJames Feist const std::vector<std::string> ¶ms) override 36846229577SJames Feist { 36946229577SJames Feist auto asyncResp = std::make_shared<AsyncResp>(res); 37046229577SJames Feist asyncResp->res.jsonValue["@odata.type"] = 37146229577SJames Feist "#TaskService.v1_1_4.TaskService"; 37246229577SJames Feist asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService"; 37346229577SJames Feist asyncResp->res.jsonValue["Name"] = "Task Service"; 37446229577SJames Feist asyncResp->res.jsonValue["Id"] = "TaskService"; 37546229577SJames Feist asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow(); 37646229577SJames Feist asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest"; 37746229577SJames Feist 37846229577SJames Feist // todo: if we enable events, change this to true 37946229577SJames Feist asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false; 38046229577SJames Feist 38146229577SJames Feist auto health = std::make_shared<HealthPopulate>(asyncResp); 38246229577SJames Feist health->populate(); 38346229577SJames Feist asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 38446229577SJames Feist asyncResp->res.jsonValue["ServiceEnabled"] = true; 38546229577SJames Feist asyncResp->res.jsonValue["Tasks"] = { 38646229577SJames Feist {"@odata.id", "/redfish/v1/TaskService/Tasks"}}; 38746229577SJames Feist } 38846229577SJames Feist }; 38946229577SJames Feist 39046229577SJames Feist } // namespace redfish 391