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.
340 lines
9.4 KiB
340 lines
9.4 KiB
/**
|
|
* @file test_default_value_transaction_isolation_attr-t.cpp
|
|
* @brief This test is meant to test feature introduced in #3466.
|
|
* @details The tests performs the following actions to verify that the feature
|
|
* is behaving properly:
|
|
* - Creates several new users with different values for the introduced
|
|
* attribute 'default-transaction_isolation'.
|
|
* - Connects with each of the create users verifying that the value has
|
|
* been correctly tracked for the frontend connection.
|
|
* - Performs the query 'SELECT @@transaction_isolation' to verify that
|
|
* the value is correctly propagated to the backend connection.
|
|
* - Explicitly sets the value and checks that ProxySQL is properly
|
|
* tracking it performing again the two previous actions.
|
|
*
|
|
* @date 2021-06-14
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <random>
|
|
#include <string>
|
|
#include <stdio.h>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include <mysql.h>
|
|
|
|
#include "tap.h"
|
|
#include "command_line.h"
|
|
#include "proxysql_utils.h"
|
|
#include "utils.h"
|
|
#include "json.hpp"
|
|
|
|
using std::string;
|
|
using namespace nlohmann;
|
|
|
|
/**
|
|
* @brief Helper function to convert a 'MYSQL_RES' into a
|
|
* nlohmann::json.
|
|
*
|
|
* @param result The 'MYSQL_RES*' to be converted into JSON.
|
|
* @param j 'nlohmann::json' output parameter holding the
|
|
* converted 'MYSQL_RES' supplied.
|
|
*/
|
|
void parse_result_json_column(MYSQL_RES *result, json& j) {
|
|
if(!result) return;
|
|
MYSQL_ROW row;
|
|
|
|
while ((row = mysql_fetch_row(result))) {
|
|
j = json::parse(row[0]);
|
|
}
|
|
}
|
|
|
|
using user_attributes = std::tuple<std::string, std::string, std::string, std::string>;
|
|
|
|
/**
|
|
* @brief User names and attributes to be check and verified.
|
|
*/
|
|
const std::vector<user_attributes> c_user_attributes {
|
|
std::make_tuple(
|
|
"sbtest1",
|
|
"sbtest1",
|
|
"{\"default-transaction_isolation\":\"READ COMMITTED\"}",
|
|
"SERIALIZABLE"
|
|
),
|
|
std::make_tuple(
|
|
"sbtest2",
|
|
"sbtest2",
|
|
"{\"default-transaction_isolation\":\"REPEATABLE READ\"}",
|
|
"READ UNCOMMITTED"
|
|
),
|
|
std::make_tuple(
|
|
"sbtest3",
|
|
"sbtest3",
|
|
"{\"default-transaction_isolation\":\"READ UNCOMMITTED\"}",
|
|
"REPEATABLE READ"
|
|
),
|
|
std::make_tuple(
|
|
"sbtest4",
|
|
"sbtest4",
|
|
"{\"default-transaction_isolation\":\"SERIALIZABLE\"}",
|
|
"READ UNCOMMITTED"
|
|
)
|
|
};
|
|
|
|
int check_front_conn_isolation_level(
|
|
MYSQL* proxysql_mysql,
|
|
const std::string& exp_iso_level,
|
|
const bool set_via_attr
|
|
) {
|
|
MYSQL_QUERY(proxysql_mysql, "PROXYSQL INTERNAL SESSION");
|
|
json j_status {};
|
|
MYSQL_RES* int_session_res = mysql_store_result(proxysql_mysql);
|
|
parse_result_json_column(int_session_res, j_status);
|
|
mysql_free_result(int_session_res);
|
|
|
|
try {
|
|
std::string front_conn_isolation_level =
|
|
j_status.at("conn").at("isolation_level");
|
|
|
|
// Set the 'ok_message' depending on whether the value for
|
|
// isolation level have been set through an attribute,
|
|
// or explicitly via a 'SET statement'
|
|
std::string ok_msg {};
|
|
if (set_via_attr) {
|
|
ok_msg = std::string {
|
|
"Tracked isolation level for frontend connection should match"
|
|
" the one specified in the supplied 'user_attribute':\n"
|
|
" - (Exp: '%s') == (Act: '%s')",
|
|
};
|
|
} else {
|
|
ok_msg = std::string {
|
|
"Tracked isolation level for frontend connection should match"
|
|
" the one explicitly set via 'SET SESSION TRANSACTION ISOLATION LEVEL':\n"
|
|
" - (Exp: '%s') == (Act: '%s')",
|
|
};
|
|
}
|
|
|
|
ok(
|
|
front_conn_isolation_level == exp_iso_level,
|
|
ok_msg.c_str(),
|
|
exp_iso_level.c_str(),
|
|
front_conn_isolation_level.c_str()
|
|
);
|
|
} catch (std::exception& e) {
|
|
diag("Test failed with exception: '%s'", e.what());
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int check_backend_conn_isolation_level(
|
|
MYSQL* proxysql_mysql,
|
|
const std::string& exp_iso_level,
|
|
const bool set_via_attr
|
|
) {
|
|
// Get 'transaction_isolation' from backend connection
|
|
std::string select_trx_iso_query { "SELECT @@transaction_isolation" };
|
|
MYSQL_QUERY(proxysql_mysql, select_trx_iso_query.c_str());
|
|
MYSQL_RES* trx_iso_res = mysql_store_result(proxysql_mysql);
|
|
MYSQL_ROW trx_iso_row = mysql_fetch_row(trx_iso_res);
|
|
std::string trx_iso_val {};
|
|
|
|
// Verify that the query produced a correct result
|
|
if (trx_iso_row && trx_iso_row[0]) {
|
|
trx_iso_val = std::string { trx_iso_row[0] };
|
|
} else {
|
|
const std::string err_msg {
|
|
"Empty result received from query '" + select_trx_iso_query + "'"
|
|
};
|
|
fprintf(
|
|
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
|
|
err_msg.c_str()
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Filter dashes from the query result
|
|
std::replace(std::begin(trx_iso_val), std::end(trx_iso_val), '-', ' ');
|
|
|
|
// Perform the check over the expected and actual value
|
|
std::string t_ok_msg {};
|
|
|
|
if (set_via_attr) {
|
|
t_ok_msg = std::string {
|
|
"Result of query '" + select_trx_iso_query + "' should match"
|
|
" the isolation level supplied in 'user_attribute':\n"
|
|
" - (Exp: '%s') == (Act: '%s')",
|
|
};
|
|
} else {
|
|
t_ok_msg = std::string {
|
|
"Result of query '" + select_trx_iso_query + "' should match"
|
|
" the isolation level explicitly set via 'SET SESSION TRANSACTION"
|
|
" ISOLATION LEVEL':\n"
|
|
" - (Exp: '%s') == (Act: '%s')",
|
|
};
|
|
}
|
|
|
|
ok(
|
|
trx_iso_val == exp_iso_level, t_ok_msg.c_str(),
|
|
exp_iso_level.c_str(), trx_iso_val.c_str()
|
|
);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int extract_exp_iso_level(
|
|
const std::string& user_attribute,
|
|
std::string& exp_iso_level
|
|
) {
|
|
try {
|
|
exp_iso_level =
|
|
nlohmann::json::parse(user_attribute)
|
|
.at("default-transaction_isolation");
|
|
} catch (const std::exception& ex) {
|
|
std::string t_err_msg {
|
|
"Invalid format supplied in 'user-attribute'. Generated"
|
|
" exception was: '%s'"
|
|
};
|
|
std::string err_msg {};
|
|
string_format(t_err_msg, err_msg, ex.what());
|
|
|
|
// Log the error while parsing the supplied attribute
|
|
diag("%s", err_msg.c_str());
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
CommandLine cl;
|
|
|
|
if (cl.getEnv()) {
|
|
diag("Failed to get the required environmental variables.");
|
|
return -1;
|
|
}
|
|
|
|
plan(c_user_attributes.size() * 4);
|
|
|
|
MYSQL* proxysql_admin = mysql_init(NULL);
|
|
MYSQL* mysql_server = mysql_init(NULL);
|
|
|
|
// Creating the new connections
|
|
if (
|
|
!mysql_real_connect(
|
|
proxysql_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(proxysql_admin)
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (
|
|
!mysql_real_connect(
|
|
mysql_server, cl.host, "root", "root", NULL, 13306, NULL, 0
|
|
)
|
|
) {
|
|
fprintf(
|
|
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
|
|
mysql_error(mysql_server)
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Creating the new required users
|
|
std::vector<user_config> users_configs {};
|
|
std::transform(
|
|
c_user_attributes.begin(), c_user_attributes.end(), std::back_inserter(users_configs),
|
|
[] (const user_attributes& u_attr) -> user_config {
|
|
return make_tuple(std::get<0>(u_attr), std::get<1>(u_attr), std::get<2>(u_attr));
|
|
}
|
|
);
|
|
int c_users_res =
|
|
create_extra_users(proxysql_admin, mysql_server, users_configs);
|
|
if (c_users_res) { return c_users_res; }
|
|
|
|
// Load ProxySQL users to runtime
|
|
MYSQL_QUERY(proxysql_admin, "LOAD MYSQL USERS TO RUNTIME");
|
|
|
|
// Performing the connection checks
|
|
std::vector<user_attributes> user_attributes { c_user_attributes };
|
|
auto rng = std::default_random_engine {};
|
|
std::shuffle(std::begin(user_attributes), std::end(user_attributes), rng);
|
|
|
|
for (const auto& user_attribute : user_attributes) {
|
|
// Create the new connection to verify
|
|
MYSQL* proxysql_mysql = mysql_init(NULL);
|
|
if (
|
|
!mysql_real_connect(
|
|
proxysql_mysql,
|
|
cl.host,
|
|
std::get<0>(user_attribute).c_str(),
|
|
std::get<1>(user_attribute).c_str(),
|
|
NULL, cl.port, NULL, 0)
|
|
) {
|
|
fprintf(
|
|
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
|
|
mysql_error(proxysql_mysql)
|
|
);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::string exp_iso_level {};
|
|
if (extract_exp_iso_level(std::get<2>(user_attribute), exp_iso_level)) {
|
|
if (tests_failed()) { return exit_status(); }
|
|
else { return EXIT_FAILURE; }
|
|
}
|
|
|
|
// Verify that the fronted connection is properly tracking the
|
|
// isolation level set in the attributes.
|
|
if (check_front_conn_isolation_level(proxysql_mysql, exp_iso_level, true)) {
|
|
if (tests_failed()) { return exit_status(); }
|
|
else { return EXIT_FAILURE; }
|
|
}
|
|
|
|
// Verify that the backend connection is properly tracking the
|
|
// isolation level set in the attributes.
|
|
if (check_backend_conn_isolation_level(proxysql_mysql, exp_iso_level, true)) {
|
|
if (tests_failed()) { return exit_status(); }
|
|
else { return EXIT_FAILURE; }
|
|
}
|
|
|
|
// Explicitly change the value for 'transaction_isolation' and
|
|
// verify it changed.
|
|
std::string t_set_trx_iso_level_query {
|
|
"SET SESSION TRANSACTION ISOLATION LEVEL %s"
|
|
};
|
|
std::string set_trx_iso_level_query {};
|
|
std::string new_exp_iso_level { std::get<3>(user_attribute) };
|
|
string_format(
|
|
t_set_trx_iso_level_query,
|
|
set_trx_iso_level_query,
|
|
new_exp_iso_level.c_str()
|
|
);
|
|
MYSQL_QUERY(proxysql_mysql, set_trx_iso_level_query.c_str());
|
|
|
|
// Check again that the expected isolation level have changed for both connections
|
|
if (check_front_conn_isolation_level(proxysql_mysql, new_exp_iso_level, false)) {
|
|
if (tests_failed()) { return exit_status(); }
|
|
else { return EXIT_FAILURE; }
|
|
}
|
|
|
|
if (check_backend_conn_isolation_level(proxysql_mysql, new_exp_iso_level, false)) {
|
|
if (tests_failed()) { return exit_status(); }
|
|
else { return EXIT_FAILURE; }
|
|
}
|
|
|
|
// Close the connection
|
|
mysql_close(proxysql_mysql);
|
|
}
|
|
|
|
return exit_status();
|
|
}
|