Fix max_connections_ff-t: Use dynamic hostgroup discovery

- Add discover_user_hostgroup() helper function to query default_hostgroup
  from mysql_users table instead of hardcoding hostgroup=0
- Update test_ff_sess_exceeds_max_conns() to use discovered hostgroup
- Update test_ff_only_one_free_conn() to use discovered hostgroup
- Update documentation to reflect dynamic hostgroup discovery
- Add test/README.md with local CI execution instructions

This allows the test to work with any infrastructure configuration
(e.g., isolated CI where sbtest1 uses hostgroup 1300 instead of 0).
pull/5484/head
Rene Cannao 2 months ago
parent 3ac0c2dd8a
commit 3acd6b31d6

@ -0,0 +1,71 @@
# ProxySQL Test Suite
This directory contains the ProxySQL test suite, including TAP tests and infrastructure for running them.
## Quick Start: Running Tests Locally
To run tests using the local CI infrastructure (Docker-based isolation):
```bash
# 1. Set up environment
export WORKSPACE=$(pwd)
export INFRA_ID="test-$(date +%s)" # Unique ID using timestamp
export TAP_GROUP="mysql84-g1" # Or another group like "legacy-g1"
export TEST_PY_TAP_INCL="test_name-t" # Optional: filter to specific test
export SKIP_CLUSTER_START=1 # Skip cluster nodes for single-node tests
source test/infra/common/env.sh
# 2. Start ProxySQL and backends
./test/infra/control/ensure-infras.bash
# 3. Run the tests
./test/infra/control/run-tests-isolated.bash
# 4. Cleanup when done
./test/infra/control/stop-proxysql-isolated.bash
```
## Documentation
- **[infra/README.md](infra/README.md)** - Complete documentation for the Unified CI infrastructure
- **[tap/groups/groups.json](tap/groups/groups.json)** - Test group definitions
## Available Test Groups
Common test groups (defined in `tap/groups/groups.json`):
| Group | Description |
|-------|-------------|
| `mysql84-g1` | MySQL 8.4 tests |
| `mysql57-g1` | MySQL 5.7 tests |
| `mariadb10-g1` | MariaDB 10 tests |
| `legacy-g1` | Legacy tests (MySQL 5.7, MariaDB 10, PostgreSQL, ClickHouse) |
## Environment Variables
| Variable | Description |
|----------|-------------|
| `INFRA_ID` | **Required**. Unique namespace for Docker containers. Use timestamp: `test-$(date +%s)` |
| `TAP_GROUP` | Test group to run (e.g., `mysql84-g1`) |
| `TEST_PY_TAP_INCL` | Regex to filter tests within the group |
| `SKIP_CLUSTER_START` | Set to `1` to skip starting additional ProxySQL nodes |
## Prerequisites
1. Build the CI base image (one-time setup):
```bash
cd test/infra/docker-base
docker build --network host -t proxysql-ci-base:latest .
cd ../../../
```
2. Build ProxySQL and TAP tests:
```bash
make -j$(nproc) && make -j$(nproc) build_tap_test
```
## Troubleshooting
- **"Directory Not Empty"**: Run `./test/infra/control/stop-proxysql-isolated.bash` with the same `INFRA_ID`
- **Container issues**: Check logs in `ci_infra_logs/${INFRA_ID}/`
- **Test failures**: Check logs in `ci_infra_logs/${INFRA_ID}/tests/`

