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.
228 lines
6.9 KiB
228 lines
6.9 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.
|
|
*
|
|
* @details The tests creates a higher number of connections than the default maximum number of file
|
|
* descriptors determined by `FD_SETSIZE` (1024). After doing this, it tries to enable the 'RESTAPI' and
|
|
* performs requests to different endpoints while constantly creating and destroying new client connections.
|
|
* This covers two scenarios:
|
|
* - The usage of the RESTAPI when ProxySQL is using more than `FD_SETSIZE` file descriptors.
|
|
* - The reproduction of the scenario leading to crash reported in issue #4001.
|
|
*/
|
|
|
|
#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;
|
|
const string base_address { "http://localhost:6070/sync/" };
|
|
|
|
const vector<honest_req_t> honest_requests {
|
|
{ { "valid_output_script", "%s.py", "POST", 1000 }, { "{}" } },
|
|
// Check that 'POST' correctly forwards supplied parameters:
|
|
// 1 - Target script performs a check on the supplied parameters and fails in case they are unexpected
|
|
{ { "valid_params_script", "%s.py", "POST", 1000 }, { "{\"param1\": \"value1\", \"param2\": \"value2\"}" } },
|
|
// On top of the previous check, also check that 'GET' allows:
|
|
// 1 - Empty parameters: We internally translate into an empty well-formed JSON '{}'
|
|
// 2 - Escaped values: As long as the JSON is correct, the RESTAPI forwarding should be able to handle it
|
|
{ { "valid_params_script", "%s.py", "GET", 1000 }, { "", "?param1='value1'¶m2='\"value2\"'" } },
|
|
};
|
|
|
|
int main(int argc, char** argv) {
|
|
CommandLine cl;
|
|
|
|
if (cl.getEnv()) {
|
|
diag("Failed to get the required environmental variables.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
diag("Setting new process limits beyond 'FD_SETSIZE'");
|
|
struct rlimit limits { 0, 0 };
|
|
getrlimit(RLIMIT_NOFILE, &limits);
|
|
diag("Old process limits: { %ld, %ld }", limits.rlim_cur, limits.rlim_max);
|
|
limits.rlim_cur = NUM_CONNECTIONS * 2 + 100;
|
|
setrlimit(RLIMIT_NOFILE, &limits);
|
|
diag("New process limits: { %ld, %ld }", limits.rlim_cur, limits.rlim_max);
|
|
|
|
MYSQL* admin = mysql_init(NULL);
|
|
|
|
// Initialize connections
|
|
if (!admin) {
|
|
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
string script_base_path { string { cl.workdir } + "reg_test_3223_scripts" };
|
|
const ept_info_t dummy_ept { "dummy_ept_script", "%s.py", "POST", 1000 };
|
|
|
|
vector<ept_info_t> v_epts_info {};
|
|
const auto ext_v_epts_info = [] (const honest_req_t& req) { return req.ept_info; };
|
|
std::transform(
|
|
honest_requests.begin(), honest_requests.end(), std::back_inserter(v_epts_info), ext_v_epts_info
|
|
);
|
|
|
|
int ept_conf_res = configure_endpoints(admin, script_base_path, v_epts_info, dummy_ept, true);
|
|
if (ept_conf_res) {
|
|
diag("Endpoint configuration failed. Skipping endpoint testing...");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::vector<MYSQL*> mysql_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);
|
|
}
|
|
|
|
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;
|
|
|
|
{
|
|
std::thread create_conns([&]() -> int {
|
|
std::chrono::nanoseconds duration;
|
|
hrc::time_point start;
|
|
hrc::time_point end;
|
|
|
|
start = hrc::now();
|
|
|
|
while (true) {
|
|
if (mysql_fails > (conn_count * FAILURE_RATE) / 100) {
|
|
diag("Too many mysql failures in connection creation, considering test as failed...");
|
|
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)) {
|
|
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
|
|
mysql_fails += 1;
|
|
}
|
|
|
|
int rc = mysql_query(proxy, "DO 1");
|
|
if (rc) {
|
|
diag("mysql_errno: '%d', mysql_error: '%s'", mysql_errno(proxy), mysql_error(proxy));
|
|
mysql_fails += 1;
|
|
}
|
|
|
|
mysql_close(proxy);
|
|
end = hrc::now();
|
|
duration = end - start;
|
|
|
|
if (duration.count() >= (test_duration*1000*1000)) {
|
|
break;
|
|
}
|
|
|
|
conn_count += 1;
|
|
}
|
|
|
|
conn_creation_res = EXIT_SUCCESS;
|
|
return EXIT_SUCCESS;
|
|
});
|
|
|
|
std::chrono::nanoseconds duration;
|
|
hrc::time_point start;
|
|
hrc::time_point end;
|
|
|
|
start = hrc::now();
|
|
|
|
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 = CURLE_COULDNT_CONNECT;
|
|
|
|
if (req.ept_info.method == "POST") {
|
|
curl_err = perform_simple_post(ept, params, curl_res_code, curl_res_data);
|
|
|
|
ok(
|
|
curl_err == CURLE_OK && curl_res_code == 200,
|
|
"'%s' over '%s' should result into a '200' res code: (curl_err: '%d', curl_res_code: '%ld')",
|
|
req.ept_info.method.c_str(), ept.c_str(), curl_err, curl_res_code
|
|
);
|
|
} else {
|
|
const string get_ept { ept + params };
|
|
curl_err = perform_simple_get(get_ept, curl_res_code, curl_res_data);
|
|
|
|
ok(
|
|
curl_err == CURLE_OK && curl_res_code == 200,
|
|
"'%s' over '%s' should result into a '200' res code: (curl_err: '%d', curl_res_code: '%ld')",
|
|
req.ept_info.method.c_str(), ept.c_str(), curl_err, curl_res_code
|
|
);
|
|
}
|
|
|
|
if (curl_err == 7) {
|
|
diag("Operation over endpoint failed with 'CURLE_COULDNT_CONNECT'. Aborting...");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
end = hrc::now();
|
|
duration = end - start;
|
|
|
|
if (duration.count() >= test_duration*1000*1000 || rc) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
create_conns.join();
|
|
|
|
ok(
|
|
conn_creation_res == EXIT_SUCCESS,
|
|
"MySQL conns operations shouldn't had more than a '%d'%% failure rate - conn_count: %ld, failures: %ld",
|
|
FAILURE_RATE, conn_count, mysql_fails
|
|
);
|
|
}
|
|
skip_endpoints_testing:
|
|
|
|
for (MYSQL* conn : mysql_connections) {
|
|
mysql_close(conn);
|
|
}
|
|
|
|
mysql_close(admin);
|
|
|
|
curl_global_cleanup();
|
|
|
|
return exit_status();
|
|
}
|