Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
|
e634bc9f48 | 2 years ago |
|
061135e2b3 | 2 years ago |
|
b027ac07ec | 2 years ago |
|
373af88ceb | 2 years ago |
|
858860b9ae | 2 years ago |
@ -1,11 +0,0 @@
|
||||
.git
|
||||
github
|
||||
.vscode
|
||||
.idea
|
||||
build
|
||||
cmake-build-debug
|
||||
|
||||
.clang-format
|
||||
.drone.yml
|
||||
.dockerignore
|
||||
Dockerfile
|
@ -1,51 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(planificador)
|
||||
|
||||
add_compile_options(-Wall -Wextra -pedantic)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
tomlplusplus
|
||||
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
||||
GIT_TAG v3.3.0
|
||||
rpclib
|
||||
GIT_REPOSITORY https://github.com/rpclib/rpclib.git
|
||||
GIT_TAG master
|
||||
)
|
||||
FetchContent_MakeAvailable(rpclib)
|
||||
|
||||
FetchContent_Declare(
|
||||
tomlplusplus
|
||||
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
||||
GIT_TAG v3.3.0
|
||||
)
|
||||
FetchContent_MakeAvailable(tomlplusplus)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/libzmq-pkg-config)
|
||||
find_package(ZeroMQ QUIET)
|
||||
|
||||
add_executable(planificador src/runner.cpp src/network.cpp src/config.cpp src/main.cpp)
|
||||
if (NOT ZeroMQ_FOUND)
|
||||
message(STATUS "ZeroMQ not found, using bundled version")
|
||||
FetchContent_Declare(
|
||||
zmq
|
||||
GIT_REPOSITORY https://github.com/zeromq/libzmq.git
|
||||
GIT_TAG v4.3.5
|
||||
)
|
||||
FetchContent_Declare(
|
||||
cppzmq
|
||||
GIT_REPOSITORY https://github.com/zeromq/cppzmq.git
|
||||
GIT_TAG v4.10.0
|
||||
)
|
||||
set(WITH_PERF_TOOL OFF)
|
||||
set(ENABLE_CPACK OFF)
|
||||
set(ZMQ_BUILD_TESTS OFF)
|
||||
set(WITH_TLS OFF)
|
||||
set(WITH_DOC OFF)
|
||||
set(BUILD_STATIC OFF)
|
||||
set(BUILD_SHARED ON)
|
||||
set(ZMQ_BUILD_TESTS OFF)
|
||||
set(CPPZMQ_BUILD_TESTS OFF)
|
||||
FetchContent_MakeAvailable(zmq cppzmq)
|
||||
include_directories(${zmq_SOURCE_DIR}/include)
|
||||
include_directories(${cppzmq_SOURCE_DIR})
|
||||
target_link_libraries(planificador PRIVATE libzmq)
|
||||
else ()
|
||||
target_link_libraries(planificador PRIVATE zmq)
|
||||
endif ()
|
||||
|
||||
target_compile_options(planificador PUBLIC -Wall -Wextra -pedantic)
|
||||
add_executable(planificador src/host.cpp src/runner.cpp src/config.cpp src/main.cpp)
|
||||
target_compile_features(planificador PUBLIC cxx_std_17)
|
||||
|
||||
include_directories(${tomlplusplus_SOURCE_DIR}/include)
|
||||
include_directories(${rpclib_SOURCE_DIR}/include)
|
||||
|
||||
SET(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(planificador PRIVATE Threads::Threads)
|
||||
target_link_libraries(planificador PRIVATE rpc)
|
||||
|
@ -1,16 +0,0 @@
|
||||
FROM alpine:3.18 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache build-base git zeromq-dev cmake \
|
||||
&& apk add --no-cache cppzmq --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing
|
||||
|
||||
COPY . .
|
||||
RUN cmake -B build -S . && cmake --build build --parallel $(nproc)
|
||||
|
||||
FROM alpine:3.18 as runner
|
||||
|
||||
RUN apk add --no-cache libc++ zeromq
|
||||
COPY --from=builder /app/build/planificador /usr/local/bin/planificador
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/planificador"]
|
@ -1,53 +0,0 @@
|
||||
planificador
|
||||
===========
|
||||
|
||||
A sandbox execution environment for untrusted code. It acts as a front-end in front of Docker+Bubblewrap.
|
||||
|
||||
Tasks are submitted using a ZeroMQ message queue, allowing quick scaling of the system.
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
*planificador* receives messages from a ZeroMQ queue in binary format in big-endian.
|
||||
|
||||
Executor bound
|
||||
--------------
|
||||
|
||||
The first byte of the message is the message type. The following types are supported:
|
||||
|
||||
* `0x00`: `SUBMIT` - Submit a new task to the system.
|
||||
* `0x02`: `CANCEL` - Cancel a task.
|
||||
|
||||
The following bytes are the payload of the message. The format of the payload depends on the message type.
|
||||
|
||||
### SUBMIT
|
||||
|
||||
- 32 bytes: Task ID
|
||||
- 4 bytes: Image field length
|
||||
- 4 bytes: Code length
|
||||
- Image field length bytes: Image field
|
||||
- Code length bytes: Code
|
||||
|
||||
### CANCEL
|
||||
|
||||
- 32 bytes: Task ID
|
||||
|
||||
Client bound
|
||||
------------
|
||||
|
||||
The first byte of the message is the message type. The following types are supported:
|
||||
|
||||
* `0x01`: `APPEND_OUT` - Append text to the task's stdout.
|
||||
* `0x02`: `APPEND_ERR` - Append text to the task's stderr.
|
||||
* `0x03`: `EXITED` - The task has exited.
|
||||
|
||||
## APPEND_OUT / APPEND_ERR
|
||||
|
||||
- 32 bytes: Task ID
|
||||
- 4 bytes: Text length
|
||||
- Text length bytes: Text
|
||||
|
||||
## EXITED
|
||||
|
||||
- 32 bytes: Task ID
|
||||
- 4 bytes: Exit code
|
@ -0,0 +1,12 @@
|
||||
[serveurs]
|
||||
[serveurs.serveur1]
|
||||
ip = '100.000.000.000'
|
||||
nbContainerMax = 10
|
||||
|
||||
[serveurs.serveur2]
|
||||
ip = '200.000.000.000'
|
||||
nbContainerMax = 20
|
||||
|
||||
[serveurs.serveur3]
|
||||
ip = '300.000.000.000'
|
||||
nbContainerMax = 30
|
@ -1,24 +0,0 @@
|
||||
[queue]
|
||||
pull = "tcp://localhost:5557"
|
||||
push = "tcp://localhost:5558"
|
||||
threads = 1
|
||||
|
||||
[runner]
|
||||
timeout = 2
|
||||
|
||||
[runners.javascript]
|
||||
type = "bubblewrap"
|
||||
args = ["node", "-e", "{}"]
|
||||
|
||||
[runners.bun]
|
||||
type = "bubblewrap"
|
||||
args = ["bun", "run", "/dev/stdin"]
|
||||
|
||||
[runners.bash]
|
||||
type = "bubblewrap"
|
||||
args = ["bash", "-c", "{}"]
|
||||
|
||||
[runners.moshell]
|
||||
type = "docker"
|
||||
image = "ghcr.io/moshell-lang/moshell:master"
|
||||
args = ["moshell", "-c", "{}"]
|
@ -1,28 +0,0 @@
|
||||
# Adapted from https://github.com/zeromq/cppzmq/blob/master/CMakeLists.txt
|
||||
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_LIBZMQ QUIET libzmq)
|
||||
|
||||
set(ZeroMQ_VERSION ${PC_LIBZMQ_VERSION})
|
||||
|
||||
find_path(ZeroMQ_INCLUDE_DIR zmq.h
|
||||
PATHS ${ZeroMQ_DIR}/include
|
||||
${PC_LIBZMQ_INCLUDE_DIRS})
|
||||
|
||||
find_library(ZeroMQ_LIBRARY
|
||||
NAMES zmq
|
||||
PATHS ${ZeroMQ_DIR}/lib
|
||||
${PC_LIBZMQ_LIBDIR}
|
||||
${PC_LIBZMQ_LIBRARY_DIRS})
|
||||
|
||||
if(ZeroMQ_LIBRARY)
|
||||
set(ZeroMQ_FOUND ON)
|
||||
endif()
|
||||
|
||||
set ( ZeroMQ_LIBRARIES ${ZeroMQ_LIBRARY} )
|
||||
set ( ZeroMQ_INCLUDE_DIRS ${ZeroMQ_INCLUDE_DIR} )
|
||||
|
||||
include ( FindPackageHandleStandardArgs )
|
||||
# handle the QUIETLY and REQUIRED arguments and set ZMQ_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args ( ZeroMQ DEFAULT_MSG ZeroMQ_LIBRARIES ZeroMQ_INCLUDE_DIRS )
|
@ -1,76 +1,31 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <toml++/toml.h>
|
||||
std::vector<sk::host> sk::config::loadHostsFromToml(const fs::path &confFile) {
|
||||
std::vector<sk::host> hosts;
|
||||
|
||||
static sk::config read(const toml::table &config) {
|
||||
std::unordered_map<std::string, sk::runner_strategy_config> strategies;
|
||||
auto *table = config["runners"].as_table();
|
||||
if (table != nullptr) {
|
||||
for (auto [name, backend_config] : *table) {
|
||||
auto *backend_table = backend_config.as_table();
|
||||
if (backend_table == nullptr) {
|
||||
std::cerr << "Invalid runner config for " << name << ": expected table\n";
|
||||
continue;
|
||||
}
|
||||
auto *backend_type = backend_table->get_as<std::string>("type");
|
||||
if (backend_type == nullptr) {
|
||||
std::cerr << "Invalid runner config for " << name << ": missing type field\n";
|
||||
continue;
|
||||
}
|
||||
auto *args = backend_table->get_as<toml::array>("args");
|
||||
if (args == nullptr) {
|
||||
std::cerr << "Invalid runner config for " << name << ": missing args field\n";
|
||||
continue;
|
||||
}
|
||||
std::vector<std::string> args_vector;
|
||||
for (const auto &arg : *args) {
|
||||
auto *arg_string = arg.as_string();
|
||||
if (arg_string == nullptr) {
|
||||
std::cerr << "Invalid runner config for " << name << ": args must be strings\n";
|
||||
continue;
|
||||
}
|
||||
args_vector.emplace_back(*arg_string);
|
||||
}
|
||||
if (*backend_type == "docker") {
|
||||
auto *image = backend_table->get_as<std::string>("image");
|
||||
if (image == nullptr) {
|
||||
std::cerr << "Invalid runner config for " << name << ": missing image field\n";
|
||||
continue;
|
||||
}
|
||||
strategies.emplace(name, sk::docker_config{{std::move(args_vector)}, std::string{*image}});
|
||||
} else if (*backend_type == "bubblewrap") {
|
||||
strategies.emplace(name, sk::bubblewrap_config{std::move(args_vector)});
|
||||
} else {
|
||||
std::cerr << "Invalid runner config for " << name << ": unknown type " << *backend_type << "\n";
|
||||
}
|
||||
}
|
||||
auto config = toml::parse_file(confFile.string());
|
||||
toml::table *serverSection = config.get_as<toml::table>("serveurs");
|
||||
if (serverSection == nullptr) {
|
||||
return hosts;
|
||||
}
|
||||
return sk::config{
|
||||
sk::queue_config{
|
||||
config["queue"]["pull"].value_or("tcp://localhost:5557"),
|
||||
config["queue"]["push"].value_or("tcp://localhost:5558"),
|
||||
config["queue"]["threads"].value_or(1u),
|
||||
},
|
||||
sk::runner_config{
|
||||
config["runner"]["timeout"].value_or(2u),
|
||||
std::move(strategies),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sk::config sk::config::read_or_default(const std::filesystem::path &path, bool expect_present) {
|
||||
std::ifstream t(path);
|
||||
if (!t) {
|
||||
if (errno == ENOENT && !expect_present) {
|
||||
std::cout << "Using default config\n";
|
||||
} else {
|
||||
std::cerr << "Failed to open config file " << path << ": " << strerror(errno) << "\n";
|
||||
// Parcourir les serveurs
|
||||
for (const auto &[serverNameKey, serverInfoValue] : *serverSection) {
|
||||
std::string serverName{serverNameKey};
|
||||
toml::table *serverInfoPtr = serverInfoValue.as_table();
|
||||
if (serverInfoPtr == nullptr) {
|
||||
}
|
||||
toml::table &serverInfo = *serverInfoPtr;
|
||||
|
||||
if (serverInfo.get_as<std::string>("ip") &&
|
||||
serverInfo.get_as<int64_t>("nbContainerMax")) {
|
||||
std::string serverIp = serverInfo.get_as<std::string>("ip")->get();
|
||||
int serverMaxContainers =
|
||||
serverInfo.get_as<int64_t>("nbContainerMax")->get();
|
||||
|
||||
hosts.push_back(sk::host(serverIp, serverMaxContainers));
|
||||
}
|
||||
return read(toml::table{});
|
||||
}
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto config = toml::parse(buffer.str(), path);
|
||||
return read(config);
|
||||
|
||||
return hosts;
|
||||
}
|
||||
|
@ -1,37 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <toml++/toml.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sk {
|
||||
struct queue_config {
|
||||
std::string pull_addr;
|
||||
std::string push_addr;
|
||||
unsigned int nb_threads;
|
||||
};
|
||||
|
||||
struct bubblewrap_config {
|
||||
std::vector<std::string> args;
|
||||
};
|
||||
|
||||
struct docker_config : bubblewrap_config {
|
||||
std::string image;
|
||||
};
|
||||
#include "host.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using runner_strategy_config = std::variant<bubblewrap_config, docker_config>;
|
||||
|
||||
struct runner_config {
|
||||
unsigned int timeout;
|
||||
std::unordered_map<std::string, runner_strategy_config> strategies;
|
||||
};
|
||||
|
||||
struct config {
|
||||
queue_config queue;
|
||||
runner_config runner;
|
||||
|
||||
static config read_or_default(const std::filesystem::path &path, bool expect_present = false);
|
||||
namespace sk {
|
||||
class config {
|
||||
public:
|
||||
static std::vector<sk::host> loadHostsFromToml(const fs::path &confFile);
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
#include "host.hpp"
|
||||
|
||||
namespace sk {
|
||||
host::host(const std::string &ip, unsigned int connectionsMax)
|
||||
: ip{ip}, connections{0}, connectionsMax{connectionsMax}, runners{} {}
|
||||
|
||||
void host::addConnection(sk::runner &runner) {
|
||||
runners.push(runner);
|
||||
connections += 1;
|
||||
}
|
||||
|
||||
const std::string &host::getIp() const { return ip; }
|
||||
|
||||
unsigned int host::getNbConnections() const { return connections; }
|
||||
|
||||
unsigned int host::getNbConnectionsMax() const { return connectionsMax; }
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "runner.hpp"
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
namespace sk {
|
||||
class host {
|
||||
std::string ip;
|
||||
|
||||
unsigned int connections;
|
||||
unsigned int connectionsMax;
|
||||
|
||||
std::queue<sk::runner> runners;
|
||||
|
||||
public:
|
||||
host(const std::string &ip, unsigned int connectionsMax);
|
||||
void addConnection(sk::runner &runner);
|
||||
|
||||
const std::string &getIp() const;
|
||||
|
||||
unsigned int getNbConnections() const;
|
||||
unsigned int getNbConnectionsMax() const;
|
||||
|
||||
bool operator<(const host &other) const {
|
||||
return (connectionsMax - connections) <
|
||||
other.getNbConnectionsMax() - other.getNbConnections();
|
||||
}
|
||||
};
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
#include "network.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace sk {
|
||||
uint32_t read_uint32(const std::byte *buffer) { return static_cast<uint32_t>(buffer[3]) | static_cast<uint32_t>(buffer[2]) << 8 | static_cast<uint32_t>(buffer[1]) << 16 | static_cast<uint32_t>(buffer[0]) << 24; }
|
||||
void write_uint32(std::byte *buffer, uint32_t value) {
|
||||
buffer[0] = static_cast<std::byte>(value >> 24);
|
||||
buffer[1] = static_cast<std::byte>(value >> 16);
|
||||
buffer[2] = static_cast<std::byte>(value >> 8);
|
||||
buffer[3] = static_cast<std::byte>(value);
|
||||
}
|
||||
|
||||
void write_string(std::byte *buffer, std::string_view text) {
|
||||
auto size = static_cast<uint32_t>(text.size());
|
||||
write_uint32(buffer, size);
|
||||
memcpy(buffer, text.data(), size);
|
||||
}
|
||||
|
||||
std::tuple<zmq::message_t, std::byte *> prepare_headers(size_t data_len, int type, std::string_view jobId) {
|
||||
zmq::message_t reply(1 + JOB_ID_LEN + data_len);
|
||||
auto *reply_bytes = static_cast<std::byte *>(reply.data());
|
||||
*reply_bytes = static_cast<std::byte>(type);
|
||||
memcpy(reply_bytes + 1, jobId.data(), JOB_ID_LEN);
|
||||
return {std::move(reply), reply_bytes + 1 + JOB_ID_LEN};
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <zmq.hpp>
|
||||
|
||||
namespace sk {
|
||||
|
||||
static constexpr uint32_t JOB_ID_LEN = 32;
|
||||
|
||||
uint32_t read_uint32(const std::byte *buffer);
|
||||
void write_uint32(std::byte *buffer, uint32_t value);
|
||||
void write_string(std::byte *buffer, std::string_view text);
|
||||
|
||||
std::tuple<zmq::message_t, std::byte *> prepare_headers(size_t data_len, int type, std::string_view jobId);
|
||||
}
|
@ -1,87 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.hpp"
|
||||
#include "program.hpp"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace sk {
|
||||
struct [[nodiscard]] run_result {
|
||||
std::string out;
|
||||
std::string err;
|
||||
int exit_code;
|
||||
};
|
||||
|
||||
struct [[nodiscard]] active_job {
|
||||
std::string job_id;
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
enum class runner_backend { BubbleWrap, Docker };
|
||||
using pattern = std::variant<std::string, void *>;
|
||||
|
||||
class execution_strategy {
|
||||
std::vector<pattern> patterns;
|
||||
|
||||
public:
|
||||
explicit execution_strategy(const std::vector<std::string> &patterns);
|
||||
explicit execution_strategy(std::vector<pattern> patterns);
|
||||
virtual std::vector<const char *> start(const program &program) = 0;
|
||||
virtual void stop(const active_job &job) = 0;
|
||||
virtual ~execution_strategy() = default;
|
||||
static std::unique_ptr<execution_strategy> create(const runner_strategy_config &config);
|
||||
|
||||
protected:
|
||||
void concat_patterns(std::vector<const char *> &args, const program &program) const;
|
||||
};
|
||||
|
||||
class bubblewrap_execution_strategy : public execution_strategy {
|
||||
public:
|
||||
explicit bubblewrap_execution_strategy(const std::vector<std::string> &patterns);
|
||||
explicit bubblewrap_execution_strategy(std::vector<pattern> patterns);
|
||||
std::vector<const char *> start(const program &program) override;
|
||||
void stop(const active_job &job) override;
|
||||
};
|
||||
|
||||
class docker_execution_strategy : public execution_strategy {
|
||||
private:
|
||||
std::string image;
|
||||
|
||||
public:
|
||||
docker_execution_strategy(const std::vector<std::string> &patterns, std::string image);
|
||||
docker_execution_strategy(std::vector<pattern> patterns, std::string image);
|
||||
std::vector<const char *> start(const program &program) override;
|
||||
void stop(const active_job &job) override;
|
||||
};
|
||||
|
||||
class runner {
|
||||
std::vector<active_job> active_jobs;
|
||||
std::mutex active_jobs_mutex;
|
||||
std::unique_ptr<execution_strategy> backend;
|
||||
int timeout;
|
||||
runner_backend backend;
|
||||
|
||||
public:
|
||||
explicit runner(const runner_strategy_config &config, unsigned int timeout = 2u);
|
||||
runner(const runner &other);
|
||||
runner(runner &&other) noexcept;
|
||||
runner &operator=(const runner &other);
|
||||
runner &operator=(runner &&other) noexcept;
|
||||
explicit runner(runner_backend backend);
|
||||
run_result run_blocking(const program &program);
|
||||
bool kill_active(const std::string &jobId);
|
||||
void exit_active_jobs();
|
||||
void exit(const active_job &job);
|
||||
};
|
||||
|
||||
class runner_list {
|
||||
std::unordered_map<std::string, runner> runners;
|
||||
|
||||
public:
|
||||
runner_list(sk::runner_backend preferred_backend, const runner_config &config);
|
||||
runner *find_runner_for(const program &program);
|
||||
bool kill_active(const std::string &jobId);
|
||||
void exit_active_jobs();
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in new issue