178 lines
7.1 KiB
C++
178 lines
7.1 KiB
C++
#include "../../include/glove/control/remote_protocol.hpp"
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (false)
|
|
|
|
class pipe_pair {
|
|
public:
|
|
pipe_pair() {
|
|
std::array<int, 2> descriptors{-1, -1};
|
|
if (::pipe(descriptors.data()) == 0) {
|
|
read_ = descriptors[0];
|
|
write_ = descriptors[1];
|
|
}
|
|
}
|
|
|
|
pipe_pair(const pipe_pair&) = delete;
|
|
auto operator=(const pipe_pair&) -> pipe_pair& = delete;
|
|
|
|
~pipe_pair() {
|
|
if (read_ >= 0) {
|
|
(void)::close(read_);
|
|
}
|
|
if (write_ >= 0) {
|
|
(void)::close(write_);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] auto read_end() const noexcept -> int { return read_; }
|
|
|
|
[[nodiscard]] auto write_end() const noexcept -> int { return write_; }
|
|
|
|
private:
|
|
int read_ = -1;
|
|
int write_ = -1;
|
|
};
|
|
|
|
auto run() -> int {
|
|
using namespace glove::control;
|
|
constexpr std::array<remote_method, 10> methods{
|
|
remote_method::remote_health,
|
|
remote_method::remote_prepare,
|
|
remote_method::remote_start,
|
|
remote_method::remote_read,
|
|
remote_method::remote_write_input,
|
|
remote_method::remote_resize,
|
|
remote_method::remote_signal,
|
|
remote_method::remote_stop,
|
|
remote_method::remote_wait,
|
|
remote_method::remote_cleanup,
|
|
};
|
|
for (const auto method : methods) {
|
|
const auto name = remote_method_name(method);
|
|
REQUIRE(!name.empty());
|
|
REQUIRE(parse_remote_method(name) == method);
|
|
}
|
|
REQUIRE(!parse_remote_method("remote_shell").has_value());
|
|
|
|
const remote_executor_identity identity{
|
|
.executor_digest = "sha256:" + std::string(64U, 'a'),
|
|
.container_image_digest = "sha256:" + std::string(64U, 'b'),
|
|
.workerd_digest = {},
|
|
.descriptor_digest = {},
|
|
};
|
|
auto health = encode_remote_request("health-1", remote_method::remote_health, 5'000);
|
|
REQUIRE(health.has_value());
|
|
const auto receive_started_at = std::chrono::steady_clock::now();
|
|
const auto received_at = receive_started_at + std::chrono::milliseconds{25};
|
|
auto request_deadline = remote_request_deadline(*health, receive_started_at, received_at);
|
|
REQUIRE(request_deadline.has_value());
|
|
REQUIRE(*request_deadline == received_at + std::chrono::milliseconds{4'975});
|
|
|
|
// A frame that consumes its request TTL while arriving is already expired.
|
|
// Synthetic monotonic points keep this regression fast and deterministic.
|
|
auto short_ttl = encode_remote_request("slow-health", remote_method::remote_health, 20);
|
|
REQUIRE(short_ttl.has_value());
|
|
auto expired_during_receive =
|
|
remote_request_deadline(*short_ttl, receive_started_at, received_at);
|
|
REQUIRE(!expired_during_receive.has_value());
|
|
REQUIRE(expired_during_receive.error().find("deadline") != std::string::npos);
|
|
auto health_reply = handle_remote_executor_request(*health, identity);
|
|
REQUIRE(health_reply.has_value());
|
|
auto decoded_health = decode_remote_response(*health_reply);
|
|
REQUIRE(decoded_health.has_value());
|
|
REQUIRE(decoded_health->id == "health-1");
|
|
REQUIRE(decoded_health->status == "not_operational");
|
|
REQUIRE(decoded_health->error_code.empty());
|
|
REQUIRE(decoded_health->executor_digest == identity.executor_digest);
|
|
REQUIRE(decoded_health->container_image_digest == identity.container_image_digest);
|
|
REQUIRE(decoded_health->observation_authority == "trusted_remote_claim");
|
|
REQUIRE(!decoded_health->independently_verified);
|
|
|
|
for (const auto method : methods) {
|
|
if (method == remote_method::remote_health) {
|
|
continue;
|
|
}
|
|
auto request = encode_remote_request("lifecycle-1", method, 5'000);
|
|
REQUIRE(request.has_value());
|
|
auto reply = handle_remote_executor_request(*request, identity);
|
|
REQUIRE(reply.has_value());
|
|
auto decoded = decode_remote_response(*reply);
|
|
REQUIRE(decoded.has_value());
|
|
REQUIRE(decoded->status.empty());
|
|
REQUIRE(decoded->error_code == "method_not_found");
|
|
}
|
|
REQUIRE(!handle_remote_executor_request(*health, {}).has_value());
|
|
REQUIRE(!encode_remote_request("bad id", remote_method::remote_health, 5'000).has_value());
|
|
REQUIRE(!encode_remote_request("health", remote_method::remote_health, 0).has_value());
|
|
REQUIRE(
|
|
!encode_remote_request("health", remote_method::remote_health, max_remote_deadline_ms + 1U)
|
|
.has_value()
|
|
);
|
|
constexpr std::string_view malformed =
|
|
R"({"jsonrpc":"2.0","id":"x","method":"remote_health","deadline_remaining_ms":1,"payload":null,"extra":true})";
|
|
REQUIRE(!handle_remote_executor_request(malformed, identity).has_value());
|
|
REQUIRE(!remote_request_deadline(malformed, receive_started_at, received_at).has_value());
|
|
REQUIRE(!handle_remote_executor_request(
|
|
*health,
|
|
remote_executor_identity{
|
|
.executor_digest = {},
|
|
.container_image_digest = {},
|
|
.workerd_digest = {},
|
|
.descriptor_digest = {},
|
|
}
|
|
)
|
|
.has_value());
|
|
|
|
pipe_pair framed;
|
|
REQUIRE(framed.read_end() >= 0);
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
REQUIRE(write_remote_frame(framed.write_end(), *health, deadline).has_value());
|
|
auto read = read_remote_frame(framed.read_end(), deadline);
|
|
REQUIRE(read == health);
|
|
REQUIRE(!write_remote_frame(
|
|
framed.write_end(), std::string(max_remote_frame_bytes + 1U, 'x'), deadline
|
|
)
|
|
.has_value());
|
|
|
|
pipe_pair oversized;
|
|
const std::array<unsigned char, 4> oversized_header{0x00U, 0x10U, 0x00U, 0x01U};
|
|
REQUIRE(
|
|
::write(oversized.write_end(), oversized_header.data(), oversized_header.size()) ==
|
|
static_cast<ssize_t>(oversized_header.size())
|
|
);
|
|
REQUIRE(!read_remote_frame(
|
|
oversized.read_end(), std::chrono::steady_clock::now() + std::chrono::seconds{1}
|
|
)
|
|
.has_value());
|
|
|
|
pipe_pair deadline_pipe;
|
|
const auto started = std::chrono::steady_clock::now();
|
|
auto timed_out =
|
|
read_remote_frame(deadline_pipe.read_end(), started + std::chrono::milliseconds{20});
|
|
REQUIRE(!timed_out.has_value());
|
|
REQUIRE(timed_out.error().find("deadline") != std::string::npos);
|
|
REQUIRE(std::chrono::steady_clock::now() - started < std::chrono::seconds{1});
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main() -> int {
|
|
return run();
|
|
}
|