@ -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 1 a : max_connections = 1 , timeout = 8000 ms - verify timeout behavior
* @ test Test 1 b : max_connections = 3 , timeout = 2000 ms - verify timeout behavior with multiple conns
* @ test Test 2 a : max_connections = 1 - verify free connection handling
* @ test Test 2 b : 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 = 8000 ms
* 3. Run test_ff_sess_exceeds_max_conns with max_conns = 3 , timeout = 2000 ms
* 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 ) ;