@ -1,19 +1,51 @@
/**
* @file max_connections_ff-t.cpp
* @brief This test verifies that 'max_connections' is honored by 'ff' connections.
* @details The test performs multiple checks for this:
* - When 'max_connections' is reached, queries for 'fast_forward' sessions trying to obtain connections
* should timeout in 'mysql-connect_timeout_server'.
*
* IMPORTANT-NOTE: Since second test is relying on 'stats_mysql_connection_pool' for checking the correct
* creation and destruction of connections, it's important to make sure that connections used between the
* two tests are *NOT COMPATIBLE*. This way we can ensure that stats from 'stats_mysql_connection_pool'
* actually correspond to the second test, and are not 'FreeConns' left from the previous test that can be
* reused, thus messing the stats. For this we impose: 'CLIENT_IGNORE_SPACE' to connections created in this
* test.
*
* - When only one non-suited 'free' connection is left, a 'fast_forward' session shouldn't try to create
* another new connection without first destroying the 'free' connection left.
* @brief Test to verify that 'max_connections' server setting is properly honored by 'fast_forward' connections.
*
* @details This test verifies that ProxySQL correctly enforces the 'max_connections' limit configured
* per MySQL server when handling 'fast_forward' (ff) connections. Fast forward connections are a special
* mode in ProxySQL where the connection is essentially passed through directly to the backend server
* with minimal intervention.
*
* The test consists of two main test scenarios:
*
* ## Test 1: test_ff_sess_exceeds_max_conns()
* This test verifies that when 'max_connections' limit is reached, new 'fast_forward' sessions
* trying to obtain backend connections should timeout with error after 'mysql-connect_timeout_server_max'
* milliseconds. The test:
* 1. Sets a specific 'max_connections' limit on the target server
* 2. Enables 'fast_forward' for the test user
* 3. Creates max_connections number of connections with open transactions (holding them)
* 4. Attempts to create one more fast_forward connection and execute a query
* 5. Verifies the query fails with a timeout within expected time bounds
*
* ## Test 2: test_ff_only_one_free_conn()
* This test verifies that when a 'free' (unused) connection exists in the pool that is NOT compatible
* with a new fast_forward session, ProxySQL correctly destroys the incompatible free connection before
* creating a new one. The test:
* 1. Sets a specific 'max_connections' limit
* 2. Enables 'fast_forward' for the test user
* 3. Creates max_connections connections with open transactions
* 4. Commits one transaction to leave one 'FreeConn' in the pool
* 5. Creates a new fast_forward connection and executes a query
* 6. Verifies via 'stats_mysql_connection_pool' that the stats are correct (old free conn destroyed, new one created)
*
* IMPORTANT-NOTE: Since the second test relies on 'stats_mysql_connection_pool' for checking the correct
* creation and destruction of connections, it's important to make sure that connections used between the
* two tests are *NOT COMPATIBLE*. This way we can ensure that stats from 'stats_mysql_connection_pool'
* actually correspond to the second test, and are not 'FreeConns' left from the previous test that can be
* reused, thus messing the stats. For this we impose: 'CLIENT_IGNORE_SPACE' flag to connections created
* in the first test, making them incompatible with the second test's connections.
*
* @test Test 1a: max_connections=1, timeout=8000ms - verify timeout behavior
* @test Test 1b: max_connections=3, timeout=2000ms - verify timeout behavior with multiple conns
* @test Test 2a: max_connections=1 - verify free connection handling
* @test Test 2b: max_connections=3 - verify free connection handling with multiple conns
*
* @pre Requires 'regular_infra' CI configuration with:
* - A MySQL server configured in some hostgroup
* - User 'sbtest1' with password 'sbtest1' configured in mysql_users
* - The test dynamically discovers the default_hostgroup for user 'sbtest1'
*/
#include <cstring>
@ -40,37 +72,54 @@ using hrc = std::chrono::high_resolution_clock;
using nlohmann::json;
/**
* @brief Get the default hostgroup for a user from mysql_users
* @brief Discovers the default hostgroup for a given user from ProxySQL.
*
* This function queries the mysql_users table to find the default_hostgroup
* configured for the specified user. This allows tests to work with any
* infrastructure configuration without hardcoding hostgroup values.
*
* @param proxy_admin Admin connection to ProxySQL
* @param username The username to query
* @return The discovered default hostgroup, or -1 if not found
*/
int get_user_default_hostgroup(MYSQL* admin, const string& username) {
string query = "SELECT default_hostgroup FROM mysql_users WHERE username='" + username + "'";
diag("Executing query `%s`...", query.c_str());
if (mysql_query(admin, query.c_str()) != 0) {
diag("Failed to query user default_hostgroup: %s", mysql_error(admin));
return -1;
}
MYSQL_RES* res = mysql_store_result(admin);
if (res == NULL) {
diag("Failed to store result: %s", mysql_error(admin));
return -1;
int discover_user_hostgroup(MYSQL* proxy_admin, const string& username) {
string hg_query = "SELECT default_hostgroup FROM mysql_users WHERE username='" + username + "' LIMIT 1";
MYSQL_RES* res = nullptr;
int target_hg = -1;
if (mysql_query(proxy_admin, hg_query.c_str()) == 0) {
res = mysql_store_result(proxy_admin);
if (res) {
MYSQL_ROW row = mysql_fetch_row(res);
if (row) target_hg = atoi(row[0]);
mysql_free_result(res);
}
}
MYSQL_ROW row = mysql_fetch_row(res);
int hg = -1;
if (row && row[0]) {
hg = atoi(row[0]);
if (target_hg < 0) {
diag("WARNING: Could not discover default_hostgroup for user '%s'", username.c_str());
}
mysql_free_result(res);
return hg;
return target_hg;
}
/**
* @brief Creates multiple MySQL connections with open transactions to hold backend connections.
*
* This function creates 'n' MySQL connections to ProxySQL and starts a transaction on each one.
* The open transactions prevent the connections from being returned to the pool, effectively
* holding 'max_connections' backend connections and preventing new ones from being created.
*
* @param cl Command line containing connection parameters (host, port, username, password)
* @param n Number of connections/transactions to create
* @param out_conns Output vector to store the created MYSQL connection handles
* @param client_flags MySQL client flags to use when connecting (default: 0)
* @return EXIT_SUCCESS on success, EXIT_FAILURE on any connection failure
*
* @note The caller is responsible for closing these connections when done
*/
int create_n_trxs(const CommandLine& cl, size_t n, vector<MYSQL*>& out_conns, int client_flags = 0) {
diag("Creating '%ld' transactions to test 'max_connections'", n);
diag(" - Connecting to: %s:%d", cl.host, cl.port);
diag(" - Username: %s", cl.username);
diag("Each transaction holds a backend connection, preventing it from being reused");
vector<MYSQL*> res_conns {};
@ -80,22 +129,32 @@ int create_n_trxs(const CommandLine& cl, size_t n, vector<MYSQL*>& out_conns, in
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_mysql));
return EXIT_FAILURE;
}
diag(" - Created connection %zu/%zu", i + 1, n);
mysql_query(proxy_mysql, "BEGIN");
res_conns.push_back(proxy_mysql);
}
diag("Successfully created %zu transaction connections", n);
out_conns = res_conns;
return EXIT_SUCCESS;
}
/**
* @brief Sets the 'max_connections' limit for all servers in a specific hostgroup.
*
* This function updates the mysql_servers table to set a new max_connections value
* for the specified hostgroup, then loads the configuration to runtime.
*
* @param proxy_admin Admin connection to ProxySQL
* @param max_conns The maximum number of connections allowed per server
* @param hg_id The hostgroup ID to update
* @return EXIT_SUCCESS on success
*/
int set_max_conns(MYSQL* proxy_admin, int max_conns, int hg_id) {
string max_conn_query {};
string_format("UPDATE mysql_servers SET max_connections=%d WHERE hostgroup_id=%d", max_conn_query, max_conns, hg_id);
diag("Setting max_connections=%d for hostgroup %d (limit backend conns per server)", max_conns, hg_id);
diag("Executing query `%s`...", max_conn_query.c_str());
MYSQL_QUERY(proxy_admin, max_conn_query.c_str());
@ -105,10 +164,22 @@ int set_max_conns(MYSQL* proxy_admin, int max_conns, int hg_id) {
return EXIT_SUCCESS;
}
/**
* @brief Sets the 'mysql-connect_timeout_server_max' global variable.
*
* This variable controls the maximum time (in milliseconds) ProxySQL will wait
* when trying to establish a connection to a backend server. When max_connections
* limit is reached, new connection attempts will timeout after this duration.
*
* @param proxy_admin Admin connection to ProxySQL
* @param connect_to Timeout value in milliseconds
* @return EXIT_SUCCESS on success
*/
int set_srv_conn_to(MYSQL* proxy_admin, int connect_to) {
string srv_conn_to_query {};
string_format("SET mysql-connect_timeout_server_max=%d", srv_conn_to_query, connect_to);
diag("Setting mysql-connect_timeout_server_max=%d ms (timeout for new backend connections)", connect_to);
diag("Executing query `%s`...", srv_conn_to_query.c_str());
MYSQL_QUERY(proxy_admin, srv_conn_to_query.c_str());
@ -118,11 +189,24 @@ int set_srv_conn_to(MYSQL* proxy_admin, int connect_to) {
return EXIT_SUCCESS;
}
/**
* @brief Enables or disables 'fast_forward' mode for a specific user.
*
* Fast forward mode is a special ProxySQL feature where the proxy essentially
* passes through the client connection directly to the backend server with
* minimal intervention. This is useful for specific use cases like MySQL
* replication connections or when ProxySQL's query processing is not needed.
*
* @param proxy_admin Admin connection to ProxySQL
* @param user The username to update
* @param ff true to enable fast_forward, false to disable
* @return EXIT_SUCCESS on success
*/
int set_ff_for_user(MYSQL* proxy_admin, const string& user, bool ff) {
string upd_ff_query {};
string_format("UPDATE mysql_users SET fast_forward=%d WHERE username='%s'", upd_ff_query, ff, user.c_str());
diag("Setting fast_forward=%d for user '%s' (enable/disable pass-through mode)", ff, user.c_str());
diag("Executing query `%s`...", upd_ff_query.c_str());
MYSQL_QUERY(proxy_admin, upd_ff_query.c_str());
@ -132,10 +216,33 @@ int set_ff_for_user(MYSQL* proxy_admin, const string& user, bool ff) {
return EXIT_SUCCESS;
}
/**
* SQL query template to retrieve connection pool statistics for a specific hostgroup.
* Returns: ConnUsed, ConnFree, ConnOk, ConnERR, MaxConnUsed
*
* - ConnUsed: Number of connections currently in use (held by client sessions)
* - ConnFree: Number of idle connections in the pool available for reuse
* - ConnOk: Total number of successful connections created
* - ConnERR: Total number of failed connection attempts
* - MaxConnUsed: Maximum number of connections used concurrently
*/
const char* CONNPOOL_STATS {
"SELECT ConnUsed,ConnFree,ConnOk,ConnERR,MaxConnUsed FROM stats.stats_mysql_connection_pool WHERE hostgroup=%d"
};
/**
* @brief Retrieves connection pool statistics for a specific hostgroup.
*
* This function queries the stats.stats_mysql_connection_pool table to get
* current statistics about the connection pool for the specified hostgroup.
*
* @param proxy_admin Admin connection to ProxySQL
* @param hg_id The hostgroup ID to query stats for
* @param out_stats Output vector containing the 5 stat columns: ConnUsed, ConnFree, ConnOk, ConnERR, MaxConnUsed
* @return EXIT_SUCCESS on success, EXIT_FAILURE if query fails or no rows returned
*
* @note Expects exactly one row (assumes 'regular_infra' with single server per hostgroup)
*/
int conn_pool_hg_stats(MYSQL* proxy_admin, int hg_id, vector<string>& out_stats) {
MYSQL_RES* my_stats_res = NULL;
@ -170,20 +277,42 @@ cleanup:
return err;
}
int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, int tg_hg, const string& username, long srv_conn_to, int max_conns) {
diag("================================================================================");
diag("TEST: test_ff_sess_exceeds_max_conns");
diag("================================================================================");
diag("This test verifies that 'max_connections' is honored by 'fast_forward' sessions.");
diag("When 'max_connections' is reached, new 'fast_forward' sessions should timeout.");
diag("");
diag("Test parameters:");
diag(" - Target hostgroup: %d", tg_hg);
diag(" - Test username: %s", username.c_str());
diag(" - Server connect timeout (ms): %ld", srv_conn_to);
diag(" - Max connections: %d", max_conns);
diag("================================================================================");
diag("");
/**
* @brief Tests that fast_forward sessions timeout when max_connections limit is reached.
*
* This test verifies the following behavior:
* 1. When max_connections limit is reached (all connections held by open transactions)
* 2. A new fast_forward session attempting to execute a query
* 3. Should timeout after 'mysql-connect_timeout_server_max' milliseconds
* 4. Should NOT exceed the timeout by more than poll_timeout + grace period
*
* Test procedure:
* - Set max_connections to the specified value for hostgroup 0
* - Enable fast_forward for user 'sbtest1'
* - Create max_connections connections with open transactions (holding all available slots)
* - Create one more fast_forward connection and attempt a query
* - Measure the time until the query fails
* - Verify the timeout is within expected bounds
*
* @param cl Command line with connection parameters
* @param proxy_admin Admin connection to ProxySQL
* @param srv_conn_to The timeout value in milliseconds to set for mysql-connect_timeout_server_max
* @param max_conns The max_connections limit to set for the server
* @return EXIT_SUCCESS on test completion (check pass/fail via ok() results)
*/
int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, long srv_conn_to, int max_conns) {
diag("=== TEST: test_ff_sess_exceeds_max_conns ===");
diag("Testing fast_forward timeout when max_connections (%d) is exceeded", max_conns);
diag("Expected: new ff connection should timeout after ~%ld ms", srv_conn_to);
// Dynamically discover the target hostgroup for user 'sbtest1'
const string username = "sbtest1";
int tg_hg = discover_user_hostgroup(proxy_admin, username);
if (tg_hg < 0) {
diag("Failed to discover hostgroup for user '%s'", username.c_str());
return EXIT_FAILURE;
}
diag("Discovered target hostgroup for user '%s': %d", username.c_str(), tg_hg);
string str_poll_timeout {};
string str_connect_timeout_server {};
@ -249,6 +378,7 @@ int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, in
}
// See 'IMPORTANT-NOTE' on file @details.
diag("Creating %d connections with CLIENT_IGNORE_SPACE flag (incompatible with test 2)", max_conns);
my_err = create_n_trxs(cl, max_conns, trx_conns, CLIENT_IGNORE_SPACE);
if (my_err) {
diag("Failed to create the required '%d' transactions", max_conns);
@ -257,6 +387,8 @@ int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, in
}
// Create a new ff connection and check that a query expires after 'connection'
diag("All %d backend connections are now held. Creating one more fast_forward connection...", max_conns);
diag("This new connection should timeout since max_connections limit is reached");
{
MYSQL* proxy_ff = mysql_init(NULL);
if (!mysql_real_connect(proxy_ff, cl.host, username.c_str(), username.c_str(), NULL, cl.port, NULL, 0)) {
@ -268,6 +400,7 @@ int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, in
std::chrono::nanoseconds duration;
hrc::time_point start = hrc::now();
diag("Executing query on the (n+1)th fast_forward connection...");
int q_err = mysql_query(proxy_ff, "DO 1");
int m_errno = mysql_errno(proxy_ff);
const char* m_error = mysql_error(proxy_ff);
@ -277,6 +410,9 @@ int test_ff_sess_exceeds_max_conns(const CommandLine& cl, MYSQL* proxy_admin, in
duration = end - start;
double duration_s = duration.count() / pow(10,9);
diag("Query completed - Error: %d, ErrMsg: %s", m_errno, m_error);
diag("Time waited: %lf seconds", duration_s);
const double srv_conn_to_s = connect_timeout / 1000.0;
const double poll_to_s = poll_timeout / 1000.0;
const double grace = 500 / 1000.0;
@ -312,25 +448,56 @@ cleanup:
return EXIT_SUCCESS;
}
int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg_hg, const string& username, int max_conns) {
diag("================================================================================");
diag("TEST: test_ff_only_one_free_conn");
diag("================================================================================");
diag("This test verifies that 'fast_forward' sessions properly reuse 'FreeConn'");
diag("connections from the connection pool instead of creating new connections.");
diag("");
diag("Test parameters:");
diag(" - Target hostgroup: %d", tg_hg);
diag(" - Test username: %s", username.c_str());
diag(" - Max connections: %d", max_conns);
diag("================================================================================");
diag("");
/**
* @brief Tests that fast_forward sessions properly handle free (incompatible) connections.
*
* This test verifies that when a 'free' connection exists in the pool but is not compatible
* with a new fast_forward session request, ProxySQL correctly:
* 1. Destroys the incompatible free connection
* 2. Creates a new connection for the fast_forward session
* 3. Updates connection pool statistics correctly
*
* The key scenario being tested:
* - max_connections limit is reached
* - One connection is released (becomes 'FreeConn')
* - A new fast_forward session is created
* - The new session should NOT reuse the incompatible free connection
* - Instead, the free connection should be destroyed and a new one created
*
* Test procedure:
* - Set max_connections to the specified value for hostgroup 0
* - Enable fast_forward for user 'sbtest1'
* - Reset connection pool stats
* - Create max_connections connections with open transactions
* - Commit one transaction to release one connection (now 'FreeConn')
* - Verify stats show ConnUsed=max_conns-1, ConnFree=1
* - Create a new fast_forward connection and execute a query
* - Verify stats show the connection was properly handled
*
* @param cl Command line with connection parameters
* @param proxy_admin Admin connection to ProxySQL
* @param max_conns The max_connections limit to set for the server
* @return EXIT_SUCCESS on test completion (check pass/fail via ok() results)
*/
int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int max_conns) {
diag("=== TEST: test_ff_only_one_free_conn ===");
diag("Testing fast_forward behavior when one free (but incompatible) connection exists");
diag("Max connections: %d - one will be freed, then reused/destroyed", max_conns);
if (proxy_admin == NULL || max_conns == 0) {
diag("'test_ff_only_one_free_conn' received invalid params.");
return EINVAL;
}
// Dynamically discover the target hostgroup for user 'sbtest1'
const string username = "sbtest1";
int tg_hg = discover_user_hostgroup(proxy_admin, username);
if (tg_hg < 0) {
diag("Failed to discover hostgroup for user '%s'", username.c_str());
return EXIT_FAILURE;
}
diag("Discovered target hostgroup for user '%s': %d", username.c_str(), tg_hg);
const char* reset_connpool_stats { "SELECT * FROM stats.stats_mysql_connection_pool_reset" };
string str_poll_timeout {};
@ -363,8 +530,9 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
}
// Reset all the current stats for 'stats_mysql_connection_pool'
diag("Resetting connection pool stats to get clean measurements");
my_err = mysql_query(proxy_admin, reset_connpool_stats);
diag("Executing query `%s` in new 'fast_forward' conn...", reset_connpool_stats);
diag("Executing query `%s`...", reset_connpool_stats);
if (my_err) {
diag("Query '%s' failed", reset_connpool_stats);
res = EXIT_FAILURE;
@ -372,6 +540,7 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
}
mysql_free_result(mysql_store_result(proxy_admin));
diag("Creating %d connections with open transactions to fill the pool", max_conns);
my_err = create_n_trxs(cl, max_conns, trx_conns);
if (my_err) {
diag("Failed to create the required '%d' transactions", max_conns);
@ -381,6 +550,7 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
{
// 1. First leave one connection 'Free' and verify it via 'stats_mysql_connection_pool'
diag("Step 1: Releasing one connection to create a 'FreeConn' in the pool");
MYSQL* trx_conn = trx_conns.back();
diag("Freeing ONE connection by committing the transaction...");
@ -396,6 +566,7 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
}
// 2. Verify there are 'max_connections - 1' as 'ConnUsed' and just one 'ConnFree'
diag("Step 2: Verifying pool stats - expect ConnUsed=%d, ConnFree=1", max_conns - 1);
vector<string> hg_stats_row {};
my_err = conn_pool_hg_stats(proxy_admin, tg_hg, hg_stats_row);
if (my_err) {
@ -416,6 +587,9 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
);
// 3. Create a new connection with a different user using 'fast_forward'
diag("Step 3: Creating a NEW fast_forward connection");
diag("This connection should NOT reuse the existing FreeConn (incompatible)");
diag("Instead, the FreeConn should be destroyed and a new connection created");
diag("Creating new 'fast_forward' connection using user '%s'", username.c_str());
MYSQL* proxy_ff = mysql_init(NULL);
@ -426,6 +600,7 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
}
// 3.1 Issue a simple query into the new 'fast_forward' connection
diag("Step 3.1: Executing query on the new fast_forward connection");
diag("Executing query `%s` in new 'fast_forward' conn...", "DO 1");
int q_my_err = mysql_query(proxy_ff, "DO 1");
if (q_my_err) {
@ -437,7 +612,9 @@ int test_ff_only_one_free_conn(const CommandLine& cl, MYSQL* proxy_admin, int tg
}
// 3.2 Check the stats have properly changed due to this new connection
diag("Checking 'stats_mysql_connection_pool' changed properly after query to 'fast_forward' session");
diag("Step 3.2: Verifying final pool stats after fast_forward query");
diag("Expected: ConnUsed=%d (all held), ConnFree=0, ConnOk=%d (total created), ConnErr=0",
max_conns, max_conns + 1);
my_err = conn_pool_hg_stats(proxy_admin, tg_hg, hg_stats_row);
if (my_err) {
res = EXIT_FAILURE;
@ -483,9 +660,37 @@ cleanup:
return EXIT_SUCCESS;
}
/**
* @brief Main entry point for the max_connections_ff test.
*
* This test verifies that ProxySQL correctly enforces the 'max_connections' limit
* for servers when handling 'fast_forward' connections.
*
* Test execution:
* 1. Connect to ProxySQL admin interface
* 2. Run test_ff_sess_exceeds_max_conns with max_conns=1, timeout=8000ms
* 3. Run test_ff_sess_exceeds_max_conns with max_conns=3, timeout=2000ms
* 4. Run test_ff_only_one_free_conn with max_conns=1
* 5. Run test_ff_only_one_free_conn with max_conns=3
*
* Total tests: 6 (1 check per test_ff_sess_exceeds_max_conns * 2 runs + 2 checks per test_ff_only_one_free_conn * 2 runs)
*
* @param argc Argument count
* @param argv Argument values
* @return Exit status from TAP framework
*/
int main(int argc, char** argv) {
CommandLine cl;
diag("===========================================================");
diag("TEST: max_connections_ff - Fast Forward Connection Limits");
diag("===========================================================");
diag("This test verifies 'max_connections' is honored by ff connections");
diag("");
// 'test_ff_sess_exceeds_max_conns' performs '1' check, 'test_ff_only_one_free_conn' performs '2' checks
plan(1 * 2 + 2 * 2);
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
@ -496,66 +701,46 @@ int main(int argc, char** argv) {
2*2 // 'test_ff_only_one_free_conn'
);
// Verbose test header
diag("================================================================================");
diag("Test: max_connections_ff-t");
diag("================================================================================");
diag("This test verifies that 'max_connections' is properly honored by 'fast_forward'");
diag("connections in ProxySQL.");
diag("");
diag("Test scenarios:");
diag(" 1. Test that exceeding max_connections causes timeout for new ff sessions");
diag(" 2. Test that ff sessions properly reuse free connections from pool");
diag("");
diag("Configuration:");
diag(" - ProxySQL admin host: %s", cl.admin_host);
diag(" - ProxySQL admin port: %d", cl.admin_port);
diag(" - ProxySQL data host: %s", cl.host);
diag(" - ProxySQL data port: %d", cl.port);
diag(" - Test username: %s", cl.username);
diag("================================================================================");
diag("");
MYSQL* proxy_admin = mysql_init(NULL);
if (!mysql_real_connect(proxy_admin, cl.admin_host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
if (!mysql_real_connect(proxy_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(proxy_admin));
return EXIT_FAILURE;
}
// Get the default hostgroup for the test user
int tg_hg = get_user_default_hostgroup(proxy_admin, cl.username);
if (tg_hg < 0) {
diag("ERROR: Failed to get default_hostgroup for user '%s'", cl.username);
return EXIT_FAILURE;
}
diag("User '%s' default_hostgroup: %d", cl.username, tg_hg);
// Check if the target hostgroup has servers configured
{
diag("Checking if hostgroup %d has servers configured...", tg_hg);
string query = "SELECT hostgroup_id, hostname, port, status FROM mysql_servers WHERE hostgroup_id=" + std::to_string(tg_hg);
MYSQL_QUERY(proxy_admin, query.c_str());
MYSQL_RES* res = mysql_store_result(proxy_admin);
int rows = mysql_num_rows(res);
if (rows == 0) {
diag("ERROR: No servers found in hostgroup %d", tg_hg);
mysql_free_result(res);
return EXIT_FAILURE;
}
diag("Found %d server(s) in hostgroup %d", rows, tg_hg);
mysql_free_result(res);
}
diag("Connected to ProxySQL admin interface at %s:%d", cl.host, cl.admin_port);
diag("");
// 1. Test for: '8000' timeout, '1' max_connections
test_ff_sess_exceeds_max_conns(cl, proxy_admin, tg_hg, cl.username, 8000, 1);
diag("-----------------------------------------------------------");
diag("TEST RUN 1: max_conns=1, connect_timeout=8000ms");
diag("-----------------------------------------------------------");
test_ff_sess_exceeds_max_conns(cl, proxy_admin, 8000, 1);
// 2. Test for: '2000' timeout, '3' max_connections
test_ff_sess_exceeds_max_conns(cl, proxy_admin, tg_hg, cl.username, 2000, 3);
diag("");
diag("-----------------------------------------------------------");
diag("TEST RUN 2: max_conns=3, connect_timeout=2000ms");
diag("-----------------------------------------------------------");
test_ff_sess_exceeds_max_conns(cl, proxy_admin, 2000, 3);
// 3. Test for only one 'FreeConn' that should be destroyed due to incoming 'fast_forward' conn - MaxConn: 1
test_ff_only_one_free_conn(cl, proxy_admin, tg_hg, cl.username, 1);
diag("");
diag("-----------------------------------------------------------");
diag("TEST RUN 3: test_ff_only_one_free_conn with max_conns=1");
diag("-----------------------------------------------------------");
test_ff_only_one_free_conn(cl, proxy_admin, 1);
// 4. Test for only one 'FreeConn' that should be destroyed due to incoming 'fast_forward' conn - MaxConn: 3
test_ff_only_one_free_conn(cl, proxy_admin, tg_hg, cl.username, 3);
diag("");
diag("-----------------------------------------------------------");
diag("TEST RUN 4: test_ff_only_one_free_conn with max_conns=3");
diag("-----------------------------------------------------------");
test_ff_only_one_free_conn(cl, proxy_admin, 3);
diag("");
diag("===========================================================");
diag("All tests completed");
diag("===========================================================");
mysql_close(proxy_admin);

Loading…
Cancel
Save