Files
daggy/daggyd/tests/unit_server.cpp
Ian Roddis 0603285c10 Adding Vue.js webui
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
2022-02-24 11:40:18 -04:00

274 lines
8.2 KiB
C++

#include <curl/curl.h>
#include <pistache/client.h>
#include <rapidjson/document.h>
#include <sys/stat.h>
#include <catch2/catch.hpp>
#include <daggy/Serialization.hpp>
#include <daggy/executors/task/ForkingTaskExecutor.hpp>
#include <daggy/executors/task/NoopTaskExecutor.hpp>
#include <daggy/loggers/dag_run/OStreamLogger.hpp>
#include <daggyd/Server.hpp>
#include <filesystem>
#include <iostream>
#include <thread>
namespace rj = rapidjson;
using namespace daggy;
TEST_CASE("rest_endpoint", "[server_basic]")
{
std::stringstream ss;
daggy::executors::task::ForkingTaskExecutor executor(10);
daggy::loggers::dag_run::OStreamLogger logger(ss);
Pistache::Address listenSpec("localhost", Pistache::Port(0));
const size_t nDAGRunners = 10, nWebThreads = 10;
daggy::daggyd::Server server(listenSpec, logger, executor, nDAGRunners,
"/dev/null");
server.init(nWebThreads);
server.start();
const std::string host = "localhost:";
const std::string baseURL = host + std::to_string(server.getPort());
SECTION("Ready Endpoint")
{
auto response = HTTP_REQUEST(baseURL + "/ready");
REQUIRE(response.code == HTTPCode::Ok);
}
SECTION("Querying a non-existent dagrunid should fail ")
{
auto response = HTTP_REQUEST(baseURL + "/v1/dagrun/100");
REQUIRE(response.code != HTTPCode::Ok);
}
SECTION("Simple DAGRun Submission")
{
std::string dagRun = R"({
"tag": "unit_server",
"parameters": { "FILE": [ "A", "B" ] },
"tasks": {
"touch": { "job": { "command": [ "/bin/touch", "dagrun_{{FILE}}" ], "environment": []} },
"cat": { "job": { "command": [ "/bin/cat", "dagrun_A", "dagrun_B" ], "environment": []},
"parents": [ "touch" ]
}
}
})";
auto dagSpec = daggy::dagFromJSON(dagRun);
// Submit, and get the runID
daggy::DAGRunID runID = 0;
{
auto response = HTTP_REQUEST(baseURL + "/v1/dagrun/", dagRun, "POST");
REQUIRE(response.code == HTTPCode::Ok);
rj::Document doc;
daggy::checkRJParse(doc.Parse(response.body.c_str()));
REQUIRE(doc.IsObject());
REQUIRE(doc.HasMember("runID"));
runID = doc["runID"].GetUint64();
}
// Ensure our runID shows up in the list of running DAGs
{
auto response = HTTP_REQUEST(baseURL + "/v1/dagruns?all=1");
REQUIRE(response.code == HTTPCode::Ok);
rj::Document doc;
daggy::checkRJParse(doc.Parse(response.body.c_str()));
REQUIRE(doc.IsArray());
REQUIRE(doc.Size() >= 1);
// Ensure that our DAG is in the list and matches our given DAGRunID
bool found = false;
const auto &runs = doc.GetArray();
for (size_t i = 0; i < runs.Size(); ++i) {
const auto &run = runs[i];
REQUIRE(run.IsObject());
REQUIRE(run.HasMember("tag"));
REQUIRE(run.HasMember("runID"));
std::string runName = run["tag"].GetString();
if (runName == "unit_server") {
REQUIRE(run["runID"].GetUint64() == runID);
found = true;
break;
}
}
REQUIRE(found);
}
// Ensure we can get one of our tasks
{
auto response = HTTP_REQUEST(baseURL + "/v1/dagrun/" +
std::to_string(runID) + "/task/cat_0");
REQUIRE(response.code == HTTPCode::Ok);
rj::Document doc;
daggy::checkRJParse(doc.Parse(response.body.c_str()));
REQUIRE_NOTHROW(daggy::taskFromJSON("cat", doc));
auto task = daggy::taskFromJSON("cat", doc);
REQUIRE(task == dagSpec.tasks.at("cat"));
}
// Wait until our DAG is complete
bool complete = true;
for (auto i = 0; i < 10; ++i) {
auto response =
HTTP_REQUEST(baseURL + "/v1/dagrun/" + std::to_string(runID));
REQUIRE(response.code == HTTPCode::Ok);
rj::Document doc;
daggy::checkRJParse(doc.Parse(response.body.c_str()));
REQUIRE(doc.IsObject());
REQUIRE(doc.HasMember("taskStates"));
const auto &taskStates = doc["taskStates"].GetObject();
size_t nStates = 0;
for (auto it = taskStates.MemberBegin(); it != taskStates.MemberEnd();
++it) {
nStates++;
}
REQUIRE(nStates == 3);
complete = true;
for (auto it = taskStates.MemberBegin(); it != taskStates.MemberEnd();
++it) {
std::string state = it->value.GetString();
if (state != "COMPLETED") {
complete = false;
break;
}
}
if (complete)
break;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
REQUIRE(complete);
std::this_thread::sleep_for(std::chrono::seconds(2));
for (const auto &pth : std::vector<fs::path>{"dagrun_A", "dagrun_B"}) {
REQUIRE(fs::exists(pth));
fs::remove(pth);
}
}
}
TEST_CASE("Server cancels and resumes execution", "[server_resume]")
{
std::stringstream ss;
daggy::executors::task::ForkingTaskExecutor executor(10);
daggy::loggers::dag_run::OStreamLogger logger(std::cout);
Pistache::Address listenSpec("localhost", Pistache::Port(0));
const size_t nDAGRunners = 10, nWebThreads = 10;
daggy::daggyd::Server server(listenSpec, logger, executor, nDAGRunners,
"/dev/null");
server.init(nWebThreads);
server.start();
const std::string host = "localhost:";
const std::string baseURL = host + std::to_string(server.getPort());
SECTION("Cancel / Resume DAGRun")
{
std::string dagRunJSON = R"({
"tag": "unit_server",
"tasks": {
"touch_A": { "job": { "command": [ "/bin/touch", "resume_touch_a" ]}, "children": ["touch_C"] },
"sleep_B": { "job": { "command": [ "/bin/sleep", "3" ]}, "children": ["touch_C"] },
"touch_C": { "job": { "command": [ "/bin/touch", "resume_touch_c" ]} }
}
})";
auto dagSpec = daggy::dagFromJSON(dagRunJSON);
// Submit, and get the runID
daggy::DAGRunID runID;
{
auto response = HTTP_REQUEST(baseURL + "/v1/dagrun/", dagRunJSON, "POST");
REQUIRE(response.code == HTTPCode::Ok);
rj::Document doc;
daggy::checkRJParse(doc.Parse(response.body.c_str()));
REQUIRE(doc.IsObject());
REQUIRE(doc.HasMember("runID"));
runID = doc["runID"].GetUint64();
}
std::this_thread::sleep_for(1s);
// Stop the current run
{
auto response = HTTP_REQUEST(
baseURL + "/v1/dagrun/" + std::to_string(runID) + "/state/KILLED", "",
"PATCH");
REQUIRE(response.code == HTTPCode::Ok);
REQUIRE(logger.getDAGRunState(runID) == +daggy::RunState::KILLED);
}
// Verify that the run still exists
{
auto dagRun = logger.getDAGRun(runID);
REQUIRE(dagRun.taskRunStates.at("touch_A_0") ==
+daggy::RunState::COMPLETED);
REQUIRE(fs::exists("resume_touch_a"));
REQUIRE(dagRun.taskRunStates.at("sleep_B_0") ==
+daggy::RunState::ERRORED);
REQUIRE(dagRun.taskRunStates.at("touch_C_0") == +daggy::RunState::QUEUED);
}
// Set the errored task state
{
auto url = baseURL + "/v1/dagrun/" + std::to_string(runID) +
"/task/sleep_B_0/state/QUEUED";
auto response = HTTP_REQUEST(url, "", "PATCH");
REQUIRE(response.code == HTTPCode::Ok);
REQUIRE(logger.getTaskState(runID, "sleep_B_0") ==
+daggy::RunState::QUEUED);
}
// Resume
{
struct stat s;
lstat("resume_touch_A", &s);
auto preMTime = s.st_mtim.tv_sec;
auto response = HTTP_REQUEST(
baseURL + "/v1/dagrun/" + std::to_string(runID) + "/state/QUEUED", "",
"PATCH");
// Wait for run to complete
std::this_thread::sleep_for(3s);
REQUIRE(logger.getDAGRunState(runID) == +daggy::RunState::COMPLETED);
REQUIRE(fs::exists("resume_touch_c"));
REQUIRE(fs::exists("resume_touch_a"));
for (const auto &[taskName, task] : dagSpec.tasks) {
REQUIRE(logger.getTaskState(runID, taskName + "_0") ==
+daggy::RunState::COMPLETED);
}
// Ensure "touch_A" wasn't run again
lstat("resume_touch_A", &s);
auto postMTime = s.st_mtim.tv_sec;
REQUIRE(preMTime == postMTime);
}
}
server.shutdown();
}