From f35798fe2c6ccd53f3c7ba95409ca6cb1643689b Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 22 Nov 2024 20:23:07 +0000 Subject: [PATCH 1/2] Implement health check endpoint and tests --- src/app/ClioApplication.cpp | 18 ++++++++++ src/etlng/impl/GrpcSource.cpp | 5 +-- src/web/impl/HttpBase.hpp | 11 ++++++ tests/common/util/TestHttpClient.cpp | 9 ++--- tests/common/util/TestHttpClient.hpp | 6 ++-- tests/unit/web/ServerTests.cpp | 51 +++++++++++++++++++++++----- 6 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/app/ClioApplication.cpp b/src/app/ClioApplication.cpp index a9573659b..aceb9a290 100644 --- a/src/app/ClioApplication.cpp +++ b/src/app/ClioApplication.cpp @@ -65,6 +65,14 @@ namespace app { namespace { +auto constexpr HealthCheckHTML = R"html( + + + Test page for Clio +

Clio Test

This page shows Clio http(s) connectivity is working.

+ +)html"; + /** * @brief Start context threads * @@ -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, diff --git a/src/etlng/impl/GrpcSource.cpp b/src/etlng/impl/GrpcSource.cpp index 67791a381..adcbba7e7 100644 --- a/src/etlng/impl/GrpcSource.cpp +++ b/src/etlng/impl/GrpcSource.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -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); } diff --git a/src/web/impl/HttpBase.hpp b/src/web/impl/HttpBase.hpp index 609eb7e2d..55a664bf9 100644 --- a/src/web/impl/HttpBase.hpp +++ b/src/web/impl/HttpBase.hpp @@ -62,6 +62,14 @@ namespace web::impl { +static auto constexpr HealthCheckHTML = R"html( + + + Test page for Clio +

Clio Test

This page shows Clio http(s) connectivity is working.

+ +)html"; + using tcp = boost::asio::ip::tcp; /** @@ -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); diff --git a/tests/common/util/TestHttpClient.cpp b/tests/common/util/TestHttpClient.cpp index 6c7a4f2a6..e90783e79 100644 --- a/tests/common/util/TestHttpClient.cpp +++ b/tests/common/util/TestHttpClient.cpp @@ -36,6 +36,7 @@ #include // IWYU pragma: keep #include #include +#include #include #include #include // IWYU pragma: keep @@ -58,7 +59,7 @@ using tcp = boost::asio::ip::tcp; namespace { -std::string +std::pair syncRequest( std::string const& host, std::string const& port, @@ -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 @@ -105,7 +106,7 @@ WebHeader::WebHeader(http::field name, std::string value) : name(name), value(st { } -std::string +std::pair HttpSyncClient::post( std::string const& host, std::string const& port, @@ -116,7 +117,7 @@ HttpSyncClient::post( return syncRequest(host, port, body, std::move(additionalHeaders), http::verb::post); } -std::string +std::pair HttpSyncClient::get( std::string const& host, std::string const& port, diff --git a/tests/common/util/TestHttpClient.hpp b/tests/common/util/TestHttpClient.hpp index 2a5e5d4e5..f5e64a8f5 100644 --- a/tests/common/util/TestHttpClient.hpp +++ b/tests/common/util/TestHttpClient.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include struct WebHeader { @@ -43,7 +45,7 @@ struct WebHeader { }; struct HttpSyncClient { - static std::string + static std::pair post( std::string const& host, std::string const& port, @@ -51,7 +53,7 @@ struct HttpSyncClient { std::vector additionalHeaders = {} ); - static std::string + static std::pair get(std::string const& host, std::string const& port, std::string const& body, diff --git a/tests/unit/web/ServerTests.cpp b/tests/unit/web/ServerTests.cpp index 658c36da3..87a3a4b79 100644 --- a/tests/unit/web/ServerTests.cpp +++ b/tests/unit/web/ServerTests.cpp @@ -214,8 +214,9 @@ TEST_F(WebServerTest, Http) { auto e = std::make_shared(); 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) @@ -233,11 +234,12 @@ TEST_F(WebServerTest, HttpInternalError) { auto e = std::make_shared(); 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) @@ -316,13 +318,16 @@ TEST_F(WebServerTest, HttpRequestOverload) { auto e = std::make_shared(); 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) @@ -349,11 +354,12 @@ TEST_F(WebServerTest, HttpPayloadOverload) std::string const s100(100, 'a'); auto e = std::make_shared(); 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) @@ -393,6 +399,26 @@ TEST_F(WebServerTest, WsTooManyConnection) EXPECT_TRUE(exceptionThrown); } +TEST_F(WebServerTest, HealthCheck) +{ + auto e = std::make_shared(); // 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(); // 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) { @@ -500,8 +526,11 @@ TEST_P(WebServerAdminTest, HttpAdminCheck) auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e); std::string const request = "Why hello"; uint32_t const webServerPort = serverConfig.value("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( @@ -618,8 +647,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) @@ -641,7 +672,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), "", @@ -652,6 +683,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) @@ -662,7 +694,7 @@ TEST_F(WebServerPrometheusTest, validResponse) auto e = std::make_shared(); 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), "", @@ -673,4 +705,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); } From 9a90b59ae568ebc5995824378ed66e6237cfd473 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Sat, 23 Nov 2024 03:05:01 +0000 Subject: [PATCH 2/2] Add tuple include --- tests/unit/web/ServerTests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/web/ServerTests.cpp b/tests/unit/web/ServerTests.cpp index 87a3a4b79..e7013457a 100644 --- a/tests/unit/web/ServerTests.cpp +++ b/tests/unit/web/ServerTests.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include