/** * @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 #include #include #include #include #include #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; using user_attributes = std::tuple; /** * @brief User names and attributes to be check and verified. */ const std::vector 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 ) { json j_status = fetch_internal_session(proxysql_mysql); 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] }; mysql_free_result(trx_iso_res); } 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)) { if (!mysql_real_connect(mysql_server, cl.mysql_host, cl.mysql_username, cl.mysql_password, NULL, cl.mysql_port, 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 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 { 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); } mysql_close(proxysql_admin); mysql_close(mysql_server); return exit_status(); }