Skip to content

Commit 7cb87ed

Browse files
committed
TAP: Add tests for session variable tracking
Signed-off-by: Wazir Ahmed <[email protected]>
1 parent 47f344f commit 7cb87ed

File tree

5 files changed

+660
-29
lines changed

5 files changed

+660
-29
lines changed

test/tap/groups/groups.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,8 @@
240240
"test_ssl_fast_forward-3_libmariadb-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
241241
"test_ssl_fast_forward-3_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
242242
"test_ignore_min_gtid-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ],
243-
"mysql-track_system_variables-t" : [ "default-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ]
243+
"mysql-session_track_variables_optional-t" : [ "default-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
244+
"mysql-session_track_variables_enforced-t" : [ "default-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
245+
"mysql-session_track_variables_ff_optional-t" : [ "default-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
246+
"mysql-session_track_variables_ff_enforced-t" : [ "default-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ]
244247
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* @file mysql-session_track_variables_enforced-t.cpp
3+
* @brief This test verifies that ProxySQL properly handles session variable tracking
4+
* in ENFORCED mode based on MySQL server version. Session tracking should work
5+
* on MySQL 5.7+ and backends without support should be rejected (queries should timeout).
6+
*/
7+
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include "json.hpp"
11+
#include "mysql.h"
12+
#include "tap.h"
13+
#include "command_line.h"
14+
#include "utils.h"
15+
16+
using nlohmann::json;
17+
18+
bool get_server_version(MYSQL* proxy, int& major, int& minor) {
19+
MYSQL_QUERY_T(proxy, "SELECT @@version");
20+
MYSQL_RES* result = mysql_store_result(proxy);
21+
if (!result) {
22+
return false;
23+
}
24+
25+
MYSQL_ROW row = mysql_fetch_row(result);
26+
if (!row) {
27+
mysql_free_result(result);
28+
return false;
29+
}
30+
31+
// Parse version string
32+
if (sscanf(row[0], "%d.%d", &major, &minor) != 2) {
33+
mysql_free_result(result);
34+
return false;
35+
}
36+
37+
mysql_free_result(result);
38+
return true;
39+
}
40+
41+
bool test_session_variables(MYSQL* proxy, int& set_value, int& backend_value, int& client_value) {
42+
set_value = -1;
43+
backend_value = -1;
44+
client_value = -1;
45+
46+
MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test");
47+
MYSQL_QUERY_T(proxy, "SELECT 1");
48+
mysql_free_result(mysql_store_result(proxy));
49+
50+
MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout");
51+
const char* create_proc =
52+
"CREATE PROCEDURE test.set_innodb_lock_wait_timeout() "
53+
"BEGIN "
54+
" SET innodb_lock_wait_timeout = CAST(FLOOR(50 + (RAND() * 100)) AS UNSIGNED); "
55+
"END";
56+
57+
MYSQL_QUERY_T(proxy, create_proc);
58+
59+
MYSQL_QUERY_T(proxy, "CALL test.set_innodb_lock_wait_timeout()");
60+
61+
MYSQL_QUERY_T(proxy, "SELECT @@innodb_lock_wait_timeout");
62+
MYSQL_RES* result = mysql_store_result(proxy);
63+
if (result) {
64+
MYSQL_ROW row = mysql_fetch_row(result);
65+
if (row) {
66+
set_value = atoi(row[0]);
67+
}
68+
mysql_free_result(result);
69+
}
70+
71+
MYSQL_QUERY(proxy, "PROXYSQL INTERNAL SESSION");
72+
result = mysql_store_result(proxy);
73+
if (!result) {
74+
return false;
75+
}
76+
77+
MYSQL_ROW row = mysql_fetch_row(result);
78+
if (!row) {
79+
mysql_free_result(result);
80+
return false;
81+
}
82+
83+
auto j_session = nlohmann::json::parse(row[0]);
84+
mysql_free_result(result);
85+
86+
if (j_session.contains("backends")) {
87+
for (auto& backend : j_session["backends"]) {
88+
if (backend != nullptr && backend.contains("conn")) {
89+
if (backend["conn"].contains("innodb_lock_wait_timeout")) {
90+
backend_value = std::stoi(backend["conn"]["innodb_lock_wait_timeout"].get<std::string>());
91+
break;
92+
}
93+
}
94+
}
95+
}
96+
97+
if (j_session.contains("conn")) {
98+
if (j_session["conn"].contains("innodb_lock_wait_timeout")) {
99+
client_value = std::stoi(j_session["conn"]["innodb_lock_wait_timeout"].get<std::string>());
100+
}
101+
}
102+
103+
return true;
104+
}
105+
106+
int main(int argc, char** argv) {
107+
CommandLine cl;
108+
if (cl.getEnv()) {
109+
diag("Failed to get the required environmental variables.");
110+
return exit_status();
111+
}
112+
113+
plan(1);
114+
115+
MYSQL* admin = init_mysql_conn(cl.admin_host, cl.admin_port, cl.admin_username, cl.admin_password);
116+
if (!admin) {
117+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
118+
return exit_status();
119+
}
120+
121+
// Set session_track_variables to ENFORCED mode (value 2)
122+
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=2");
123+
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
124+
125+
MYSQL* proxy = init_mysql_conn(cl.host, cl.port, cl.username, cl.password, true);
126+
if (!proxy) {
127+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
128+
return exit_status();
129+
}
130+
131+
int major = 0, minor = 0;
132+
if (!get_server_version(proxy, major, minor)) {
133+
diag("Failed to get server version");
134+
return exit_status();
135+
}
136+
137+
diag("Detected MySQL version: %d.%d", major, minor);
138+
139+
int set_value = -1;
140+
int backend_value = -1;
141+
int client_value = -1;
142+
bool test_result = test_session_variables(proxy, set_value, backend_value, client_value);
143+
144+
// Verify results based on server version
145+
bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7);
146+
147+
if (mysql57_plus) {
148+
if (!test_result) {
149+
diag("Failed to run test");
150+
return exit_status();
151+
}
152+
153+
ok(set_value == backend_value && set_value == client_value,
154+
"MySQL %d.%d supports session tracking. Match innodb_lock_wait_timeout value with backend & client variable list. Expected: %d, Backend: %d, Client: %d",
155+
major, minor, set_value, backend_value, client_value);
156+
} else {
157+
// MySQL 5.6 or below: enforced mode should reject connections with error 9001
158+
if (test_result) {
159+
diag("Test unexpectedly passed on MySQL %d.%d without session tracking support", major, minor);
160+
return exit_status();
161+
}
162+
163+
int error_code = mysql_errno(proxy);
164+
const char* error_msg = mysql_error(proxy);
165+
166+
ok(error_code == 9001,
167+
"MySQL %d.%d lacks session tracking. ENFORCED mode. Connection should timeout with 9001. Error: %d, Message: %s",
168+
major, minor, error_code, error_msg);
169+
}
170+
171+
// Cleanup
172+
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0");
173+
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
174+
175+
mysql_close(proxy);
176+
mysql_close(admin);
177+
return exit_status();
178+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* @file mysql-session_track_variables_ff_enforced-t.cpp
3+
* @brief This test verifies that ProxySQL properly handles session variable tracking
4+
* in ENFORCED mode with fast_forward enabled. Session tracking should work
5+
* on MySQL 5.7+ and backends without support should be rejected (queries fail).
6+
*/
7+
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include "mysql.h"
11+
#include "tap.h"
12+
#include "command_line.h"
13+
#include "utils.h"
14+
15+
bool get_server_version(MYSQL* proxy, int& major, int& minor) {
16+
MYSQL_QUERY_T(proxy, "SELECT @@version");
17+
MYSQL_RES* result = mysql_store_result(proxy);
18+
if (!result) {
19+
return false;
20+
}
21+
22+
MYSQL_ROW row = mysql_fetch_row(result);
23+
if (!row) {
24+
mysql_free_result(result);
25+
return false;
26+
}
27+
28+
// Parse version string like "5.7.33" or "8.0.25"
29+
if (sscanf(row[0], "%d.%d", &major, &minor) != 2) {
30+
mysql_free_result(result);
31+
return false;
32+
}
33+
34+
mysql_free_result(result);
35+
return true;
36+
}
37+
38+
bool extract_session_variable(MYSQL* proxy, const char* var_name, int& tracked_value) {
39+
tracked_value = -1;
40+
41+
if ((proxy != nullptr)
42+
&& (proxy->net.last_errno == 0)
43+
&& (proxy->server_status & SERVER_SESSION_STATE_CHANGED)) {
44+
const char *data;
45+
size_t length;
46+
47+
if (mysql_session_track_get_first(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) {
48+
std::string current_var_name(data, length);
49+
// get_first() returns a variable_name
50+
// get_next() will return the value
51+
bool expect_value = true;
52+
53+
while (mysql_session_track_get_next(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) {
54+
if (expect_value) {
55+
// This is the value for current_var_name
56+
if (current_var_name == var_name) {
57+
std::string value_str(data, length);
58+
tracked_value = atoi(value_str.c_str());
59+
return true;
60+
}
61+
// got a value in this iteration
62+
// in the next iteration, we have to expect a variable_name
63+
expect_value = false;
64+
} else {
65+
current_var_name = std::string(data, length);
66+
// got a variable_name in this iteration
67+
// in the next iteration, we have to expect the value of this variable
68+
expect_value = true;
69+
}
70+
}
71+
}
72+
}
73+
74+
return false;
75+
}
76+
77+
bool test_session_variables(MYSQL* proxy, int& set_value, int& tracked_value) {
78+
set_value = -1;
79+
tracked_value = -1;
80+
81+
MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test");
82+
MYSQL_QUERY_T(proxy, "SELECT 1");
83+
mysql_free_result(mysql_store_result(proxy));
84+
85+
MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout");
86+
const char* create_proc =
87+
"CREATE PROCEDURE test.set_innodb_lock_wait_timeout() "
88+
"BEGIN "
89+
" SET innodb_lock_wait_timeout = CAST(FLOOR(50 + (RAND() * 100)) AS UNSIGNED); "
90+
"END";
91+
92+
MYSQL_QUERY_T(proxy, create_proc);
93+
94+
MYSQL_QUERY_T(proxy, "CALL test.set_innodb_lock_wait_timeout()");
95+
96+
extract_session_variable(proxy, "innodb_lock_wait_timeout", tracked_value);
97+
98+
MYSQL_QUERY_T(proxy, "SELECT @@innodb_lock_wait_timeout");
99+
MYSQL_RES* result = mysql_store_result(proxy);
100+
if (result) {
101+
MYSQL_ROW row = mysql_fetch_row(result);
102+
if (row) {
103+
set_value = atoi(row[0]);
104+
}
105+
mysql_free_result(result);
106+
}
107+
108+
return true;
109+
}
110+
111+
int main(int argc, char** argv) {
112+
CommandLine cl;
113+
if (cl.getEnv()) {
114+
diag("Failed to get the required environmental variables.");
115+
return exit_status();
116+
}
117+
118+
plan(1);
119+
120+
MYSQL* admin = init_mysql_conn(cl.admin_host, cl.admin_port, cl.admin_username, cl.admin_password);
121+
if (!admin) {
122+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
123+
return exit_status();
124+
}
125+
126+
// Set session_track_variables to ENFORCED mode (value 2)
127+
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=2");
128+
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
129+
130+
// Enable fast_forward for test user
131+
MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'");
132+
MYSQL_QUERY_T(admin, "INSERT INTO mysql_users (username,password,fast_forward,default_hostgroup) VALUES ('ff_test_user','ff_test_pass',1,0)");
133+
MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
134+
135+
MYSQL* proxy = mysql_init(NULL);
136+
// Enable CLIENT_DEPRECATE_EOF. This is required for session tracking
137+
proxy->options.client_flag |= CLIENT_DEPRECATE_EOF;
138+
if (!mysql_real_connect(proxy, cl.host, "ff_test_user", "ff_test_pass", NULL, cl.port, NULL, 0)) {
139+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
140+
return exit_status();
141+
}
142+
143+
// Enable session tracking on client for fast_forward mode
144+
MYSQL_QUERY_T(proxy, "SET session_track_system_variables='*'");
145+
MYSQL_QUERY_T(proxy, "SET session_track_state_change=ON");
146+
147+
int major = 0, minor = 0;
148+
if (!get_server_version(proxy, major, minor)) {
149+
diag("Failed to get server version");
150+
return exit_status();
151+
}
152+
153+
diag("Detected MySQL version: %d.%d", major, minor);
154+
155+
int set_value = -1;
156+
int tracked_value = -1;
157+
bool test_result = test_session_variables(proxy, set_value, tracked_value);
158+
159+
// Verify results based on server version
160+
bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7);
161+
162+
if (mysql57_plus) {
163+
if (!test_result) {
164+
diag("Failed to run test");
165+
return exit_status();
166+
}
167+
168+
ok(set_value == tracked_value,
169+
"MySQL %d.%d supports session tracking. Fast forward with ENFORCED mode: Expected: %d, Tracked: %d",
170+
major, minor, set_value, tracked_value);
171+
} else {
172+
// MySQL 5.6 or below: enforced mode should reject connections with error 9001
173+
if (test_result) {
174+
diag("Test unexpectedly passed on MySQL %d.%d without session tracking support", major, minor);
175+
return exit_status();
176+
}
177+
178+
int error_code = mysql_errno(proxy);
179+
const char* error_msg = mysql_error(proxy);
180+
181+
ok(error_code == 9001,
182+
"MySQL %d.%d lacks session tracking. Fast forward with ENFORCED mode. Connection should timeout with 9001. Error: %d, Message: %s",
183+
major, minor, error_code, error_msg);
184+
}
185+
186+
// Cleanup
187+
MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'");
188+
MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME");
189+
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0");
190+
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
191+
192+
mysql_close(proxy);
193+
mysql_close(admin);
194+
return exit_status();
195+
}

0 commit comments

Comments
 (0)