Squashed commit of the following: commit 29571182b1ec3b5be2cec3212c2bea1121a3dac2 Author: Ian Roddis <tech@kinesin.ca> Date: Thu Feb 24 11:29:47 2022 -0400 Adding more elegant handling of tasks with no attempts commit 18c8ccb0863abbf6c9cc0efe5cc68df03a9eb80d Author: Ian Roddis <tech@kinesin.ca> Date: Thu Feb 24 11:18:59 2022 -0400 Better handling of no attempts at all commit 962f9f6e5e17f71bc3766553913774631f66e7ef Author: Ian Roddis <tech@kinesin.ca> Date: Thu Feb 24 11:10:28 2022 -0400 Adding fix for missing attempts commit 19b8203e952b3d21f4ff3f9b97a01c4d567ff1e7 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 16:56:37 2022 -0400 Adding webui instructions to readme commit 81383c80f01101828c0c49868916a2712d140f42 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 16:48:31 2022 -0400 Adding in route splatting to support static assets commit c9b39b307916c0fb1e88769d6986ddf7c3ba183a Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 12:11:11 2022 -0400 Cleanup commit 177819a1439cd1a0f32c652abf670f54457e105a Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 12:09:40 2022 -0400 Setting explicit url for extra CSS commit 78261129511c50657e7902934cee396eb1e4e3a8 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 12:08:27 2022 -0400 Moving webui commit 9f8db6e2c2c8a231060217cb82f1b13aabe4eae2 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 12:06:25 2022 -0400 Reorganizing elements, adding regex for run list commit f114250c9a506b2c0e9d642cc75749e99cc76cef Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 10:52:41 2022 -0400 Adding regex filtering to tasks commit 2de2f218416210443119aa88fa49c714197f4b16 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 10:42:22 2022 -0400 Adding in task details and getting the plumbing working commit 660a2078e22799ba51b4b8bbe5c12cd0f9315b0a Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 09:38:13 2022 -0400 Fixing remaining settings commit 1aa0dfe1c971a12dfed183586ee5a3206d452409 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 09:36:25 2022 -0400 Playing with settings commit 84cbd11c45651c7c6c96c16714e741b6aee10bc5 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 23 08:52:52 2022 -0400 Removing extra code commit 6e31646b7c62368cab22b3844a70943e0149ddc7 Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 22 17:29:47 2022 -0400 Adding linter, renaming components to meet standards, fixing some mixups in settings commit 225442ee5732d007867e485ccea05293e3e5e1b7 Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 22 17:25:27 2022 -0400 Fixing sorters commit eb0d7a4c4c30d8e8b43b574ed0c2f97515bb9353 Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 22 16:46:41 2022 -0400 Controls are coming together commit b1789d1cc3c0bae170e0ca1a47cccfd344197244 Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 22 11:08:09 2022 -0400 More refactoring commit 6d0afce429aad00864482a2cc7dd731a53312e14 Author: Ian Roddis <tech@kinesin.ca> Date: Sun Feb 20 22:29:43 2022 -0400 figuring out layout commit 6af498f3aa7fe2f45121df2278cdfac297165c5c Author: Ian Roddis <tech@kinesin.ca> Date: Sun Feb 20 12:30:49 2022 -0400 Migrating to prop drilling / emiting commit dffe7059ce01209d2def6ef7c03bc750e31fe741 Author: Ian Roddis <tech@kinesin.ca> Date: Fri Feb 18 17:20:46 2022 -0400 Checkpointing work for now commit d6428ad59c9c05ab7fba82ce3c0441ac3f568796 Author: Ian Roddis <tech@kinesin.ca> Date: Fri Feb 18 17:05:37 2022 -0400 Adding in toggling for states commit b9a4f2dc02f327d3529821e217d3b6a00a84f202 Author: Ian Roddis <tech@kinesin.ca> Date: Fri Feb 18 16:43:01 2022 -0400 Reorganizing everything commit d33691d022597d1ff8f588450e147c72555be9f4 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 17:04:54 2022 -0400 Removing console logging commit 4537376ccad6fc0c52f0a7cfd2b2bf23f708196c Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 17:04:27 2022 -0400 Refresh timer working now commit 213a3da4fd07c82cd18cd8c3b2422ddc78bd6fb4 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 16:40:45 2022 -0400 Adding timer commit ff495ac69563689ff4fc07119936079e57608ea7 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 16:02:53 2022 -0400 Refactoring some code, adding in endpoint to kill a running task commit 97ff28b9b1910e03e0f2725a3f54d2a07e53714c Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 14:56:15 2022 -0400 Renaming UI commit affab06ad657833b73588eac919250935b353f31 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 13:29:31 2022 -0400 moving to bootstrap commit c40a2e58a86362863c905470f4417753aaf0dac2 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 12:33:08 2022 -0400 adding task button commit 420463b8d7f964baa0dfc7c87c2e9024bc8284cc Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 10:51:11 2022 -0400 checkpoint commit a7aa3db731255e7e13bc58d901b8eb1e30ede39c Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 09:33:01 2022 -0400 Fixing up state commit 361b4cbcd8f1268eb9b494084d6862a6ab8f3a27 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 09:29:14 2022 -0400 Fixing event callbacks commit 388cada692dc8d7e0eff611467d4c77ce897a54c Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 09:24:39 2022 -0400 Adding global state, task view and buttons commit cb5a3acef0bd982621678fbd44a133db56420871 Author: Ian Roddis <tech@kinesin.ca> Date: Wed Feb 16 07:49:30 2022 -0400 Adding RunView commit 4c78ef1250709e7c8f5ef3433640fd8d1d319a8d Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 15 17:20:23 2022 -0400 checkpoint commit 2c5b610101e9c18ef1ad8f962d7309b63c80743c Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 15 17:10:06 2022 -0400 Adding explicit payload headers, adding vue and react apps commit 95ac6c05903bc83c6934db58b48649eee2038c3d Author: Ian Roddis <tech@kinesin.ca> Date: Tue Feb 15 12:56:57 2022 -0400 Adding CORS support, rough-in of webui
694 lines
20 KiB
C++
694 lines
20 KiB
C++
#include <enum.h>
|
|
|
|
#include <daggy/Serialization.hpp>
|
|
#include <daggy/Utilities.hpp>
|
|
#include <daggyd/Server.hpp>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <mutex>
|
|
#include <numeric>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
#define REQ_RESPONSE(code, msg) \
|
|
std::stringstream ss; \
|
|
ss << R"({"message": )" << std::quoted(msg) << "}\n"; \
|
|
response.send(Pistache::Http::Code::code, ss.str()); \
|
|
return;
|
|
|
|
using namespace Pistache;
|
|
|
|
namespace daggy::daggyd {
|
|
|
|
void addResponseHeaders(Pistache::Http::ResponseWriter &response)
|
|
{
|
|
response.headers().add(
|
|
std::make_shared<Pistache::Http::Header::AccessControlAllowOrigin>(
|
|
"*"));
|
|
response.headers().add(
|
|
std::make_shared<Pistache::Http::Header::AccessControlAllowHeaders>(
|
|
"content-type"));
|
|
response.headers().add(
|
|
std::make_shared<Pistache::Http::Header::AccessControlAllowMethods>(
|
|
"PUT, OPTIONS, PATCH, GET, HEAD, CONNECT, DELETE, POST, TRACE"));
|
|
response.headers().add(
|
|
std::make_shared<Pistache::Http::Header::ContentType>(
|
|
"application/javascript"));
|
|
}
|
|
|
|
void Server::init(size_t threads)
|
|
{
|
|
auto opts = Http::Endpoint::options()
|
|
.threads(threads)
|
|
.flags(Pistache::Tcp::Options::ReuseAddr |
|
|
Pistache::Tcp::Options::ReusePort)
|
|
.maxRequestSize(4294967296)
|
|
.maxResponseSize(4294967296);
|
|
endpoint_.init(opts);
|
|
createDescription();
|
|
}
|
|
|
|
Server::Server(const Pistache::Address &listenSpec,
|
|
loggers::dag_run::DAGRunLogger &logger,
|
|
executors::task::TaskExecutor &executor, size_t nDAGRunners,
|
|
const fs::path &staticAssetsDir)
|
|
: endpoint_(listenSpec)
|
|
, desc_("Daggy API", "0.1")
|
|
, logger_(logger)
|
|
, executor_(executor)
|
|
, runnerPool_(nDAGRunners)
|
|
, staticAssetsDir_(staticAssetsDir)
|
|
{
|
|
}
|
|
|
|
Server::~Server()
|
|
{
|
|
shutdown();
|
|
}
|
|
|
|
void Server::start()
|
|
{
|
|
router_.initFromDescription(desc_);
|
|
|
|
endpoint_.setHandler(router_.handler());
|
|
endpoint_.serveThreaded();
|
|
}
|
|
|
|
Server &Server::setSSLCertificates(const fs::path &cert, const fs::path &key)
|
|
{
|
|
endpoint_.useSSL(cert, key);
|
|
return *this;
|
|
}
|
|
|
|
void Server::shutdown()
|
|
{
|
|
endpoint_.shutdown();
|
|
runnerPool_.shutdown();
|
|
}
|
|
|
|
uint16_t Server::getPort() const
|
|
{
|
|
return endpoint_.getPort();
|
|
}
|
|
|
|
void Server::createDescription()
|
|
{
|
|
desc_.info().license("MIT", "https://opensource.org/licenses/MIT");
|
|
|
|
auto backendErrorResponse =
|
|
desc_.response(Http::Code::Internal_Server_Error,
|
|
R"({"error": "An error occurred with the backend"})");
|
|
|
|
desc_.route(desc_.get("/"))
|
|
.bind(&Server::handleStatic, this)
|
|
.response(Http::Code::Ok, "Serve static assets")
|
|
.hide();
|
|
desc_.route(desc_.get("/*"))
|
|
.bind(&Server::handleStatic, this)
|
|
.response(Http::Code::Ok, "Serve static assets")
|
|
.hide();
|
|
desc_.route(desc_.get("/*/*"))
|
|
.bind(&Server::handleStatic, this)
|
|
.response(Http::Code::Ok, "Serve static assets")
|
|
.hide();
|
|
|
|
desc_.schemes(Rest::Scheme::Http)
|
|
.basePath("/v1")
|
|
.produces(MIME(Application, Json))
|
|
.consumes(MIME(Application, Json));
|
|
|
|
desc_.route(desc_.get("/ready"))
|
|
.bind(&Server::handleReady, this)
|
|
.response(Http::Code::Ok, "Response to the /ready call")
|
|
.hide();
|
|
|
|
auto versionPath = desc_.path("/v1");
|
|
|
|
/*
|
|
DAG Run Summaries
|
|
*/
|
|
auto dagRunsPath = versionPath.path("/dagruns");
|
|
|
|
dagRunsPath.route(desc_.options("/")).bind(&Server::handleCORS, this);
|
|
dagRunsPath.route(desc_.get("/"))
|
|
.bind(&Server::handleQueryDAGs, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "List summaries DAGs");
|
|
|
|
/*
|
|
Individual DAG Run routes
|
|
*/
|
|
auto dagRunPath = versionPath.path("/dagrun");
|
|
|
|
dagRunPath.route(desc_.options("/")).bind(&Server::handleCORS, this);
|
|
dagRunPath.route(desc_.post("/"))
|
|
.bind(&Server::handleRunDAG, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Run a DAG");
|
|
|
|
dagRunPath.route(desc_.options("/validate"))
|
|
.bind(&Server::handleCORS, this);
|
|
dagRunPath.route(desc_.post("/validate"))
|
|
.bind(&Server::handleValidateDAG, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Validate a DAG Run Spec");
|
|
|
|
/*
|
|
Management of a specific DAG
|
|
*/
|
|
auto specificDAGRunPath = dagRunPath.path("/:runID");
|
|
|
|
specificDAGRunPath.route(desc_.options("/"))
|
|
.bind(&Server::handleCORS, this);
|
|
specificDAGRunPath.route(desc_.del("/"))
|
|
.bind(&Server::handleStopDAGRun, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Kill a running dag");
|
|
|
|
specificDAGRunPath.route(desc_.get("/"))
|
|
.bind(&Server::handleGetDAGRun, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Full DAG Run");
|
|
|
|
specificDAGRunPath.route(desc_.options("/state"))
|
|
.bind(&Server::handleCORS, this);
|
|
specificDAGRunPath.route(desc_.get("/state"))
|
|
.bind(&Server::handleGetDAGRunState, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok,
|
|
"Structure of a DAG and DAG and Task run states");
|
|
|
|
specificDAGRunPath.route(desc_.options("/state/:state"))
|
|
.bind(&Server::handleCORS, this);
|
|
specificDAGRunPath.route(desc_.patch("/state/:state"))
|
|
.bind(&Server::handleSetDAGRunState, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Change the state of a DAG");
|
|
|
|
/*
|
|
Task paths
|
|
*/
|
|
auto taskPath = specificDAGRunPath.path("/task/:taskName");
|
|
taskPath.route(desc_.options("/")).bind(&Server::handleCORS, this);
|
|
taskPath.route(desc_.get("/"))
|
|
.bind(&Server::handleGetTask, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Details of a specific task");
|
|
taskPath.route(desc_.del("/"))
|
|
.bind(&Server::handleStopTask, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Kill a specific task");
|
|
|
|
/*
|
|
Task State paths
|
|
*/
|
|
auto taskStatePath = taskPath.path("/state");
|
|
|
|
taskStatePath.route(desc_.options("/")).bind(&Server::handleCORS, this);
|
|
taskStatePath.route(desc_.get("/"))
|
|
.bind(&Server::handleGetTaskState, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Get a task state");
|
|
|
|
taskStatePath.route(desc_.options("/:state"))
|
|
.bind(&Server::handleCORS, this);
|
|
taskStatePath.route(desc_.patch("/:state"))
|
|
.bind(&Server::handleSetTaskState, this)
|
|
.produces(MIME(Application, Json))
|
|
.response(Http::Code::Ok, "Set a task state");
|
|
}
|
|
|
|
void Server::handleCORS(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
response.send(Pistache::Http::Code::Ok, "");
|
|
}
|
|
|
|
void Server::handleRunDAG(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
if (!handleAuth(request))
|
|
return;
|
|
addResponseHeaders(response);
|
|
|
|
DAGRunID runID = 0;
|
|
try {
|
|
DAGSpec dagSpec;
|
|
dagSpec = dagFromJSON(request.body());
|
|
dagSpec.tasks =
|
|
expandTaskSet(dagSpec.tasks, executor_, dagSpec.taskConfig.variables);
|
|
// Get a run ID
|
|
runID = logger_.startDAGRun(dagSpec);
|
|
auto dag = buildDAGFromTasks(dagSpec.tasks);
|
|
queueDAG_(runID, dag, dagSpec.taskConfig);
|
|
}
|
|
catch (std::runtime_error &e) {
|
|
REQ_RESPONSE(Not_Acceptable, e.what());
|
|
}
|
|
|
|
response.send(Pistache::Http::Code::Ok,
|
|
R"({"runID": )" + std::to_string(runID) + "}\n");
|
|
}
|
|
|
|
void Server::handleValidateDAG(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
try {
|
|
dagFromJSON(request.body());
|
|
response.send(Pistache::Http::Code::Ok, R"({"valid": true}\n)");
|
|
}
|
|
catch (std::exception &e) {
|
|
std::string error = e.what();
|
|
response.send(
|
|
Pistache::Http::Code::Ok,
|
|
std::string{R"({"valid": false, "error": })"} + error + "}\n");
|
|
}
|
|
}
|
|
|
|
void Server::handleQueryDAGs(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
bool all = false;
|
|
std::string tag = "";
|
|
|
|
if (request.query().has("tag")) {
|
|
tag = request.query().get("tag").value();
|
|
}
|
|
|
|
if (request.query().has("all")) {
|
|
auto val = request.query().get("all").value();
|
|
if (val == "true" or val == "1") {
|
|
all = true;
|
|
}
|
|
}
|
|
|
|
auto dagRuns = logger_.queryDAGRuns(tag, all);
|
|
std::stringstream ss;
|
|
// default to json
|
|
ss << '[';
|
|
|
|
bool first = true;
|
|
for (const auto &run : dagRuns) {
|
|
if (first) {
|
|
first = false;
|
|
}
|
|
else {
|
|
ss << ", ";
|
|
}
|
|
|
|
ss << " {"
|
|
<< R"("runID": )" << run.runID << ',' << R"("tag": )"
|
|
<< std::quoted(run.tag) << ","
|
|
<< R"("state": )" << std::quoted(run.runState._to_string()) << ","
|
|
<< R"("startTime": )" << std::quoted(timePointToString(run.startTime))
|
|
<< ',' << R"("lastUpdate": )"
|
|
<< std::quoted(timePointToString(run.lastUpdate)) << ','
|
|
<< R"("taskCounts": {)";
|
|
bool firstState = true;
|
|
for (const auto &[state, count] : run.taskStateCounts) {
|
|
if (firstState) {
|
|
firstState = false;
|
|
}
|
|
else {
|
|
ss << ", ";
|
|
}
|
|
ss << std::quoted(state._to_string()) << ':' << count;
|
|
}
|
|
ss << '}' // end of taskCounts
|
|
<< '}'; // end of item
|
|
}
|
|
ss << "]\n";
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
|
|
void Server::handleGetDAGRun(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
if (!request.hasParam(":runID")) {
|
|
REQ_RESPONSE(Not_Found, "No runID provided in URL");
|
|
}
|
|
auto runID = request.param(":runID").as<size_t>();
|
|
auto run = logger_.getDAGRun(runID);
|
|
|
|
std::optional<RunState> filterState;
|
|
if (request.query().has("state")) {
|
|
auto val = request.query().get("state").value();
|
|
filterState = RunState::_from_string(val.c_str());
|
|
}
|
|
|
|
std::stringstream ss;
|
|
bool first = true;
|
|
ss << "{"
|
|
<< R"("runID": )" << runID << ',' << R"("tag": )"
|
|
<< std::quoted(run.dagSpec.tag) << ',' << R"("tasks": )"
|
|
<< tasksToJSON(run.dagSpec.tasks) << ',';
|
|
|
|
// task run states
|
|
ss << R"("taskStates": { )";
|
|
first = true;
|
|
for (const auto &[name, state] : run.taskRunStates) {
|
|
if (first) {
|
|
first = false;
|
|
}
|
|
else {
|
|
ss << ',';
|
|
}
|
|
ss << std::quoted(name) << ": " << std::quoted(state._to_string());
|
|
}
|
|
ss << "},";
|
|
|
|
// Attempt records
|
|
first = true;
|
|
ss << R"("taskAttempts": { )";
|
|
for (const auto &[taskName, attempts] : run.taskAttempts) {
|
|
if (first) {
|
|
first = false;
|
|
}
|
|
else {
|
|
ss << ',';
|
|
}
|
|
ss << std::quoted(taskName) << ": [";
|
|
bool firstAttempt = true;
|
|
for (const auto &attempt : attempts) {
|
|
if (firstAttempt) {
|
|
firstAttempt = false;
|
|
}
|
|
else {
|
|
ss << ',';
|
|
}
|
|
ss << attemptRecordToJSON(attempt);
|
|
}
|
|
ss << ']';
|
|
}
|
|
ss << "},";
|
|
|
|
// DAG state changes
|
|
first = true;
|
|
ss << R"("dagStateChanges": [ )";
|
|
for (const auto &change : run.dagStateChanges) {
|
|
if (first) {
|
|
first = false;
|
|
}
|
|
else {
|
|
ss << ',';
|
|
}
|
|
ss << stateUpdateRecordToJSON(change);
|
|
}
|
|
ss << "]";
|
|
ss << "}\n";
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
|
|
void Server::handleStopDAGRun(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
if (!request.hasParam(":runID")) {
|
|
REQ_RESPONSE(Not_Found, "No runID provided in URL");
|
|
}
|
|
auto runID = request.param(":runID").as<size_t>();
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(runnerGuard_);
|
|
auto it = runners_.find(runID);
|
|
if (it != runners_.end()) {
|
|
it->second->stop(true, false);
|
|
}
|
|
}
|
|
|
|
response.send(Pistache::Http::Code::Ok, "");
|
|
}
|
|
|
|
void Server::handleGetDAGRunState(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
DAGRunID runID = request.param(":runID").as<DAGRunID>();
|
|
RunState state = RunState::QUEUED;
|
|
try {
|
|
state = logger_.getDAGRunState(runID);
|
|
std::stringstream ss;
|
|
ss << R"({ "runID": )" << runID << R"(, "state": )"
|
|
<< std::quoted(state._to_string()) << '}';
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
catch (std::exception &e) {
|
|
REQ_RESPONSE(Not_Found, e.what());
|
|
}
|
|
}
|
|
|
|
void Server::queueDAG_(DAGRunID runID, const TaskDAG &dag,
|
|
const TaskParameters &taskParameters)
|
|
{
|
|
std::lock_guard<std::mutex> lock(runnerGuard_);
|
|
/*
|
|
auto it = runners_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(runID),
|
|
std::forward_as_tuple(runID, executor_, logger_, dag,
|
|
taskParameters));
|
|
*/
|
|
auto it = runners_.emplace(
|
|
runID, std::make_shared<DAGRunner>(runID, executor_, logger_, dag,
|
|
taskParameters));
|
|
|
|
if (!it.second)
|
|
throw std::runtime_error("A DAGRun with the same ID is already running");
|
|
auto runner = it.first->second;
|
|
runnerPool_.addTask([runner, runID, this]() {
|
|
runner->run();
|
|
std::lock_guard<std::mutex> lock(this->runnerGuard_);
|
|
this->runners_.extract(runID);
|
|
});
|
|
}
|
|
|
|
void Server::handleSetDAGRunState(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
if (!handleAuth(request))
|
|
return;
|
|
addResponseHeaders(response);
|
|
|
|
// TODO handle state transition
|
|
DAGRunID runID = request.param(":runID").as<DAGRunID>();
|
|
RunState newState = RunState::_from_string(
|
|
request.param(":state").as<std::string>().c_str());
|
|
|
|
std::shared_ptr<DAGRunner> runner{nullptr};
|
|
{
|
|
std::lock_guard<std::mutex> lock(runnerGuard_);
|
|
auto it = runners_.find(runID);
|
|
if (runners_.find(runID) != runners_.end()) {
|
|
runner = it->second;
|
|
}
|
|
}
|
|
|
|
if (runner) {
|
|
switch (newState) {
|
|
case RunState::PAUSED:
|
|
case RunState::KILLED: {
|
|
runner->stop(true, true);
|
|
logger_.updateDAGRunState(runID, newState);
|
|
break;
|
|
}
|
|
default: {
|
|
REQ_RESPONSE(Method_Not_Allowed,
|
|
std::string{"Cannot transition to state "} +
|
|
newState._to_string());
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (newState) {
|
|
case RunState::QUEUED: {
|
|
auto dagRun = logger_.getDAGRun(runID);
|
|
auto dag =
|
|
buildDAGFromTasks(dagRun.dagSpec.tasks, dagRun.taskStateChanges);
|
|
dag.resetRunning();
|
|
queueDAG_(runID, dag, dagRun.dagSpec.taskConfig);
|
|
break;
|
|
}
|
|
default:
|
|
REQ_RESPONSE(
|
|
Method_Not_Allowed,
|
|
std::string{"DAG not running, cannot transition to state "} +
|
|
newState._to_string());
|
|
}
|
|
}
|
|
REQ_RESPONSE(Ok, "");
|
|
}
|
|
|
|
void Server::handleGetTask(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
auto runID = request.param(":runID").as<DAGRunID>();
|
|
auto taskName = request.param(":taskName").as<std::string>();
|
|
|
|
std::stringstream ss;
|
|
Task task;
|
|
try {
|
|
task = logger_.getTask(runID, taskName);
|
|
}
|
|
catch (std::exception &e) {
|
|
REQ_RESPONSE(Not_Found, e.what());
|
|
}
|
|
ss << taskToJSON(task);
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
|
|
void Server::handleStopTask(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
auto runID = request.param(":runID").as<DAGRunID>();
|
|
auto taskName = request.param(":taskName").as<std::string>();
|
|
|
|
std::shared_ptr<DAGRunner> runner{nullptr};
|
|
{
|
|
std::lock_guard<std::mutex> lock(runnerGuard_);
|
|
auto it = runners_.find(runID);
|
|
if (runners_.find(runID) != runners_.end()) {
|
|
runner = it->second;
|
|
}
|
|
}
|
|
|
|
if (runner) {
|
|
runner->stopTask(taskName);
|
|
}
|
|
REQ_RESPONSE(Ok, "");
|
|
}
|
|
|
|
void Server::handleGetTaskState(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
auto runID = request.param(":runID").as<DAGRunID>();
|
|
auto taskName = request.param(":taskName").as<std::string>();
|
|
|
|
try {
|
|
auto state = logger_.getTaskState(runID, taskName);
|
|
std::stringstream ss;
|
|
ss << R"({ "runID": )" << runID << R"(, "taskName": )"
|
|
<< std::quoted(taskName) << R"(, "state": )"
|
|
<< std::quoted(state._to_string()) << "}\n";
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
catch (std::exception &e) {
|
|
REQ_RESPONSE(Not_Found, e.what());
|
|
}
|
|
}
|
|
|
|
void Server::handleSetTaskState(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
if (!handleAuth(request))
|
|
return;
|
|
|
|
// TODO implement handling of task state
|
|
auto runID = request.param(":runID").as<DAGRunID>();
|
|
auto taskName = request.param(":taskName").as<std::string>();
|
|
RunState state = RunState::_from_string(
|
|
request.param(":state").as<std::string>().c_str());
|
|
|
|
try {
|
|
logger_.updateTaskState(runID, taskName, state);
|
|
response.send(Pistache::Http::Code::Ok, "");
|
|
}
|
|
catch (std::exception &e) {
|
|
REQ_RESPONSE(Not_Found, e.what());
|
|
}
|
|
}
|
|
|
|
void Server::handleReady(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
REQ_RESPONSE(Ok, "Ya like DAGs?");
|
|
}
|
|
|
|
void Server::handleStatic(const Pistache::Rest::Request &request,
|
|
Pistache::Http::ResponseWriter response)
|
|
{
|
|
addResponseHeaders(response);
|
|
std::string file = "index.html";
|
|
|
|
auto splats = request.splat();
|
|
if (!splats.empty()) {
|
|
file = splats[0].as<std::string>();
|
|
for (size_t i = 1; i < splats.size(); ++i)
|
|
file += "/" + splats[i].as<std::string>();
|
|
}
|
|
|
|
auto fn = staticAssetsDir_ / file;
|
|
|
|
auto ext = fn.extension();
|
|
|
|
if (!fs::exists(fn)) {
|
|
std::cout << "Can't find " << fn << std::endl;
|
|
REQ_RESPONSE(Not_Found, "");
|
|
}
|
|
|
|
std::string contentType;
|
|
if (ext == ".svg") {
|
|
contentType = "image/svg+xml";
|
|
}
|
|
else if (ext == ".html") {
|
|
contentType = "text/html";
|
|
}
|
|
else if (ext == ".css") {
|
|
contentType = "text/css";
|
|
}
|
|
else if (ext == ".js") {
|
|
contentType = "text/javascript";
|
|
}
|
|
else {
|
|
REQ_RESPONSE(Bad_Request, "I don't know how to serve that kind of file");
|
|
}
|
|
|
|
response.headers().remove<Pistache::Http::Header::ContentType>();
|
|
response.headers().add(
|
|
std::make_shared<Pistache::Http::Header::ContentType>(contentType));
|
|
|
|
std::stringstream ss;
|
|
std::ifstream ifh;
|
|
ifh.open(fn, std::ios::binary);
|
|
ss << ifh.rdbuf();
|
|
|
|
response.send(Pistache::Http::Code::Ok, ss.str());
|
|
}
|
|
|
|
/*
|
|
* handleAuth will check any auth methods and handle any responses in the
|
|
* case of failed auth. If it returns false, callers should cease handling
|
|
* the response
|
|
*/
|
|
bool Server::handleAuth(const Pistache::Rest::Request &request)
|
|
{
|
|
return true;
|
|
}
|
|
} // namespace daggy::daggyd
|