summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Feist <james.feist@linux.intel.com>2020-02-20 02:11:58 +0300
committerJames Feist <james.feist@linux.intel.com>2020-03-02 19:56:00 +0300
commit462295771281bbd9901c688b8684b6c6930322c3 (patch)
treea69b887604c11311d51b46d22beff00b2cb627bf
parentf723d7332bbdd7b0d4fbe4aa730b63dfd8db7eff (diff)
downloadbmcweb-462295771281bbd9901c688b8684b6c6930322c3.tar.xz
Add TaskService
This adds tasks service to Redfish and creates an example for crashdump. The TaskData object creates tasks that can be updated based on d-bus matches. It also has a configurable timeout using timers. Task Monitor uses these task objects to reply with a 202 until the async task is done, then a 204 when it is either failed or completed. Messages support will come in future commit. Tested: Validator passed, wrote script to poll monitor, verified that got 202 with location header and retry-after set correctly, then 204, then 404. Change-Id: I109e671baa1c1eeff1a11ae578e7361bf6ef9f14 Signed-off-by: James Feist <james.feist@linux.intel.com>
-rw-r--r--redfish-core/include/redfish.hpp6
-rw-r--r--redfish-core/lib/log_services.hpp52
-rw-r--r--redfish-core/lib/service_root.hpp1
-rw-r--r--redfish-core/lib/task.hpp389
4 files changed, 426 insertions, 22 deletions
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index b1fd820f14..cba2882a0c 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -33,6 +33,7 @@
#include "../lib/service_root.hpp"
#include "../lib/storage.hpp"
#include "../lib/systems.hpp"
+#include "../lib/task.hpp"
#include "../lib/thermal.hpp"
#include "../lib/update_service.hpp"
#ifdef BMCWEB_ENABLE_VM_NBDPROXY
@@ -165,6 +166,11 @@ class RedfishService
nodes.emplace_back(std::make_unique<SensorCollection>(app));
nodes.emplace_back(std::make_unique<Sensor>(app));
+ nodes.emplace_back(std::make_unique<TaskMonitor>(app));
+ nodes.emplace_back(std::make_unique<TaskService>(app));
+ nodes.emplace_back(std::make_unique<TaskCollection>(app));
+ nodes.emplace_back(std::make_unique<Task>(app));
+
for (const auto& node : nodes)
{
node->initPrivileges();
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index aede3661b3..2f065eca1d 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -19,6 +19,7 @@
#include "registries.hpp"
#include "registries/base_message_registry.hpp"
#include "registries/openbmc_message_registry.hpp"
+#include "task.hpp"
#include <systemd/sd-journal.h>
@@ -1849,30 +1850,37 @@ class OnDemandCrashdump : public Node
{
std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
- auto generateonDemandLogCallback =
- [asyncResp](const boost::system::error_code ec,
- const std::string &resp) {
- if (ec)
+ auto generateonDemandLogCallback = [asyncResp](
+ const boost::system::error_code
+ ec,
+ const std::string &resp) {
+ if (ec)
+ {
+ if (ec.value() == boost::system::errc::operation_not_supported)
{
- if (ec.value() ==
- boost::system::errc::operation_not_supported)
- {
- messages::resourceInStandby(asyncResp->res);
- }
- else if (ec.value() ==
- boost::system::errc::device_or_resource_busy)
- {
- messages::serviceTemporarilyUnavailable(asyncResp->res,
- "60");
- }
- else
- {
- messages::internalError(asyncResp->res);
- }
- return;
+ messages::resourceInStandby(asyncResp->res);
}
- asyncResp->res.result(boost::beast::http::status::no_content);
- };
+ else if (ec.value() ==
+ boost::system::errc::device_or_resource_busy)
+ {
+ messages::serviceTemporarilyUnavailable(asyncResp->res,
+ "60");
+ }
+ else
+ {
+ messages::internalError(asyncResp->res);
+ }
+ return;
+ }
+ std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
+ [](boost::system::error_code, sdbusplus::message::message &,
+ const std::shared_ptr<task::TaskData> &) { return true; },
+ "type='signal',interface='org.freedesktop.DBus.Properties',"
+ "member='PropertiesChanged',arg0namespace='com.intel."
+ "crashdump'");
+ task->startTimer(std::chrono::minutes(5));
+ task->populateResp(asyncResp->res);
+ };
crow::connections::systemBus->async_method_call(
std::move(generateonDemandLogCallback), crashdumpObject,
crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog");
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 318639d515..1f4343e9a4 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -66,6 +66,7 @@ class ServiceRoot : public Node
res.jsonValue["UUID"] = uuid;
res.jsonValue["CertificateService"] = {
{"@odata.id", "/redfish/v1/CertificateService"}};
+ res.jsonValue["Tasks"] = {{"@odata.id", "/redfish/v1/TaskService"}};
res.end();
}
diff --git a/redfish-core/lib/task.hpp b/redfish-core/lib/task.hpp
new file mode 100644
index 0000000000..4540c81bf3
--- /dev/null
+++ b/redfish-core/lib/task.hpp
@@ -0,0 +1,389 @@
+/*
+// Copyright (c) 2020 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include "node.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <variant>
+
+namespace redfish
+{
+
+namespace task
+{
+constexpr size_t maxTaskCount = 100; // arbitrary limit
+
+static std::deque<std::shared_ptr<struct TaskData>> tasks;
+
+struct TaskData : std::enable_shared_from_this<TaskData>
+{
+ private:
+ TaskData(std::function<bool(boost::system::error_code,
+ sdbusplus::message::message &,
+ const std::shared_ptr<TaskData> &)> &&handler,
+ const std::string &match, size_t idx) :
+ callback(std::move(handler)),
+ matchStr(match), index(idx),
+ startTime(std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now())),
+ status("OK"), state("Running"), messages(nlohmann::json::array()),
+ timer(crow::connections::systemBus->get_io_context())
+
+ {
+ }
+ TaskData() = delete;
+
+ public:
+ static std::shared_ptr<TaskData> &createTask(
+ std::function<bool(boost::system::error_code,
+ sdbusplus::message::message &,
+ const std::shared_ptr<TaskData> &)> &&handler,
+ const std::string &match)
+ {
+ static size_t lastTask = 0;
+ struct MakeSharedHelper : public TaskData
+ {
+ MakeSharedHelper(
+ std::function<bool(
+ boost::system::error_code, sdbusplus::message::message &,
+ const std::shared_ptr<TaskData> &)> &&handler,
+ const std::string &match, size_t idx) :
+ TaskData(std::move(handler), match, idx)
+ {
+ }
+ };
+
+ if (tasks.size() >= maxTaskCount)
+ {
+ auto &last = tasks.front();
+
+ // destroy all references
+ last->timer.cancel();
+ last->match.reset();
+ tasks.pop_front();
+ }
+
+ return tasks.emplace_back(std::make_shared<MakeSharedHelper>(
+ std::move(handler), match, lastTask++));
+ }
+
+ void populateResp(crow::Response &res, size_t retryAfterSeconds = 30)
+ {
+ if (!endTime)
+ {
+ res.result(boost::beast::http::status::accepted);
+ std::string strIdx = std::to_string(index);
+ std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx;
+ res.jsonValue = {{"@odata.id", uri},
+ {"@odata.type", "#Task.v1_4_3.Task"},
+ {"Id", strIdx},
+ {"TaskState", state},
+ {"TaskStatus", status}};
+ res.addHeader(boost::beast::http::field::location,
+ uri + "/Monitor");
+ res.addHeader(boost::beast::http::field::retry_after,
+ std::to_string(retryAfterSeconds));
+ }
+ else if (!gave204)
+ {
+ res.result(boost::beast::http::status::no_content);
+ gave204 = true;
+ }
+ }
+
+ void finishTask(void)
+ {
+ endTime = std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now());
+ }
+
+ void startTimer(const std::chrono::seconds &timeout)
+ {
+ match = std::make_unique<sdbusplus::bus::match::match>(
+ static_cast<sdbusplus::bus::bus &>(*crow::connections::systemBus),
+ matchStr,
+ [self = shared_from_this()](sdbusplus::message::message &message) {
+ boost::system::error_code ec;
+
+ // set to complete before callback incase user wants a different
+ // status
+ self->state = "Completed";
+
+ // callback to return True if callback is done, callback needs
+ // to update status itself if needed
+ if (self->callback(ec, message, self))
+ {
+ self->timer.cancel();
+ self->finishTask();
+
+ // reset the match after the callback was successful
+ crow::connections::systemBus->get_io_context().post(
+ [self] { self->match.reset(); });
+ return;
+ }
+
+ // set back to running if callback returns false to keep
+ // callback alive
+ self->state = "Running";
+ });
+ timer.expires_after(timeout);
+ timer.async_wait(
+ [self = shared_from_this()](boost::system::error_code ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ return; // completed succesfully
+ }
+ if (!ec)
+ {
+ // change ec to error as timer expired
+ ec = boost::asio::error::operation_aborted;
+ }
+ self->match.reset();
+ sdbusplus::message::message msg;
+ self->finishTask();
+ self->state = "Cancelled";
+ self->status = "Warning";
+ self->callback(ec, msg, self);
+ });
+ }
+
+ std::function<bool(boost::system::error_code, sdbusplus::message::message &,
+ const std::shared_ptr<TaskData> &)>
+ callback;
+ std::string matchStr;
+ size_t index;
+ time_t startTime;
+ std::string status;
+ std::string state;
+ nlohmann::json messages;
+ boost::asio::steady_timer timer;
+ std::unique_ptr<sdbusplus::bus::match::match> match;
+ std::optional<time_t> endTime;
+ bool gave204 = false;
+};
+
+} // namespace task
+
+class TaskMonitor : public Node
+{
+ public:
+ TaskMonitor(CrowApp &app) :
+ Node((app), "/redfish/v1/TaskService/Tasks/<str>/Monitor",
+ std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ if (params.size() != 1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ const std::string &strParam = params[0];
+ auto find = std::find_if(
+ task::tasks.begin(), task::tasks.end(),
+ [&strParam](const std::shared_ptr<task::TaskData> &task) {
+ if (!task)
+ {
+ return false;
+ }
+
+ // we compare against the string version as on failure strtoul
+ // returns 0
+ return std::to_string(task->index) == strParam;
+ });
+
+ if (find == task::tasks.end())
+ {
+ messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
+ return;
+ }
+ std::shared_ptr<task::TaskData> &ptr = *find;
+ // monitor expires after 204
+ if (ptr->gave204)
+ {
+ messages::resourceNotFound(asyncResp->res, "Monitor", strParam);
+ return;
+ }
+ ptr->populateResp(asyncResp->res);
+ }
+};
+
+class Task : public Node
+{
+ public:
+ Task(CrowApp &app) :
+ Node((app), "/redfish/v1/TaskService/Tasks/<str>", std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ if (params.size() != 1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ const std::string &strParam = params[0];
+ auto find = std::find_if(
+ task::tasks.begin(), task::tasks.end(),
+ [&strParam](const std::shared_ptr<task::TaskData> &task) {
+ if (!task)
+ {
+ return false;
+ }
+
+ // we compare against the string version as on failure strtoul
+ // returns 0
+ return std::to_string(task->index) == strParam;
+ });
+
+ if (find == task::tasks.end())
+ {
+ messages::resourceNotFound(asyncResp->res, "Tasks", strParam);
+ return;
+ }
+
+ std::shared_ptr<task::TaskData> &ptr = *find;
+
+ asyncResp->res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task";
+ asyncResp->res.jsonValue["Id"] = strParam;
+ asyncResp->res.jsonValue["Name"] = "Task " + strParam;
+ asyncResp->res.jsonValue["TaskState"] = ptr->state;
+ asyncResp->res.jsonValue["StartTime"] =
+ crow::utility::getDateTime(ptr->startTime);
+ if (ptr->endTime)
+ {
+ asyncResp->res.jsonValue["EndTime"] =
+ crow::utility::getDateTime(*(ptr->endTime));
+ }
+ asyncResp->res.jsonValue["TaskStatus"] = ptr->status;
+ asyncResp->res.jsonValue["Messages"] = ptr->messages;
+ asyncResp->res.jsonValue["@odata.id"] =
+ "/redfish/v1/TaskService/Tasks/" + strParam;
+ if (!ptr->gave204)
+ {
+ asyncResp->res.jsonValue["TaskMonitor"] =
+ "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor";
+ }
+ }
+};
+
+class TaskCollection : public Node
+{
+ public:
+ TaskCollection(CrowApp &app) : Node(app, "/redfish/v1/TaskService/Tasks")
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ asyncResp->res.jsonValue["@odata.type"] =
+ "#TaskCollection.TaskCollection";
+ asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService/Tasks";
+ asyncResp->res.jsonValue["Name"] = "Task Collection";
+ asyncResp->res.jsonValue["Members@odata.count"] = task::tasks.size();
+ nlohmann::json &members = asyncResp->res.jsonValue["Members"];
+ members = nlohmann::json::array();
+
+ for (const std::shared_ptr<task::TaskData> &task : task::tasks)
+ {
+ if (task == nullptr)
+ {
+ continue; // shouldn't be possible
+ }
+ members.emplace_back(
+ nlohmann::json{{"@odata.id", "/redfish/v1/TaskService/Tasks/" +
+ std::to_string(task->index)}});
+ }
+ }
+};
+
+class TaskService : public Node
+{
+ public:
+ TaskService(CrowApp &app) : Node(app, "/redfish/v1/TaskService")
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ asyncResp->res.jsonValue["@odata.type"] =
+ "#TaskService.v1_1_4.TaskService";
+ asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TaskService";
+ asyncResp->res.jsonValue["Name"] = "Task Service";
+ asyncResp->res.jsonValue["Id"] = "TaskService";
+ asyncResp->res.jsonValue["DateTime"] = crow::utility::dateTimeNow();
+ asyncResp->res.jsonValue["CompletedTaskOverWritePolicy"] = "Oldest";
+
+ // todo: if we enable events, change this to true
+ asyncResp->res.jsonValue["LifeCycleEventOnTaskStateChange"] = false;
+
+ auto health = std::make_shared<HealthPopulate>(asyncResp);
+ health->populate();
+ asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
+ asyncResp->res.jsonValue["ServiceEnabled"] = true;
+ asyncResp->res.jsonValue["Tasks"] = {
+ {"@odata.id", "/redfish/v1/TaskService/Tasks"}};
+ }
+};
+
+} // namespace redfish