diff --git a/.gitignore b/.gitignore index b2302274c9..2882af55e1 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,7 @@ deps/libssl/openssl-1.1.1b/ deps/libssl/openssl-1.1.1d/ deps/libssl/openssl-1.1.1g/ deps/libssl/openssl-1.1.1j/ +deps/libssl/openssl-openssl-3.0.0/ #google coredumper deps/google-coredumper/google-coredumper/ diff --git a/README.md b/README.md index ab763f062a..60352d75a6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Useful links =============== - [Official website](https://www.proxysql.com/) +- [Subscriptions and Support](https://proxysql.com/services/support/) - [Documentation](https://www.proxysql.com/Documentation) - [DockerHub Repository](https://hub.docker.com/r/proxysql/proxysql) - [Benchmarks and blog posts](http://www.proxysql.blogspot.com/) diff --git a/deps/Makefile b/deps/Makefile index 4bfda4fb77..778cff2aad 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -55,8 +55,9 @@ libssl/openssl/libssl.a: cd libssl && rm -rf openssl-1.1.0h || true cd libssl && rm -rf openssl-1.1.1g || true cd libssl && rm -rf openssl-1.1.1j || true - cd libssl && tar -zxf openssl-1.1.1j.tar.gz - cd libssl/openssl && ./config no-ssl3 + cd libssl && rm -rf openssl-openssl-3.0.0 || true + cd libssl && tar -zxf openssl-3.0.0.tar.gz + cd libssl/openssl && ./config no-ssl3 no-tests cd libssl/openssl && CC=${CC} CXX=${CXX} ${MAKE} cd libssl/openssl && ln -s . lib # curl wants this path libssl: libssl/openssl/libssl.a @@ -288,6 +289,7 @@ cleanall: cd libssl && rm -rf openssl-1.1.1d || true cd libssl && rm -rf openssl-1.1.1g || true cd libssl && rm -rf openssl-1.1.1j || true + cd libssl && rm -rf openssl-openssl-3.0.0 || true cd libconfig && rm -rf libconfig-1.7.2 || true cd prometheus-cpp && rm -rf prometheus-cpp-0.9.0 || true .PHONY: cleanall diff --git a/deps/libssl/openssl b/deps/libssl/openssl index b4df187df9..06e4a19616 120000 --- a/deps/libssl/openssl +++ b/deps/libssl/openssl @@ -1 +1 @@ -openssl-1.1.1j \ No newline at end of file +openssl-openssl-3.0.0 \ No newline at end of file diff --git a/deps/libssl/openssl-3.0.0.tar.gz b/deps/libssl/openssl-3.0.0.tar.gz new file mode 100644 index 0000000000..c25713bd73 Binary files /dev/null and b/deps/libssl/openssl-3.0.0.tar.gz differ diff --git a/docker/images/proxysql/rhel-compliant/rhel6/entrypoint/entrypoint.bash b/docker/images/proxysql/rhel-compliant/rhel6/entrypoint/entrypoint.bash index 9c4af5e350..b541f2e6e7 100755 --- a/docker/images/proxysql/rhel-compliant/rhel6/entrypoint/entrypoint.bash +++ b/docker/images/proxysql/rhel-compliant/rhel6/entrypoint/entrypoint.bash @@ -4,14 +4,6 @@ set -eu echo "==> Build environment:" env -echo "==> Dirty patching to ensure OS deps are installed" - -yum -y install gnutls-devel libtool || true -yum -y install epel-release -sed -i "s/mirrorlist=https/mirrorlist=http/" /etc/yum.repos.d/epel.repo -yum -y install http://repo.okay.com.mx/centos/6/x86_64/release/okay-release-1-1.noarch.rpm -yum -y upgrade automake autoconf - echo "==> Cleaning" # Delete package if exists rm -f /opt/proxysql/binaries/proxysql-${CURVER}-1-${PKG_RELEASE}.x86_64.rpm || true diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index cc88dfcc87..4029085f2d 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -95,7 +95,6 @@ class GTID_Server_Data { class MySrvConnList { private: - PtrArray *conns; MySrvC *mysrvc; int find_idx(MySQL_Connection *c) { //for (unsigned int i=0; i mysql_thread___connect_retries_on_failure ? mysql_thread___connect_retries_on_failure : mysql_thread___shun_on_failures) ; if (__sync_add_and_fetch(&connect_ERR_at_time_last_detected_error,1) >= (unsigned int)max_failures) { bool _shu=false; - MyHGM->wrlock(); // to prevent race conditions, lock here. See #627 + if (get_mutex==true) + MyHGM->wrlock(); // to prevent race conditions, lock here. See #627 if (status==MYSQL_SERVER_STATUS_ONLINE) { status=MYSQL_SERVER_STATUS_SHUNNED; shunned_automatic=true; @@ -928,7 +930,8 @@ void MySrvC::connect_error(int err_num) { } else { _shu=false; } - MyHGM->wrunlock(); + if (get_mutex==true) + MyHGM->wrunlock(); if (_shu) { proxy_error("Shunning server %s:%d with %u errors/sec. Shunning for %u seconds\n", address, port, connect_ERR_at_time_last_detected_error , mysql_thread___shun_recovery_time_sec); } @@ -2813,6 +2816,13 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ mysrvc->shunned_and_kill_all_connections=false; mysrvc->connect_ERR_at_time_last_detected_error=0; mysrvc->time_last_detected_error=0; + // note: the following function scans all the hostgroups. + // This is ok for now because we only have a global mutex. + // If one day we implement a mutex per hostgroup (unlikely, + // but possible), this must be taken into consideration + if (mysql_thread___unshun_algorithm == 1) { + MyHGM->unshun_server_all_hostgroups(mysrvc->address, mysrvc->port, t, max_wait_sec, &mysrvc->myhgc->hid); + } // if a server is taken back online, consider it immediately if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far if (gtid_trxid) { @@ -2935,9 +2945,9 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ return NULL; // if we reach here, we couldn't find any target } +/* unsigned int New_sum=0; unsigned int New_TotalUsedConn=0; - // we will now scan again to ignore overloaded servers for (j=0; jthread->variables.min_num_servers_lantency_awareness) { + if (sess && sess->thread->variables.min_num_servers_lantency_awareness) { if ((int) num_candidates >= sess->thread->variables.min_num_servers_lantency_awareness) { unsigned int servers_with_latency = 0; unsigned int total_latency_us = 0; @@ -3222,6 +3234,47 @@ MySQL_Connection * MySrvConnList::get_random_MyConn(MySQL_Session *sess, bool ff return NULL; // never reach here } +void MySQL_HostGroups_Manager::unshun_server_all_hostgroups(const char * address, uint16_t port, time_t t, int max_wait_sec, unsigned int *skip_hid) { + // we scan all hostgroups looking for a specific server to unshun + // if skip_hid is not NULL , the specific hostgroup is skipped + int i, j; + for (i=0; i<(int)MyHostGroups->len; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + if (skip_hid != NULL && myhgc->hid == *skip_hid) { + // if skip_hid is not NULL, we skip that specific hostgroup + continue; + } + bool found = false; // was this server already found in this hostgroup? + for (j=0; found==false && j<(int)myhgc->mysrvs->cnt(); j++) { + MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); + if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { + // we only care for SHUNNED nodes + // Note that we check for address and port only for status==MYSQL_SERVER_STATUS_SHUNNED , + // that means that potentially we will pass by the matching node and still looping . + // This is potentially an optimization because we only check status and do not perform any strcmp() + if (strcmp(mysrvc->address,address)==0 && mysrvc->port==port) { + // we found the server in this hostgroup + // no need to process more servers in the same hostgroup + found = true; + if (t > mysrvc->time_last_detected_error && (t - mysrvc->time_last_detected_error) > max_wait_sec) { + if ( + (mysrvc->shunned_and_kill_all_connections==false) // it is safe to bring it back online + || + (mysrvc->shunned_and_kill_all_connections==true && mysrvc->ConnectionsUsed->conns_length()==0 && mysrvc->ConnectionsFree->conns_length()==0) // if shunned_and_kill_all_connections is set, ensure all connections are already dropped + ) { + mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; + mysrvc->shunned_automatic=false; + mysrvc->shunned_and_kill_all_connections=false; + mysrvc->connect_ERR_at_time_last_detected_error=0; + mysrvc->time_last_detected_error=0; + } + } + } + } + } + } +} + MySQL_Connection * MySQL_HostGroups_Manager::get_MyConn_from_pool(unsigned int _hid, MySQL_Session *sess, bool ff, char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms) { MySQL_Connection * conn=NULL; wrlock(); @@ -3548,8 +3601,22 @@ int MySQL_HostGroups_Manager::get_multiple_idle_connections(int _hid, unsigned l drop_all_idle_connections(); int num_conn_current=0; int j,k; - MyHGC* myhgc = MyHGC_find(_hid); - if (myhgc) { + MyHGC* myhgc = NULL; + for (int i=0; i<(int)MyHostGroups->len; i++) { + if (_hid == -1) { + // all hostgroups must be examined + // as of version 2.3.2 , this is always the case + myhgc=(MyHGC *)MyHostGroups->index(i); + } else { + // only one hostgroup is examined + // as of version 2.3.2 , this never happen + // but the code support this functionality + myhgc = MyHGC_find(_hid); + i = (int)MyHostGroups->len; // to exit from this "for" loop + if (myhgc == NULL) + continue; // immediately exit + } + if (_hid >= 0 && _hid!=(int)myhgc->hid) continue; for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); //PtrArray *pa=mysrvc->ConnectionsFree->conns; @@ -6978,4 +7045,28 @@ void MySQL_HostGroups_Manager::update_aws_aurora_set_reader(int _whid, int _rhid free(domain_name); } +MySrvC* MySQL_HostGroups_Manager::find_server_in_hg(unsigned int _hid, const std::string& addr, int port) { + MySrvC* f_server = nullptr; + + MyHGC* myhgc = nullptr; + for (uint32_t i = 0; i < MyHostGroups->len; i++) { + myhgc = static_cast(MyHostGroups->index(i)); + + if (myhgc->hid == _hid) { + break; + } + } + + if (myhgc != nullptr) { + for (uint32_t j = 0; j < myhgc->mysrvs->cnt(); j++) { + MySrvC* mysrvc = static_cast(myhgc->mysrvs->servers->index(j)); + + if (strcmp(mysrvc->address, addr.c_str()) == 0 && mysrvc->port == port) { + f_server = mysrvc; + } + } + } + + return f_server; +} diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 2b020b566c..8293280379 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -3739,6 +3739,21 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { case FAST_FORWARD: mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); break; + // This state is required because it covers the following situation: + // 1. A new connection is created by a client and the 'FAST_FORWARD' mode is enabled. + // 2. The first packet received for this connection isn't a whole packet, i.e, it's either + // split into multiple packets, or it doesn't fit 'queueIN' size (typically + // QUEUE_T_DEFAULT_SIZE). + // 3. Session is still in 'CONNECTING_SERVER' state, BUT further packets remain to be received + // from the initial split packet. + // + // Because of this, packets received during 'CONNECTING_SERVER' when the previous state is + // 'FAST_FORWARD' should be pushed to 'PSarrayOUT'. + case CONNECTING_SERVER: + if (previous_status.empty() == false && previous_status.top() == FAST_FORWARD) { + mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); + break; + } case session_status___NONE: default: handler___status_NONE_or_default(pkt); @@ -4553,8 +4568,12 @@ int MySQL_Session::handler() { if (mysql_thread___log_mysql_warnings_enabled) { auto warn_no = mysql_warning_count(myconn->mysql); if (warn_no > 0) { + RequestEnd(myds); + writeout(); + myconn->async_state_machine=ASYNC_IDLE; myds->DSS=STATE_MARIADB_GENERIC; + NEXT_IMMEDIATE(SHOW_WARNINGS); } } @@ -4658,26 +4677,41 @@ int MySQL_Session::handler() { break; case SHOW_WARNINGS: + // Performs a 'SHOW WARNINGS' query over the current backend connection and returns the connection back + // to the connection pool when finished. Actual logging of received warnings is performed in + // 'MySQL_Connection' while processing 'ASYNC_USE_RESULT_CONT'. { MySQL_Data_Stream *myds=mybe->server_myds; MySQL_Connection *myconn=myds->myconn; - int pre_rc = myconn->async_query(mybe->server_myds->revents,(char *)"show warnings", strlen((char *)"show warnings")); - if (pre_rc==0) { - int myerr=mysql_errno(myconn->mysql); + + // Setting POLLOUT is required just in case this state has been reached when 'RunQuery' from + // 'PROCESSING_QUERY' state has immediately return. This is because in case 'mysql_real_query_start' + // immediately returns with '0' the session is never processed again by 'MySQL_Thread', and 'revents' is + // never updated with the result of polling through the 'MySQL_Thread::mypolls'. + myds->revents |= POLLOUT; + + int rc = myconn->async_query( + mybe->server_myds->revents,(char *)"SHOW WARNINGS", strlen((char *)"SHOW WARNINGS") + ); + if (rc == 0 || rc == -1) { + // Cleanup the connection resulset from 'SHOW WARNINGS' for the next query. + if (myconn->MyRS != NULL) { + delete myconn->MyRS; + myconn->MyRS = NULL; + } + + if (rc == -1) { + int myerr = mysql_errno(myconn->mysql); + proxy_error( + "'SHOW WARNINGS' failed to be executed over backend connection with error: '%d'\n", myerr + ); + } RequestEnd(myds); - writeout(); + finishQuery(myds,myconn,prepared_stmt_with_no_params); handler_ret = 0; return handler_ret; - if ( myerr > 0 ) { - char sqlstate[10]; - sprintf(sqlstate,"%s",mysql_sqlstate(mybe->server_myds->myconn->mysql)); - RequestEnd(mybe->server_myds); - break; - } - mybe->server_myds->myconn->async_free_result(); - NEXT_IMMEDIATE(PROCESSING_QUERY); } else { goto handler_again; } diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 2c755a4f83..7873eb88e1 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -411,6 +411,7 @@ void MySQL_Listeners_Manager::del(unsigned int idx) { static char * mysql_thread_variables_names[]= { (char *)"shun_on_failures", (char *)"shun_recovery_time_sec", + (char *)"unshun_algorithm", (char *)"query_retries_on_failure", (char *)"client_multi_statements", (char *)"client_host_cache_size", @@ -1033,6 +1034,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { memset(&variables, 0, sizeof(variables)); variables.shun_on_failures=5; variables.shun_recovery_time_sec=10; + variables.unshun_algorithm=0; variables.query_retries_on_failure=1; variables.client_multi_statements=true; variables.client_host_cache_size=0; @@ -1175,7 +1177,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.ssl_p2s_cipher=NULL; variables.ssl_p2s_crl=NULL; variables.ssl_p2s_crlpath=NULL; - variables.keep_multiplexing_variables=strdup((char *)"tx_isolation,version"); + variables.keep_multiplexing_variables=strdup((char *)"tx_isolation,transaction_isolation,version"); #ifdef DEBUG variables.session_debug=true; #endif /*debug */ @@ -2176,6 +2178,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["reset_connection_algorithm"] = make_tuple(&variables.reset_connection_algorithm, 1, 2, false); VariablesPointers_int["shun_on_failures"] = make_tuple(&variables.shun_on_failures, 0, 10000000, false); VariablesPointers_int["shun_recovery_time_sec"] = make_tuple(&variables.shun_recovery_time_sec, 0, 3600*24*365, false); + VariablesPointers_int["unshun_algorithm"] = make_tuple(&variables.unshun_algorithm, 0, 1, false); VariablesPointers_int["hostgroup_manager_verbose"] = make_tuple(&variables.hostgroup_manager_verbose, 1, 2, false); VariablesPointers_int["tcp_keepalive_time"] = make_tuple(&variables.tcp_keepalive_time, 0, 7200, false); VariablesPointers_int["min_num_servers_lantency_awareness"] = make_tuple(&variables.min_num_servers_lantency_awareness, 0, 10000, false); @@ -2543,7 +2546,10 @@ void MySQL_Threads_Handler::update_client_host_cache(struct sockaddr* client_soc if (error) { pthread_mutex_lock(&mutex_client_host_cache); // If the cache is full, find the oldest entry on it, and update/remove it. - if (client_host_cache.size() >= static_cast(mysql_thread___client_host_cache_size)) { + if ( + mysql_thread___client_host_cache_size && + client_host_cache.size() >= static_cast(mysql_thread___client_host_cache_size) + ) { auto older_elem = std::min_element( client_host_cache.begin(), client_host_cache.end(), @@ -2553,8 +2559,10 @@ void MySQL_Threads_Handler::update_client_host_cache(struct sockaddr* client_soc return f_entry.second.updated_at < s_entry.second.updated_at; } ); - if (older_elem->first != client_addr) { - client_host_cache.erase(older_elem); + if (older_elem != client_host_cache.end()) { + if (older_elem->first != client_addr) { + client_host_cache.erase(older_elem); + } } } @@ -3711,7 +3719,9 @@ void MySQL_Thread::process_all_sessions() { if (sess_time/1000 > (unsigned long long)mysql_thread___connect_timeout_client) { proxy_warning("Closing not established client connection %s:%d after %llums\n",sess->client_myds->addr.addr,sess->client_myds->addr.port, sess_time/1000); sess->healthy = 0; - GloMTH->update_client_host_cache(sess->client_myds->client_addr, true); + if (mysql_thread___client_host_cache_size) { + GloMTH->update_client_host_cache(sess->client_myds->client_addr, true); + } } } if (maintenance_loop) { @@ -3829,6 +3839,7 @@ void MySQL_Thread::refresh_variables() { mysql_thread___ping_timeout_server=GloMTH->get_variable_int((char *)"ping_timeout_server"); mysql_thread___shun_on_failures=GloMTH->get_variable_int((char *)"shun_on_failures"); mysql_thread___shun_recovery_time_sec=GloMTH->get_variable_int((char *)"shun_recovery_time_sec"); + mysql_thread___unshun_algorithm=GloMTH->get_variable_int((char *)"unshun_algorithm"); mysql_thread___query_retries_on_failure=GloMTH->get_variable_int((char *)"query_retries_on_failure"); mysql_thread___connect_retries_on_failure=GloMTH->get_variable_int((char *)"connect_retries_on_failure"); mysql_thread___client_multi_statements=(bool)GloMTH->get_variable_int((char *)"client_multi_statements"); diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index d4ef4bf967..338b1ab7d5 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -12,6 +12,7 @@ #include "proxysql.h" #include "proxysql_config.h" #include "proxysql_restapi.h" +#include "proxysql_utils.h" #include "cpp.h" #include "MySQL_Data_Stream.h" @@ -3598,6 +3599,84 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { } } } +#ifdef DEBUG + /** + * @brief Handles the 'PROXYSQL_SIMULATOR' command. Performing the operation specified in the payload + * format. + * @details The 'PROXYSQL_SIMULATOR' command is specified the following format. Allowing to perform a + * certain internal state changing operation. Payload spec: + * ``` + * PROXYSQL_SIMULATOR ${operation} ${hg} ${address}:${port} ${operation_params} + * ``` + * + * Supported operations include: + * - mysql_error: Find the server specified by 'hostname:port' in the specified hostgroup and calls + * 'MySrvC::connect_error()' with the provider 'error_code'. + * + * Payload example: + * ``` + * PROXYSQL_SIMULATOR mysql_error 1 127.0.0.1 3306 1234 + * ``` + */ + if (!strncasecmp("PROXYSQL_SIMULATOR ", query_no_space, strlen("PROXYSQL_SIMULATOR "))) { + if (sess->session_type == PROXYSQL_SESSION_ADMIN) { // no stats + proxy_warning("Received PROXYSQL_SIMULATOR command: %s\n", query_no_space); + + re2::RE2::Options opts = re2::RE2::Options(RE2::Quiet); + re2::RE2 pattern("\\s*(\\w+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+) (\\d+)\\s*\\;*", opts); + re2::StringPiece input(query_no_space + strlen("PROXYSQL_SIMULATOR")); + + std::string command, s_hg, srv_addr, s_port, s_errcode {}; + bool c_res = re2::RE2::Consume(&input, pattern, &command, &s_hg, &srv_addr, &s_port, &s_errcode); + + long i_hg = 0; + long i_port = 0; + long i_errcode = 0; + + if (c_res == true) { + char* endptr = nullptr; + i_hg = std::strtol(s_hg.c_str(), &endptr, 10); + if (errno == ERANGE || errno == EINVAL) i_hg = LONG_MIN; + i_port = std::strtol(s_port.c_str(), &endptr, 10); + if (errno == ERANGE || errno == EINVAL) i_port = LONG_MIN; + i_errcode = std::strtol(s_errcode.c_str(), &endptr, 10); + if (errno == ERANGE || errno == EINVAL) i_errcode = LONG_MIN; + } + + if (c_res == true && i_hg != LONG_MIN && i_port != LONG_MIN && i_errcode != LONG_MIN) { + MyHGM->wrlock(); + + MySrvC* mysrvc = MyHGM->find_server_in_hg(i_hg, srv_addr, i_port); + if (mysrvc != nullptr) { + int backup_mysql_thread___shun_on_failures = mysql_thread___shun_on_failures; + mysql_thread___shun_on_failures = 1; + + // Set the error twice to surpass 'mysql_thread___shun_on_failures' value. + mysrvc->connect_error(i_errcode, false); + mysrvc->connect_error(i_errcode, false); + + mysql_thread___shun_on_failures = backup_mysql_thread___shun_on_failures; + + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL); + } else { + std::string t_err_msg { "Supplied server '%s:%d' wasn't found in hg '%d'" }; + std::string err_msg {}; + string_format(t_err_msg, err_msg, srv_addr.c_str(), i_port, i_hg); + + proxy_info("%s\n", err_msg.c_str()); + SPA->send_MySQL_ERR(&sess->client_myds->myprot, const_cast(err_msg.c_str())); + } + + MyHGM->wrunlock(); + } else { + SPA->send_MySQL_ERR(&sess->client_myds->myprot, (char*)"Invalid arguments supplied with query 'PROXYSQL_SIMULATOR'"); + } + + run_query=false; + goto __run_query; + } + } +#endif // DEBUG if (!strncasecmp("PROXYSQLTEST ", query_no_space, strlen("PROXYSQLTEST "))) { if (sess->session_type == PROXYSQL_SESSION_ADMIN) { // no stats int test_n = 0; @@ -3791,6 +3870,7 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { run_query=false; } break; +#ifdef DEBUG case 51: { char msg[256]; @@ -3800,6 +3880,29 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { run_query=false; } break; + case 52: + { + char msg[256]; + SPA->mysql_servers_wrlock(); + SPA->admindb->execute("DELETE FROM mysql_servers WHERE hostgroup_id=5211"); + SPA->admindb->execute("INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight) VALUES (5211,'127.0.0.2',3306,10000)"); + SPA->admindb->execute("INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight) VALUES (5211,'127.0.0.3',3306,8000)"); + SPA->admindb->execute("INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight) VALUES (5211,'127.0.0.4',3306,8000)"); + SPA->admindb->execute("INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight) VALUES (5211,'127.0.0.5',3306,7000)"); + SPA->load_mysql_servers_to_runtime(); + SPA->mysql_servers_wrunlock(); + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded mysql servers to RUNTIME\n"); + unsigned long long d = SPA->ProxySQL_Test___MySQL_HostGroups_Manager_Balancing_HG5211(); + sprintf(msg, "Tested in %llums\n", d); + SPA->mysql_servers_wrlock(); + SPA->admindb->execute("DELETE FROM mysql_servers WHERE hostgroup_id=5211"); + SPA->load_mysql_servers_to_runtime(); + SPA->mysql_servers_wrunlock(); + SPA->send_MySQL_OK(&sess->client_myds->myprot, msg, NULL); + run_query=false; + } + break; +#endif // DEBUG default: SPA->send_MySQL_ERR(&sess->client_myds->myprot, (char *)"Invalid test"); run_query=false; @@ -12569,6 +12672,7 @@ unsigned long long ProxySQL_Admin::ProxySQL_Test___MySQL_HostGroups_Manager_read return d; } +#ifdef DEBUG // NEVER USED THIS FUNCTION IN PRODUCTION. // THIS IS FOR TESTING PURPOSE ONLY // IT ACCESSES MyHGM without lock @@ -12598,3 +12702,51 @@ unsigned long long ProxySQL_Admin::ProxySQL_Test___MySQL_HostGroups_Manager_HG_l unsigned long long d = t2-t1; return d; } + +// NEVER USED THIS FUNCTION IN PRODUCTION. +// THIS IS FOR TESTING PURPOSE ONLY +// IT ACCESSES MyHGM without lock +unsigned long long ProxySQL_Admin::ProxySQL_Test___MySQL_HostGroups_Manager_Balancing_HG5211() { + unsigned long long t1 = monotonic_time(); + const unsigned int NS = 4; + unsigned int cu[NS] = { 50, 10, 10, 0 }; + unsigned int hid = 0; + MyHGC * myhgc = NULL; + myhgc = MyHGM->MyHGC_lookup(5211); + assert(myhgc); + assert(myhgc->mysrvs->servers->len == NS); + unsigned int cnt[NS]; + for (unsigned int i=0; imysrvs->servers->index(i); + m->ConnectionsUsed->conns->len=cu[i]; + } + unsigned int NL = 1000; + for (unsigned int i=0; iget_random_MySrvC(NULL, NULL, -1, NULL); + assert(mysrvc); + for (unsigned int k=0; kmysrvs->servers->index(k); + if (m == mysrvc) + cnt[k]++; + } + } + { + unsigned int tc = 0; + for (unsigned int k=0; kquery_digest_text_filter_select_v; char* query_digest_text_filter_select_tok = NULL; + char* save_query_digest_text_ptr = NULL; if (query_digest_text_filter_select) { - query_digest_text_filter_select_tok = strtok(query_digest_text_filter_select, ","); + query_digest_text_filter_select_tok = strtok_r(query_digest_text_filter_select, ",", &save_query_digest_text_ptr); } while(query_digest_text_filter_select_tok){ //filter "as"/space/alias,such as select @@version as a, @@version b @@ -2328,19 +2330,20 @@ bool MySQL_Connection::IsKeepMultiplexEnabledVariables(char *query_digest_text) }else{ query_digest_text_filter_select_v.push_back(query_digest_text_filter_select_tok); } - query_digest_text_filter_select_tok=strtok(NULL, ","); + query_digest_text_filter_select_tok=strtok_r(NULL, ",", &save_query_digest_text_ptr); } std::vectorkeep_multiplexing_variables_v; char* keep_multiplexing_variables_tmp; + char* save_keep_multiplexing_variables_ptr = NULL; unsigned long keep_multiplexing_variables_len=strlen(mysql_thread___keep_multiplexing_variables); keep_multiplexing_variables_tmp=(char*)malloc(keep_multiplexing_variables_len+1); memcpy(keep_multiplexing_variables_tmp, mysql_thread___keep_multiplexing_variables, keep_multiplexing_variables_len); keep_multiplexing_variables_tmp[keep_multiplexing_variables_len]='\0'; - char* keep_multiplexing_variables_tok=strtok(keep_multiplexing_variables_tmp, " ,"); + char* keep_multiplexing_variables_tok=strtok_r(keep_multiplexing_variables_tmp, " ,", &save_keep_multiplexing_variables_ptr); while (keep_multiplexing_variables_tok){ keep_multiplexing_variables_v.push_back(keep_multiplexing_variables_tok); - keep_multiplexing_variables_tok=strtok(NULL, " ,"); + keep_multiplexing_variables_tok=strtok_r(NULL, " ,", &save_keep_multiplexing_variables_ptr); } for (std::vector::iterator it=query_digest_text_filter_select_v.begin();it!=query_digest_text_filter_select_v.end();it++){ diff --git a/src/main.cpp b/src/main.cpp index 2ba2b93033..e78cf1d263 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1168,7 +1168,7 @@ bool ProxySQL_daemonize_phase2() { //daemon_log(LOG_INFO, "Starting ProxySQL\n"); //daemon_log(LOG_INFO, "Sucessfully started"); proxy_info("Starting ProxySQL\n"); - proxy_info("Sucessfully started\n"); + proxy_info("Successfully started\n"); return true; } @@ -1464,6 +1464,10 @@ int main(int argc, const char * argv[]) { #ifdef DEBUG std::cerr << "WARNING: this is a DEBUG release and can be slow or perform poorly. Do not use it in production" << std::endl; #endif + proxy_info("For information about products and services visit: https://proxysql.com/\n"); + proxy_info("For online documentation visit: https://proxysql.com/documentation/\n"); + proxy_info("For support visit: https://proxysql.com/services/support/\n"); + proxy_info("For consultancy visit: https://proxysql.com/services/consulting/\n"); { unsigned int missed_heartbeats = 0; diff --git a/src/proxy_tls.cpp b/src/proxy_tls.cpp index eff307a117..44143b01cb 100644 --- a/src/proxy_tls.cpp +++ b/src/proxy_tls.cpp @@ -92,67 +92,6 @@ int callback_ssl_verify_peer(int ok, X509_STORE_CTX* ctx) { return 1; } -/* - -generated with: $ openssl dhparam -5 -C 2048 - ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEAtS5UPzxesyj7QtLe6hRGE1Cv4TnDbSzKTmy0izFabdn0wR1QVmij -S8YSb1jE+O7IGImtk84Wg4y141PAHkCMTEeCMKH5tOD0WfiVyuQDTp4Vbt0vOReM -hK7tgLHLC1P3v0nxFCcce3U6IXmXBQ9IkNMFcXSRIAdBOjPkFPfbZ648qSgcoX+z -gfEP9WAXeeNGk62rDb3R0mguA9HcQ4NyKk6ETBVsZD4bTAcSIBaX05ISV7qY2eLj -9HFYBXYX4cxBfMyiqGrCj2IMg8aRKmf7rTvwBQXT0cWmu+kpnlpXIjx6vdpBmeKd -hSypLEcUVIvzc6rtfWlYKT35wQ+AGKNADwIBBQ== ------END DH PARAMETERS----- - -*/ - - - -#ifndef HEADER_DH_H -#include -#endif -DH *get_dh2048() - { - static unsigned char dh2048_p[]={ - 0xB5,0x2E,0x54,0x3F,0x3C,0x5E,0xB3,0x28,0xFB,0x42,0xD2,0xDE, - 0xEA,0x14,0x46,0x13,0x50,0xAF,0xE1,0x39,0xC3,0x6D,0x2C,0xCA, - 0x4E,0x6C,0xB4,0x8B,0x31,0x5A,0x6D,0xD9,0xF4,0xC1,0x1D,0x50, - 0x56,0x68,0xA3,0x4B,0xC6,0x12,0x6F,0x58,0xC4,0xF8,0xEE,0xC8, - 0x18,0x89,0xAD,0x93,0xCE,0x16,0x83,0x8C,0xB5,0xE3,0x53,0xC0, - 0x1E,0x40,0x8C,0x4C,0x47,0x82,0x30,0xA1,0xF9,0xB4,0xE0,0xF4, - 0x59,0xF8,0x95,0xCA,0xE4,0x03,0x4E,0x9E,0x15,0x6E,0xDD,0x2F, - 0x39,0x17,0x8C,0x84,0xAE,0xED,0x80,0xB1,0xCB,0x0B,0x53,0xF7, - 0xBF,0x49,0xF1,0x14,0x27,0x1C,0x7B,0x75,0x3A,0x21,0x79,0x97, - 0x05,0x0F,0x48,0x90,0xD3,0x05,0x71,0x74,0x91,0x20,0x07,0x41, - 0x3A,0x33,0xE4,0x14,0xF7,0xDB,0x67,0xAE,0x3C,0xA9,0x28,0x1C, - 0xA1,0x7F,0xB3,0x81,0xF1,0x0F,0xF5,0x60,0x17,0x79,0xE3,0x46, - 0x93,0xAD,0xAB,0x0D,0xBD,0xD1,0xD2,0x68,0x2E,0x03,0xD1,0xDC, - 0x43,0x83,0x72,0x2A,0x4E,0x84,0x4C,0x15,0x6C,0x64,0x3E,0x1B, - 0x4C,0x07,0x12,0x20,0x16,0x97,0xD3,0x92,0x12,0x57,0xBA,0x98, - 0xD9,0xE2,0xE3,0xF4,0x71,0x58,0x05,0x76,0x17,0xE1,0xCC,0x41, - 0x7C,0xCC,0xA2,0xA8,0x6A,0xC2,0x8F,0x62,0x0C,0x83,0xC6,0x91, - 0x2A,0x67,0xFB,0xAD,0x3B,0xF0,0x05,0x05,0xD3,0xD1,0xC5,0xA6, - 0xBB,0xE9,0x29,0x9E,0x5A,0x57,0x22,0x3C,0x7A,0xBD,0xDA,0x41, - 0x99,0xE2,0x9D,0x85,0x2C,0xA9,0x2C,0x47,0x14,0x54,0x8B,0xF3, - 0x73,0xAA,0xED,0x7D,0x69,0x58,0x29,0x3D,0xF9,0xC1,0x0F,0x80, - 0x18,0xA3,0x40,0x0F, - }; - static unsigned char dh2048_g[]={ - 0x05, - }; - DH *dh; - - if ((dh=DH_new()) == NULL) return(NULL); - dh->p=BN_bin2bn(dh2048_p,sizeof(dh2048_p),NULL); - dh->g=BN_bin2bn(dh2048_g,sizeof(dh2048_g),NULL); - if ((dh->p == NULL) || (dh->g == NULL)) - { DH_free(dh); return(NULL); } - return(dh); -} - - - X509 * generate_x509(EVP_PKEY *pkey, const unsigned char *cn, uint32_t serial, int days, X509 *ca_x509, EVP_PKEY *ca_pkey) { int rc; X509 * x = NULL; @@ -301,8 +240,6 @@ int ssl_mkit(X509 **x509ca, X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial X509 *x2; EVP_PKEY *pk; RSA *rsa; - DH *dh; - //X509_NAME *name = NULL; // relative path to datadir of ssl files const char * ssl_key_rp = (const char *)"proxysql-key.pem"; @@ -435,10 +372,8 @@ int ssl_mkit(X509 **x509ca, X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial *x509p = x2; *pkeyp = pk; - dh = get_dh2048(); - if (bootstrap == true) { - if (SSL_CTX_set_tmp_dh(GloVars.global.ssl_ctx, dh) == 0) { + if (SSL_CTX_set_dh_auto(GloVars.global.ssl_ctx, 1) == 0) { proxy_error("Error in SSL while initializing DH: %s . Shutting down.\n",ERR_error_string(ERR_get_error(), NULL)); exit(EXIT_SUCCESS); // EXIT_SUCCESS to avoid a restart loop } @@ -446,7 +381,9 @@ int ssl_mkit(X509 **x509ca, X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial SSL_METHOD *ssl_method; ssl_method = (SSL_METHOD *)TLS_server_method(); GloVars.global.tmp_ssl_ctx = SSL_CTX_new(ssl_method); - if (SSL_CTX_set_tmp_dh(GloVars.global.tmp_ssl_ctx, dh) == 0) { + + if (SSL_CTX_set_dh_auto(GloVars.global.ssl_ctx, 1) == 0) { + proxy_error("Error in SSL while initializing DH: %s . Shutting down.\n",ERR_error_string(ERR_get_error(), NULL)); proxy_error("Aborting PROXYSQL RELOAD TLS. Error in SSL while initializing DH: %s\n",ERR_error_string(ERR_get_error(), NULL)); msg = "RELOAD TLS failed: Error initializing DH. "; msg += ERR_error_string(ERR_get_error(), NULL); @@ -465,8 +402,6 @@ int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg) { int ret = 0; - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF); - bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); if (bootstrap == true) { diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index 7ef2b5c9f0..411d894166 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -1,10 +1,16 @@ #include -#include +#include #include -#include +#include +#include +#include +#include +#include #include #include +#include + #include "tap.h" #include "utils.h" @@ -561,6 +567,7 @@ int wait_until_enpoint_ready( return res; } + MARIADB_CHARSET_INFO * proxysql_find_charset_collate(const char *collatename) { MARIADB_CHARSET_INFO *c = (MARIADB_CHARSET_INFO *)mariadb_compiled_charsets; do { @@ -663,4 +670,117 @@ int create_extra_users( } return EXIT_SUCCESS; + +std::string random_string(std::size_t strSize) { + std::string dic { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" }; + + std::random_device rd {}; + std::mt19937 generator { rd() }; + + std::shuffle(dic.begin(), dic.end(), generator); + + if (strSize < dic.size()) { + return dic.substr(0, strSize); + } else { + std::size_t req_modulus = static_cast(strSize / dic.size()); + std::size_t req_reminder = strSize % dic.size(); + std::string random_str {}; + + for (std::size_t i = 0; i < req_modulus; i++) { + random_str.append(dic); + } + + random_str.append(dic.substr(0, req_reminder)); + + return random_str; + } } + +const double COLISSION_PROB = 1e-8; + +int wait_for_replication( + MYSQL* proxy, MYSQL* proxy_admin, const std::string& check, uint32_t timeout, uint32_t read_hg +) { + if (proxy == NULL) { return EXIT_FAILURE; } + + const std::string t_count_reader_hg_servers { + "SELECT COUNT(*) FROM mysql_servers WHERE hostgroup_id=%d" + }; + std::string count_reader_hg_servers {}; + size_t size = + snprintf( nullptr, 0, t_count_reader_hg_servers.c_str(), read_hg) + 1; + { + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, t_count_reader_hg_servers.c_str(), read_hg); + count_reader_hg_servers = std::string(buf.get(), buf.get() + size - 1); + } + + MYSQL_QUERY(proxy_admin, count_reader_hg_servers.c_str()); + MYSQL_RES* hg_count_res = mysql_store_result(proxy_admin); + MYSQL_ROW row = mysql_fetch_row(hg_count_res); + uint32_t srv_count = strtoul(row[0], NULL, 10); + mysql_free_result(hg_count_res); + + if (srv_count > UINT_MAX) { + return EXIT_FAILURE; + } + + int waited = 0; + int queries = 0; + int result = EXIT_FAILURE; + + if (srv_count != 0) { + int retries = ceil(log10(COLISSION_PROB) / log10(static_cast(1)/srv_count)); + auto start = std::chrono::system_clock::now(); + std::chrono::duration elapsed {}; + + while (elapsed.count() < timeout && queries < retries) { + int rc = mysql_query(proxy, check.c_str()); + bool correct_result = false; + + if (rc == EXIT_SUCCESS) { + MYSQL_RES* st_res = mysql_store_result(proxy); + if (st_res) { + uint32_t field_num = mysql_num_fields(st_res); + uint32_t row_num = mysql_num_rows(st_res); + + if (field_num == 1 && row_num == 1) { + MYSQL_ROW row = mysql_fetch_row(st_res); + + std::string exp_res { "TRUE" }; + if (strcasecmp(exp_res.c_str(), row[0]) == 0) { + correct_result = true; + queries += 1; + } + } + + mysql_free_result(st_res); + } + } else { + diag( + "Replication check failed due to query error: ('%d','%s')", + mysql_errno(proxy), mysql_error(proxy) + ); + } + + if (correct_result == false) { + queries = 0; + waited += 1; + sleep(1); + } else { + continue; + } + + auto it_end = std::chrono::system_clock::now(); + elapsed = it_end - start; + } + + if (queries == retries) { + result = EXIT_SUCCESS; + } + } else { + result = EXIT_SUCCESS; + } + + return result; +} \ No newline at end of file diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 12cda5dc86..78144cca3c 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -192,6 +192,33 @@ using user_config = std::tuple; */ int create_extra_users( MYSQL* proxysql_admin, MYSQL* mysql_server, const std::vector& users_config + * @brief Generates a random string of the length of the provider 'strSize' + * parameter. + * + * @param strSize The size of the string to be generated. + * @return A random string. + */ +std::string random_string(std::size_t strSize); + +/** + * @brief Helper function to wait for replication to complete, + * performs a simple supplied queried until it succeed or the + * timeout expires. + * + * @param proxy A already opened MYSQL connection to ProxySQL. + * @param proxy_admin A already opened MYSQL connection to ProxySQL Admin interface. + * @param check Query to perform until timeout expires. The query is expected to produce + * a result with only one ROW and one FIELD containing non-case sensitive strings "TRUE" + * or "FALSE". "TRUE" meaning that the replication check succeed. + * @param timeout The timeout in seconds to retry the query. + * @param reader_hg The current 'reader hostgroup' for which + * servers replication needs to be waited. + * + * @return EXIT_SUCCESS in case of success, EXIT_FAILURE + * otherwise. + */ +int wait_for_replication( + MYSQL* proxy, MYSQL* proxy_admin, const std::string& check, uint32_t timeout, uint32_t reader_hg ); #endif // #define UTILS_H diff --git a/test/tap/tests/admin_various_commands2-t.cpp b/test/tap/tests/admin_various_commands2-t.cpp index b37e39d6a2..3f3b73da73 100644 --- a/test/tap/tests/admin_various_commands2-t.cpp +++ b/test/tap/tests/admin_various_commands2-t.cpp @@ -166,7 +166,7 @@ int main() { for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { - proxysql_admin = mysql_init(NULL); // local scope + MYSQL* proxysql_admin = mysql_init(NULL); // local scope if (!proxysql_admin) { fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); return -1; diff --git a/test/tap/tests/client_host_err/create_netns_n.sh b/test/tap/tests/client_host_err/create_netns_n.sh index 4128030d6a..0d12ae49a1 100755 --- a/test/tap/tests/client_host_err/create_netns_n.sh +++ b/test/tap/tests/client_host_err/create_netns_n.sh @@ -1,4 +1,9 @@ #!/bin/bash + +# NOTE: This script is currently unused, but it's left here because it's useful +# for creating network namespaces in a easy way. It could be of use for future +# testing. + if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 diff --git a/test/tap/tests/client_host_err/delete_netns_n.sh b/test/tap/tests/client_host_err/delete_netns_n.sh index 80dfbcc5fc..19ace75d15 100755 --- a/test/tap/tests/client_host_err/delete_netns_n.sh +++ b/test/tap/tests/client_host_err/delete_netns_n.sh @@ -1,4 +1,9 @@ #!/bin/bash + +# NOTE: This script is currently unused, but it's left here because it's useful +# for deleting network namespaces in a easy way. It could be of use for future +# testing. + if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 diff --git a/test/tap/tests/firewall_commands1-t.cpp b/test/tap/tests/firewall_commands1-t.cpp index 51611e434a..f3bfd1a631 100644 --- a/test/tap/tests/firewall_commands1-t.cpp +++ b/test/tap/tests/firewall_commands1-t.cpp @@ -85,7 +85,7 @@ int main() { plan(queries.size()); for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { - proxysql_admin = mysql_init(NULL); // local scope . We intentionally create new connections + MYSQL* proxysql_admin = mysql_init(NULL); // local scope . We intentionally create new connections if (!proxysql_admin) { fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); return -1; diff --git a/test/tap/tests/mysql-mirror1-t.cpp b/test/tap/tests/mysql-mirror1-t.cpp index 2c4a3f9b2d..42e00c8726 100644 --- a/test/tap/tests/mysql-mirror1-t.cpp +++ b/test/tap/tests/mysql-mirror1-t.cpp @@ -127,6 +127,8 @@ int main(int argc, char** argv) { } int rows_read = 0; + sleep(1); // some INSERT may still be running + // at this point the table sbtest should have 600 rows: // 300 rows from normal insert, and 100 rows from mirror rc = run_q(conns[0], "SELECT * FROM test.sbtest1"); diff --git a/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp index a6eca44bdc..5f8743d9ee 100644 --- a/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp +++ b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp @@ -94,7 +94,7 @@ int main(int argc, char** argv) { } // Force the 'hostgroup' for the 'SELECT' query to avoid replication issues - std::string query_t = "SELECT /*+ ;hostgroup=0,%s */ * FROM test.reg_test_1574"; + std::string query_t = "SELECT /* ;hostgroup=0,%s */ * FROM test.reg_test_1574"; std::string query (static_cast(query_t.size() + 20), '\0'); std::string rnd_str(static_cast(20), '\0'); diff --git a/test/tap/tests/reg_test_3317-lock_hostgroup_special_queries-t.cpp b/test/tap/tests/reg_test_3317-lock_hostgroup_special_queries-t.cpp index 71f041fe48..46264349c1 100644 --- a/test/tap/tests/reg_test_3317-lock_hostgroup_special_queries-t.cpp +++ b/test/tap/tests/reg_test_3317-lock_hostgroup_special_queries-t.cpp @@ -33,7 +33,7 @@ void check_set_names(MYSQL* proxysql_mysql) { return; } - query_res = mysql_query(proxysql_mysql, "SELECT /*+ ;hostgroup=0 */ @@character_set_client, @@character_set_results, @@character_set_connection"); + query_res = mysql_query(proxysql_mysql, "SELECT /* ;hostgroup=0 */ @@character_set_client, @@character_set_results, @@character_set_connection"); if (query_res) { diag("Query failed with error: %s", mysql_error(proxysql_mysql)); return; @@ -77,7 +77,7 @@ void check_set_names(MYSQL* proxysql_mysql) { * @param proxysql_mysql A MYSQL handle to an already stablished MySQL connection. */ void check_autocommit(MYSQL* proxysql_mysql) { - int query_res = mysql_query(proxysql_mysql, "SELECT /*+ ;hostgroup=0 */ @@autocommit"); + int query_res = mysql_query(proxysql_mysql, "SELECT /* ;hostgroup=0 */ @@autocommit"); if (query_res) { diag("Query failed with error: %s", mysql_error(proxysql_mysql)); return; @@ -104,7 +104,7 @@ void check_autocommit(MYSQL* proxysql_mysql) { } // Check new status on @@autocommit - query_res = mysql_query(proxysql_mysql, "SELECT /*+ ;hostgroup=0 */ @@autocommit"); + query_res = mysql_query(proxysql_mysql, "SELECT /* ;hostgroup=0 */ @@autocommit"); if (query_res) { diag("Query failed with error: %s", mysql_error(proxysql_mysql)); return; @@ -139,7 +139,7 @@ void check_session_character_set_server(MYSQL* proxysql_mysql) { return; } - query_res = mysql_query(proxysql_mysql, "SELECT /*+ ;hostgroup=0 */ @@character_set_server"); + query_res = mysql_query(proxysql_mysql, "SELECT /* ;hostgroup=0 */ @@character_set_server"); if (query_res) { diag("Query failed with error: %s", mysql_error(proxysql_mysql)); return; @@ -178,7 +178,7 @@ void check_session_character_set_results(MYSQL* proxysql_mysql) { return; } - query_res = mysql_query(proxysql_mysql, "SELECT /*+ ;hostgroup=0 */ @@character_set_results"); + query_res = mysql_query(proxysql_mysql, "SELECT /* ;hostgroup=0 */ @@character_set_results"); if (query_res) { diag("Query failed with error: %s", mysql_error(proxysql_mysql)); return; diff --git a/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp b/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp index 91bed356e5..e59c166dcf 100644 --- a/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp +++ b/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp @@ -77,7 +77,6 @@ int main(int argc, char** argv) { } } - MYSQL_STMT* stmt = nullptr; MYSQL* proxysql_mysql = mysql_init(NULL); MYSQL* proxysql_admin = mysql_init(NULL); @@ -91,13 +90,6 @@ int main(int argc, char** argv) { return -1; } - stmt = mysql_stmt_init(proxysql_mysql); - if (!stmt) { - diag("mysql_stmt_init(), out of memory"); - res = EXIT_FAILURE; - goto exit; - } - // Insert data in the table to be queried // ************************************************************************* @@ -156,14 +148,21 @@ int main(int argc, char** argv) { std::string query_t {}; if (param) { - query_t = "SELECT /*+ ;hostgroup=0;%d */ * FROM test.reg_test_3427 WHERE id IN (?)"; + query_t = "SELECT /* ;hostgroup=0;%d */ * FROM test.reg_test_3427 WHERE id IN (?)"; } else { - query_t = "SELECT /*+ ;hostgroup=0;%d */ * FROM test.reg_test_3427"; + query_t = "SELECT /* ;hostgroup=0;%d */ * FROM test.reg_test_3427"; } std::string query {}; string_format(query_t, query, query_id); + MYSQL_STMT* stmt = mysql_stmt_init(proxysql_mysql); + if (!stmt) { + diag("mysql_stmt_init(), out of memory"); + res = EXIT_FAILURE; + goto exit; + } + if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) { diag("mysql_stmt_prepare at line %d failed: %s", __LINE__ , mysql_error(proxysql_mysql)); mysql_close(proxysql_mysql); @@ -261,11 +260,12 @@ int main(int argc, char** argv) { res = EXIT_FAILURE; goto exit; } + + mysql_stmt_close(stmt); } } exit: - if (stmt) { mysql_stmt_close(stmt); } mysql_close(proxysql_mysql); mysql_close(proxysql_admin); diff --git a/test/tap/tests/reg_test_3434-text_stmt_mix-t.cpp b/test/tap/tests/reg_test_3434-text_stmt_mix-t.cpp index 1d5437e751..b232ab5c27 100644 --- a/test/tap/tests/reg_test_3434-text_stmt_mix-t.cpp +++ b/test/tap/tests/reg_test_3434-text_stmt_mix-t.cpp @@ -221,7 +221,7 @@ std::string build_random_select_query( ) { // Force the 'hostgroup' for the 'SELECT' query std::string t_query { - "SELECT /*+ ;hostgroup=%d,%s */ * FROM %s WHERE id IN (" + "SELECT /* ;hostgroup=%d,%s */ * FROM %s WHERE id IN (" }; for (uint32_t i = 0; i < num_params; i++) { diff --git a/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp b/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp index 79c89f0e8b..e096bfe35d 100644 --- a/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp +++ b/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp @@ -50,19 +50,19 @@ int main(int argc, char** argv) { MYSQL* proxysql_mysql = mysql_init(NULL); - db_query.push_back(std::make_pair("reg_test_3493_use_comment", "/*+ placeholder_comment */ USE reg_test_3493_use_comment")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-a1`", "USE /*+ placeholder_comment */ `reg_test_3493_use_comment-a1`")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_1", " USE /*+ placeholder_comment */ `reg_test_3493_use_comment_1`")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_2", "USE/*+ placeholder_comment */ `reg_test_3493_use_comment_2`")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_3", "USE /*+ placeholder_comment */`reg_test_3493_use_comment_3`")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_4", " USE /*+ placeholder_comment */ reg_test_3493_use_comment_4")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_5", "USE/*+ placeholder_comment */ reg_test_3493_use_comment_5")); - db_query.push_back(std::make_pair("reg_test_3493_use_comment_6", "USE /*+ placeholder_comment */reg_test_3493_use_comment_6")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-1`", " USE /*+ placeholder_comment */ `reg_test_3493_use_comment-1`")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-2`", "USE/*+ placeholder_comment */ `reg_test_3493_use_comment-2`")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-3`", "USE /*+ placeholder_comment */`reg_test_3493_use_comment-3`")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-4`", "/*+ placeholder_comment */USE `reg_test_3493_use_comment-4`")); - db_query.push_back(std::make_pair("`reg_test_3493_use_comment-5`", "USE/*+ placeholder_comment */`reg_test_3493_use_comment-5`")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment", "/* placeholder_comment */ USE reg_test_3493_use_comment")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-a1`", "USE /* placeholder_comment */ `reg_test_3493_use_comment-a1`")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_1", " USE /* placeholder_comment */ `reg_test_3493_use_comment_1`")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_2", "USE/* placeholder_comment */ `reg_test_3493_use_comment_2`")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_3", "USE /* placeholder_comment */`reg_test_3493_use_comment_3`")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_4", " USE /* placeholder_comment */ reg_test_3493_use_comment_4")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_5", "USE/* placeholder_comment */ reg_test_3493_use_comment_5")); + db_query.push_back(std::make_pair("reg_test_3493_use_comment_6", "USE /* placeholder_comment */reg_test_3493_use_comment_6")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-1`", " USE /* placeholder_comment */ `reg_test_3493_use_comment-1`")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-2`", "USE/* placeholder_comment */ `reg_test_3493_use_comment-2`")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-3`", "USE /* placeholder_comment */`reg_test_3493_use_comment-3`")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-4`", "/* placeholder_comment */USE `reg_test_3493_use_comment-4`")); + db_query.push_back(std::make_pair("`reg_test_3493_use_comment-5`", "USE/* placeholder_comment */`reg_test_3493_use_comment-5`")); db_query.push_back(std::make_pair("`reg_test_3493_use_comment-6`", "/* comment */USE`reg_test_3493_use_comment-6`")); db_query.push_back(std::make_pair("`reg_test_3493_use_comment-7`", "USE`reg_test_3493_use_comment-7`")); @@ -107,19 +107,19 @@ int main(int argc, char** argv) { // statement. switch (i%5) { case 0: - MYSQL_QUERY(proxysql_mysql, "/*+ ;create_new_connection=1 */ SELECT DATABASE()"); + MYSQL_QUERY(proxysql_mysql, "/* ;create_new_connection=1 */ SELECT DATABASE()"); break; case 1: - MYSQL_QUERY(proxysql_mysql, "/*+ ;create_new_connection=1 */SELECT DATABASE()"); + MYSQL_QUERY(proxysql_mysql, "/* ;create_new_connection=1 */SELECT DATABASE()"); break; case 2: - MYSQL_QUERY(proxysql_mysql, "SELECT /*+ ;create_new_connection=1 */ DATABASE()"); + MYSQL_QUERY(proxysql_mysql, "SELECT /* ;create_new_connection=1 */ DATABASE()"); break; case 3: - MYSQL_QUERY(proxysql_mysql, "SELECT/*+ ;create_new_connection=1 */ DATABASE()"); + MYSQL_QUERY(proxysql_mysql, "SELECT/* ;create_new_connection=1 */ DATABASE()"); break; case 4: - MYSQL_QUERY(proxysql_mysql, "SELECT /*+ ;create_new_connection=1 */DATABASE()"); + MYSQL_QUERY(proxysql_mysql, "SELECT /* ;create_new_connection=1 */DATABASE()"); break; default: assert(0); diff --git a/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp b/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp index 2f07ca1647..82c0e3323f 100644 --- a/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp +++ b/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp @@ -47,7 +47,7 @@ int prepare_stmt( ) { int res = EXIT_SUCCESS; std::string query { - "SELECT /*+ ;hostgroup=0 */ id,c1,c2 FROM test.reg_test_3546 WHERE date IN (?)" + "SELECT /* ;hostgroup=0 */ id,c1,c2 FROM test.reg_test_3546 WHERE date IN (?)" }; if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) { @@ -89,7 +89,6 @@ int main(int argc, char** argv) { return -1; } - MYSQL_STMT* stmt_param = nullptr; MYSQL* proxysql_mysql = mysql_init(NULL); MYSQL* proxysql_admin = mysql_init(NULL); @@ -103,12 +102,6 @@ int main(int argc, char** argv) { return -1; } - stmt_param = mysql_stmt_init(proxysql_mysql); - if (!stmt_param) { - diag("mysql_stmt_init(), out of memory"); - goto exit; - } - // Insert the row to be queried with the prepared statement. // ************************************************************************* MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test"); @@ -132,6 +125,13 @@ int main(int argc, char** argv) { } { + MYSQL_STMT* stmt_param = nullptr; + stmt_param = mysql_stmt_init(proxysql_mysql); + if (!stmt_param) { + diag("mysql_stmt_init(), out of memory"); + goto exit; + } + // Set the number of maximum connections for servers in the writer hostgroup std::string t_update_mysql_servers { "UPDATE mysql_servers SET max_connections=1 WHERE hostgroup_id=%d" @@ -246,10 +246,11 @@ int main(int argc, char** argv) { ); } } + + mysql_stmt_close(stmt_param); } exit: - if (stmt_param) { mysql_stmt_close(stmt_param); } mysql_close(proxysql_mysql); mysql_close(proxysql_admin); diff --git a/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp b/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp index 5243c78411..d035c691e1 100644 --- a/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp +++ b/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp @@ -138,19 +138,19 @@ std::vector> test_definitions { "simple_set_autocommit_no_lock", { { "SET autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 } + { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "simple_set_autocommit_no_lock_2", { { "SET autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 } + { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { @@ -158,9 +158,9 @@ std::vector> test_definitions { { { "SET @session_var=1", 1 }, { "SET autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 } + { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { @@ -168,10 +168,10 @@ std::vector> test_definitions { { { "SET @session_var=1", 1 }, { "SET autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 } + { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { @@ -181,19 +181,19 @@ std::vector> test_definitions { "complex_set_autocommit_no_lock_1", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 } + { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "complex_set_autocommit_no_lock_2", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 } + { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { @@ -201,9 +201,9 @@ std::vector> test_definitions { { { "SET @session_var=1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 } + { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { @@ -211,26 +211,26 @@ std::vector> test_definitions { { { "SET @session_var=1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 } + { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { "mix_set_autocommit_1", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, { "BEGIN", 1 }, { "SET @session_var=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "COMMIT", 1 }, } }, @@ -238,16 +238,16 @@ std::vector> test_definitions { "mix_set_autocommit_2", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 1 }, + { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "BEGIN", 0 }, { "SET @session_var=1", 0 }, - { "SELECT /*+ ;hostgroup=0 */ 1", 0 }, + { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, } }, diff --git a/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp b/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp index 3e78a049ec..c6a19cb872 100644 --- a/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp +++ b/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp @@ -348,7 +348,7 @@ int main(int argc, char** argv) { int rc; std::string query_i = "INSERT INTO test.reg_test_3585 VALUES (? , ? , ? , ? , ? , ? , ?)"; // Force the 'hostgroup' for the 'SELECT' query to avoid replication issues - std::string query_s = "SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3585 WHERE id=?"; + std::string query_s = "SELECT /* ;hostgroup=0 */ * FROM test.reg_test_3585 WHERE id=?"; // init and prepare INSERT MYSQL_STMT *stmti = mysql_stmt_init(proxysql_mysql); diff --git a/test/tap/tests/reg_test_3603-stmt_metadata-t.cpp b/test/tap/tests/reg_test_3603-stmt_metadata-t.cpp index e116142da4..1f4eb5b693 100644 --- a/test/tap/tests/reg_test_3603-stmt_metadata-t.cpp +++ b/test/tap/tests/reg_test_3603-stmt_metadata-t.cpp @@ -555,7 +555,7 @@ int main(int argc, char** argv) { "start_time=?,trck_num=? where id=?"; // Force the 'hostgroup' for the 'SELECT' query to avoid replication issues - std::string query_s = "SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3603 WHERE id=?"; + std::string query_s = "SELECT /* ;hostgroup=0 */ * FROM test.reg_test_3603 WHERE id=?"; // init and prepare INSERT MYSQL_STMT *stmti = mysql_stmt_init(proxysql_mysql); diff --git a/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp b/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp new file mode 100644 index 0000000000..412bc68b61 --- /dev/null +++ b/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp @@ -0,0 +1,570 @@ +/** + * @file reg_test_3606_mysql_warnings-t.cpp + * @brief Regression test that performs multiple queries against a MySQL table + * with 'mysql-log_mysql_warnings_enabled' feature enabled. The issued queries are known + * to generate MySQL warnings thus providing a regression test for issue #3606. + * @details Test covers the following cases: + * * Mixed prepared statements and text protocol queries are executed correctly when + * 'mysql-log_mysql_warnings_enabled' is enabled. + * * Mixed queries generating warnings and not are correctly executed. + * * Multistatements queries mixed with the previous ones are also properly executed. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tap.h" +#include "command_line.h" +#include "utils.h" +#include "utils.h" + +using std::vector; +using std::tuple; +using std::string; + +enum query_type { + TEXT_SELECT_WARNING = 0, + TEXT_SELECT_NO_WARNING, + TEXT_MULTISTATEMENT_SELECT_WARNING, + TEXT_INSERT_WARNING, + TEXT_UPDATE_WARNING, + TEXT_UPDATE_NO_WARNING +}; + +std::vector queries { + "SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3606_mysql_warnings WHERE id=%d", + "SELECT * FROM test.reg_test_3606_mysql_warnings WHERE id=%d", + "SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3606_mysql_warnings WHERE id=%d; SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3606_mysql_warnings WHERE id=%d;", + "INSERT /*+ ;hostgroup=0 */ INTO test.reg_test_3606_mysql_warnings (a, c, pad) VALUES ('%d', '%s', '%s')", + "UPDATE /*+ ;hostgroup=0 */ test.reg_test_3606_mysql_warnings SET a=%d, c='%s', pad='%s' WHERE id=%d", + "UPDATE test.reg_test_3606_mysql_warnings SET a=%d, c='%s', pad='%s' WHERE id=%d" +}; + +enum stmt_query_type { + STMT_SELECT_NO_WARNING = 0, + STMT_INSERT_WARNING, + STMT_UPDATE_WARNING +}; + +std::vector stmt_queries { + "SELECT /*+ ;hostgroup=0 */ id, a, c, pad FROM test.reg_test_3606_mysql_warnings WHERE id=?", + "INSERT /*+ ;hostgroup=0 */ INTO test.reg_test_3606_mysql_warnings (a, c, pad) VALUES (?, ?, ?)", + "UPDATE /*+ ;hostgroup=0 */ test.reg_test_3606_mysql_warnings SET a=?, c=?, pad=? WHERE id=?" +}; + +int create_testing_tables(MYSQL* mysql_server) { + // Create the testing database + mysql_query(mysql_server, "CREATE DATABASE IF NOT EXISTS test"); + mysql_query(mysql_server, "DROP TABLE IF EXISTS test.reg_test_3606_mysql_warnings"); + + mysql_query( + mysql_server, + "CREATE TABLE IF NOT EXISTS test.reg_test_3606_mysql_warnings (" + " id INTEGER NOT NULL AUTO_INCREMENT," + " a TINYINT NOT NULL," + " c varchar(255)," + " pad CHAR(60)," + " PRIMARY KEY (id)" + ")" + ); + + return mysql_errno(mysql_server); +} + +int main(int argc, char** argv) { + CommandLine cl; + + uint32_t c_operations = 500; + + double plan_val = + 1.0 + // Table creation + (double)c_operations + // Initial table filling insert queries + floor(((double)c_operations - 1.0) * (1.0 / 4.0) * 3.0) + // Number of reguarl selects checks + floor(((double)c_operations - 1.0) * (1.0 / 8.0) * 3.0) + // Number of non-multistatement select checks + floor(((double)c_operations - 1.0) * (1.0 / 8.0) * 5.0) + // Number of multistatements select checks + floor(((double)c_operations - 1.0) * (1.0 / 2.0)); // Number of updates checks + plan(plan_val); + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + MYSQL* proxy_mysql = mysql_init(NULL); + MYSQL* proxy_admin = mysql_init(NULL); + + // Initialize connections + if (!proxy_mysql) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_mysql)); + return EXIT_FAILURE; + } + if (!proxy_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_admin)); + return EXIT_FAILURE; + } + + if ( + !mysql_real_connect(proxy_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, + CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS) + ) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_mysql)); + return EXIT_FAILURE; + } + + if (!mysql_real_connect(proxy_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(proxy_admin)); + return EXIT_FAILURE; + } + + MYSQL_QUERY(proxy_admin, "SET mysql-log_mysql_warnings_enabled='true'"); + MYSQL_QUERY(proxy_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + MYSQL_QUERY(proxy_mysql, "SET sql_mode=ANSI"); + + int c_err = create_testing_tables(proxy_mysql); + ok(c_err == 0, "Table creation should succeed. ErrCode: %d", c_err); + if (tests_failed()) { + std::string error = mysql_error(proxy_mysql); + diag("MySQL Error: '%s'", error.c_str()); + + return exit_status(); + } + + vector> stored_pairs {}; + + // Include one initial null element to make index match + stored_pairs.push_back(tuple{0,"", ""}); + srand(time(NULL)); + + for (auto i = 0; i < c_operations; i++) { + std::string rnd_c = random_string(rand() % 80 + 1); + std::string rnd_pad = random_string(rand() % 15 + 1); + const std::string& t_insert_query = queries[TEXT_INSERT_WARNING]; + std::string insert_query {}; + + // Store the random generated strings + stored_pairs.push_back(tuple{ 300, rnd_c, rnd_pad }); + + // Execute the INSERT queries + string_format(t_insert_query, insert_query, 300, rnd_c.c_str(), rnd_pad.c_str()); + int i_res = mysql_query(proxy_mysql, insert_query.c_str()); + uint64_t i_err = mysql_errno(proxy_mysql); + + ok(i_err == 0, "Insert queries should be executed correctly. ErrCode: %ld", i_err); + if (tests_failed()) { + std::string error = mysql_error(proxy_mysql); + diag("MySQL Error: '%s'", error.c_str()); + + return exit_status(); + } + } + + const std::string rep_check_query { + "SELECT CASE WHEN (SELECT COUNT(*) FROM test.reg_test_3606_mysql_warnings) = 500 THEN 'TRUE' ELSE 'FALSE' END" + }; + int wait_res = wait_for_replication(proxy_mysql, proxy_admin, rep_check_query, 10, 1); + if (wait_res != EXIT_SUCCESS) { + diag("Waiting for replication failed... Exiting"); + return EXIT_FAILURE; + } + + // Prepare STMT queries + std::vector stmts {}; + for (std::size_t i = 0; i < stmt_queries.size(); i++) { + MYSQL_STMT* stmt = mysql_stmt_init(proxy_mysql); + if (stmt == nullptr) { + diag( + "'mysql_stmt_init' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_SELECT_NO_WARNING].c_str(), mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + stmts.push_back(stmt); + } + + // Prepare SELECT + { + int err = mysql_stmt_prepare( + stmts[0], stmt_queries[STMT_SELECT_NO_WARNING].c_str(), stmt_queries[STMT_SELECT_NO_WARNING].size() + ); + if (err != EXIT_SUCCESS) { + diag( + "'mysql_stmt_prepare' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_SELECT_NO_WARNING].c_str(), mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + } + // Prepare UPDATE + { + int err = mysql_stmt_prepare( + stmts[2], stmt_queries[STMT_UPDATE_WARNING].c_str(), stmt_queries[STMT_UPDATE_WARNING].size() + ); + if (err != EXIT_SUCCESS) { + diag( + "'mysql_stmt_prepare' failed for 'SELECT' with err: ('%s','%s')", stmt_queries[0].c_str(), + mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + } + + bool multistatement = true; + bool select_stmt_query = true; + bool update_stmt_query = true; + + for (auto id = 1; id < c_operations; id++) { + int64_t op = id % 2; + int64_t produce_warning = rand() % 2; + + if (op == 0) { // Do a random SELECT + if (select_stmt_query) { + diag("Performing 'STMT SELECT' query..."); + + MYSQL_BIND bindsi[1]; + memset(bindsi, 0, sizeof(bindsi)); + int copyid = id; + + bindsi[0].buffer_type= MYSQL_TYPE_LONG; + bindsi[0].buffer= (char *)©id; + bindsi[0].is_null= 0; + bindsi[0].length= 0; + + int rc = mysql_stmt_bind_param(stmts[0], bindsi); + if (rc) { + diag( + "'mysql_stmt_bind_param' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_SELECT_NO_WARNING].c_str(), mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + + MYSQL_BIND binds[4]; + memset(binds, 0, sizeof(binds)); + + int64_t id_res; + int res_a; + char res_c_buf[256]; + char res_pad_buf[256]; + memset(res_c_buf, 0, sizeof(res_c_buf)); + memset(res_pad_buf, 0, sizeof(res_pad_buf)); + + unsigned long length[4] = { 0 }; + my_bool is_null[4] = { 0 }; + my_bool error[4] = { 0 }; + + binds[0].buffer_type= MYSQL_TYPE_LONGLONG; + binds[0].buffer= (char *)&id_res; + binds[0].is_null= &is_null[0]; + binds[0].length= &length[0]; + binds[0].error= &error[0]; + + binds[1].buffer_type= MYSQL_TYPE_LONG; + binds[1].buffer= (char *)&res_a; + binds[1].is_null= &is_null[3]; + binds[1].length= &length[3]; + binds[1].error= &error[3]; + + binds[2].buffer_type= MYSQL_TYPE_VAR_STRING; + binds[2].buffer= (char *)res_c_buf; + binds[2].buffer_length= sizeof(res_c_buf); + binds[2].is_null= &is_null[1]; + binds[2].length= &length[1]; + binds[2].error= &error[1]; + + binds[3].buffer_type= MYSQL_TYPE_VAR_STRING; + binds[3].buffer= (char *)res_pad_buf; + binds[3].buffer_length= sizeof(res_pad_buf); + binds[3].is_null= &is_null[2]; + binds[3].length= &length[2]; + binds[3].error= &error[2]; + + rc = mysql_stmt_execute(stmts[0]); + ok( + rc == 0, "Select queries should be executed correctly. Err: ('%s', '%d')", + mysql_error(proxy_mysql), mysql_errno(proxy_mysql) + ); + + if (rc) { + diag( + "'mysql_stmt_execute' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_SELECT_NO_WARNING].c_str(), mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + + rc = mysql_stmt_bind_result(stmts[0], binds); + if (rc) { + diag( + "'mysql_stmt_execute' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_SELECT_NO_WARNING].c_str(), mysql_error(proxy_mysql) + ); + return EXIT_FAILURE; + } + + MYSQL_RES *prepare_meta_result; + prepare_meta_result = mysql_stmt_result_metadata(stmts[0]); + if (prepare_meta_result == NULL) { + diag("mysql_stmt_result_metadata() failed: %s", mysql_stmt_error(stmts[0])); + return EXIT_FAILURE; + } + + rc = mysql_stmt_store_result(stmts[0]); + if (rc) { + diag("mysql_stmt_store_result() failed: %s", mysql_stmt_error(stmts[0])); + return EXIT_FAILURE; + } + + unsigned long long row_count= mysql_stmt_num_rows(stmts[0]); + uint32_t field_count= mysql_stmt_field_count(stmts[0]); + ok( + field_count == 4 && row_count == 1, + "Received resulset should have: Exp - ['field_count'='3','row_count'='1']," + " Actual: ['field_count'='%d','row_count'='%lld'].", + field_count, row_count + ); + + rc = mysql_stmt_fetch(stmts[0]); + if (rc && rc != MYSQL_DATA_TRUNCATED) { + diag("mysql_stmt_fetch() failed: ('%d','%s')", rc, mysql_stmt_error(stmts[0])); + return EXIT_FAILURE; + } + + bool same_a = std::get<0>(stored_pairs[id]) == 300 && 127 == res_a; + bool same_c = std::get<1>(stored_pairs[id]) == std::string { res_c_buf }; + bool same_pad = std::get<2>(stored_pairs[id]) == std::string { res_pad_buf }; + + ok( + same_a && same_c && same_pad, + "Received 'a', 'c' and 'pad' matches expected values." + " ('a': '%d') == ('exp_a': '%d'), ('c': '%s') == ('exp_c': '%s'), ('pad': '%s') == ('exp_pad': '%s')", + res_a, 127, res_c_buf, std::get<1>(stored_pairs[id]).c_str(), + res_pad_buf, std::get<2>(stored_pairs[id]).c_str() + ); + + mysql_stmt_free_result(stmts[0]); + mysql_free_result(prepare_meta_result); + + if (tests_failed()) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + } else { + if (multistatement) { + diag("Performing 'TEXT PROTOCOL MULTISTATEMENT SELECT' query..."); + } else { + diag("Performing 'TEXT PROTOCOL SELECT' query..."); + } + + std::string t_select_query {}; + std::string select_query {}; + + if (multistatement) { + t_select_query = queries[TEXT_MULTISTATEMENT_SELECT_WARNING]; + string_format(t_select_query, select_query, id, id); + } else { + if (produce_warning) { + t_select_query = queries[TEXT_SELECT_WARNING]; + } else { + t_select_query = queries[TEXT_SELECT_NO_WARNING]; + } + string_format(t_select_query, select_query, id); + } + + int rc = mysql_query(proxy_mysql, select_query.c_str()); + ok( + rc == 0, "Select queries should be executed correctly. Err: ('%s', '%d')", + mysql_error(proxy_mysql), mysql_errno(proxy_mysql) + ); + if (rc != 0) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + + const auto check_result = [&](MYSQL_RES* select_res) -> bool { + int field_count = mysql_field_count(proxy_mysql); + int row_count = mysql_num_rows(select_res); + + ok( + field_count == 4 && row_count == 1, + "Received resulset should have: Exp - ['field_count'='3','row_count'='1']," + " Actual: ['field_count'='%d','row_count'='%d'].", + field_count, row_count + ); + + if (tests_failed()) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(select_res); + bool same_a = std::get<0>(stored_pairs[id]) == 300 && 127 == std::atoi(row[1]); + bool same_c = std::get<1>(stored_pairs[id]) == row[2]; + bool same_pad = std::get<2>(stored_pairs[id]) == row[3]; + + ok( + same_a && same_c && same_pad, + "Received 'a', 'c' and 'pad' matches expected values." + " ('a': '%d') == ('exp_a': '%d'), ('c': '%s') == ('exp_c': '%s'), ('pad': '%s') == ('exp_pad': '%s')", + std::atoi(row[1]), 127, row[2], std::get<1>(stored_pairs[id]).c_str(), row[3], + std::get<2>(stored_pairs[id]).c_str() + ); + + return tests_failed() == 0; + }; + + // Check that the SELECT resultset isn't illformed + int next_result = 0; + int result_count = 0; + while(next_result == 0) { + result_count++; + + MYSQL_RES* select_res = mysql_store_result(proxy_mysql); + bool check_res = check_result(select_res); + mysql_free_result(select_res); + if (check_res == false) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + next_result = mysql_next_result(proxy_mysql); + } + + if (tests_failed()) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + + // Next iteration perform the opposite operation + multistatement = !multistatement; + } + + // Next iteration perform the opposite operation + select_stmt_query = !select_stmt_query; + } else { // Do a random UPDATE + if (update_stmt_query) { + diag("Performing 'STMT UPDATE' query..."); + + MYSQL_BIND binds_u[4]; + memset(binds_u, 0, sizeof(binds_u)); + unsigned long length[4] = { 0 }; + + int64_t id_param = id; + int param_a = 99; + + if (produce_warning) { + param_a = 255; + } + + std::string param_c { random_string(rand() % 100 + 5) }; + std::string param_pad { random_string(rand() % 60 + 5) }; + + unsigned long param_c_len = param_c.size(); + unsigned long param_pad_len = param_pad.size(); + + my_bool is_null[4] = { 0 }; + + binds_u[0].buffer_type= MYSQL_TYPE_LONG; + binds_u[0].buffer= (char *)¶m_a; + binds_u[0].is_null= 0; + binds_u[0].length= 0; + + binds_u[1].buffer_type= MYSQL_TYPE_VAR_STRING; + binds_u[1].buffer= (char *)param_c.c_str(); + binds_u[1].buffer_length= param_c.size(); + binds_u[1].is_null= 0; + binds_u[1].length= ¶m_c_len; + + binds_u[2].buffer_type= MYSQL_TYPE_VAR_STRING; + binds_u[2].buffer= (char *)param_pad.c_str(); + binds_u[2].buffer_length= param_pad.size(); + binds_u[2].is_null= 0; + binds_u[2].length= ¶m_pad_len; + + binds_u[3].buffer_type= MYSQL_TYPE_LONGLONG; + binds_u[3].buffer= (char *)&id_param; + binds_u[3].is_null= 0; + binds_u[3].length= &length[0]; + + int rc = mysql_stmt_bind_param(stmts[2], binds_u); + if (rc) { + diag( + "'mysql_stmt_bind_param' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_UPDATE_WARNING].c_str(), mysql_stmt_error(stmts[2]) + ); + return EXIT_FAILURE; + } + + rc = mysql_stmt_execute(stmts[2]); + if (rc) { + diag( + "'mysql_stmt_execute' failed for 'SELECT' with err: ('%s','%s')", + stmt_queries[STMT_UPDATE_WARNING].c_str(), mysql_error(proxy_mysql) + ); + + goto cleanup; + } + + int affected_rows= mysql_stmt_affected_rows(stmts[2]); + ok( + rc == 0 && affected_rows == 1, + "Update queries should be executed correctly. ErrCode: %d", mysql_stmt_errno(stmts[2]) + ); + + if (tests_failed()) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + } else { + diag("Performing 'TEXT PROTOCOL UPDATE' query..."); + + std::string rnd_c = random_string(rand() % 100 + 5); + std::string rnd_pad = random_string(rand() % 60 + 5); + + // Store the new random generated strings + std::tuple new_values { 255, rnd_c, rnd_pad }; + stored_pairs[id] = new_values; + + std::string update_query {}; + + if (produce_warning) { + const std::string& t_update_query = queries[TEXT_UPDATE_WARNING]; + string_format(t_update_query, update_query, 300, rnd_c.c_str(), rnd_pad.c_str(), id); + } else { + const std::string& t_update_query = queries[TEXT_UPDATE_NO_WARNING]; + string_format(t_update_query, update_query, 100, rnd_c.c_str(), rnd_pad.c_str(), id); + } + int u_res = mysql_query(proxy_mysql, update_query.c_str()); + + ok( + u_res == 0, "Update queries should be executed correctly. ErrCode: %d", + mysql_errno(proxy_mysql) + ); + if (tests_failed()) { + diag("Failed, aborting further tests..."); + goto cleanup; + } + } + + // Next iteration perform the opposite operation + update_stmt_query = !update_stmt_query; + } + } +cleanup: + + for (MYSQL_STMT* stmt : stmts) { + mysql_stmt_close(stmt); + } + mysql_close(proxy_mysql); + mysql_close(proxy_admin); + + return exit_status(); +} diff --git a/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp b/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp new file mode 100644 index 0000000000..a302543202 --- /dev/null +++ b/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp @@ -0,0 +1,126 @@ +/** + * @file reg_test_fast_forward_split_packet-t.cpp + * @brief This is a simple regression test for checking that 'FAST_FORWARD' is able to handle split packets + * received in a connection that is yet in 'CONNECTING_SERVER' state. + * @details In order to achieve this behavior consistently, the test performs the following operations: + * 1. Enables 'fast_forward' for 'mysql_users'. + * 2. Creates a number of connections. + * 3. Performs a unique 'INSERT' query of increasing size in each of the connections. + * + * If the issue is present and ProxySQL cannot handle this situation, the test can fail before reaching + * 'QUEUE_T_DEFAULT_SIZE', but will for sure fail after this threshold is reached. Since the input buffer + * is filled by the query, there won't be other option that processing the received packet in two different + * iterations of 'MySQL_Session::handler', which will force the 'CONNECTING_SERVER' situation. + */ + +#include +#include +#include +#include +#include +#include + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +const int NUM_CONNS = 35; + +MYSQL* conns[NUM_CONNS]; + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + // One query that should succeed per-connection + plan(NUM_CONNS); + + MYSQL* proxysql_admin = mysql_init(NULL); + if (!proxysql_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + + 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 -1; + } + + MYSQL_QUERY(proxysql_admin, "UPDATE mysql_users SET fast_forward=1"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL USERS TO RUNTIME"); + + std::random_device rd {}; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0.0, 9.0); + + for (int i = 0; i < NUM_CONNS ; i++) { + MYSQL * mysql = mysql_init(NULL); + if (!mysql) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return EXIT_FAILURE; + } + + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return EXIT_FAILURE; + } + conns[i] = mysql; + } + + MYSQL_QUERY(conns[0], "DROP TABLE IF EXISTS test.reg_test_fast_forward_split"); + MYSQL_QUERY( + conns[0], + "CREATE TABLE IF NOT EXISTS test.reg_test_fast_forward_split (" + " `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `k` int(10) unsigned NOT NULL DEFAULT '0'," + " `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT ''," + " PRIMARY KEY (`id`), KEY `k_1` (`k`) " + ")" + ); + + int query_num = 10; + std::string q { "INSERT INTO test.reg_test_fast_forward_split (k, c, pad) values " }; + bool put_comma=false; + + for (int j = 0; j < NUM_CONNS; j++) { + MYSQL* proxysql_mysql = conns[j]; + + for (int i=0; i< query_num; ++i) { + int k = dist(mt); + std::string c; + for (int j=0; j<10; j++) { + for (int k=0; k<11; k++) { + c += std::to_string(dist(mt)); + } + if (j<9) + c += "-"; + } + std::string pad; + for (int j=0; j<5; j++) { + for (int k=0; k<11; k++) { + pad += std::to_string(dist(mt)); + } + if (j<4) + pad += "-"; + } + if (put_comma) q += ","; + if (!put_comma) put_comma=true; + + q += "(" + std::to_string(k) + ",'" + c + "','" + pad + "')"; + } + + int q_err = mysql_query(proxysql_mysql, q.c_str()); + ok(q_err == EXIT_SUCCESS, "Executing query of size: '%ld', should succeed", q.size()); + } + + for (int j = 0; j < NUM_CONNS; j++) { + mysql_close(conns[j]); + } + + mysql_close(proxysql_admin); + + return exit_status(); +} diff --git a/test/tap/tests/test_client_limit_error-t.cpp b/test/tap/tests/test_client_limit_error-t.cpp new file mode 100644 index 0000000000..ecc158bab9 --- /dev/null +++ b/test/tap/tests/test_client_limit_error-t.cpp @@ -0,0 +1,948 @@ +/** + * @file test_client_limit_error.cpp + * @brief This test aims to verify the logic for the 'client_limit_error' feature. + * NOTE: This test isn't executed automatically because it requires elevated privileges + * for being able to run the scripts 'create_netns_n' and 'delete_netns_n' that creates + * and delete the networks namespaces required for it. + * + * @details Right now the test verifies the following cases: + * 1. Enable the feature and checks that the error count is incremented when a single + * client tries to connect and that the cache entry values match expected ones. + * 2. Flush the entries, and check that counting is performed properly for a + * single cache entry. + * 3. Flush the entries, and check that counting is performed properly for + * multiple cache entries. + * 4. Flush the entries, and check: + * 1. That counting is performed properly for multiple cache entries. + * 2. Connections fail after the limit for one client. + * 3. Clients are deleted after a succesfull connection is performed. + * 5. Flush the entries, fill the cache and check that when the + * 'mysql-client_host_error_counts' is changed at runtime, connections are denied + * to a client exceeding the new limit. + * 6. Flush the entries, fill the cache and check that the when the + * 'mysql-client_host_cache_size' is changed at runtime: + * 1. The exceeding elements are cleaned with each new connection. + * 2. Check that is not relevant if the element was or not present + * in the cache. + * 3. Checks that a proper connection is performed succesfully and the element is + * removed from the cache + * 7. Flush the entries and checks that client connections timeouts interact with the + * cache in the same way as regular client connection errors. + * + * @date 2021-09-10 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "json.hpp" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +const uint32_t NUM_LOOPBACK_ADDRS = 5; + +using host_cache_entry = std::tuple; + +inline unsigned long long monotonic_time() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (((unsigned long long) ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000); +} + +std::vector get_client_host_cache_entries(MYSQL* proxysql_admin) { + int rc = mysql_query( + proxysql_admin, + "SELECT * FROM stats.stats_mysql_client_host_cache ORDER BY client_address" + ); + + MYSQL_ROW row = NULL; + MYSQL_RES* proxy_res = mysql_store_result(proxysql_admin); + std::vector host_cache_entries {}; + + while ((row = mysql_fetch_row(proxy_res))) { + std::string client_address = row[0]; + uint32_t error_count = atoi(row[1]); + uint64_t last_update = atoll(row[2]); + + host_cache_entries.push_back({client_address, error_count, last_update}); + } + + mysql_free_result(proxy_res); + + return host_cache_entries; +} + +int invalid_proxysql_conn(const std::string& addr, const CommandLine& cl) { + MYSQL* proxysql = mysql_init(NULL); + int my_err = EXIT_SUCCESS; + + if (!mysql_real_connect(proxysql, addr.c_str(), "limit_inv_user", "limit_inv_pass", NULL, 6033, NULL, 0)) { + my_err = mysql_errno(proxysql); + } + + mysql_close(proxysql); + + return my_err; +} + +int invalid_proxysql_conn(const std::string& addr, const CommandLine& cl, std::string& err_msg) { + MYSQL* proxysql = mysql_init(NULL); + int my_err = EXIT_SUCCESS; + + if (!mysql_real_connect(proxysql, addr.c_str(), "limit_inv_user", "limit_inv_pass", NULL, 6033, NULL, 0)) { + my_err = mysql_errno(proxysql); + err_msg = mysql_error(proxysql); + } + + mysql_close(proxysql); + + return my_err; +} + +int valid_proxysql_conn(const std::string& addr, const CommandLine& cl, std::string& err_msg) { + MYSQL* proxysql = mysql_init(NULL); + int my_err = EXIT_SUCCESS; + + if (!mysql_real_connect(proxysql, addr.c_str(), cl.username, cl.password, NULL, 6033, NULL, 0)) { + my_err = mysql_errno(proxysql); + err_msg = mysql_error(proxysql); + } + + mysql_close(proxysql); + + return my_err; +} + +/** + * @brief Enable the feature check that error count is incremented when a + * new client fails to connect, and that the cache entry values are the + * expected ones. + * + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_cache_filled_by_invalid_conn(const CommandLine& cl, MYSQL* proxysql_admin) { + diag(" START TEST NUMBER 1 "); + diag("-------------------------------------------------------------"); + + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=1"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + + uint64_t pre_command_time = monotonic_time(); + const std::string exp_client_addr { "127.0.0.2" }; + + diag("Performing connection to fill 'client_host_cache'"); + int inv_user_errno = invalid_proxysql_conn(exp_client_addr, cl); + if (inv_user_errno == EXIT_SUCCESS) { + diag("Expected failure but client connection succeed"); + return EXIT_FAILURE; + } + + diag("Performing checks over 'client_host_cache'"); + std::vector entries = get_client_host_cache_entries(proxysql_admin); + + ok( + entries.size() == 1, + "'client_host_cache' entries should be '1' after issuing 'PROXYSQL FLUSH" + " MYSQL CLIENT HOSTS' and one failed connection." + ); + + if (entries.size() == 1) { + host_cache_entry unique_entry { entries.back() }; + const std::string client_addr { std::get<0>(unique_entry) }; + const uint32_t error_count { std::get<1>(unique_entry) }; + const uint64_t last_updated { std::get<2>(unique_entry) }; + uint64_t post_command_time = monotonic_time(); + + ok( + client_addr == exp_client_addr && error_count == 1 && (pre_command_time < last_updated < post_command_time), + "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," + " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", + exp_client_addr.c_str(), 1, pre_command_time, last_updated, post_command_time, client_addr.c_str(), error_count, + pre_command_time, last_updated, post_command_time + ); + } + + return EXIT_SUCCESS; +} + +/** + * @brief Flush the entries, and check that counting is performed properly for a single cache entry. + * + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_cache_entry_count_by_invalid_conn(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 2 "); + diag("-------------------------------------------------------------"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + int errors = 0; + const std::string exp_client_addr { "127.0.0.2" }; + uint64_t pre_command_time = monotonic_time(); + + diag("Performing connection to fill 'client_host_cache'"); + for (errors = 0; errors < 5; errors++) { + int inv_user_errno = invalid_proxysql_conn(exp_client_addr, cl); + if (inv_user_errno == EXIT_SUCCESS) { + diag("Expected failure but client connection succeed"); + return EXIT_FAILURE; + } + } + + diag("Performing checks over 'client_host_cache'"); + std::vector entries = + get_client_host_cache_entries(proxysql_admin); + + ok( + entries.size() == 1, + "'client_host_cache' entries should be '1' after issuing 'PROXYSQL FLUSH" + " MYSQL CLIENT HOSTS' and one failed connection." + ); + + if (entries.size() == 1) { + host_cache_entry unique_entry { entries.back() }; + const std::string client_addr { std::get<0>(unique_entry) }; + const uint32_t error_count { std::get<1>(unique_entry) }; + const uint64_t last_updated { std::get<2>(unique_entry) }; + uint64_t post_command_time = monotonic_time(); + + ok( + client_addr == exp_client_addr && error_count == errors && + (pre_command_time < last_updated < post_command_time), + "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," + " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", + exp_client_addr.c_str(), 1, pre_command_time, last_updated, post_command_time, + client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time + ); + } + + return EXIT_SUCCESS; +} + +/** + * @brief Flush the entries, and check that counting is performed properly for multiple cache entries. + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_cache_entry_count_by_mult_invalid_conns(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 3 "); + diag("-------------------------------------------------------------"); + + // Increase cache size + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + int errors = 0; + + uint64_t pre_command_time = monotonic_time(); + + printf("\n"); + diag("Performing connections to fill 'client_host_cache'"); + for (int i = 2; i < NUM_LOOPBACK_ADDRS; i++) { + std::string loopback_addr { "127.0.0." + std::to_string(i) }; + for (errors = 0; errors < 2; errors++) { + int inv_user_errno = invalid_proxysql_conn(loopback_addr, cl); + diag("Client connection failed with error: %d", inv_user_errno); + } + } + + diag("Performing checks over 'client_host_cache'"); + + std::vector entries = + get_client_host_cache_entries(proxysql_admin); + + ok( + entries.size() == NUM_LOOPBACK_ADDRS - 2, + "'client_host_cache' entries should be 'NUM_LOOPBACK_ADDRS' after issuing 'PROXYSQL FLUSH" + " MYSQL CLIENT HOSTS' and 'NUM_LOOPBACK_ADDRS' failed connections. Entries: '%ld'", + entries.size() + ); + + if (entries.size() == NUM_LOOPBACK_ADDRS - 2) { + uint32_t entry_num = 2; + + for (const auto& entry : entries) { + const std::string client_addr { std::get<0>(entry) }; + const uint32_t error_count { std::get<1>(entry) }; + const uint64_t last_updated { std::get<2>(entry) }; + uint64_t post_command_time = monotonic_time(); + + std::string exp_client_addr { "127.0.0." + std::to_string(entry_num) }; + + ok( + client_addr == exp_client_addr && error_count == errors && + (pre_command_time < last_updated < post_command_time), + "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," + " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", + exp_client_addr.c_str(), errors, pre_command_time, last_updated, post_command_time, + client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time + ); + + entry_num += 1; + } + } + + return EXIT_SUCCESS; +} + +/** + * @brief Flush the entries, and check: + * 1. That counting is performed properly for multiple cache entries. + * 2. Connections fail after the limit for one client. + * 3. Clients are deleted after a succesfull connection is performed. + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_client_exceeding_cache_error_limit(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 4 "); + diag("-------------------------------------------------------------"); + + // Increase cache size + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + int errors = 0; + + std::vector loopback_addrs {}; + for (int i = 2; i < NUM_LOOPBACK_ADDRS; i++) { + loopback_addrs.push_back("127.0.0." + std::to_string(i)); + } + + uint64_t pre_command_time = monotonic_time(); + + printf("\n"); + diag("Performing connections to fill 'client_host_cache'"); + for (const auto loopback_addr : loopback_addrs) { + for (errors = 0; errors < 3; errors++) { + int inv_user_errno = invalid_proxysql_conn(loopback_addr, cl); + diag("Client connection failed with error: %d", inv_user_errno); + } + } + + printf("\n"); + diag("1. Check that counting is perfomred properly over multiple 'client_host_cache'"); + + std::vector entries = + get_client_host_cache_entries(proxysql_admin); + + ok( + entries.size() == NUM_LOOPBACK_ADDRS - 2, + "'client_host_cache' entries should be 'NUM_LOOPBACK_ADDRS' after issuing 'PROXYSQL FLUSH" + " MYSQL CLIENT HOSTS' and 'NUM_LOOPBACK_ADDRS' failed connections. Entries: '%ld'", + entries.size() + ); + + if (entries.size() == NUM_LOOPBACK_ADDRS - 2) { + uint32_t entry_num = 2; + + for (const auto& entry : entries) { + const std::string client_addr { std::get<0>(entry) }; + const uint32_t error_count { std::get<1>(entry) }; + const uint64_t last_updated { std::get<2>(entry) }; + uint64_t post_command_time = monotonic_time(); + + std::string exp_client_addr { "127.0.0." + std::to_string(entry_num) }; + + ok( + client_addr == exp_client_addr && error_count == errors && + (pre_command_time < last_updated < post_command_time), + "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," + " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", + exp_client_addr.c_str(), errors, pre_command_time, last_updated, post_command_time, + client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time + ); + + entry_num += 1; + } + } + + printf("\n"); + diag("Performing connections to fill 'client_host_cache'"); + + const std::string loopback_addr { "127.0.0.4" }; + uint32_t expected_errors = 5; + + int limits = errors; + + std::string command_res {}; + int limit_conn_err = EXIT_SUCCESS; + + for (int limits = errors; limits < 5 + 1; limits++) { + limit_conn_err = invalid_proxysql_conn(loopback_addr, cl, command_res); + diag("Client connection failed with error: (%d, %s)", limit_conn_err, command_res.c_str()); + } + printf("\n"); + + diag("2. Checking the connection is denied when the limit is reached."); + + ok( + limit_conn_err == 2013, + "Last connection should fail with 'ERROR 2013', it exceeded the error limit. ErrMsg: '%s'", + command_res.c_str() + ); + + std::vector new_entries { + get_client_host_cache_entries(proxysql_admin) + }; + + auto cache_entry = + std::find_if( + std::begin(new_entries), + std::end(new_entries), + [&] (const host_cache_entry& elem) -> bool { + return std::get<0>(elem) == loopback_addr; + } + ); + + bool found_exp_values = false; + std::string client_address {}; + uint32_t found_errors = 0; + + if (cache_entry != std::end(new_entries)) { + client_address = std::get<0>(*cache_entry); + found_errors = std::get<1>(*cache_entry); + + found_exp_values = + std::get<0>(*cache_entry) == loopback_addr && + std::get<1>(*cache_entry) == expected_errors; + } + + ok( + found_exp_values, + "Entry should match expected values - exp(addr: %s, err_count: %d), act(addr: %s, err_count: %d)", + loopback_addr.c_str(), expected_errors, client_address.c_str(), found_errors + ); + + diag("3. Check that clients are deleted from the cache when the connections are succesfully performed"); + + for (int i = 1; i < NUM_LOOPBACK_ADDRS; i++) { + // This client as exceeded the max failures + + std::string loopback_addr { "127.0.0." + std::to_string(i) }; + + // Client has exceeded maximum connections failure is expected + if (i == 4) { + std::string conn_err_msg {}; + int limit_conn_err = valid_proxysql_conn(loopback_addr, cl, conn_err_msg); + + ok( + limit_conn_err == 2013, + "Connection should fail due to limit exceeded. ErrMsg: '%s'", conn_err_msg.c_str() + ); + } else { + std::string command_res {}; + int command_err = valid_proxysql_conn(loopback_addr, cl, command_res); + ok( + command_err == 0, + "Connection should succeed for clients which limit haven't been exceeded." + ); + } + } + + new_entries = get_client_host_cache_entries(proxysql_admin); + std::string last_client_addr { "" }; + if (new_entries.size()) { + last_client_addr = std::get<0>(new_entries.back()); + } + + ok( + new_entries.size() == 1 && last_client_addr == "127.0.0.4", + "Only client address exceeding the limit should remain in the cache - exp('127.0.0.4'), act('%s')", + last_client_addr.c_str() + ); + return EXIT_SUCCESS; +} + +/** + * @brief Flush the entries, fill the cache and check that the when the 'mysql-client_host_error_counts' + * is changed at runtime, connections are denied to a client exceeding the new limit. + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_client_exceeding_changed_error_limit(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 5 "); + diag("-------------------------------------------------------------"); + + // Increase cache size + printf("\n"); + diag("Setting the value of 'mysql-client_host_cache_size' to '5'"); + + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + int errors = 0; + + + uint64_t pre_command_time = monotonic_time(); + + std::string loopback_addr { "127.0.0.2" }; + diag("Performing connections to fill 'client_host_cache'"); + + for (int i = 0; i < 4; i++) { + int inv_user_errno = invalid_proxysql_conn(loopback_addr, cl); + diag("Client connection failed with error: %d", inv_user_errno); + } + + diag("Decreasing the value of 'mysql-client_host_error_counts' to '3'"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=3"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + { + printf("\n"); + + std::string conn_err_msg {}; + int valid_user_err = valid_proxysql_conn(loopback_addr, cl, conn_err_msg); + diag("Client connection failed with error: (%d, %s)", valid_user_err, conn_err_msg.c_str()); + + ok( + valid_user_err == 2013, + "Last connection should fail with 'ERROR 2013', it exceeded the error limit. ErrMsg: '%s'", + conn_err_msg.c_str() + ); + } + + return EXIT_SUCCESS; +} + +/** + * @brief Flush the entries, set 'mysql-client_host_cache_size' to '0', and check + * that cache isn't filled by connections timing out. Increase 'mysql-client_host_cache_size' + * and check that cache is filled by timeout connections. + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_cache_size_decrease_by_new_connections(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 6 "); + diag("-------------------------------------------------------------"); + + // Increase cache size + printf("\n"); + diag("Setting the value of 'mysql-client_host_cache_size' to '5'"); + + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + int errors = 0; + + std::vector loopback_addrs {}; + for (int i = 2; i < NUM_LOOPBACK_ADDRS; i++) { + loopback_addrs.push_back("127.0.0." + std::to_string(i)); + } + + uint64_t pre_command_time = monotonic_time(); + + printf("\n"); + diag("Performing connections to fill 'client_host_cache'"); + for (const auto loopback_addr : loopback_addrs) { + for (errors = 0; errors < 3; errors++) { + int inv_user_errno = invalid_proxysql_conn(loopback_addr, cl); + diag("Client connection failed with error: %d", inv_user_errno); + } + } + + diag("Decreasing the value of 'mysql-client_host_cache_size' to '3'"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=1"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // Update the latest entry in the cache, oldest member "10.200.1.2" should go away. + { + uint64_t pre_command_time = monotonic_time(); + + diag("1. Checking that the connection updates the entry and the oldest entry is removed"); + + std::string loopback_addr { "127.0.0.4" }; + + printf("\n"); + int inv_user_err = invalid_proxysql_conn(loopback_addr, cl); + diag("Client connection failed with error: %d", inv_user_err); + + std::vector updated_entries { + get_client_host_cache_entries(proxysql_admin) + }; + + std::string exp_client_addr { "127.0.0.4" }; + + auto entry = std::find_if( + std::begin(updated_entries), + std::end(updated_entries), + [&] (const host_cache_entry& entry) -> bool { + return std::get<0>(entry) == exp_client_addr; + } + ); + + std::string act_client_addr {}; + uint64_t last_updated = 0; + + if (entry != std::end(updated_entries)) { + act_client_addr = std::get<0>(*entry); + last_updated = std::get<2>(*entry); + } + + ok( + exp_client_addr == act_client_addr && last_updated > pre_command_time, + "Entry should be present and updated with the following values -" + " exp('%s', %ld > %ld), act('%s', %ld > %ld)", exp_client_addr.c_str(), + last_updated, pre_command_time, act_client_addr.c_str(), last_updated, + pre_command_time + ); + + // Oldest member shouldn't be present + std::string oldest_member { "127.0.0.2" }; + + auto oldest_entry = std::find_if( + std::begin(updated_entries), + std::end(updated_entries), + [&] (const host_cache_entry& entry) -> bool { + return std::get<0>(entry) == oldest_member; + } + ); + + ok( + oldest_entry == std::end(updated_entries), + "Oldest entry '%s' shouldn't be present in the cache.", oldest_member.c_str() + ); + + printf("\n"); + diag("2. Checking that the same behavior is observed if connection comes from a non-cached address"); + + const std::string new_member { "127.0.0.5" }; + + inv_user_err = invalid_proxysql_conn(new_member, cl); + diag("Client connection failed with error: %d", inv_user_err); + + diag("2.1 Checking that the address hasn't been added"); + + updated_entries = get_client_host_cache_entries(proxysql_admin); + auto new_entry = std::find_if( + std::begin(updated_entries), std::end(updated_entries), + [&] (const host_cache_entry& entry) -> bool { + return std::get<0>(entry) == new_member; + } + ); + + ok( + new_entry == std::end(updated_entries), + "New entry from address '127.0.0.5' shouldn't be present in the cache" + ); + + diag("2.1 Checking that the oldest address has been removed"); + oldest_member = "127.0.0.3"; + + oldest_entry = std::find_if( + std::begin(updated_entries), + std::end(updated_entries), + [&] (const host_cache_entry& entry) -> bool { + return std::get<0>(entry) == oldest_member; + } + ); + + ok( + oldest_entry == std::end(updated_entries), + "Oldest entry '%s' shouldn't be present in the cache.", oldest_member.c_str() + ); + + diag("2.2 Checking that a successful connection gets a client removed"); + + const std::string forgotten_address { "127.0.0.4" }; + std::string err_msg {}; + int valid_conn_err = valid_proxysql_conn(forgotten_address, cl, err_msg); + if (valid_conn_err) { + diag("Failed to execute 'valid_proxysql_conn' at ('%s':'%d')", __FILE__, __LINE__); + } + + updated_entries = get_client_host_cache_entries(proxysql_admin); + auto forgot_entry = std::find_if( + std::begin(updated_entries), std::end(updated_entries), + [&] (const host_cache_entry& entry) -> bool { + return std::get<0>(entry) == forgotten_address; + } + ); + + ok( + forgot_entry == std::end(updated_entries), + "Entry '%s' should have been forgotten due to successful connection.", + forgotten_address.c_str() + ); + } + + return EXIT_SUCCESS; +} + +int create_tcp_conn(const CommandLine& cl, const std::string& addr) { + int sock = 0; + struct sockaddr_in serv_addr; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + diag("_socket creation error"); + return EXIT_FAILURE; + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(cl.port); + + if(inet_pton(AF_INET, addr.c_str(), &serv_addr.sin_addr)<=0) { + diag("Invalid address/ Address not supported"); + return EXIT_FAILURE; + } + + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + diag("Connection Failed"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/** + * @brief Checks that client connections timeouts interact with the cache in the same way + * as regular client connection errors. + * @param cl CommandLine info for connection creation. + * @param proxysql_admin An already oppened connection to ProxySQL Admin. + * + * @return 'EXIT_SUCCESS' in case of success, 'EXIT_FAILURE' otherwise. + */ +int test_cache_populated_timeout_conns(const CommandLine& cl, MYSQL* proxysql_admin) { + printf("\n"); + diag(" START TEST NUMBER 7 "); + diag("-------------------------------------------------------------"); + + // Increase cache size + printf("\n"); + diag("Setting the value of 'mysql-client_host_cache_size' to '0'"); + + // Disable the cache + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=0"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=0"); + + // Decrease client connection timeout + const int client_timeout = 1000; + std::string set_connect_timeout_client { "SET mysql-connect_timeout_client=" + std::to_string(client_timeout) }; + + MYSQL_QUERY(proxysql_admin, set_connect_timeout_client.c_str()); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // There shouldn't be any other entries in the cache for the start of this test. + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + + int errors = 0; + + // Create some connections timeout + printf("\n"); + diag("Performing timeout connections to fill 'client_host_cache'"); + std::vector sockets {}; + for (int i = 2; i < NUM_LOOPBACK_ADDRS; i++) { + std::string loopback_addr { "127.0.0." + std::to_string(i) }; + int inv_user_errno = create_tcp_conn(cl, loopback_addr); + diag("Client connection failed with error: %d", inv_user_errno); + } + sleep((client_timeout / 1000) * 2 + 1); + + std::vector updated_entries = get_client_host_cache_entries(proxysql_admin); + ok(updated_entries.size() == 0, "Entries should be empty because cache was disabled."); + + diag("Setting the value of 'mysql-client_host_cache_size' to '10'"); + // Disable the cache + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=10"); + MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=10"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); + + uint64_t pre_command_time = monotonic_time(); + + for (int i = 2; i < NUM_LOOPBACK_ADDRS; i++) { + std::string loopback_addr { "127.0.0." + std::to_string(i) }; + for (errors = 0; errors < 2; errors++) { + int inv_user_errno = create_tcp_conn(cl, loopback_addr); + diag("Client connection timeout out with error: %d", inv_user_errno); + } + } + sleep((client_timeout / 1000) * 2 + 1); + + updated_entries = get_client_host_cache_entries(proxysql_admin); + ok(updated_entries.size() == 3, "Cache should hold three entries for the timed out connections."); + + int entry_num = 0; + for (const auto& entry : updated_entries) { + const std::string client_addr { std::get<0>(entry) }; + const uint32_t error_count { std::get<1>(entry) }; + const uint64_t last_updated { std::get<2>(entry) }; + uint64_t post_command_time = monotonic_time(); + + std::string exp_client_addr { "127.0.0." + std::to_string(entry_num + 2) }; + + ok( + client_addr == exp_client_addr && error_count == errors && + (pre_command_time < last_updated < post_command_time), + "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," + " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", + exp_client_addr.c_str(), errors, pre_command_time, last_updated, post_command_time, + client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time + ); + + entry_num += 1; + } + + + return EXIT_SUCCESS; +} + +int main(int, char**) { + int res = 0; + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + plan(30); + + MYSQL* proxysql_admin = mysql_init(NULL); + + // Initialize connections + if (!proxysql_admin) { + fprintf( + stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, + mysql_error(proxysql_admin) + ); + return -1; + } + + // Connect to ProxySQL Admin + 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 -1; + } + + // Setup the virtual namespaces to be used by the test + diag(" Setting up extra loopback addresses "); + diag("*********************************************************************"); + printf("\n"); + + int setup_ns_i = 0; + const std::string t_create_loopback_addr { "sudo ip addr add 127.0.0.%d dev lo" }; + const std::string t_delete_loopback_addr { "sudo ip addr delete 127.0.0.%d/32 dev lo" }; + + for (setup_ns_i = 2; setup_ns_i < NUM_LOOPBACK_ADDRS; setup_ns_i++) { + std::string create_loopback_addr {}; + string_format(t_create_loopback_addr, create_loopback_addr, setup_ns_i); + + int c_err = system(create_loopback_addr.c_str()); + if (c_err) { + diag( + "Failed to create netns number '%d' with err: '%d'", + setup_ns_i, c_err + ); + goto cleanup; + } + } + + // Create two extra loopback addresses for testing 'connection_timeout' + for (; setup_ns_i < NUM_LOOPBACK_ADDRS + 2; setup_ns_i++) { + std::string create_ns_command {}; + string_format(t_create_loopback_addr, create_ns_command, setup_ns_i); + + int c_err = system(create_ns_command.c_str()); + if (c_err) { + diag( + "Failed to create netns number '%d' with err: '%d'", + setup_ns_i, c_err + ); + goto cleanup; + } + } + + printf("\n"); + diag("*********************************************************************"); + printf("\n"); + + diag(" Performing queries and checks "); + diag("*********************************************************************"); + printf("\n"); + + + test_cache_filled_by_invalid_conn(cl, proxysql_admin); + test_cache_entry_count_by_invalid_conn(cl, proxysql_admin); + test_cache_entry_count_by_mult_invalid_conns(cl, proxysql_admin); + test_client_exceeding_cache_error_limit(cl, proxysql_admin); + test_client_exceeding_changed_error_limit(cl, proxysql_admin); + test_cache_size_decrease_by_new_connections(cl, proxysql_admin); + test_cache_populated_timeout_conns(cl, proxysql_admin); + +cleanup: + // Cleanup the virtual namespaces to be used by the test + printf("\n"); + diag(" Cleanup of testing network namespaces "); + diag("*********************************************************************"); + printf("\n"); + + for (int i = 2; i < setup_ns_i; i++) { + std::string delete_loopback_addr {}; + string_format(t_delete_loopback_addr, delete_loopback_addr, i); + system(delete_loopback_addr.c_str()); + } + + mysql_close(proxysql_admin); + + return exit_status(); +} diff --git a/test/tap/tests/test_client_limit_error.cpp b/test/tap/tests/test_client_limit_error.cpp deleted file mode 100644 index ace8bdd76d..0000000000 --- a/test/tap/tests/test_client_limit_error.cpp +++ /dev/null @@ -1,776 +0,0 @@ -/** - * @file test_client_limit_error.cpp - * @brief This test aims to verify the logic for the 'client_limit_error' feature. - * NOTE: This test isn't executed automatically because it requires elevated privileges - * for being able to run the scripts 'create_netns_n' and 'delete_netns_n' that creates - * and delete the networks namespaces required for it. - * - * @details Right now the test verifies the following cases: - * 1. Enable the feature and checks that the error count is incremented when a single - * client tries to connect and that the cache entry values match expected ones. - * 2. Flush the entries, and check that counting is performed properly for a - * single cache entry. - * 3. Flush the entries, and check that counting is performed properly for - * multiple cache entries. - * 4. Flush the entries, and check: - * 1. That counting is performed properly for multiple cache entries. - * 2. Connections fail after the limit for one client. - * 3. Clients are deleted after a succesfull connection is performed. - * 5. Flush the entries, fill the cache and check that when the - * 'mysql-client_host_error_counts' is changed at runtime, connections are denied - * to a client exceeding the new limit. - * 6. Flush the entries, fill the cache and check that the when the - * 'mysql-client_host_cache_size' is changed at runtime: - * 1. The exceeding elements are cleaned with each new connection. - * 2. Check that is not relevant if the element was or not present - * in the cache. - * 3. Checks that a proper connection is performed succesfully and the element is - * removed from the cache - * - * @date 2021-09-10 - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "tap.h" -#include "command_line.h" -#include "utils.h" - -const uint32_t NUM_NETWORK_NAMESPACES = 5; - -using host_cache_entry = std::tuple; - -inline unsigned long long monotonic_time() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (((unsigned long long) ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000); -} - -std::vector get_client_host_cache_entries(MYSQL* proxysql_admin) { - int rc = mysql_query( - proxysql_admin, - "SELECT * FROM stats.stats_mysql_client_host_cache ORDER BY client_address" - ); - - MYSQL_ROW row = NULL; - MYSQL_RES* proxy_res = mysql_store_result(proxysql_admin); - std::vector host_cache_entries {}; - - while ((row = mysql_fetch_row(proxy_res))) { - std::string client_address = row[0]; - uint32_t error_count = atoi(row[1]); - uint64_t last_update = atoll(row[2]); - - host_cache_entries.push_back({client_address, error_count, last_update}); - } - - mysql_free_result(proxy_res); - - return host_cache_entries; -} - -int main(int, char**) { - int res = 0; - CommandLine cl; - - if (cl.getEnv()) { - diag("Failed to get the required environmental variables."); - return -1; - } - - plan(27); - - MYSQL* proxysql_admin = mysql_init(NULL); - - // Initialize connections - if (!proxysql_admin) { - fprintf( - stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, - mysql_error(proxysql_admin) - ); - return -1; - } - - // Connect to ProxySQL Admin - 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 -1; - } - - // Setup the virtual namespaces to be used by the test - diag(" Setting up testing network namespaces "); - diag("*********************************************************************"); - printf("\n"); - - int setup_ns_i = 0; - const std::string t_create_ns_command = - std::string { cl.workdir } + "/client_host_err/create_netns_n.sh %d"; - for (setup_ns_i = 1; setup_ns_i < NUM_NETWORK_NAMESPACES; setup_ns_i++) { - std::string create_ns_command {}; - string_format(t_create_ns_command, create_ns_command, setup_ns_i); - - int c_err = system(create_ns_command.c_str()); - if (c_err) { - diag( - "Failed to create netns number '%d' with err: '%d'", - setup_ns_i, c_err - ); - goto cleanup; - } - } - - printf("\n"); - diag("*********************************************************************"); - printf("\n"); - - diag(" Performing queries and checks "); - diag("*********************************************************************"); - printf("\n"); - - { - const std::string t_inv_user_command = - "ip netns exec ns%d mysql -h10.200.%d.1 -uinv_user -pinv_pass -P6033"; - const std::string t_valid_connection_command { - "ip netns exec ns%d mysql -h10.200.%d.1 -uroot -proot -P6033 -e'DO 1' 2>&1" - }; - - // 1. Enable the feature check that error count is incremented when a - // new client fails to connect, and that the cache entry values are the - // expected ones. - { - printf("\n"); - diag(" START TEST NUMBER 1 "); - diag("-------------------------------------------------------------"); - - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=1"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, 1, 1); - uint64_t pre_command_time = monotonic_time(); - - diag("Performing connections to fill 'client_host_cache'"); - - printf("\n"); - int inv_user_errno = system(inv_user_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - printf("\n"); - - diag("Performing checks over 'client_host_cache'"); - - std::vector entries = - get_client_host_cache_entries(proxysql_admin); - - ok( - entries.size() == 1, - "'client_host_cache' entries should be '1' after issuing 'PROXYSQL FLUSH" - " MYSQL CLIENT HOSTS' and one failed connection." - ); - - if (entries.size() == 1) { - host_cache_entry unique_entry { entries.back() }; - const std::string client_addr { std::get<0>(unique_entry) }; - const uint32_t error_count { std::get<1>(unique_entry) }; - const uint64_t last_updated { std::get<2>(unique_entry) }; - uint64_t post_command_time = monotonic_time(); - - ok( - client_addr == "10.200.1.2" && error_count == 1 && - (pre_command_time < last_updated < post_command_time), - "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," - " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", - "10.200.1.2", 1, pre_command_time, last_updated, post_command_time, - client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time - ); - } - } - - // 2. Flush the entries, and check that counting is performed properly for a - // single cache entry. - { - printf("\n"); - diag(" START TEST NUMBER 2 "); - diag("-------------------------------------------------------------"); - - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - int errors = 0; - - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, 1, 1); - uint64_t pre_command_time = monotonic_time(); - - printf("\n"); - diag("Performing connections to fill 'client_host_cache'"); - for (errors = 0; errors < 5; errors++) { - printf("\n"); - int inv_user_errno = system(inv_user_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - } - printf("\n"); - - diag("Performing checks over 'client_host_cache'"); - - std::vector entries = - get_client_host_cache_entries(proxysql_admin); - - ok( - entries.size() == 1, - "'client_host_cache' entries should be '1' after issuing 'PROXYSQL FLUSH" - " MYSQL CLIENT HOSTS' and one failed connection." - ); - - if (entries.size() == 1) { - host_cache_entry unique_entry { entries.back() }; - const std::string client_addr { std::get<0>(unique_entry) }; - const uint32_t error_count { std::get<1>(unique_entry) }; - const uint64_t last_updated { std::get<2>(unique_entry) }; - uint64_t post_command_time = monotonic_time(); - - ok( - client_addr == "10.200.1.2" && error_count == errors && - (pre_command_time < last_updated < post_command_time), - "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," - " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", - "10.200.1.2", 1, pre_command_time, last_updated, post_command_time, - client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time - ); - } - } - - // 3. Flush the entries, and check that counting is performed properly for - // multiple cache entries. - { - printf("\n"); - diag(" START TEST NUMBER 3 "); - diag("-------------------------------------------------------------"); - - // Increase cache size - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - int errors = 0; - - // Prepare several commands from different network namespaces - std::vector inv_connection_commands {}; - for (int i = 1; i < NUM_NETWORK_NAMESPACES; i++) { - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, i, i); - inv_connection_commands.push_back(inv_user_command); - } - - uint64_t pre_command_time = monotonic_time(); - - printf("\n"); - diag("Performing connections to fill 'client_host_cache'"); - for (const auto inv_conn_command : inv_connection_commands) { - for (errors = 0; errors < 2; errors++) { - printf("\n"); - int inv_user_errno = system(inv_conn_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - } - printf("\n"); - } - - diag("Performing checks over 'client_host_cache'"); - - std::vector entries = - get_client_host_cache_entries(proxysql_admin); - - ok( - entries.size() == NUM_NETWORK_NAMESPACES - 1, - "'client_host_cache' entries should be 'NUM_NETWORK_NAMESPACES' after issuing 'PROXYSQL FLUSH" - " MYSQL CLIENT HOSTS' and 'NUM_NETWORK_NAMESPACES' failed connections. Entries: '%ld'", - entries.size() - ); - - if (entries.size() == NUM_NETWORK_NAMESPACES - 1) { - uint32_t entry_num = 1; - - for (const auto& entry : entries) { - const std::string client_addr { std::get<0>(entry) }; - const uint32_t error_count { std::get<1>(entry) }; - const uint64_t last_updated { std::get<2>(entry) }; - uint64_t post_command_time = monotonic_time(); - - std::string t_exp_client_addr { "10.200.%d.2" }; - std::string exp_client_addr {}; - string_format(t_exp_client_addr, exp_client_addr, entry_num); - - ok( - client_addr == exp_client_addr && error_count == errors && - (pre_command_time < last_updated < post_command_time), - "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," - " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", - exp_client_addr.c_str(), errors, pre_command_time, last_updated, post_command_time, - client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time - ); - - entry_num += 1; - } - } - } - - // 4. Flush the entries, and check: - // 1. That counting is performed properly for multiple cache entries. - // 2. Connections fail after the limit for one client. - // 3. Clients are deleted after a succesfull connection is performed. - { - printf("\n"); - diag(" START TEST NUMBER 4 "); - diag("-------------------------------------------------------------"); - - // Increase cache size - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - int errors = 0; - - // Prepare several commands from different network namespaces - std::vector inv_connection_commands {}; - for (int i = 1; i < NUM_NETWORK_NAMESPACES; i++) { - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, i, i); - inv_connection_commands.push_back(inv_user_command); - } - - uint64_t pre_command_time = monotonic_time(); - - printf("\n"); - diag("Performing connections to fill 'client_host_cache'"); - for (const auto inv_conn_command : inv_connection_commands) { - for (errors = 0; errors < 3; errors++) { - printf("\n"); - int inv_user_errno = system(inv_conn_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - } - printf("\n"); - } - - printf("\n"); - diag("1. Check that counting is perfomred properly over multiple 'client_host_cache'"); - - std::vector entries = - get_client_host_cache_entries(proxysql_admin); - - ok( - entries.size() == NUM_NETWORK_NAMESPACES - 1, - "'client_host_cache' entries should be 'NUM_NETWORK_NAMESPACES' after issuing 'PROXYSQL FLUSH" - " MYSQL CLIENT HOSTS' and 'NUM_NETWORK_NAMESPACES' failed connections. Entries: '%ld'", - entries.size() - ); - - if (entries.size() == NUM_NETWORK_NAMESPACES - 1) { - uint32_t entry_num = 1; - - for (const auto& entry : entries) { - const std::string client_addr { std::get<0>(entry) }; - const uint32_t error_count { std::get<1>(entry) }; - const uint64_t last_updated { std::get<2>(entry) }; - uint64_t post_command_time = monotonic_time(); - - std::string t_exp_client_addr { "10.200.%d.2" }; - std::string exp_client_addr {}; - string_format(t_exp_client_addr, exp_client_addr, entry_num); - - ok( - client_addr == exp_client_addr && error_count == errors && - (pre_command_time < last_updated < post_command_time), - "Entry should match expected values - exp(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)," - " act(addr: %s, err_count: %d, last_updated: %ld < %ld < %ld)", - exp_client_addr.c_str(), errors, pre_command_time, last_updated, post_command_time, - client_addr.c_str(), error_count, pre_command_time, last_updated, post_command_time - ); - - entry_num += 1; - } - } - - printf("\n"); - diag("Performing connections to fill 'client_host_cache'"); - - uint32_t expected_ns = 4; - const std::string expected_address { "10.200.4.2" }; - uint32_t expected_errors = 5; - - int limits = errors; - std::string inv_user_command_limit {}; - const std::string t_stderr_inv_user_command { - "ip netns exec ns%d mysql -h10.200.%d.1 -uinv_user -pinv_pass -P6033 2>&1" - }; - string_format(t_stderr_inv_user_command, inv_user_command_limit, 4, 4); - - std::string command_res {}; - for (int limits = errors; limits < 5 + 1; limits++) { - printf("\n"); - int inv_user_limit_err = exec(inv_user_command_limit.c_str(), command_res); - diag("Client connection failed with error: (%d, %s)", inv_user_limit_err, command_res.c_str()); - } - printf("\n"); - - diag("2. Checking the connection is denied when the limit is reached."); - - auto limit_error = command_res.find("ERROR 2013 (HY000)"); - ok( - limit_error != std::string::npos, - "Last connection should fail with 'ERROR 2013', it exceeded the error limit. ErrMsg: '%s'", - command_res.c_str() - ); - - std::vector new_entries { - get_client_host_cache_entries(proxysql_admin) - }; - - auto cache_entry = - std::find_if( - std::begin(new_entries), - std::end(new_entries), - [&] (const host_cache_entry& elem) -> bool { - return std::get<0>(elem) == expected_address; - } - ); - - bool found_exp_values = false; - std::string client_address {}; - uint32_t found_errors = 0; - - if (cache_entry != std::end(new_entries)) { - client_address = std::get<0>(*cache_entry); - found_errors = std::get<1>(*cache_entry); - - found_exp_values = - std::get<0>(*cache_entry) == expected_address && - std::get<1>(*cache_entry) == expected_errors; - } - - ok( - found_exp_values, - "Entry should match expected values - exp(addr: %s, err_count: %d), act(addr: %s, err_count: %d)", - expected_address.c_str(), expected_errors, client_address.c_str(), found_errors - ); - - diag("3. Check that clients are deleted from the cache when the connections are succesfully performed"); - - for (int i = 1; i < NUM_NETWORK_NAMESPACES; i++) { - // This client as exceeded the max failures - - std::string valid_connection_command {}; - string_format(t_valid_connection_command, valid_connection_command, i, i); - - // Client has exceeded maximum connections failure is expected - if (i == 4) { - std::string command_res {}; - exec(valid_connection_command, command_res); - auto limit_error = command_res.find("ERROR 2013 (HY000)"); - - ok( - limit_error != std::string::npos, - "Connection should fail due to limit exceeded. ErrMsg: '%s'", command_res.c_str() - ); - } else { - int command_err = system(valid_connection_command.c_str()); - ok( - command_err == 0, - "Connection should succeed for clients which limit haven't been exceeded." - ); - } - } - - new_entries = get_client_host_cache_entries(proxysql_admin); - ok( - new_entries.size() == 1 && - std::get<0>(new_entries.back()) == "10.200.4.2", - "Only client address exceeding the limit should remain in the cache -" - " exp('10.200.4.2'), act('%s')", std::get<0>(new_entries.back()).c_str() - ); - } - - // 5. Flush the entries, fill the cache and check that the when the - // 'mysql-client_host_error_counts' is changed at runtime, connections are denied - // to a client exceeding the new limit. - { - printf("\n"); - diag(" START TEST NUMBER 5 "); - diag("-------------------------------------------------------------"); - - // Increase cache size - printf("\n"); - diag("Setting the value of 'mysql-client_host_cache_size' to '5'"); - - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - int errors = 0; - - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, 1, 1); - uint64_t pre_command_time = monotonic_time(); - - diag("Performing connections to fill 'client_host_cache'"); - - for (int i = 0; i < 4; i++) { - printf("\n"); - int inv_user_errno = system(inv_user_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - printf("\n"); - } - - diag("Decreasing the value of 'mysql-client_host_error_counts' to '3'"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=3"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - { - printf("\n"); - std::string valid_user_command {}; - string_format(t_valid_connection_command, valid_user_command, 1, 1); - - std::string command_res {}; - int valid_user_err = exec(valid_user_command.c_str(), command_res); - diag("Client connection failed with error: (%d, %s)", valid_user_err, command_res.c_str()); - - auto limit_error = command_res.find("ERROR 2013 (HY000)"); - ok( - limit_error != std::string::npos, - "Last connection should fail with 'ERROR 2013', it exceeded the error limit. ErrMsg: '%s'", - command_res.c_str() - ); - } - } - - // 6. Flush the entries, fill the cache and check that the when the - // 'mysql-client_host_cache_size' is changed at runtime, the exceeding - // elements are cleaned with each new connection. Without being relevant if was - // present or not in the cache. - { - printf("\n"); - diag(" START TEST NUMBER 6 "); - diag("-------------------------------------------------------------"); - - // Increase cache size - printf("\n"); - diag("Setting the value of 'mysql-client_host_cache_size' to '5'"); - - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=5"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_error_counts=5"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // There shouldn't be any other entries in the cache for this test. - MYSQL_QUERY(proxysql_admin, "PROXYSQL FLUSH MYSQL CLIENT HOSTS"); - int errors = 0; - - // Fill the cache with the entries from all the created namespaces - { - std::vector inv_connection_commands {}; - for (int i = 1; i < NUM_NETWORK_NAMESPACES; i++) { - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, i, i); - inv_connection_commands.push_back(inv_user_command); - } - - printf("\n"); - diag("Performing connections to fill 'client_host_cache'"); - for (const auto inv_conn_command : inv_connection_commands) { - for (errors = 0; errors < 3; errors++) { - printf("\n"); - int inv_user_errno = system(inv_conn_command.c_str()); - diag("Client connection failed with error: %d", inv_user_errno); - } - printf("\n"); - } - printf("\n"); - } - - diag("Decreasing the value of 'mysql-client_host_cache_size' to '3'"); - MYSQL_QUERY(proxysql_admin, "SET mysql-client_host_cache_size=1"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - // Update the latest entry in the cache, oldest member "10.200.1.2" should go away. - { - uint64_t pre_command_time = monotonic_time(); - - diag("1. Checking that the connection updates the entry and the oldest entry is removed"); - - std::string inv_user_command {}; - string_format(t_inv_user_command, inv_user_command, 4, 4); - - printf("\n"); - int inv_user_err = system(inv_user_command.c_str()); - diag("Client connection failed with error: %d", inv_user_err); - printf("\n"); - - std::vector updated_entries { - get_client_host_cache_entries(proxysql_admin) - }; - - std::string exp_client_addr { "10.200.4.2" }; - - auto entry = std::find_if( - std::begin(updated_entries), - std::end(updated_entries), - [&] (const host_cache_entry& entry) -> bool { - return std::get<0>(entry) == exp_client_addr; - } - ); - - std::string act_client_addr {}; - uint64_t last_updated = 0; - - if (entry != std::end(updated_entries)) { - act_client_addr = std::get<0>(*entry); - last_updated = std::get<2>(*entry); - } - - ok( - exp_client_addr == act_client_addr && last_updated > pre_command_time, - "Entry should be present and updated with the following values -" - " exp('%s', %ld > %ld), act('%s', %ld > %ld)", exp_client_addr.c_str(), - last_updated, pre_command_time, act_client_addr.c_str(), last_updated, - pre_command_time - ); - - // Oldest member shouldn't be present - std::string oldest_member { "10.200.1.2" }; - - auto oldest_entry = std::find_if( - std::begin(updated_entries), - std::end(updated_entries), - [&] (const host_cache_entry& entry) -> bool { - return std::get<0>(entry) == oldest_member; - } - ); - - ok( - oldest_entry == std::end(updated_entries), - "Oldest entry '%s' shouldn't be present in the cache.", oldest_member.c_str() - ); - - printf("\n"); - diag("2. Checking that the same behavior is observed if connection comes from a non-cached address"); - - string_format(t_inv_user_command, inv_user_command, 1, 1); - - printf("\n"); - inv_user_err = system(inv_user_command.c_str()); - diag("Client connection failed with error: %d", inv_user_err); - printf("\n"); - - diag("2.1 Checking that the address hasn't been added"); - - const std::string new_member { "10.200.1.2" }; - - updated_entries = get_client_host_cache_entries(proxysql_admin); - auto new_entry = std::find_if( - std::begin(updated_entries), - std::end(updated_entries), - [&] (const host_cache_entry& entry) -> bool { - return std::get<0>(entry) == new_member; - } - ); - - ok( - new_entry == std::end(updated_entries), - "New entry from address '10.200.1.2' shouldn't be present in the cache" - ); - - printf("\n"); - diag("2.1 Checking that the oldest address has been removed"); - oldest_member = "10.200.2.2"; - - oldest_entry = std::find_if( - std::begin(updated_entries), - std::end(updated_entries), - [&] (const host_cache_entry& entry) -> bool { - return std::get<0>(entry) == oldest_member; - } - ); - - ok( - oldest_entry == std::end(updated_entries), - "Oldest entry '%s' shouldn't be present in the cache.", oldest_member.c_str() - ); - - printf("\n"); - diag("2.2 Checking that a successful connection gets a client removed"); - printf("\n"); - - std::string valid_connection_command {}; - string_format(t_valid_connection_command, valid_connection_command, 3, 3); - system(valid_connection_command.c_str()); - - const std::string forgotten_address { "10.200.3.2" }; - - updated_entries = get_client_host_cache_entries(proxysql_admin); - auto forgot_entry = std::find_if( - std::begin(updated_entries), - std::end(updated_entries), - [&] (const host_cache_entry& entry) -> bool { - return std::get<0>(entry) == forgotten_address; - } - ); - - ok( - forgot_entry == std::end(updated_entries), - "Entry '%s' should have been forgotten due to successful connection.", - forgotten_address.c_str() - ); - } - } - } - -cleanup: - // Cleanup the virtual namespaces to be used by the test - printf("\n"); - diag(" Cleanup of testing network namespaces "); - diag("*********************************************************************"); - printf("\n"); - - const std::string t_delete_ns_command = - std::string { cl.workdir } + "/client_host_err/delete_netns_n.sh %d"; - for (int i = 1; i < setup_ns_i; i++) { - std::string delete_ns_command {}; - string_format(t_delete_ns_command, delete_ns_command, i); - system(delete_ns_command.c_str()); - } - - mysql_close(proxysql_admin); - - return exit_status(); -} diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp index cf1f7bcd2a..f121500820 100644 --- a/test/tap/tests/test_cluster_sync-t.cpp +++ b/test/tap/tests/test_cluster_sync-t.cpp @@ -168,8 +168,9 @@ int main(int, char**) { const std::string stats_db = std::string(cl.workdir) + "test_cluster_sync_config/proxysql_stats.db"; const std::string docker_command = - std::string("docker run -p 16032:6032 ") + "-v " + std::string(cl.workdir) + "../../../:/tmp/proxysql" - " ubuntu:19.10 sh -c \"./tmp/proxysql/src/proxysql -f -M -c /tmp/proxysql/test/tap/tests/test_cluster_sync_config/test_cluster_sync.cnf\" " + + std::string("docker run -p 16032:6032 -e ASAN_OPTIONS=abort_on_error=0:halt_on_error=0:fast_unwind_on_fatal=1:detect_leaks=0 ") + + "-v " + std::string(cl.workdir) + "../../../:/tmp/proxysql ubuntu:20.04 sh -c" + + " \"./tmp/proxysql/src/proxysql -f -M -c /tmp/proxysql/test/tap/tests/test_cluster_sync_config/test_cluster_sync.cnf\" " + std::string("> ") + cluster_sync_node_stderr + " 2>&1"; int exec_res = system(docker_command.c_str()); @@ -1267,11 +1268,13 @@ int main(int, char**) { mysql_options(proxysql_replica, MYSQL_OPT_WRITE_TIMEOUT, &mysql_timeout); mysql_query(proxysql_replica, "PROXYSQL SHUTDOWN"); proxy_replica_th.join(); + mysql_close(proxysql_replica); remove(fmt_config_file.c_str()); MYSQL_QUERY(proxysql_admin, "DELETE FROM proxysql_servers"); MYSQL_QUERY(proxysql_admin, "LOAD PROXYSQL SERVERS TO RUNTIME"); + mysql_close(proxysql_admin); return exit_status(); } diff --git a/test/tap/tests/test_connection_annotation-t.cpp b/test/tap/tests/test_connection_annotation-t.cpp index 7c8e6a78be..662f9f0ae4 100644 --- a/test/tap/tests/test_connection_annotation-t.cpp +++ b/test/tap/tests/test_connection_annotation-t.cpp @@ -71,7 +71,7 @@ int main(int argc, char** argv) { srand(time(NULL)); int rand_conn = rand() % 100; for (int i = 0; i < rand_conn; i++) { - MYSQL_QUERY(proxysql_mysql, "SELECT /*+ ;create_new_connection=1 */ 1"); + MYSQL_QUERY(proxysql_mysql, "SELECT /* ;create_new_connection=1 */ 1"); proxy_res = mysql_store_result(proxysql_mysql); mysql_free_result(proxy_res); } diff --git a/test/tap/tests/test_keep_multiplexing_variables-t.cpp b/test/tap/tests/test_keep_multiplexing_variables-t.cpp new file mode 100644 index 0000000000..2ed9285493 --- /dev/null +++ b/test/tap/tests/test_keep_multiplexing_variables-t.cpp @@ -0,0 +1,157 @@ +/** + * @file test_keep_multiplexing_variables-t.cpp + * @brief This test checks that selecting on '@@session.*' and '@@*' variables disables multiplexing when + * target variables are not specified by 'mysql-keep_multiplexing_variables'. + * @date 2021-09-30 + */ + +#include +#include +#include +#include + +#include "tap.h" +#include "command_line.h" +#include "utils.h" +#include "json.hpp" + +using std::string; +using namespace nlohmann; + +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]); + } +} + +std::vector select_queries { + "select @@session.autocommit, @@session.big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@autocommit, @@sql_mode, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@session.transaction_isolation, @@version,@@transaction_isolation", + "select @@autocommit, @@sql_mode, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@session.transaction_isolation, @@version,@@transaction_isolation", + "select @@autocommit, @@sql_mode, @@big_tables, @@autocommit,@@session.bulk_insert_buffer_size, @@character_set_database,@@session.transaction_isolation, @@version,@@transaction_isolation", + "select @@sql_mode, @@autocommit, @@big_tables, @@autocommit, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@sql_mode, @@autocommit, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@sql_mode, @@autocommit, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@session.autocommit, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@big_tables, @@session.autocommit, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", + "select @@session.autocommit, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database, @@version,@@session.transaction_isolation", + "select @@session.autocommit, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", +}; + +int check_multiplexing_disabled(const CommandLine& cl, const std::string query, bool& multiplex_disabled) { + MYSQL* proxysql_mysql = mysql_init(NULL); + + if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); + return EXIT_FAILURE; + } + + MYSQL_QUERY(proxysql_mysql, query.c_str()); + MYSQL_RES* dummy_res = mysql_store_result(proxysql_mysql); + mysql_free_result(dummy_res); + + 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); + + if (j_status.contains("backends")) { + for (auto& backend : j_status["backends"]) { + if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { + multiplex_disabled = backend["conn"]["MultiplexDisabled"]; + } + } + } + + mysql_close(proxysql_mysql); + + return EXIT_SUCCESS; +} + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan(26); + + MYSQL* proxysql_admin = mysql_init(NULL); + + 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; + } + + // Clean the 'keep_multiplexing_variables' + MYSQL_QUERY(proxysql_admin, "SET mysql-keep_multiplexing_variables='version'"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + diag("Cleaning 'mysql-keep_multiplexing_variables' to check multiplexing disabling."); + + // Check that any query will disable multiplexing + { + bool disabled_multiplexing = false; + int check_multiplexing_err = check_multiplexing_disabled(cl, "SELECT @@sql_mode", disabled_multiplexing); + ok (disabled_multiplexing == true, "Simple 'SELECT @@*' should disable multiplexing."); + } + + { + bool disabled_multiplexing = false; + int check_multiplexing_err = check_multiplexing_disabled(cl, "SELECT @@SESSION.sql_mode", disabled_multiplexing); + ok (disabled_multiplexing == true, "Simple 'SELECT @@SESSION.*' should disable multiplexing."); + } + + // Adding the variable to 'keep_multiplexing_variables' should keep multiplexing enabled + MYSQL_QUERY(proxysql_admin, "SET mysql-keep_multiplexing_variables='version,sql_mode'"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + diag("Setting 'mysql-keep_multiplexing_variables' to keep multiplexing enabled."); + + // Check that any query will disable multiplexing + { + bool disabled_multiplexing = false; + int check_multiplexing_err = check_multiplexing_disabled(cl, "SELECT @@sql_mode", disabled_multiplexing); + ok (disabled_multiplexing == false, "Simple 'SELECT @@*' should keep multiplexing enabled."); + } + + { + bool disabled_multiplexing = false; + int check_multiplexing_err = check_multiplexing_disabled(cl, "SELECT @@SESSION.sql_mode", disabled_multiplexing); + ok (disabled_multiplexing == false, "Simple 'SELECT @@SESSION.*' should keep multiplexing enabled."); + } + + // Clean the 'keep_multiplexing_variables' + MYSQL_QUERY(proxysql_admin, "SET mysql-keep_multiplexing_variables='version'"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + diag("Cleaning 'mysql-keep_multiplexing_variables' to check multiplexing disabling."); + + { + for (const std::string& query : select_queries) { + bool disabled_multiplexing = true; + int check_multiplexing_err = check_multiplexing_disabled(cl, query, disabled_multiplexing); + ok (disabled_multiplexing == true, "Complex 'SELECT @@SESSION.*, @@*' should disable multiplexing."); + } + } + + // Adding multiple variables to 'keep_multiplexing_variables' should keep multiplexing enabled + MYSQL_QUERY(proxysql_admin, "SET mysql-keep_multiplexing_variables='version,sql_mode,autocommit,big_tables,bulk_insert_buffer_size,character_set_database,transaction_isolation'"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + diag("Setting 'mysql-keep_multiplexing_variables' to keep multiplexing enabled."); + + { + for (const std::string& query : select_queries) { + bool disabled_multiplexing = false; + int check_multiplexing_err = check_multiplexing_disabled(cl, query, disabled_multiplexing); + ok (disabled_multiplexing == false, "Complex 'SELECT @@SESSION.*, @@*' queries should keep multiplexing enabled."); + } + } + + mysql_close(proxysql_admin); + + return exit_status(); +} diff --git a/test/tap/tests/test_mysqlsh-t.cpp b/test/tap/tests/test_mysqlsh-t.cpp index 98ccaa5c71..e8879f207b 100644 --- a/test/tap/tests/test_mysqlsh-t.cpp +++ b/test/tap/tests/test_mysqlsh-t.cpp @@ -89,8 +89,6 @@ int main(int argc, char** argv) { } else { ok(false, "Invalid resulset. Expected 'num_fields' = 1, not %d", concat_num_fields); } - - mysql_free_result(concat_res); } mysql_free_result(concat_res); diff --git a/test/tap/tests/test_query_rules_routing-t.cpp b/test/tap/tests/test_query_rules_routing-t.cpp index ba5ec6037b..165e08543e 100644 --- a/test/tap/tests/test_query_rules_routing-t.cpp +++ b/test/tap/tests/test_query_rules_routing-t.cpp @@ -80,31 +80,31 @@ std::vector dst_hostgroup_tests { }, { { - "SELECT /*+ ;%s */ 1", + "SELECT /* ;%s */ 1", 1 }, { - "SELECT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id=1", + "SELECT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id=1", 1 }, { - "SELECT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", + "SELECT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", 1 }, { - "SELECT /*+ ;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", + "SELECT /* ;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", 1 }, { - "INSERT /*+ ;%s */ INTO test.reg_test_3427_0 (k) VALUES (2)", + "INSERT /* ;%s */ INTO test.reg_test_3427_0 (k) VALUES (2)", 0 }, { - "UPDATE /*+ ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", + "UPDATE /* ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", 0 }, { - "SELECT DISTINCT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", + "SELECT DISTINCT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", 1 } } @@ -118,23 +118,23 @@ std::vector dst_hostgroup_tests { }, { { - "UPDATE /*+ ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", + "UPDATE /* ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", 0 }, { - "SELECT DISTINCT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", + "SELECT DISTINCT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", 1 }, { - "SELECT /*+ ;%s */ c FROM test.reg_test_3427_1 WHERE id BETWEEN 1 AND 10 ORDER BY c", + "SELECT /* ;%s */ c FROM test.reg_test_3427_1 WHERE id BETWEEN 1 AND 10 ORDER BY c", 0 }, { - "INSERT /*+ ;%s */ INTO test.reg_test_3427_0 (k) VALUES (2)", + "INSERT /* ;%s */ INTO test.reg_test_3427_0 (k) VALUES (2)", 0 }, { - "SELECT DISTINCT /*+ ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", + "SELECT DISTINCT /* ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10 ORDER BY c", 0 }, } @@ -148,31 +148,31 @@ std::vector dst_hostgroup_tests { }, { { - "UPDATE /*+ ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", + "UPDATE /* ;%s */ test.reg_test_3427_0 SET pad=\"random\" WHERE id=2", 0 }, { - "SELECT /*+ ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id=1", + "SELECT /* ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id=1", 0 }, { - "SELECT /*+ ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", + "SELECT /* ;hostgroup=0;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", 0 }, { - "SELECT /*+ ;hostgroup=0;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", + "SELECT /* ;hostgroup=0;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", 0 }, { - "SELECT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id=1", + "SELECT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id=1", 1 }, { - "SELECT /*+ ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", + "SELECT /* ;%s */ c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 20", 1 }, { - "SELECT /*+ ;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", + "SELECT /* ;%s */ SUM(k) c FROM test.reg_test_3427_0 WHERE id BETWEEN 1 AND 10", 1 } } @@ -312,100 +312,6 @@ int create_testing_tables(MYSQL* proxysql, uint32_t num_tables) { return EXIT_SUCCESS; } -const double COLISSION_PROB = 1e-8; - -/** - * @brief Helper function to wait for replication to complete, - * performs a simple supplied queried until it succeed or the - * timeout expires. - * - * @param proxysql A already opened MYSQL connection to ProxySQL. - * @param proxysql_admin A already opened MYSQL connection to ProxySQL Admin interface. - * @param check The query to perform until timeout expires. - * @param timeout The timeout in seconds to retry the query. - * @param reader_hostgroup The current 'reader hostgroup' for which - * servers replication needs to be waited. - * - * @return EXIT_SUCCESS in case of success, EXIT_FAILURE - * otherwise. - */ -int wait_for_replication( - MYSQL* proxysql, - MYSQL* proxysql_admin, - const std::string& check, - uint32_t timeout, - uint32_t read_hostgroup -) { - if (proxysql == NULL) { return EXIT_FAILURE; } - - const std::string t_count_reader_hg_servers { - "SELECT COUNT(*) FROM mysql_servers WHERE hostgroup_id=%d" - }; - std::string count_reader_hg_servers {}; - size_t size = - snprintf( - nullptr, 0, t_count_reader_hg_servers.c_str(), read_hostgroup - ) + 1; - { - std::unique_ptr buf(new char[size]); - snprintf(buf.get(), size, t_count_reader_hg_servers.c_str(), read_hostgroup); - count_reader_hg_servers = std::string(buf.get(), buf.get() + size - 1); - } - - MYSQL_QUERY(proxysql_admin, count_reader_hg_servers.c_str()); - MYSQL_RES* hg_count_res = mysql_store_result(proxysql_admin); - MYSQL_ROW row = mysql_fetch_row(hg_count_res); - uint32_t srv_count = strtoul(row[0], NULL, 10); - mysql_free_result(hg_count_res); - - if (srv_count > UINT_MAX) { - return EXIT_FAILURE; - } - - int waited = 0; - int queries = 0; - int result = EXIT_FAILURE; - - if (srv_count != 0) { - int retries = - ceil( - log10(COLISSION_PROB) / - log10(static_cast(1)/srv_count) - ); - auto start = std::chrono::system_clock::now(); - std::chrono::duration elapsed {}; - - while (elapsed.count() < timeout && queries < retries) { - int rc = mysql_query(proxysql, check.c_str()); - - if (rc == EXIT_SUCCESS) { - MYSQL_RES* st_res = mysql_store_result(proxysql); - if (st_res) { - mysql_free_result(st_res); - } - - queries += 1; - continue; - } else { - queries = 0; - waited += 1; - sleep(1); - } - - auto it_end = std::chrono::system_clock::now(); - elapsed = it_end - start; - } - - if (queries == retries) { - result = EXIT_SUCCESS; - } - } else { - result = EXIT_SUCCESS; - } - - return result; -} - int main(int argc, char** argv) { CommandLine cl; @@ -442,13 +348,10 @@ int main(int argc, char** argv) { int c_table_res = create_testing_tables(proxysql_text, 2); if (c_table_res) { return EXIT_FAILURE; } - int rep_err = wait_for_replication( - proxysql_text, - proxysql_admin, - "SELECT c FROM test.reg_test_3427_0 WHERE id=1", - 10, - 1 - ); + const std::string rep_check_query { + "SELECT CASE WHEN (SELECT COUNT(*) FROM test.reg_test_3427_0 WHERE id=1) = 1 THEN 'TRUE' ELSE 'FALSE' END" + }; + int rep_err = wait_for_replication(proxysql_text, proxysql_admin, rep_check_query, 10, 1); if (rep_err) { fprintf(stderr, "File %s, line %d, Error: %s\n", diff --git a/test/tap/tests/test_set_collation-t.cpp b/test/tap/tests/test_set_collation-t.cpp index 13beabd1c1..b4f7b5de65 100644 --- a/test/tap/tests/test_set_collation-t.cpp +++ b/test/tap/tests/test_set_collation-t.cpp @@ -87,7 +87,7 @@ int query_and_check_session_variables(MYSQL *mysql, std::string collation, int i MYSQL_RES* proxy_res = nullptr; std::string query = ""; if (new_connection) - query += "/*+ ;create_new_connection=1 */ "; + query += "/* ;create_new_connection=1 */ "; query += "SELECT lower(variable_name), variable_value FROM performance_schema.session_variables WHERE"; query +=" Variable_name IN ('character_set_client', 'character_set_connection', 'character_set_results', 'collation_connection') ORDER BY Variable_name"; diff --git a/test/tap/tests/test_unshun_algorithm-t.cpp b/test/tap/tests/test_unshun_algorithm-t.cpp new file mode 100644 index 0000000000..b5924b10a7 --- /dev/null +++ b/test/tap/tests/test_unshun_algorithm-t.cpp @@ -0,0 +1,350 @@ +/** + * @file test_unshun_algorithm-t.cpp + * @brief This test verifies the implementation of the new introduced variable 'mysql-unshun_algorithm'. + * @details Test performs the following checks: + * 1. Check that the variable default values and get/set operations works as expected. + * 2. Check that the 'PROXYSQL_SIMULATOR' command is working properly. + * 2. Check that the old 'SHUNNING' and 'UNSHUNNING' behavior works as expected. For this, multiple + * 3. Check that the new 'SHUNNING' and 'UNSHUNNING' behavior 'mysql-unshun_algorithm=1' works as expected. + * + * In order to check that 'SHUNNING' and 'UNSHUNNING' behavior holds: + * 1. 10 fake servers are placed alone in 10 different hostgroups. + * 2. The same servers are also placed incrementally in 10 other different hostgroups holding one more + * search each time. + * 3. All the servers are 'SHUNNED' using 'PROXYSQL_SIMULATOR' command. + * 4. Each of the servers placed in the individual hostgroups is 'UNSHUNNED', checking the proper behavior + * related to the other servers depending on 'mysql-unshun_algorithm'. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "proxysql_utils.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +const uint32_t SHUN_RECOVERY_TIME = 1; +const uint32_t VALID_RANGE = 1; +const uint32_t SERVERS_COUNT = 10; + +using std::string; + +int shunn_server(MYSQL* proxysql_admin, uint32_t i, uint32_t j) { + std::string t_simulator_error_query { "PROXYSQL_SIMULATOR mysql_error %d 127.0.0.1:330%d 1234" }; + std::string simulator_error_q_i {}; + string_format(t_simulator_error_query, simulator_error_q_i, i, j); + MYSQL_QUERY(proxysql_admin, simulator_error_q_i.c_str()); + + return EXIT_SUCCESS; +} + +int shunn_all_servers(MYSQL* proxysql_admin) { + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + shunn_server(proxysql_admin, i, i); + + for (uint32_t j = 0; j <= i; j++) { + shunn_server(proxysql_admin, i + SERVERS_COUNT, j); + } + } + + return EXIT_SUCCESS; +} + +int wakup_target_server(MYSQL* proxysql_mysql, uint32_t i) { + std::string t_simple_do_query { "DO /* ;hostgroup=%d */ 1" }; + std::string simple_do_query {}; + string_format(t_simple_do_query, simple_do_query, i); + + mysql_query(proxysql_mysql, simple_do_query.c_str()); + sleep(SHUN_RECOVERY_TIME * 2); + mysql_query(proxysql_mysql, simple_do_query.c_str()); + + return EXIT_SUCCESS; +} + +int server_status_checker(MYSQL* admin, const string& f_st, const string& n_st, uint32_t i) { + std::string t_server_status_query { + "SELECT status FROM runtime_mysql_servers WHERE port=330%d order by hostgroup_id" + }; + std::string server_status_query {}; + string_format(t_server_status_query, server_status_query, i); + MYSQL_QUERY(admin, server_status_query.c_str()); + + MYSQL_RES* status_res = mysql_store_result(admin); + bool unexp_row_value = false; + + int num_rows = mysql_num_rows(status_res); + if (num_rows == 0) { + unexp_row_value = true; + } else { + uint32_t row_num = 0; + MYSQL_ROW row = nullptr; + + while (( row = mysql_fetch_row(status_res) )) { + std::string status { row[0] }; + diag("Status found for server '127.0.0.1:330%d' was '%s'", i, status.c_str()); + if (row_num == 0) { + if (status != f_st) { + unexp_row_value = true; + break; + } + } else { + if (status != n_st) { + unexp_row_value = true; + break; + } + } + row_num++; + } + } + + mysql_free_result(status_res); + + return unexp_row_value; +} + +int test_unshun_algorithm_variable(MYSQL* proxysql_admin) { + const auto get_current_unshun_algorithm_val = [](MYSQL* proxysql_admin) -> int32_t { + int32_t cur_unshun_value = -1; + + int err = mysql_query(proxysql_admin, "SELECT * FROM global_variables WHERE variable_name='mysql-unshun_algorithm'"); + if (err != EXIT_SUCCESS) { + diag( + "Query for retrieving value of 'mysql-unshun_algorithm' failed with error: (%d, %s)", + mysql_errno(proxysql_admin), mysql_error(proxysql_admin) + ); + return cur_unshun_value; + } + + MYSQL_RES* myres_unshun_var = mysql_store_result(proxysql_admin); + if (myres_unshun_var != nullptr) { + int num_rows = mysql_num_rows(myres_unshun_var); + MYSQL_ROW row = mysql_fetch_row(myres_unshun_var); + + if (num_rows && row != nullptr) { + char* endptr = nullptr; + cur_unshun_value = strtol(row[1], &endptr, SERVERS_COUNT); + } + } + mysql_free_result(myres_unshun_var); + + return cur_unshun_value; + }; + + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES FROM DISK"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + int32_t def_unshun_value = get_current_unshun_algorithm_val(proxysql_admin); + ok(def_unshun_value == 0, "Default 'mysql-unshun_algorithm' should be '0', actual: %d", def_unshun_value); + + std::string t_set_unshun { "SET mysql-unshun_algorithm=%d" }; + + for (uint32_t i = 0; i <= VALID_RANGE; i++) { + std::string set_unshun {}; + string_format(t_set_unshun, set_unshun, i); + MYSQL_QUERY(proxysql_admin, set_unshun.c_str()); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int32_t cur_unshun_val = get_current_unshun_algorithm_val(proxysql_admin); + ok(cur_unshun_val == i, "Settings and getting 'mysql-unshun_algorithm' works for range value: %d", i); + } + + { + std::string set_unshun {}; + string_format(t_set_unshun, set_unshun, VALID_RANGE + 1); + MYSQL_QUERY(proxysql_admin, set_unshun.c_str()); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int32_t cur_unshun_val = get_current_unshun_algorithm_val(proxysql_admin); + ok( + cur_unshun_val != VALID_RANGE + 1, + "Settings and getting 'mysql-unshun_algorithm' doesn't work fo invalid range value: %d", + VALID_RANGE + 1 + ); + } + + return EXIT_SUCCESS; +} + +int test_proxysql_simulator_error(MYSQL* proxysql_admin) { + MYSQL_QUERY(proxysql_admin, "DELETE FROM mysql_servers"); + const std::string t_insert_server_query { "INSERT INTO mysql_servers (hostgroup_id,hostname,port) VALUES (%d,'127.0.0.1',330%d)" }; + + // Create ten initial servers not sharing hostgroup + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + std::string insert_server_query {}; + string_format(t_insert_server_query, insert_server_query, i, i); + MYSQL_QUERY(proxysql_admin, insert_server_query.c_str()); + } + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + // Check that ALL the servers are in the expected 'ONLINE' state + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + int check_res = server_status_checker(proxysql_admin, "ONLINE", "ONLINE", i); + if (check_res != false) { + diag("Found server in a different state than 'ONLINE' 'test_proxysql_simulator_error' can't be performed"); + return EXIT_FAILURE; + } + } + + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + shunn_server(proxysql_admin, i, i); + int check_res = server_status_checker(proxysql_admin, "SHUNNED", "SHUNNED", i); + ok(check_res == false, "'PROXYSQL_SIMULATOR' should set the servers with errors to 'SHUNNED'"); + } + + + // Check that 'PROXYSQL_SIMULATOR' command fails when the server specified isn't found + int shunn_err = shunn_server(proxysql_admin, 20, 20); + ok(shunn_err == 1, "SHUNNING operation should have failed for a non-existing server."); + + return EXIT_SUCCESS; +} + +int test_unshun_algorithm_behavior(MYSQL* proxysql_mysql, MYSQL* proxysql_admin) { + // Configure Admin variables with lower thresholds + MYSQL_QUERY(proxysql_admin, "SET mysql-shun_recovery_time_sec=1"); + // NOTE: The following varible value is set here just as a reminder. This change isn't properly propagated + // to the 'error setting operation' since this is performed from 'ProxySQL_Admin' thread when + // 'PROXYSQL_SIMULATOR' command is received. Because of this, it's in 'PROXYSQL_SIMULATOR' command impl in + // 'ProxySQL_Admin' where this variable value is updated before setting the error. + MYSQL_QUERY(proxysql_admin, "SET mysql-shun_on_failures=5"); + MYSQL_QUERY(proxysql_admin, "SET mysql-connect_retries_on_failure=0"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + // Cleanup the servers and create a good number of hostgroups + MYSQL_QUERY(proxysql_admin, "DELETE FROM mysql_servers"); + + const std::string t_insert_server_query { "INSERT INTO mysql_servers (hostgroup_id,hostname,port) VALUES (%d,'127.0.0.1',330%d)" }; + + // Create ten initial servers not sharing hostgroup + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + std::string insert_server_query {}; + string_format(t_insert_server_query, insert_server_query, i, i); + MYSQL_QUERY(proxysql_admin, insert_server_query.c_str()); + } + + // Place the same servers incrementally in ten new hostgroups + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + for (uint32_t j = 0; j <= i; j++) { + std::string insert_server_query {}; + string_format(t_insert_server_query, insert_server_query, i + SERVERS_COUNT, j); + MYSQL_QUERY(proxysql_admin, insert_server_query.c_str()); + } + } + + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + { + MYSQL_QUERY(proxysql_admin, "SET mysql-unshun_algorithm=0"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int shunn_err = shunn_all_servers(proxysql_admin); + if (shunn_err) { return EXIT_FAILURE; } + + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + wakup_target_server(proxysql_mysql, i); + + // Check that only server from first hostgroup is 'SHUNNED' + bool unexp_row_value = server_status_checker(proxysql_admin, "ONLINE", "SHUNNED", i); + ok(unexp_row_value == false, "Server from first hg was set 'ONLINE' while others remained 'SHUNNED'"); + } + } + + { + MYSQL_QUERY(proxysql_admin, "SET mysql-unshun_algorithm=1"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int shunn_err = shunn_all_servers(proxysql_admin); + if (shunn_err) { return EXIT_FAILURE; } + + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + wakup_target_server(proxysql_mysql, i); + + bool unexp_row_value = server_status_checker(proxysql_admin, "ONLINE", "ONLINE", i); + ok(unexp_row_value == false, "Server from first hg was set 'ONLINE' while others remained 'ONLINE'"); + } + } + + { + MYSQL_QUERY(proxysql_admin, "SET mysql-unshun_algorithm=0"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int shunn_err = shunn_all_servers(proxysql_admin); + if (shunn_err) { return EXIT_FAILURE; } + + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + wakup_target_server(proxysql_mysql, i); + } + + MYSQL_QUERY(proxysql_admin, "SET mysql-unshun_algorithm=1"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + for (uint32_t i = 0; i < SERVERS_COUNT; i++) { + wakup_target_server(proxysql_mysql, i); + + bool unexp_row_value = server_status_checker(proxysql_admin, "ONLINE", "SHUNNED", i); + ok(unexp_row_value == false, "Server from first hg was set 'ONLINE' while others remained 'SHUNNED'"); + } + } + + + return EXIT_SUCCESS; +} +int main(int argc, char** argv) { + CommandLine cl; + + plan( + 1 + (VALID_RANGE + 1) + 1 + // Variable tests + SERVERS_COUNT + 1 + // Simulator error tests + SERVERS_COUNT * 3 // Testing unshun_algorithm behavior + ); + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + MYSQL* proxysql_mysql = mysql_init(NULL); + MYSQL* proxysql_admin = mysql_init(NULL); + + if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); + return EXIT_FAILURE; + } + 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_mysql)); + return EXIT_FAILURE; + } + + { + int unshun_var_err = test_unshun_algorithm_variable(proxysql_admin); + if (unshun_var_err == EXIT_FAILURE) { goto cleanup; } + } + + // Disable Monitor for the following tests + MYSQL_QUERY(proxysql_admin, "SET mysql-monitor_enabled=0"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + { + int simulator_err = test_proxysql_simulator_error(proxysql_admin); + if (simulator_err == EXIT_FAILURE) { goto cleanup; } + } + + { + int unshun_algorithm_err = test_unshun_algorithm_behavior(proxysql_mysql, proxysql_admin); + if (unshun_algorithm_err == EXIT_FAILURE) { goto cleanup; } + } + +cleanup: + + mysql_close(proxysql_admin); + mysql_close(proxysql_mysql); + + return exit_status(); +} diff --git a/test/tap/tests/test_unsupported_queries-t.cpp b/test/tap/tests/test_unsupported_queries-t.cpp index 2c02045efe..fff49064fc 100644 --- a/test/tap/tests/test_unsupported_queries-t.cpp +++ b/test/tap/tests/test_unsupported_queries-t.cpp @@ -284,7 +284,7 @@ void helper_test_load_data_local_infile( // Check that the data has actually been loaded to the database // NOTE: Specifically target 'hostgroup=0' to avoid replication lag. - int myerr = mysql_query(proxysql, "SELECT * /*+ ;hostgroup=0 */ FROM test.load_data_local"); + int myerr = mysql_query(proxysql, "SELECT * /* ;hostgroup=0 */ FROM test.load_data_local"); if (myerr) { diag( "Query 'SELECT * FROM test.load_data_local' for table preparation failed"