mirror of https://github.com/sysown/proxysql
Added new test checking the introduced attribute 'default-transaction_isolation' #3466
parent
52f1d0f1ec
commit
1f8a29673b
@ -0,0 +1,460 @@
|
||||
/**
|
||||
* @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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a MySQL user for testing purposes in the server determined
|
||||
* by supplied *already established* MySQL connection.
|
||||
*
|
||||
* @param mysql_server An already opened connection to a MySQL server.
|
||||
* @param user The name of the user to be created.
|
||||
* @param pass The password for the user to be created.
|
||||
*
|
||||
* @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise.
|
||||
*/
|
||||
int create_mysql_user(
|
||||
MYSQL* mysql_server,
|
||||
const std::string& user,
|
||||
const std::string& pass
|
||||
) {
|
||||
const std::string t_drop_user_query { "DROP USER IF EXISTS %s@'%%'" };
|
||||
std::string drop_user_query {};
|
||||
string_format(t_drop_user_query, drop_user_query, user.c_str());
|
||||
|
||||
const std::string t_create_user_query {
|
||||
"CREATE USER IF NOT EXISTS %s@'%%' IDENTIFIED WITH 'mysql_native_password' BY \"%s\""
|
||||
};
|
||||
std::string create_user_query {};
|
||||
string_format(t_create_user_query, create_user_query, user.c_str(), pass.c_str());
|
||||
|
||||
const std::string t_grant_usage_query { "GRANT USAGE ON *.* TO %s@'%%'" };
|
||||
std::string grant_usage_query { };
|
||||
string_format(t_grant_usage_query, grant_usage_query, user.c_str());
|
||||
|
||||
MYSQL_QUERY(mysql_server, drop_user_query.c_str());
|
||||
MYSQL_QUERY(mysql_server, create_user_query.c_str());
|
||||
MYSQL_QUERY(mysql_server, grant_usage_query.c_str());
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates the new supplied user in ProxySQL with the provided
|
||||
* attributes.
|
||||
*
|
||||
* @param proxysql_admin An already opened connection to ProxySQL Admin.
|
||||
* @param user The username of the user to be created.
|
||||
* @param pass The password of the user to be created.
|
||||
* @param attributes The 'attributes' value for the 'attributes' column
|
||||
* for the user to be created.
|
||||
*
|
||||
* @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise.
|
||||
*/
|
||||
int create_proxysql_user(
|
||||
MYSQL* proxysql_admin,
|
||||
const std::string& user,
|
||||
const std::string& pass,
|
||||
const std::string& attributes
|
||||
) {
|
||||
std::string t_del_user_query { "DELETE FROM mysql_users WHERE username='%s'" };
|
||||
std::string del_user_query {};
|
||||
string_format(t_del_user_query, del_user_query, user.c_str());
|
||||
|
||||
std::string t_insert_user {
|
||||
"INSERT INTO mysql_users (username,password,active,attributes)"
|
||||
" VALUES ('%s','%s',1,'%s')"
|
||||
};
|
||||
std::string insert_user {};
|
||||
string_format(t_insert_user, insert_user, user.c_str(), pass.c_str(), attributes.c_str());
|
||||
|
||||
MYSQL_QUERY(proxysql_admin, del_user_query.c_str());
|
||||
MYSQL_QUERY(proxysql_admin, insert_user.c_str());
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
using user_config = std::tuple<std::string, std::string, std::string, std::string>;
|
||||
|
||||
/**
|
||||
* @brief Create the extra required users for the test in
|
||||
* both MYSQL and ProxySQL.
|
||||
*
|
||||
* @param proxysql_admin An already opened connection to ProxySQL admin
|
||||
* interface.
|
||||
* @param mysql_server An already opened connection to a backend MySQL
|
||||
* server.
|
||||
* @param user_attributes The user attributes whose should be part of user
|
||||
* configuration in ProxySQL side.
|
||||
*
|
||||
* @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise.
|
||||
*/
|
||||
int create_extra_users(
|
||||
MYSQL* proxysql_admin,
|
||||
MYSQL* mysql_server,
|
||||
const std::vector<user_config>& users_config
|
||||
) {
|
||||
std::vector<std::pair<std::string, std::string>> v_user_pass {};
|
||||
std::transform(
|
||||
std::begin(users_config),
|
||||
std::end(users_config),
|
||||
std::back_inserter(v_user_pass),
|
||||
[](const user_config& u_config) {
|
||||
return std::pair<std::string, std::string> {
|
||||
std::get<0>(u_config),
|
||||
std::get<1>(u_config)
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// create the MySQL users
|
||||
for (const auto& user_pass : v_user_pass) {
|
||||
int c_user_res =
|
||||
create_mysql_user(mysql_server, user_pass.first, user_pass.second);
|
||||
if (c_user_res) {
|
||||
return c_user_res;
|
||||
}
|
||||
}
|
||||
|
||||
// create the ProxySQL users
|
||||
for (const auto& user_config : users_config) {
|
||||
int c_p_user_res =
|
||||
create_proxysql_user(
|
||||
proxysql_admin,
|
||||
std::get<0>(user_config),
|
||||
std::get<1>(user_config),
|
||||
std::get<2>(user_config)
|
||||
);
|
||||
if (c_p_user_res) {
|
||||
return c_p_user_res;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief User names and attributes to be check and verified.
|
||||
*/
|
||||
const std::vector<user_config> 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
|
||||
int c_users_res =
|
||||
create_extra_users(proxysql_admin, mysql_server, c_user_attributes);
|
||||
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_config> 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();
|
||||
}
|
||||
Loading…
Reference in new issue