mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
7.2 KiB
193 lines
7.2 KiB
/**
|
|
* @file reg_test_4001-restapi_scripts_num_fds-t.cpp
|
|
* @brief Regression test for checking that RESTAPI is able to execute scripts when ProxySQL is handling more
|
|
* than 'FD_SETSIZE' file descriptors.
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <chrono>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <string>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include "mysql.h"
|
|
|
|
#include "command_line.h"
|
|
#include "proxysql_utils.h"
|
|
#include "json.hpp"
|
|
#include "tap.h"
|
|
#include "utils.h"
|
|
|
|
using nlohmann::json;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
const int NUM_CONNECTIONS = 1300;
|
|
// base_address is constructed at runtime using the ProxySQL host from the environment.
|
|
// It is defined as a mutable global so check functions at file scope can access it.
|
|
static string base_address { "http://proxysql:6070/sync/" };
|
|
|
|
const vector<honest_req_t> honest_requests {
|
|
{ { "valid_output_script", "%s.py", "POST", 5000 }, { "{}" } },
|
|
{ { "valid_params_script", "%s.py", "POST", 5000 }, { "{\"param1\": \"value1\", \"param2\": \"value2\"}" } },
|
|
{ { "valid_params_script", "%s.py", "GET", 5000 }, { "", "?param1='value1'¶m2='\"value2\"'" } },
|
|
};
|
|
|
|
int main(int argc, char** argv) {
|
|
CommandLine cl;
|
|
|
|
diag("================================================================================");
|
|
diag("TEST DESCRIPTION: RESTAPI Stability with High File Descriptor Usage");
|
|
diag("This test verifies that ProxySQL's RESTAPI remains functional even when the");
|
|
diag("number of concurrent MySQL connections exceeds FD_SETSIZE (1024).");
|
|
diag("================================================================================");
|
|
|
|
if (cl.getEnv()) {
|
|
diag("ERROR: Failed to get the required environmental variables.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Build the RESTAPI base address using the actual ProxySQL host from the environment.
|
|
base_address = string("http://") + string(cl.host) + ":6070/sync/";
|
|
|
|
diag("Connection Context:");
|
|
diag(" - Target Host: %s", cl.host);
|
|
diag(" - Admin Port: %d", cl.admin_port);
|
|
diag(" - RESTAPI Base: %s", base_address.c_str());
|
|
|
|
const char* ws_env = getenv("WORKSPACE");
|
|
if (!ws_env) {
|
|
diag("ERROR: WORKSPACE environment variable not set. Cannot locate scripts.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// PROVISIONING: Copy scripts to /var/lib/proxysql so ProxySQL container can see them
|
|
string script_src = string(ws_env) + "/test/tap/tests/reg_test_3223_scripts";
|
|
string script_dst = "/var/lib/proxysql/reg_test_3223_scripts";
|
|
diag("Provisioning scripts:");
|
|
diag(" - Source: %s", script_src.c_str());
|
|
diag(" - Dest: %s", script_dst.c_str());
|
|
|
|
string cmd = "mkdir -p " + script_dst + " && cp -rp " + script_src + "/. " + script_dst + "/ && chmod -R 777 " + script_dst;
|
|
if (system(cmd.c_str()) != 0) {
|
|
diag("ERROR: Failed to provision scripts to %s", script_dst.c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
diag("Provisioning successful.");
|
|
|
|
diag("Setting new process limits beyond 'FD_SETSIZE'");
|
|
struct rlimit limits { 0, 0 };
|
|
getrlimit(RLIMIT_NOFILE, &limits);
|
|
diag("Old process limits: { %ld, %ld }", (long)limits.rlim_cur, (long)limits.rlim_max);
|
|
limits.rlim_cur = NUM_CONNECTIONS * 2 + 100;
|
|
setrlimit(RLIMIT_NOFILE, &limits);
|
|
diag("New process limits: { %ld, %ld }", (long)limits.rlim_cur, (long)limits.rlim_max);
|
|
|
|
MYSQL* admin = mysql_init(NULL);
|
|
if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
|
|
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
diag("Enabling RESTAPI in ProxySQL...");
|
|
MYSQL_QUERY(admin, "SET admin-restapi_enabled='true'");
|
|
MYSQL_QUERY(admin, "LOAD ADMIN VARIABLES TO RUNTIME");
|
|
diag("RESTAPI enabled.");
|
|
|
|
string script_base_path = script_dst;
|
|
const ept_info_t dummy_ept { "dummy_ept_script", "%s.py", "POST", 5000 };
|
|
|
|
vector<ept_info_t> v_epts_info {};
|
|
for (const auto& req : honest_requests) v_epts_info.push_back(req.ept_info);
|
|
|
|
diag("Configuring RESTAPI endpoints using scripts in: %s", script_base_path.c_str());
|
|
int ept_conf_res = configure_endpoints(admin, script_base_path, v_epts_info, dummy_ept, true);
|
|
if (ept_conf_res) {
|
|
diag("Endpoint configuration failed. Check ProxySQL log for execution errors.");
|
|
mysql_close(admin);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::vector<MYSQL*> mysql_connections {};
|
|
diag("Establishing %d baseline connections to ProxySQL...", NUM_CONNECTIONS);
|
|
for (int i = 0; i < NUM_CONNECTIONS; i++) {
|
|
MYSQL* proxy = mysql_init(NULL);
|
|
if (!mysql_real_connect(proxy, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
|
|
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
|
|
return EXIT_FAILURE;
|
|
}
|
|
mysql_connections.push_back(proxy);
|
|
if ((i + 1) % 200 == 0) diag(" Established %d/%d connections...", i + 1, NUM_CONNECTIONS);
|
|
}
|
|
|
|
typedef std::chrono::high_resolution_clock hrc;
|
|
const uint64_t test_duration = 10000;
|
|
const uint32_t FAILURE_RATE = 10;
|
|
int conn_creation_res = EXIT_FAILURE;
|
|
uint64_t conn_count = 0;
|
|
uint64_t mysql_fails = 0;
|
|
|
|
diag("Starting simultaneous RESTAPI requests and connection churn...");
|
|
{
|
|
std::thread create_conns([&]() -> int {
|
|
hrc::time_point start = hrc::now();
|
|
while (true) {
|
|
if (conn_count > 0 && mysql_fails > (conn_count * FAILURE_RATE) / 100) {
|
|
conn_creation_res = EXIT_FAILURE;
|
|
return EXIT_FAILURE;
|
|
}
|
|
MYSQL* proxy = mysql_init(NULL);
|
|
if (!mysql_real_connect(proxy, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
|
|
mysql_fails += 1;
|
|
} else {
|
|
if (mysql_query(proxy, "DO 1")) mysql_fails += 1;
|
|
mysql_close(proxy);
|
|
}
|
|
if (std::chrono::duration_cast<std::chrono::milliseconds>(hrc::now() - start).count() >= test_duration) break;
|
|
conn_count += 1;
|
|
}
|
|
conn_creation_res = EXIT_SUCCESS;
|
|
return EXIT_SUCCESS;
|
|
});
|
|
|
|
hrc::time_point start = hrc::now();
|
|
int api_request_count = 0;
|
|
while (true) {
|
|
int rc = 0;
|
|
for (const honest_req_t& req : honest_requests) {
|
|
for (const string& params : req.params) {
|
|
const string ept { join_path(base_address, req.ept_info.name) };
|
|
std::string curl_res_data {};
|
|
uint64_t curl_res_code = 0;
|
|
CURLcode curl_err = (req.ept_info.method == "POST") ?
|
|
perform_simple_post(ept, params, curl_res_code, curl_res_data) :
|
|
perform_simple_get(ept + params, curl_res_code, curl_res_data);
|
|
|
|
ok(curl_err == CURLE_OK && curl_res_code == 200, "RESTAPI request should succeed");
|
|
if (curl_err != CURLE_OK || curl_res_code != 200) {
|
|
diag(" Failure detail: method=%s, endpoint=%s, curl_err=%d, res_code=%ld, data=%s",
|
|
req.ept_info.method.c_str(), ept.c_str(), curl_err, curl_res_code, curl_res_data.c_str());
|
|
}
|
|
if (curl_err == 7) { rc = 1; break; }
|
|
api_request_count++;
|
|
}
|
|
if (rc) break;
|
|
}
|
|
if (std::chrono::duration_cast<std::chrono::milliseconds>(hrc::now() - start).count() >= test_duration || rc) break;
|
|
}
|
|
create_conns.join();
|
|
diag("Test finished. API requests: %d, Churn conns: %ld, Failures: %ld", api_request_count, conn_count, mysql_fails);
|
|
ok(conn_creation_res == EXIT_SUCCESS, "MySQL failure rate within limits");
|
|
}
|
|
|
|
for (MYSQL* conn : mysql_connections) mysql_close(conn);
|
|
mysql_close(admin);
|
|
curl_global_cleanup();
|
|
return exit_status();
|
|
}
|