From ceffceefaf796ea39d6f62207aa7044362a5357c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 5 Feb 2025 15:34:05 +0100 Subject: [PATCH] Add test for conn matching based on 'DEPRECATE_EOF' capability --- test/tap/tap/utils.cpp | 83 +- test/tap/tap/utils.h | 42 +- test/tap/tests/Makefile | 2 + test/tap/tests/test_match_eof_conn_cap.cpp | 1014 ++++++++++++++++++++ 4 files changed, 1125 insertions(+), 16 deletions(-) create mode 100644 test/tap/tests/test_match_eof_conn_cap.cpp diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index a15411033..a4d8e74af 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -239,7 +239,7 @@ std::size_t count_matches(const string& str, const string& substr) { } int mysql_query_t__(MYSQL* mysql, const char* query, const char* f, int ln, const char* fn) { - diag("%s:%d:%s(): Issuing query '%s' to ('%s':%d)", f, ln, fn, query, mysql->host, mysql->port); + diag("%s:%d:%s(): Issuing query \"%s\" to ('%s':%d)", f, ln, fn, query, mysql->host, mysql->port); return mysql_query(mysql, query); } @@ -582,9 +582,9 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const int32_t& d if (row.empty() || row.front().empty()) { return { -1, def_val, {} }; } else { - errno = 0; - char* p_end {}; - const int32_t val = std::strtol(row.front().c_str(), &p_end, 10); + errno = 0; + char* p_end {}; + const int32_t val = std::strtol(row.front().c_str(), &p_end, 10); if (row[0] == p_end || errno == ERANGE) { return { -2, def_val, string { row[0] } }; @@ -598,9 +598,9 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const uint32_t& if (row.empty() || row.front().empty()) { return { -1, def_val, {} }; } else { - errno = 0; - char* p_end {}; - const uint32_t val = std::strtoul(row.front().c_str(), &p_end, 10); + errno = 0; + char* p_end {}; + const uint32_t val = std::strtoul(row.front().c_str(), &p_end, 10); if (row[0] == p_end || errno == ERANGE) { return { -2, def_val, string { row[0] } }; @@ -615,9 +615,9 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const int64_t& d if (row.empty() || row.front().empty()) { return { -1, def_val, {} }; } else { - errno = 0; - char* p_end {}; - const int64_t val = std::strtoll(row.front().c_str(), &p_end, 10); + errno = 0; + char* p_end {}; + const int64_t val = std::strtoll(row.front().c_str(), &p_end, 10); if (row[0] == p_end || errno == ERANGE) { return { -2, def_val, string { row[0] } }; @@ -631,9 +631,9 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const uint64_t& if (row.empty() || row.front().empty()) { return { -1, def_val, {} }; } else { - errno = 0; - char* p_end {}; - const uint64_t val = std::strtoull(row.front().c_str(), &p_end, 10); + errno = 0; + char* p_end {}; + const uint64_t val = std::strtoull(row.front().c_str(), &p_end, 10); if (row[0] == p_end || errno == ERANGE) { return { -2, def_val, string { row[0] } }; @@ -1789,6 +1789,61 @@ int dump_conn_stats(MYSQL* admin, const vector hgs) { return EXIT_SUCCESS; } +string row_to_str(const mysql_res_row& row) { + string res { "[" }; + + for (const auto& e : row) { + res += "\"" + e + "\""; + + if (&e != &row.back()) { + res += ","; + } + } + + res += "]"; + + return res; +} + +ext_val_t ext_single_row_val(const mysql_res_row& row, const hg_pool_st_t& def_val) { + if (row.empty() || row.size() != sizeof(hg_pool_st_t)/sizeof(uint32_t)) { + return { -1, def_val, {} }; + } else { + for (int i = 0; i < sizeof(hg_pool_st_t)/sizeof(uint32_t); i++) { + if (row[i].empty()) { + return { -1, def_val, {} }; + } + } + + errno = 0; + char* p_end { nullptr }; + hg_pool_st_t res {}; + const string row_str { row_to_str(row) }; + + res.hostgroup = std::strtoull(row.front().c_str(), &p_end, 10); + if (row[0].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.conn_used = std::strtoull(row[1].c_str(), &p_end, 10); + if (row[1].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.conn_free = std::strtoull(row[2].c_str(), &p_end, 10); + if (row[2].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.conn_ok = std::strtoull(row[3].c_str(), &p_end, 10); + if (row[3].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.conn_err = std::strtoull(row[4].c_str(), &p_end, 10); + if (row[4].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.max_conn_used = std::strtoull(row[5].c_str(), &p_end, 10); + if (row[5].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + res.max_conn_used = std::strtoull(row[6].c_str(), &p_end, 10); + if (row[6].c_str() == p_end || errno == ERANGE) { return { -2, def_val, row_str }; } + + return { EXIT_SUCCESS, res, row_str }; + } +} + +ext_val_t get_conn_pool_hg_stats(MYSQL* admin, uint32_t hg) { + const string HG_STATS_QUERY { gen_conn_stats_query({ hg }) }; + return mysql_query_ext_val(admin, HG_STATS_QUERY, hg_pool_st_t {}); +} + pair fetch_conn_stats(MYSQL* admin, const vector hgs) { const string stats_query { gen_conn_stats_query(hgs) }; const pair> conn_pool_stats { exec_dql_query(admin, stats_query, true) }; @@ -1841,6 +1896,8 @@ int check_cond(MYSQL* mysql, const string& q) { res = 0; } } + + mysql_free_result(myres); } } else { diag("Check failed with error '%s'", mysql_error(mysql)); diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 29eeb4c73..16b85efdb 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -54,6 +54,14 @@ my_bool mysql_stmt_close_override(MYSQL_STMT* stmt, const char* file, int line); } +static inline int mysql_query_override(MYSQL* mysql, const std::string& query, const char* file, int line) { + return mysql_query_override(mysql, query.c_str(), file, line); +} + +#else +static inline int mysql_query(MYSQL* mysql, const std::string& query) { + return mysql_query(mysql, query.c_str()); +} #endif /** @@ -86,6 +94,9 @@ inline std::string get_formatted_time() { * @return Result of calling 'mysql_query'. */ int mysql_query_t__(MYSQL* mysql, const char* query, const char* f, int ln, const char* fn); +inline static int mysql_query_t__(MYSQL* mysql, const std::string& query, const char* f, int ln, const char* fn) { + return mysql_query_t__(mysql, query.c_str(), f, ln, fn); +} /** * @brief Convenience macro with query logging. @@ -112,8 +123,7 @@ int mysql_query_t__(MYSQL* mysql, const char* query, const char* f, int ln, cons #define MYSQL_QUERY_T(mysql, query) \ do { \ - diag("Issuing query '%s' to ('%s':%d)", query, mysql->host, mysql->port); \ - if (mysql_query(mysql, query)) { \ + if (mysql_query_t(mysql, query)) { \ fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); \ return EXIT_FAILURE; \ } \ @@ -182,7 +192,7 @@ struct ext_val_t { }; /** - * @brief Specifications of function 'ext_single_row_val' for different types. + * @brief Specializations of function 'ext_single_row_val' for different types. * @details These functions serve as the extension point for `mysql_query_ext_val`. A new specialization of * the function is required for each type that `mysql_query_ext_val` should support for the default value. * @param row The row from which the first value is going to be extracted and parsed. @@ -725,6 +735,31 @@ struct POOL_STATS_IDX { }; }; +struct hg_pool_st_t { + uint32_t hostgroup; + uint32_t conn_used; + uint32_t conn_free; + uint32_t conn_ok; + uint32_t conn_err; + uint32_t max_conn_used; + uint32_t queries; +}; + +/** + * @brief A more complex type specialization of 'ext_single_row_val'. + * @details For internal use of function 'get_conn_pool_hg_stats'. + * @param row The row from which the first value is going to be extracted and parsed. + * @param def_val The default value to use in case of failure to extract. + * @return An `ext_val_t` where T is the type of the provided default value. + */ +ext_val_t ext_single_row_val(const mysql_res_row& row, const hg_pool_st_t& def_val); +/** + * @brief Fetches the stats from a particular hostgroup. + * @param admin An already opened connection to MySQL admin. + * @return An `ext_val_t` wrapping the extracted values. + */ +ext_val_t get_conn_pool_hg_stats(MYSQL* admin, uint32_t hg); + /** * @brief Dumps a resultset with fields from the supplied hgs from 'stats_mysql_connection_pool'. * @details The fetched fields are 'hostgroup,ConnUsed,ConnFree,ConnOk,ConnERR,MaxConnUsed,Queries'. @@ -741,6 +776,7 @@ using pool_state_t = std::map; * @return A pair of the shape {err_code, pool_state_t}. */ std::pair fetch_conn_stats(MYSQL* admin, const std::vector hgs); + /** * @brief Waits until the condition specified by the 'query' holds, or 'timeout' is reached. * @details Several details about the function impl: diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index 4e99576cf..e3455107b 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -213,6 +213,8 @@ tests: tests-cpp \ reg_test_3504-change_user_libmysql_helper \ mysql_reconnect_libmariadb-t \ mysql_reconnect_libmysql-t \ + test_match_eof_conn_cap_libmysql-t \ + test_match_eof_conn_cap_libmariadb-t \ setparser_test2 setparser_test2-t \ setparser_test3 setparser_test3-t \ set_testing-240.csv \ diff --git a/test/tap/tests/test_match_eof_conn_cap.cpp b/test/tap/tests/test_match_eof_conn_cap.cpp new file mode 100644 index 000000000..7f169f42d --- /dev/null +++ b/test/tap/tests/test_match_eof_conn_cap.cpp @@ -0,0 +1,1014 @@ +/** + * @file test_match_eof_conn_cap.cpp + * @brief Checks that ProxySQL correctly matches capabilities when matching client and backend conns. + * @details When a client session requires a backend connection, it requests it to the connection pool. + * A filtering (via `MySQL_Connection::match_tracked_options`) takes place for conn selection, if no + * suitable conn is found, it should be created with the requested session options. This test checks this + * logic correctness and exercises it for all possible combinations of client and backend conns capabilities + * (`CLIENT_DEPRECATE_EOF`). For doing this, the test: + * 1. Configs ProxySQL as MySQL backend of itself, via the `SQLite3` interface. This way, support for + * all frontend-backend capabilities combinations can be tested with a single instance. + * 2. Configs a client user with 'fast_forward'. Since fast-forward is relevant for conn-matching, the + * config for the user will be switched (on/off) for each test, to exercise different combinations. + * Then, the test performs the following checks for conn creation: + * 1. Creates a conn to ProxySQL with one of the possible caps combination. + * 1. Switches (or not) the capability support for client/backend conns. + * 2. Attempts to perform a query, forcing the creation of a backend conn. + * 3. Performs multiple checks over the query and ProxySQL error log metrics: + * - Checks if the query was expected to fail/succeed (conn creation). + * - Checks error log for conn creation failures (caps mismatch). + * - Checks audit log for number of conn received (SQLite3 backend ProxySQL-ProxySQL). + * - Checks stats on connection creation to be increased by the expected amount. + * A similar flow is exercised for conn matching, with the difference that the previous steps are performed + * against an already warm-up conn-pool. + * + * For being able to test `CLIENT_DEPRECATE_EOF` disabled, and ensuring the correct handling of sessions by + * ProxySQL the test is compiled against both `libmaridb` and `libmysql`. Executing both binaries **is + * required** as right now it's the only way to exercise all the possibilities. + * + * NOTE: The checks on this test are oriented for the capability `CLIENT_DEPRECATE_EOF`. Yet, the flow + * present in this test is more generic, as it should match the one for any other client capability that + * requires to be matched between frontend and backend conns. This is even more relevant if the match is + * required for an smooth transition between regular and `fast-forward` sessions, as it's the case for + * `CLIENT_DEPRECATE_EOF`. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mysql.h" + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::fstream; +using std::string; +using std::vector; + +#define _S(s) ( std::string {s} ) +#define _TO_S(s) ( std::to_string(s) ) + +#define CHECK_EXT_VAL(val)\ + do {\ + if (val.err) {\ + diag("%s:%d: Query failed err=\"%s\"", __func__, __LINE__, val.str.c_str());\ + return EXIT_FAILURE;\ + }\ + } while(0) + +MYSQL* create_mysql_conn(const conn_opts_t& opts) { + const char* host { opts.host.c_str() }; + const char* user { opts.user.c_str() }; + const char* pass { opts.pass.c_str() }; + + MYSQL* conn = mysql_init(NULL); + +#ifndef LIBMYSQL_HELPER8 + conn->options.client_flag &= ~CLIENT_DEPRECATE_EOF; +#endif + +#ifdef LIBMYSQL_HELPER8 + { + enum mysql_ssl_mode ssl_mode = SSL_MODE_DISABLED; + mysql_options(conn, MYSQL_OPT_SSL_MODE, &ssl_mode); + } +#endif + + if (!mysql_real_connect(conn, host, user, pass, NULL, opts.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(conn)); + return NULL; + } + + return conn; +} + +#define SELECT_RUNTIME_VAR "SELECT variable_value FROM runtime_global_variables WHERE variable_name=" +#define TAP_NAME "TAP_FAST_FORWARD_CONNS_MATCHING_FLAGS___" + +// Test specific ENV variables +const int HG_ID = get_env_int(TAP_NAME"MYSQL_SERVER_HOSTGROUP", 0); +const int SQLITE3_HG = get_env_int(TAP_NAME"SQLITE3_HOSTGROUP", 1459); +const int SQLITE3_PORT = get_env_int(TAP_NAME"SQLITE3_HOSTGROUP", 6030); +const int CONN_POOL_WARNMUP = get_env_int(TAP_NAME"CONN_POOL_WARNMUP", 10); +const char* FF_USER = get_env_str(TAP_NAME"FF_USER", "sbtest2"); +const char* FF_PASS = get_env_str(TAP_NAME"FF_PASS", "sbtest2"); +const int RETRIES_DELAY = get_env_int(TAP_NAME"CONNECT_RETRIES_DELAY", 500); +const int TO_SERVER_MAX = get_env_int(TAP_NAME"CONNECT_TIMEOUT_SERVER_MAX", 2000); + +// Not specific ENV variables +const string PROXYSQL_LOG_PATH { get_env_str("REGULAR_INFRA_DATADIR", "/tmp/") + _S("/proxysql.log") }; +const string PROXYSQL_AUDIT_DIR { get_env_str("REGULAR_INFRA_DATADIR", "/tmp/datadir") }; + +void dump_as_table(MYSQL* conn, const string& msg, const std::string& query) { + int rc = mysql_query_t(conn, query.c_str()); + if (rc) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(conn)); + exit(1); + } + + MYSQL_RES* myres { mysql_store_result(conn) }; + diag("%s\n%s", msg.c_str(), dump_as_table(myres).c_str()); + mysql_free_result(myres); +} + +void run_setup_query(MYSQL* conn, const string& query) { + int rc = mysql_query_t(conn, query.c_str()); + if (rc) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(conn)); + exit(1); + } +} + +const vector& mysql_variables_setup { + "SET mysql-have_ssl='false'", + "LOAD MYSQL VARIABLES TO RUNTIME", +}; + +const vector& sqlite3_server_setup { + "DELETE FROM mysql_servers WHERE hostgroup_id = " + _TO_S(SQLITE3_HG), + "INSERT INTO mysql_servers (hostgroup_id, hostname, port, use_ssl)" + " VALUES (" + _TO_S(SQLITE3_HG) + ", '127.0.0.1', " + _TO_S(SQLITE3_PORT) + ", 0)", + "LOAD MYSQL SERVERS TO RUNTIME", + + "DELETE FROM mysql_users WHERE username = '" + _S(FF_USER) + "'", + "INSERT INTO mysql_users (username,password,fast_forward,default_hostgroup)" + " VALUES ('" + _S(FF_USER) + "','" + _S(FF_PASS) + "',1," + _TO_S(SQLITE3_HG) + ")", + "LOAD MYSQL USERS TO RUNTIME", +}; + +int wait_for_conn_pool_st(MYSQL* admin, int32_t tg_hg, int32_t count) { + int to = wait_for_cond(admin, + "SELECT IIF(" + "(SELECT SUM(ConnUsed) + SUM(ConnFree)" + " FROM stats_mysql_connection_pool" + " WHERE hostgroup=" + _TO_S(tg_hg) + ")=" + _TO_S(count) + "," + " TRUE, FALSE" + ")", + 10 + ); + + if (to == EXIT_FAILURE) { + diag("Waiting for conn_pool status failed tg_hg=%d conn_count=%d", tg_hg, count); + dump_as_table(admin, "'stats_mysql_connection_pool' status after wait:", + "SELECT hostgroup, srv_host, srv_port, ConnFree, ConnUsed, ConnOK, ConnERR" + " FROM stats_mysql_connection_pool" + ); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +const string SELECT_CONN_SUM { + "SELECT SUM(ConnUsed) + SUM(ConnFree) FROM stats_mysql_connection_pool" + " WHERE hostgroup='" + std::to_string(SQLITE3_HG) + "'" +}; + +const string CONN_POOL_HG_STATUS { + "SELECT hostgroup, srv_host, srv_port, ConnFree, ConnUsed, ConnOK, ConnERR" + " FROM stats_mysql_connection_pool WHERE hostgroup='" + std::to_string(SQLITE3_HG) + "'" +}; + +int conn_pool_cleanup(MYSQL* admin, int tg_hg, int count) { + ext_val_t hg_conn_sum { mysql_query_ext_val(admin, SELECT_CONN_SUM, -1) }; + + if (hg_conn_sum.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, hg_conn_sum) }; + diag("Fetching conn_pool status failed err:`%s`", err.c_str()); + return EXIT_FAILURE; + } + + diag("Checking conn_pool status ConnUsed+ConnFree=%d tg=%d", hg_conn_sum.val, count); + + if (hg_conn_sum.val >= 1) { + MYSQL_QUERY_T(admin, + "UPDATE mysql_servers SET max_connections=" + _TO_S(count) + + " WHERE hostgroup_id=" + _TO_S(SQLITE3_HG) + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + int rc = wait_for_conn_pool_st(admin, tg_hg, count); + + if (rc == EXIT_SUCCESS) { + MYSQL_QUERY_T(admin, + "UPDATE mysql_servers SET max_connections=" + _TO_S(1000) + + " WHERE hostgroup_id=" + _TO_S(SQLITE3_HG) + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + } + + return rc; + } + + return EXIT_SUCCESS; +} + +const vector test_conn_creation___mysql_config { + "SET mysql-connect_retries_delay=" + _TO_S(RETRIES_DELAY), + "SET mysql-connect_timeout_server=" + _TO_S(100), + "SET mysql-connect_timeout_server_max=" + _TO_S(TO_SERVER_MAX), + "LOAD MYSQL VARIABLES TO RUNTIME" +}; + +const char* get_ext (const char *fspec) { + const char* e { strrchr (fspec, '.') }; + + if (e == NULL) { + return ""; + } else { + return e; + } +} + +string find_latest_split(const std::string &dir_path, const string& prefix) { + DIR *dir = opendir(dir_path.c_str()); + if (!dir) { + printf("Failed to open directory path=\"%s\"", dir_path.c_str()); + return {}; + } + + struct dirent* entry { nullptr }; + string latest_split_fname {}; + uint64_t latest_split_ext { 0 }; + + while ((entry = readdir(dir)) != nullptr) { + const string fname { entry->d_name }; + + if (fname == "." || fname == "..") { + continue; + } + + if (fname.rfind(prefix) == 0) { + const string ext { get_ext(fname.c_str()) }; + const string e_ext { ext.size() > 1 ? ext.substr(1) : "" }; + + errno = 0; + char* p_end { nullptr }; + const uint64_t ext_val { std::strtoull(e_ext.c_str(), &p_end, 10) }; + + if (ext.c_str() == p_end) { + continue; + } else if (latest_split_ext < ext_val) { + latest_split_ext = ext_val; + latest_split_fname = fname; + } + } + } + + closedir(dir); + + return latest_split_fname; +} + +struct pool_cnf_t { + bool warmup { false }; + uint32_t conn_caps { 0 }; +}; + +struct conn_conf_t { + const CommandLine& cl; + bool fast_forward { false }; + uint32_t conn_caps { 0 }; +}; + +/** + * @details + * Scenario 1 -> 0: + * Should be tested through simple config and dynamic (on-the-fly) switch. When demanding the scenario, no + * conn should be found for either case (backend doesn't support either ProxySQL supports the creation). We + * need to test conn creation and conn_pool warmup here. + * + * Scenario 0 -> 1: + * ProxySQL won't allow a mismatch **for conn creation**, since the backend connection support is + * automatically disabled based on client requirements. This scenario can still be tested with a warmup + * connection pool. Dynamic switch shouldn't make a difference here as it's only relevant for connection + * creation. + */ +struct proxy_cnf_t { + bool cli_depr_eof { false }; + bool srv_depr_eof { false }; + bool force_mismatch { false }; +}; + +struct test_cnf_t { + pool_cnf_t pool_status; + conn_conf_t conn_conf; + proxy_cnf_t proxy_conf; +}; + +vector gen_all_proxy_cnfs() { + vector> all_bin_vec { get_all_bin_vec(4) }; + std::sort(all_bin_vec.begin(), all_bin_vec.end()); + + vector all_proxy_cnfs {}; + for (const vector& bin_vec : all_bin_vec) { + all_proxy_cnfs.push_back({ bin_vec[3], bin_vec[2], bin_vec[1] }); + } + + return all_proxy_cnfs; +} + +string to_string(const pool_cnf_t& st) { + return "{" + "\"is_warmup\": " + _TO_S(st.warmup) + ",\"conn_caps\":" + _TO_S(st.conn_caps) + + "}"; +} + +string to_string(const conn_conf_t& cnf) { + return "{" + "\"fast_forward\": " + _TO_S(cnf.fast_forward) + ",\"conn_caps\":" + _TO_S(cnf.conn_caps) + + "}"; +} + +string to_string(const proxy_cnf_t& cnf) { + return "{" + "\"client_deprecate_eof\": " + _TO_S(cnf.cli_depr_eof) + + ",\"server_deprecate_eof\":" + _TO_S(cnf.srv_depr_eof) + + ",\"force_mismatch\":" + _TO_S(cnf.force_mismatch) + + "}"; +} + +int apply_proxy_conf(MYSQL* admin, const proxy_cnf_t& cnf) { + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(cnf.cli_depr_eof)); + MYSQL_QUERY_T(admin, "SET mysql-enable_server_deprecate_eof=" + _TO_S(cnf.srv_depr_eof)); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + return EXIT_SUCCESS; +} + +string to_string(const test_cnf_t& test_conf) { + return "{" + "\"pool_status\":" + to_string(test_conf.pool_status) + + ",\"conn_conf\":" + to_string(test_conf.conn_conf) + + ",\"proxy_conf\":" + to_string(test_conf.proxy_conf) + + "}"; +} + +int test_conn_acquisition(MYSQL* admin, const test_cnf_t& test_conf) { + const conn_conf_t& conn_cnf { test_conf.conn_conf }; + const pool_cnf_t& pool_st { test_conf.pool_status }; + const proxy_cnf_t& proxy_cnf { test_conf.proxy_conf }; + + diag("Started '%s' %s", __func__, to_string(test_conf).c_str()); + + diag("Setting test global 'mysql-variables' test='%s'", __func__); + std::for_each(test_conn_creation___mysql_config.begin(), test_conn_creation___mysql_config.end(), + [&admin] (const string& q) { return run_setup_query(admin, q); } + ); + + // UPDATE GENERIC TEST CONFIG + /////////////////////////////////////////////////////////////////////////// + const ext_val_t retries_delay { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-connect_retries_delay'", -1) + }; + CHECK_EXT_VAL(retries_delay); + const ext_val_t to_server_max { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-connect_timeout_server_max'", -1) + }; + CHECK_EXT_VAL(to_server_max); + /////////////////////////////////////////////////////////////////////////// + + diag( + "Update 'fast-forward' for testing user user=\"%s\" fast_forward=%d", + FF_USER, conn_cnf.fast_forward + ); + MYSQL_QUERY_T(admin, + "UPDATE mysql_users SET fast_forward=" + _TO_S(conn_cnf.fast_forward) + + " WHERE username='" + _S(FF_USER) + "'" + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + diag("Using %s 'mysql-enable_client/server_deprecate_eof'", proxy_cnf.force_mismatch ? "DYNAMIC" : "STATIC"); + int rc = apply_proxy_conf(admin, proxy_cnf); + if (rc) { return EXIT_FAILURE; } + + diag("Create client MySQL conn user=\"%s\" port=\"%d\"", FF_USER, conn_cnf.cl.port); + MYSQL* proxy { create_mysql_conn({ conn_cnf.cl.host, FF_USER, FF_PASS, conn_cnf.cl.port }) }; + if (proxy == NULL) { return EXIT_FAILURE; } + dump_as_table(admin, "'connection_pool' status after client conn:", CONN_POOL_HG_STATUS); + + if (proxy_cnf.force_mismatch) { + diag("Revert 'client_deprecate_eof' client_deprecate_eof=%d", !proxy_cnf.cli_depr_eof); + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(!proxy_cnf.cli_depr_eof)); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + } + + diag("Get pre-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t pre_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(pre_hg_st); + const ext_val_t pre_srv_conns { mysql_query_ext_val(admin, + "SELECT variable_value FROM stats.stats_mysql_global WHERE variable_name='Server_Connections_created'", + int64_t(-1) + )}; + CHECK_EXT_VAL(pre_srv_conns); + + fstream logfile_fs {}; + + { + diag("Open General log to check for errors path=\"%s\"", PROXYSQL_LOG_PATH.c_str()); + int of_err = open_file_and_seek_end(PROXYSQL_LOG_PATH, logfile_fs); + if (of_err != EXIT_SUCCESS) { return of_err; } + } + + diag("Open ProxySQL audit log to check the connections attempts path=\"%s\"", PROXYSQL_LOG_PATH.c_str()); + const ext_val_t audit_fname { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-auditlog_filename'", _S("audit.log")) + }; + CHECK_EXT_VAL(audit_fname); + + const string latest_split { find_latest_split(PROXYSQL_AUDIT_DIR, audit_fname.val) }; + const string audit_path { PROXYSQL_AUDIT_DIR + "/" + latest_split }; + fstream auditlog_fs {}; + + { + diag("Open Audit log to check for conns path=\"%s\"", audit_path.c_str()); + int of_err = open_file_and_seek_end(audit_path, auditlog_fs); + if (of_err != EXIT_SUCCESS) { return of_err; } + } + + diag("Issuing query (trx) creating new backend conn"); + rc = mysql_query_t(proxy, "BEGIN"); + if (rc == 0) { + diag("Previous query successful; closing trx is required"); + mysql_query_t(proxy, "COMMIT"); + } + + // Sanity check; query should **NEVER** fail if mismatch is allowed (no fast-forward). + if (rc != 0 && !conn_cnf.fast_forward) { + diag( + "Config should allow capabilities mismatch, but query failed. Aborting test error=\"%s\"", + mysql_error(proxy) + ); + return EXIT_FAILURE; + } + + if ( + // * -> (* *) -> *: + // No match required without fast-forward + !conn_cnf.fast_forward + || + // * -> (* *) -> *: + // ConnPool warmup scenarios: + ( + // For pool to serve the query, warmup conns caps need to match config on client-side. Client + // conn caps must be checked (not only config) as disabled always win negotiation. + pool_st.warmup && !conn_cnf.fast_forward && ( + (bool(pool_st.conn_caps & CLIENT_DEPRECATE_EOF) == + (proxy_cnf.cli_depr_eof && (conn_cnf.conn_caps & CLIENT_DEPRECATE_EOF))) + ) + ) + || + ( + // 1 -> (1 *) -> 0 + conn_cnf.fast_forward && ( + !( + bool(conn_cnf.conn_caps & CLIENT_DEPRECATE_EOF) + && proxy_cnf.cli_depr_eof == 1 + && proxy_cnf.force_mismatch + ) + ) + ) + ) { + ok( + rc == 0, + "Query should SUCCEED (backend-conn match) conn_conf='%s' proxy_conf='%s' pool_st='%s'", + to_string(conn_cnf).c_str(), to_string(proxy_cnf).c_str(), to_string(pool_st).c_str() + ); + } else { + ok( + rc != 0, + "Query should FAIL (no backend-conn match) conn_conf='%s' proxy_conf='%s' pool_st='%s'", + to_string(conn_cnf).c_str(), to_string(proxy_cnf).c_str(), to_string(pool_st).c_str() + ); + } + + uint32_t exp_conns { 0 }; + + // * -> (* *) -> *: + // If conn_pool is warm-up coons matches 'proxy_cnf.cli_depr_eof', conns should be reused from the pool. + if ( + // fast-forward avoids the connection pool + pool_st.warmup && !conn_cnf.fast_forward + ) { + exp_conns = 0; + } + // Conn creation is required; no warmup pool + else { + // A match is required between client and backend conns: + if ( + // No fast-forward should always work (no match-enforced) + !conn_cnf.fast_forward + || + // Match is enforced by fast-forward + ( + conn_cnf.fast_forward + && + ( + // 1 -> (0 *) -> 1: + // Disabled by server (ProxySQL) (fast-forward propagation) + ( + !proxy_cnf.cli_depr_eof || !(conn_cnf.conn_caps & CLIENT_DEPRECATE_EOF) + ) + || + // 0 -> (* *) -> *: + // Disabled by client always wins (fast-forward propagation) + ( + !(conn_cnf.conn_caps & CLIENT_DEPRECATE_EOF) && proxy_cnf.cli_depr_eof + ) + || + // 1 -> (1 1) -> 0: + !(proxy_cnf.cli_depr_eof && proxy_cnf.force_mismatch) + ) + ) + || + // * -> (0 0) -> *: Client caps should always match backend + (!proxy_cnf.cli_depr_eof && !proxy_cnf.srv_depr_eof) + ) { + exp_conns = 1; + } else { + exp_conns = TO_SERVER_MAX / RETRIES_DELAY; + } + } + + const string conn_match_regex { + "Failed to obtain suitable connection for fast-forward; server lacks the required capabilities" + " hostgroup=" + _TO_S(SQLITE3_HG) + " client_flags=\\d+ server_capabilities=\\d+" + }; + + diag("Check ProxySQL log for connection mismatches regex=\"%s\"", conn_match_regex.c_str()); + vector match_lines { get_matching_lines(logfile_fs, conn_match_regex)}; + diag("Found General log matching lines count=%ld", match_lines.size()); + + uint32_t exp_lines { exp_conns == 1 || exp_conns == 0 ? 0 : exp_conns }; + ok( + match_lines.size() == exp_lines, + "Error log should hold conn match failures lines=%ld exp_lines=%d", + match_lines.size(), exp_lines + ); + + diag("Check Audit log for connections attempts on SQLite3"); + vector audit_lines { get_matching_lines(auditlog_fs, + "SQLite3_Connect_OK.*" + _S(FF_USER) + )}; + diag("Found Audit log matching lines count=%ld", audit_lines.size()); + + ok( + audit_lines.size() == exp_conns, + "Audit log should contain SQLite3 created conns lines=%ld exp_conns=%d", + audit_lines.size(), exp_conns + ); + + diag("Get post-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t post_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(post_hg_st); + + ok( + pre_hg_st.val.conn_ok + exp_conns == post_hg_st.val.conn_ok, + "Conn created should have increased by query attempt pre-ConnOK=%d post-ConnOK=%d", + pre_hg_st.val.conn_ok, post_hg_st.val.conn_ok + ); + + const ext_val_t post_srv_conns { + mysql_query_ext_val( + admin, + "SELECT variable_value FROM stats.stats_mysql_global" + " WHERE variable_name='Server_Connections_created'", + int64_t(-1) + ) + }; + CHECK_EXT_VAL(post_srv_conns); + + ok( + pre_srv_conns.val + exp_conns == post_srv_conns.val, + "Conn created should have increased by query attempt" + " pre-Server_Connections_created=%ld post-Server_Connections_created=%ld", + pre_srv_conns.val, post_srv_conns.val + ); + + dump_as_table(admin, "'connection_pool' status after client conn:", CONN_POOL_HG_STATUS); + + mysql_close(proxy); + + return EXIT_SUCCESS; +} + +int test_conn_acquisition( + MYSQL* admin, const CommandLine& cl, bool ff, bool client_eof, bool force_match, bool warmup_pool +) { + diag("Started '%s' ff=%d client_eof=%d force_match=%d", __func__, ff, client_eof, force_match); + + diag("Setting test global 'mysql-variables' test='%s'", __func__); + std::for_each(test_conn_creation___mysql_config.begin(), test_conn_creation___mysql_config.end(), + [&admin] (const string& q) { return run_setup_query(admin, q); } + ); + + ////// + const ext_val_t retries_delay { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-connect_retries_delay'", -1) + }; + CHECK_EXT_VAL(retries_delay); + const ext_val_t to_server_max { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-connect_timeout_server_max'", -1) + }; + CHECK_EXT_VAL(to_server_max); + ////// + + diag("Update 'fast-forward' for testing user user=\"%s\" fast_forward=%d", FF_USER, ff); + MYSQL_QUERY_T(admin, + "UPDATE mysql_users SET fast_forward=" + _TO_S(ff) + " WHERE username='" + _S(FF_USER) + "'" + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + diag("Update 'mysql-enable_client_deprecate_eof' enable_client_deprecate_eof=%d", client_eof); + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(client_eof)); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + diag("Create client MySQL conn user=\"%s\" port=\"%d\"", FF_USER, cl.port); + MYSQL* proxy { create_mysql_conn({ cl.host, FF_USER, FF_PASS, cl.port }) }; + if (proxy == NULL) { return EXIT_FAILURE; } + dump_as_table(admin, "'connection_pool' status after client conn:", CONN_POOL_HG_STATUS); + + diag("Revert support for 'enable_client_deprecate_eof' client_eof=%d", client_eof); + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(!client_eof)); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + diag("Get pre-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t pre_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(pre_hg_st); + const ext_val_t pre_srv_conns { mysql_query_ext_val(admin, + "SELECT variable_value FROM stats.stats_mysql_global WHERE variable_name='Server_Connections_created'", + int64_t(-1) + )}; + CHECK_EXT_VAL(pre_srv_conns); + + fstream logfile_fs {}; + + { + diag("Open General log to check for errors path=\"%s\"", PROXYSQL_LOG_PATH.c_str()); + int of_err = open_file_and_seek_end(PROXYSQL_LOG_PATH, logfile_fs); + if (of_err != EXIT_SUCCESS) { return of_err; } + } + + diag("Open ProxySQL audit log to check the connections attempts path=\"%s\"", PROXYSQL_LOG_PATH.c_str()); + const ext_val_t audit_fname { + mysql_query_ext_val(admin, SELECT_RUNTIME_VAR"'mysql-auditlog_filename'", _S("audit.log")) + }; + CHECK_EXT_VAL(audit_fname); + + const string latest_split { find_latest_split(PROXYSQL_AUDIT_DIR, audit_fname.val) }; + const string audit_path { PROXYSQL_AUDIT_DIR + "/" + latest_split }; + fstream auditlog_fs {}; + + { + diag("Open Audit log to check for conns path=\"%s\"", audit_path.c_str()); + int of_err = open_file_and_seek_end(audit_path, auditlog_fs); + if (of_err != EXIT_SUCCESS) { return of_err; } + } + + diag("Issue query (start trx) to create new backend conn query=\"%s\"", "BEGIN"); + int rc = mysql_query_t(proxy, "BEGIN"); + if (rc == 0) { + diag("Previous query successful; closing trx is required"); + mysql_query_t(proxy, "COMMIT"); + } else if (rc != 0 && !(force_match || ff)) { + diag("Query failed. Aborting test error=\"%s\"", mysql_error(proxy)); + return EXIT_FAILURE; + } + + if ((force_match || ff) && client_eof) { + ok( + rc != 0, + "Query should FAIL (no backend-conn match) ff=%d client_eof=%d force_match=%d", + ff, client_eof, force_match + ); + } else { + ok( + rc == 0, + "Query should SUCCEED (backend-conn match) ff=%d client_eof=%d force_match=%d", + ff, client_eof, force_match + ); + } + + const int32_t exp_conns { + !(force_match || ff) && warmup_pool ? 0 : + (force_match || ff || client_eof) ? TO_SERVER_MAX/RETRIES_DELAY : 1 + }; + const string conn_match_regex { + "Failed to obtain suitable connection for fast-forward; server lacks the required capabilities" + " hostgroup=" + _TO_S(SQLITE3_HG) + " client_flags=\\d+ server_capabilities=\\d+" + }; + + diag("Check ProxySQL log for connection mismatches regex=\"%s\"", conn_match_regex.c_str()); + vector match_lines { get_matching_lines(logfile_fs, conn_match_regex)}; + diag("Found General log matching lines count=%ld", match_lines.size()); + + bool exp_lines {}; + ok( + force_match || ff ? match_lines.size() == exp_conns : match_lines.size() == 0, + "Error log should hold conn match failures lines=%ld exp_lines=%d", + match_lines.size(), exp_conns + ); + + diag("Check Audit log for connections attempts on SQLite3"); + vector audit_lines { get_matching_lines(auditlog_fs, + "SQLite3_Connect_OK.*" + _S(FF_USER) + )}; + diag("Found Audit log matching lines count=%ld", audit_lines.size()); + + ok( + audit_lines.size() == exp_conns, + "Audit log should contain SQLite3 created conns lines=%ld exp_lines=%d", + audit_lines.size(), exp_conns + ); + + diag("Get post-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t post_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(post_hg_st); + + ok( + pre_hg_st.val.conn_ok + exp_conns == post_hg_st.val.conn_ok, + "Conn created should have increased by query attempt pre-ConnOK=%d post-ConnOK=%d", + pre_hg_st.val.conn_ok, post_hg_st.val.conn_ok + ); + + const ext_val_t post_srv_conns { + mysql_query_ext_val( + admin, + "SELECT variable_value FROM stats.stats_mysql_global" + " WHERE variable_name='Server_Connections_created'", + int64_t(-1) + ) + }; + CHECK_EXT_VAL(post_srv_conns); + + ok( + pre_srv_conns.val + exp_conns == post_srv_conns.val, + "Conn created should have increased by query attempt" + " pre-Server_Connections_created=%ld post-Server_Connections_created=%ld", + pre_srv_conns.val, post_srv_conns.val + ); + + dump_as_table(admin, "'connection_pool' status after client conn:", CONN_POOL_HG_STATUS); + + mysql_close(proxy); + + return EXIT_SUCCESS; +} + +int test_conn_creation( + MYSQL* admin, const CommandLine& cl, bool ff, bool client_eof, bool force_match +) { + diag("Started '%s' ff=%d client_eof=%d force_match=%d", __func__, ff, client_eof, force_match); + + diag("Initial connpool cleanup on hg hg=%d conn_tg=%d", SQLITE3_HG, 0); + int rc = conn_pool_cleanup(admin, SQLITE3_HG, 0); + if (rc) { return EXIT_FAILURE; } + + dump_as_table(admin, "'stats_mysql_connection_pool' status:", CONN_POOL_HG_STATUS); + + return test_conn_acquisition(admin, cl, ff, client_eof, force_match, false); +} + +int test_conn_creation(MYSQL* admin, const conn_conf_t& conn_cnf, const proxy_cnf_t& proxy_cnf) { + const test_cnf_t test_cnf { pool_cnf_t { false, 0 }, conn_cnf, proxy_cnf }; + diag("Started '%s' %s", __func__, to_string(test_cnf).c_str()); + + diag("Initial connpool cleanup on hg hg=%d conn_tg=%d", SQLITE3_HG, 0); + int rc = conn_pool_cleanup(admin, SQLITE3_HG, 0); + if (rc) { return EXIT_FAILURE; } + + dump_as_table(admin, "'stats_mysql_connection_pool' status:", CONN_POOL_HG_STATUS); + + return test_conn_acquisition(admin, test_cnf); +} + +// CONN_POOL_WARMUP: Check backend conns created... Support vs Not-Supported +int test_conn_matching(MYSQL* admin, const test_cnf_t& test_cnf) { + const conn_conf_t& conn_cnf { test_cnf.conn_conf }; + const proxy_cnf_t& proxy_cnf { test_cnf.proxy_conf }; + + diag("Started '%s' %s", __func__, to_string(test_cnf).c_str()); + + diag("Initial connpool cleanup on hg hg=%d conn_tg=%d", SQLITE3_HG, 0); + int rc = conn_pool_cleanup(admin, SQLITE3_HG, 0); + if (rc) { return EXIT_FAILURE; } + + dump_as_table(admin, "'stats_mysql_connection_pool' status:", CONN_POOL_HG_STATUS); + + bool pool_depr_eof { bool(test_cnf.pool_status.conn_caps & CLIENT_DEPRECATE_EOF) }; + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(pool_depr_eof)); + MYSQL_QUERY_T(admin, "SET mysql-enable_server_deprecate_eof=" + _TO_S(pool_depr_eof)); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + diag( + "Warming-up conn-pool with conns conns=%d eof_support=%d", + CONN_POOL_WARNMUP, !proxy_cnf.cli_depr_eof + ); + + { + diag("Update 'fast-forward' for testing user user=\"%s\" ff=%d", FF_USER, conn_cnf.fast_forward); + MYSQL_QUERY_T(admin, + "UPDATE mysql_users SET fast_forward=" + _TO_S(0) + " WHERE username='" + _S(FF_USER) + "'" + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + diag("Create client MySQL conn user=\"%s\" port=\"%d\"", FF_USER, conn_cnf.cl.port); + MYSQL* proxy_warmup { create_mysql_conn({ conn_cnf.cl.host, FF_USER, FF_PASS, conn_cnf.cl.port }) }; + // MYSQL* proxy_warmup { create_mysql_conn({ cl.host, cl.username, cl.password, cl.port }) }; + if (proxy_warmup == NULL) { return EXIT_FAILURE; } + + for (int i = 0; i < CONN_POOL_WARNMUP; i++) { + MYSQL_QUERY_T(proxy_warmup, + "/* create_new_connection=1 */ BEGIN" + // "/* create_new_connection=1;hostgroup=" + std::to_string(SQLITE3_HG) + " */ BEGIN" + ); + MYSQL_QUERY_T(proxy_warmup, "COMMIT"); + } + + mysql_close(proxy_warmup); + + diag("Update 'fast-forward' for testing user user=\"%s\" ff=%d", FF_USER, conn_cnf.fast_forward); + MYSQL_QUERY_T(admin, + "UPDATE mysql_users SET fast_forward=" + _TO_S(1) + " WHERE username='" + _S(FF_USER) + "'" + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + } + + dump_as_table(admin, "'stats_mysql_connection_pool' status:", CONN_POOL_HG_STATUS); + + return test_conn_acquisition(admin, test_cnf); +} + +#ifdef LIBMYSQL_HELPER8 + +/** + * @brief Checks that client is disconnected when a conn is trying converted to 'fast-forward' and the + * currently locked backend connection doesn't match the correct capabilities. + * @param admin An already oppened MySQL connection to the admin interface. + * @return EXIT_SUCCESS if test was able to be performed, EXIT_FAILURE otherwise. + */ +int test_conn_ff_conv(MYSQL* admin, const CommandLine& cl, bool client_eof) { + diag("Started '%s' client_eof=%d", __func__, client_eof); + + diag("Initial connpool cleanup on hg hg=%d conn_tg=%d", SQLITE3_HG, 0); + int rc = conn_pool_cleanup(admin, SQLITE3_HG, 0); + if (rc) { return EXIT_FAILURE; } + + diag("Update 'mysql-enable_client_deprecate_eof' enable_client_deprecate_eof=%d", client_eof); + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(client_eof)); + diag("Allowing capabilities mismatch when selecting backend connections"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + diag("Pairing frontend conn with backend one with mismatching capabilities"); + diag("Create client MySQL conn user=\"%s\" port=\"%d\"", cl.username, cl.port); + MYSQL* proxy { create_mysql_conn({ cl.host, cl.username, cl.password, cl.port }) }; + if (proxy == NULL) { return EXIT_FAILURE; } + dump_as_table(admin, "'connection_pool' status after client conn:", CONN_POOL_HG_STATUS); + + diag("Revert support for 'enable_client_deprecate_eof' client_eof=%d", !client_eof); + MYSQL_QUERY_T(admin, "SET mysql-enable_client_deprecate_eof=" + _TO_S(!client_eof)); + MYSQL_QUERY_T(admin, "SET mysql-enable_server_deprecate_eof=1"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + diag("Get pre-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t pre_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(pre_hg_st); + const ext_val_t pre_srv_conns { mysql_query_ext_val(admin, + "SELECT variable_value FROM stats.stats_mysql_global WHERE variable_name='Server_Connections_created'", + int64_t(-1) + )}; + CHECK_EXT_VAL(pre_srv_conns); + + diag("Issue query (start trx) to create new backend conn query=\"%s\"", "BEGIN"); + rc = mysql_query_t(proxy, "/* hostgroup=" + std::to_string(SQLITE3_HG) + " */ BEGIN"); + if (rc) { + diag("Query failed. Aborting test error=\"%s\"", mysql_error(proxy)); + return EXIT_FAILURE; + } + + diag("Get post-conn attempt stats from target hostgroup tg=%d", SQLITE3_HG); + const ext_val_t post_hg_st { get_conn_pool_hg_stats(admin, SQLITE3_HG) }; + CHECK_EXT_VAL(post_hg_st); + + ok( + pre_hg_st.val.conn_used + 1 == post_hg_st.val.conn_used, + "Conn created should have increased by query attempt pre-ConnUsed=%d post-ConnUsed=%d", + pre_hg_st.val.conn_used, post_hg_st.val.conn_used + ); + + diag("Switching now the session to FAST-FORWARD (opening binlog); DISCONNECT should be enforced"); + MYSQL_RPL rpl {}; + rc = mysql_binlog_open(proxy, &rpl); + + diag( + "Error after starting replication rc=%d errno=%d error=\"%s\"", + rc, mysql_errno(proxy), mysql_error(proxy) + ); + + rc = mysql_binlog_fetch(proxy, &rpl); + + diag( + "Error when trying to fetch replication data rc=%d errno=%d error=\"%s\"", + rc, mysql_errno(proxy), mysql_error(proxy) + ); + + mysql_close(proxy); + + return EXIT_SUCCESS; +} + +#endif + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan( + 5 * gen_all_proxy_cnfs().size() +#ifdef LIBMYSQL_HELPER8 + * 4 + 2 +#else + * 2 +#endif + ); + + MYSQL* admin { create_mysql_conn({ cl.admin_host, cl.admin_username, cl.admin_password, cl.admin_port }) }; + if (admin == NULL) { return EXIT_FAILURE; } + + diag("Setting up global 'mysql-variables' config for the all tests..."); + std::for_each(mysql_variables_setup.begin(), mysql_variables_setup.end(), + [&admin] (const string& q) { return run_setup_query(admin, q); } + ); + diag("Setting up global 'SQLite3 server' config for the test"); + std::for_each(sqlite3_server_setup.begin(), sqlite3_server_setup.end(), + [&admin] (const string& q) { return run_setup_query(admin, q); } + ); + + int rc = 0; + uint32_t conn_caps { +#ifdef LIBMYSQL_HELPER8 + CLIENT_DEPRECATE_EOF +#else + 0 +#endif + }; + + for (const proxy_cnf_t& proxy_cnf : gen_all_proxy_cnfs()) { + rc = test_conn_creation(admin, { cl, true, conn_caps }, proxy_cnf ); + if (rc) { goto cleanup; } + } + for (const proxy_cnf_t& proxy_cnf : gen_all_proxy_cnfs()) { + rc = test_conn_creation(admin, { cl, false, conn_caps }, proxy_cnf ); + if (rc) { goto cleanup; } + } + +#ifdef LIBMYSQL_HELPER8 + + for (const proxy_cnf_t& proxy_cnf : gen_all_proxy_cnfs()) { + rc = test_conn_matching(admin, { + { true, CLIENT_DEPRECATE_EOF }, { cl, true, conn_caps }, proxy_cnf + }); + if (rc) { goto cleanup; } + } + for (const proxy_cnf_t& proxy_cnf : gen_all_proxy_cnfs()) { + rc = test_conn_matching(admin, { + { true, CLIENT_DEPRECATE_EOF }, { cl, false, conn_caps }, proxy_cnf + }); + if (rc) { goto cleanup; } + } + + rc = test_conn_ff_conv(admin, cl, true); + if (rc) { goto cleanup; } + + rc = test_conn_ff_conv(admin, cl, false); + if (rc) { goto cleanup; } + +#endif + +cleanup: + + diag("Recover DISK 'mysql_servers' config"); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS FROM DISK"); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + diag("Recover DISK 'mysql_variables' config"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES FROM DISK"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + mysql_close(admin); + + return exit_status(); +}