Skip to content

Commit

Permalink
feat: Healthcheck endpoint (#1751)
Browse files Browse the repository at this point in the history
Fixes #1759
  • Loading branch information
godexsoft authored Nov 26, 2024
1 parent 541bf43 commit fd73b90
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 19 deletions.
18 changes: 18 additions & 0 deletions src/app/ClioApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ namespace app {

namespace {

auto constexpr HealthCheckHTML = R"html(
<!DOCTYPE html>
<html>
<head><title>Test page for Clio</title></head>
<body><h1>Clio Test</h1><p>This page shows Clio http(s) connectivity is working.</p></body>
</html>
)html";

/**
* @brief Start context threads
*
Expand Down Expand Up @@ -178,6 +186,16 @@ ClioApplication::run(bool const useNgWebServer)
}
);

httpServer->onGet(
"/health",
[](web::ng::Request const& request,
web::ng::ConnectionMetadata&,
web::SubscriptionContextPtr,
boost::asio::yield_context) -> web::ng::Response {
return web::ng::Response{boost::beast::http::status::ok, HealthCheckHTML, request};
}
);

util::Logger webServerLog{"WebServer"};
auto onRequest = [adminVerifier, &webServerLog, &handler](
web::ng::Request const& request,
Expand Down
5 changes: 1 addition & 4 deletions src/etlng/impl/GrpcSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
#include <cstddef>
#include <cstdint>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
Expand All @@ -49,10 +48,8 @@ resolve(std::string const& ip, std::string const& port)
{
web::Resolver resolver;

if (auto const results = resolver.resolve(ip, port); not results.empty()) {
std::cout << "resolved ip: '" << results.at(0) << '\n';
if (auto const results = resolver.resolve(ip, port); not results.empty())
return results.at(0);
}

throw std::runtime_error("Failed to resolve " + ip + ":" + port);
}
Expand Down
11 changes: 11 additions & 0 deletions src/web/impl/HttpBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@

namespace web::impl {

static auto constexpr HealthCheckHTML = R"html(
<!DOCTYPE html>
<html>
<head><title>Test page for Clio</title></head>
<body><h1>Clio Test</h1><p>This page shows Clio http(s) connectivity is working.</p></body>
</html>
)html";

using tcp = boost::asio::ip::tcp;

/**
Expand Down Expand Up @@ -207,6 +215,9 @@ class HttpBase : public ConnectionBase {
if (ec)
return httpFail(ec, "read");

if (req_.method() == http::verb::get and req_.target() == "/health")
return sender_(httpResponse(http::status::ok, "text/html", HealthCheckHTML));

// Update isAdmin property of the connection
ConnectionBase::isAdmin_ = adminVerification_->isAdmin(req_, this->clientIp);

Expand Down
9 changes: 5 additions & 4 deletions tests/common/util/TestHttpClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <boost/beast/http.hpp> // IWYU pragma: keep
#include <boost/beast/http/field.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/beast/http/write.hpp> // IWYU pragma: keep
Expand All @@ -58,7 +59,7 @@ using tcp = boost::asio::ip::tcp;

namespace {

std::string
std::pair<boost::beast::http::status, std::string>
syncRequest(
std::string const& host,
std::string const& port,
Expand Down Expand Up @@ -96,7 +97,7 @@ syncRequest(
boost::beast::error_code ec;
stream.socket().shutdown(tcp::socket::shutdown_both, ec);

return res.body();
return {res.result(), res.body()};
}

} // namespace
Expand All @@ -105,7 +106,7 @@ WebHeader::WebHeader(http::field name, std::string value) : name(name), value(st
{
}

std::string
std::pair<boost::beast::http::status, std::string>
HttpSyncClient::post(
std::string const& host,
std::string const& port,
Expand All @@ -116,7 +117,7 @@ HttpSyncClient::post(
return syncRequest(host, port, body, std::move(additionalHeaders), http::verb::post);
}

std::string
std::pair<boost::beast::http::status, std::string>
HttpSyncClient::get(
std::string const& host,
std::string const& port,
Expand Down
6 changes: 4 additions & 2 deletions tests/common/util/TestHttpClient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http/field.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/string_body.hpp>

#include <chrono>
#include <expected>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

struct WebHeader {
Expand All @@ -43,15 +45,15 @@ struct WebHeader {
};

struct HttpSyncClient {
static std::string
static std::pair<boost::beast::http::status, std::string>
post(
std::string const& host,
std::string const& port,
std::string const& body,
std::vector<WebHeader> additionalHeaders = {}
);

static std::string
static std::pair<boost::beast::http::status, std::string>
get(std::string const& host,
std::string const& port,
std::string const& body,
Expand Down
52 changes: 43 additions & 9 deletions tests/unit/web/ServerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -214,8 +215,9 @@ TEST_F(WebServerTest, Http)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const res = HttpSyncClient::post("localhost", port, R"({"Hello":1})");
auto const [status, res] = HttpSyncClient::post("localhost", port, R"({"Hello":1})");
EXPECT_EQ(res, R"({"Hello":1})");
EXPECT_EQ(status, boost::beast::http::status::ok);
}

TEST_F(WebServerTest, Ws)
Expand All @@ -233,11 +235,12 @@ TEST_F(WebServerTest, HttpInternalError)
{
auto e = std::make_shared<ExceptionExecutor>();
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const res = HttpSyncClient::post("localhost", port, R"({})");
auto const [status, res] = HttpSyncClient::post("localhost", port, R"({})");
EXPECT_EQ(
res,
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response"})"
);
EXPECT_EQ(status, boost::beast::http::status::internal_server_error);
}

TEST_F(WebServerTest, WsInternalError)
Expand Down Expand Up @@ -316,13 +319,16 @@ TEST_F(WebServerTest, HttpRequestOverload)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, dosGuardOverload, e);
auto res = HttpSyncClient::post("localhost", port, R"({})");
auto [status, res] = HttpSyncClient::post("localhost", port, R"({})");
EXPECT_EQ(res, "{}");
res = HttpSyncClient::post("localhost", port, R"({})");
EXPECT_EQ(status, boost::beast::http::status::ok);

std::tie(status, res) = HttpSyncClient::post("localhost", port, R"({})");
EXPECT_EQ(
res,
R"({"error":"slowDown","error_code":10,"error_message":"You are placing too much load on the server.","status":"error","type":"response"})"
);
EXPECT_EQ(status, boost::beast::http::status::service_unavailable);
}

TEST_F(WebServerTest, WsRequestOverload)
Expand All @@ -349,11 +355,12 @@ TEST_F(WebServerTest, HttpPayloadOverload)
std::string const s100(100, 'a');
auto e = std::make_shared<EchoExecutor>();
auto server = makeServerSync(cfg, ctx, dosGuardOverload, e);
auto const res = HttpSyncClient::post("localhost", port, fmt::format(R"({{"payload":"{}"}})", s100));
auto const [status, res] = HttpSyncClient::post("localhost", port, fmt::format(R"({{"payload":"{}"}})", s100));
EXPECT_EQ(
res,
R"({"payload":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","warning":"load","warnings":[{"id":2003,"message":"You are about to be rate limited"}]})"
);
EXPECT_EQ(status, boost::beast::http::status::ok);
}

TEST_F(WebServerTest, WsPayloadOverload)
Expand Down Expand Up @@ -393,6 +400,26 @@ TEST_F(WebServerTest, WsTooManyConnection)
EXPECT_TRUE(exceptionThrown);
}

TEST_F(WebServerTest, HealthCheck)
{
auto e = std::make_shared<ExceptionExecutor>(); // request handled before we get to executor
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/health");

EXPECT_FALSE(res.empty());
EXPECT_EQ(status, boost::beast::http::status::ok);
}

TEST_F(WebServerTest, GetOtherThanHealthCheck)
{
auto e = std::make_shared<ExceptionExecutor>(); // request handled before we get to executor
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const [status, res] = HttpSyncClient::get("localhost", port, "", "/");

EXPECT_FALSE(res.empty());
EXPECT_EQ(status, boost::beast::http::status::bad_request);
}

std::string
JSONServerConfigWithAdminPassword(uint32_t const port)
{
Expand Down Expand Up @@ -500,8 +527,11 @@ TEST_P(WebServerAdminTest, HttpAdminCheck)
auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e);
std::string const request = "Why hello";
uint32_t const webServerPort = serverConfig.value<uint32_t>("server.port");
auto const res = HttpSyncClient::post("localhost", std::to_string(webServerPort), request, GetParam().headers);
auto const [status, res] =
HttpSyncClient::post("localhost", std::to_string(webServerPort), request, GetParam().headers);

EXPECT_EQ(res, fmt::format("{} {}", request, GetParam().expectedResponse));
EXPECT_EQ(status, boost::beast::http::status::ok);
}

INSTANTIATE_TEST_CASE_P(
Expand Down Expand Up @@ -618,8 +648,10 @@ TEST_F(WebServerPrometheusTest, rejectedWithoutAdminPassword)
uint32_t const webServerPort = tests::util::generateFreePort();
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::get("localhost", std::to_string(webServerPort), "", "/metrics");
auto const [status, res] = HttpSyncClient::get("localhost", std::to_string(webServerPort), "", "/metrics");

EXPECT_EQ(res, "Only admin is allowed to collect metrics");
EXPECT_EQ(status, boost::beast::http::status::unauthorized);
}

TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
Expand All @@ -641,7 +673,7 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
Config const serverConfig{boost::json::parse(JSONServerConfigWithDisabledPrometheus)};
PrometheusService::init(serverConfig);
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::get(
auto const [status, res] = HttpSyncClient::get(
"localhost",
std::to_string(webServerPort),
"",
Expand All @@ -652,6 +684,7 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
)}
);
EXPECT_EQ(res, "Prometheus is disabled in clio config");
EXPECT_EQ(status, boost::beast::http::status::forbidden);
}

TEST_F(WebServerPrometheusTest, validResponse)
Expand All @@ -662,7 +695,7 @@ TEST_F(WebServerPrometheusTest, validResponse)
auto e = std::make_shared<EchoExecutor>();
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::get(
auto const [status, res] = HttpSyncClient::get(
"localhost",
std::to_string(webServerPort),
"",
Expand All @@ -673,4 +706,5 @@ TEST_F(WebServerPrometheusTest, validResponse)
)}
);
EXPECT_EQ(res, "# TYPE test_counter counter\ntest_counter 1\n\n");
EXPECT_EQ(status, boost::beast::http::status::ok);
}

0 comments on commit fd73b90

Please sign in to comment.