diff --git a/include/Base_HostGroups_Manager.h b/include/Base_HostGroups_Manager.h new file mode 100644 index 000000000..315ce737b --- /dev/null +++ b/include/Base_HostGroups_Manager.h @@ -0,0 +1,1300 @@ +template class BaseSrvList; +template class BaseHGC; +template class Base_HostGroups_Manager; + +class MyHGC; +class PgSQL_HGC; +class MySrvC; +class PgSQL_SrvC; +class MySrvList; +class PgSQL_SrvList; + +#include "proxysql.h" +#include "cpp.h" +#include "proxysql_gtid.h" + +#include +#include +#include +#include + +// Headers for declaring Prometheus counters +#include "prometheus/counter.h" +#include "prometheus/gauge.h" + +#include "thread.h" +#include "wqueue.h" + +#include "ev.h" + +#ifndef SPOOKYV2 +#include "SpookyV2.h" +#define SPOOKYV2 +#endif + +#ifndef PROXYJSON +#define PROXYJSON +namespace nlohmann { class json; } +#endif // PROXYJSON + +#include + +#ifndef CLASS_BASE_HOSTGROUPS_MANAGER_H +#define CLASS_BASE_HOSTGROUPS_MANAGER_H + +#ifdef DEBUG +/* */ +// Enabling STRESSTEST_POOL ProxySQL will do a lot of loops in the connection pool +// This is for internal testing ONLY!!!! +//#define STRESSTEST_POOL +#endif // DEBUG + +#if 0 + +// we have 2 versions of the same tables: with (debug) and without (no debug) checks +#ifdef DEBUG +#define MYHGM_MYSQL_SERVERS "CREATE TABLE mysql_servers ( hostgroup_id INT NOT NULL DEFAULT 0 , hostname VARCHAR NOT NULL , port INT NOT NULL DEFAULT 3306 , gtid_port INT NOT NULL DEFAULT 0 , weight INT CHECK (weight >= 0) NOT NULL DEFAULT 1 , status INT CHECK (status IN (0, 1, 2, 3, 4)) NOT NULL DEFAULT 0 , compression INT CHECK (compression >=0 AND compression <= 102400) NOT NULL DEFAULT 0 , max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 1000 , max_replication_lag INT CHECK (max_replication_lag >= 0 AND max_replication_lag <= 126144000) NOT NULL DEFAULT 0 , use_ssl INT CHECK (use_ssl IN(0,1)) NOT NULL DEFAULT 0 , max_latency_ms INT UNSIGNED CHECK (max_latency_ms>=0) NOT NULL DEFAULT 0 , comment VARCHAR NOT NULL DEFAULT '' , mem_pointer INT NOT NULL DEFAULT 0 , PRIMARY KEY (hostgroup_id, hostname, port) )" +#define MYHGM_MYSQL_SERVERS_INCOMING "CREATE TABLE mysql_servers_incoming ( hostgroup_id INT NOT NULL DEFAULT 0 , hostname VARCHAR NOT NULL , port INT NOT NULL DEFAULT 3306 , gtid_port INT NOT NULL DEFAULT 0 , weight INT CHECK (weight >= 0) NOT NULL DEFAULT 1 , status INT CHECK (status IN (0, 1, 2, 3, 4)) NOT NULL DEFAULT 0 , compression INT CHECK (compression >=0 AND compression <= 102400) NOT NULL DEFAULT 0 , max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 1000 , max_replication_lag INT CHECK (max_replication_lag >= 0 AND max_replication_lag <= 126144000) NOT NULL DEFAULT 0 , use_ssl INT CHECK (use_ssl IN(0,1)) NOT NULL DEFAULT 0 , max_latency_ms INT UNSIGNED CHECK (max_latency_ms>=0) NOT NULL DEFAULT 0 , comment VARCHAR NOT NULL DEFAULT '' , PRIMARY KEY (hostgroup_id, hostname, port))" +#else +#define MYHGM_MYSQL_SERVERS "CREATE TABLE mysql_servers ( hostgroup_id INT NOT NULL DEFAULT 0 , hostname VARCHAR NOT NULL , port INT NOT NULL DEFAULT 3306 , gtid_port INT NOT NULL DEFAULT 0 , weight INT NOT NULL DEFAULT 1 , status INT NOT NULL DEFAULT 0 , compression INT NOT NULL DEFAULT 0 , max_connections INT NOT NULL DEFAULT 1000 , max_replication_lag INT NOT NULL DEFAULT 0 , use_ssl INT NOT NULL DEFAULT 0 , max_latency_ms INT UNSIGNED NOT NULL DEFAULT 0 , comment VARCHAR NOT NULL DEFAULT '' , mem_pointer INT NOT NULL DEFAULT 0 , PRIMARY KEY (hostgroup_id, hostname, port) )" +#define MYHGM_MYSQL_SERVERS_INCOMING "CREATE TABLE mysql_servers_incoming ( hostgroup_id INT NOT NULL DEFAULT 0 , hostname VARCHAR NOT NULL , port INT NOT NULL DEFAULT 3306 , gtid_port INT NOT NULL DEFAULT 0 , weight INT NOT NULL DEFAULT 1 , status INT NOT NULL DEFAULT 0 , compression INT NOT NULL DEFAULT 0 , max_connections INT NOT NULL DEFAULT 1000 , max_replication_lag INT NOT NULL DEFAULT 0 , use_ssl INT NOT NULL DEFAULT 0 , max_latency_ms INT UNSIGNED NOT NULL DEFAULT 0 , comment VARCHAR NOT NULL DEFAULT '' , PRIMARY KEY (hostgroup_id, hostname, port))" +#endif /* DEBUG */ +#define MYHGM_MYSQL_REPLICATION_HOSTGROUPS "CREATE TABLE mysql_replication_hostgroups (writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY , reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>=0) , check_type VARCHAR CHECK (LOWER(check_type) IN ('read_only','innodb_read_only','super_read_only','read_only|innodb_read_only','read_only&innodb_read_only')) NOT NULL DEFAULT 'read_only' , comment VARCHAR NOT NULL DEFAULT '' , UNIQUE (reader_hostgroup))" + +#define MYHGM_MYSQL_GROUP_REPLICATION_HOSTGROUPS "CREATE TABLE mysql_group_replication_hostgroups (writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY , backup_writer_hostgroup INT CHECK (backup_writer_hostgroup>=0 AND backup_writer_hostgroup<>writer_hostgroup) NOT NULL , reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND backup_writer_hostgroup<>reader_hostgroup AND reader_hostgroup>0) , offline_hostgroup INT NOT NULL CHECK (offline_hostgroup<>writer_hostgroup AND offline_hostgroup<>reader_hostgroup AND backup_writer_hostgroup<>offline_hostgroup AND offline_hostgroup>=0) , active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1 , max_writers INT NOT NULL CHECK (max_writers >= 0) DEFAULT 1 , writer_is_also_reader INT CHECK (writer_is_also_reader IN (0,1,2)) NOT NULL DEFAULT 0 , max_transactions_behind INT CHECK (max_transactions_behind>=0) NOT NULL DEFAULT 0 , comment VARCHAR , UNIQUE (reader_hostgroup) , UNIQUE (offline_hostgroup) , UNIQUE (backup_writer_hostgroup))" + +#define MYHGM_MYSQL_GALERA_HOSTGROUPS "CREATE TABLE mysql_galera_hostgroups (writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY , backup_writer_hostgroup INT CHECK (backup_writer_hostgroup>=0 AND backup_writer_hostgroup<>writer_hostgroup) NOT NULL , reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND backup_writer_hostgroup<>reader_hostgroup AND reader_hostgroup>0) , offline_hostgroup INT NOT NULL CHECK (offline_hostgroup<>writer_hostgroup AND offline_hostgroup<>reader_hostgroup AND backup_writer_hostgroup<>offline_hostgroup AND offline_hostgroup>=0) , active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1 , max_writers INT NOT NULL CHECK (max_writers >= 0) DEFAULT 1 , writer_is_also_reader INT CHECK (writer_is_also_reader IN (0,1,2)) NOT NULL DEFAULT 0 , max_transactions_behind INT CHECK (max_transactions_behind>=0) NOT NULL DEFAULT 0 , comment VARCHAR , UNIQUE (reader_hostgroup) , UNIQUE (offline_hostgroup) , UNIQUE (backup_writer_hostgroup))" + +#define MYHGM_MYSQL_AWS_AURORA_HOSTGROUPS "CREATE TABLE mysql_aws_aurora_hostgroups (writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY , reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>0) , " \ + "active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1 , aurora_port INT NOT NUlL DEFAULT 3306 , domain_name VARCHAR NOT NULL DEFAULT '' , " \ + "max_lag_ms INT NOT NULL CHECK (max_lag_ms>= 10 AND max_lag_ms <= 600000) DEFAULT 600000 , " \ + "check_interval_ms INT NOT NULL CHECK (check_interval_ms >= 100 AND check_interval_ms <= 600000) DEFAULT 1000 , " \ + "check_timeout_ms INT NOT NULL CHECK (check_timeout_ms >= 80 AND check_timeout_ms <= 3000) DEFAULT 800 , " \ + "writer_is_also_reader INT CHECK (writer_is_also_reader IN (0,1)) NOT NULL DEFAULT 0 , " \ + "new_reader_weight INT CHECK (new_reader_weight >= 0 AND new_reader_weight <=10000000) NOT NULL DEFAULT 1 , " \ + "add_lag_ms INT NOT NULL CHECK (add_lag_ms >= 0 AND add_lag_ms <= 600000) DEFAULT 30 , " \ + "min_lag_ms INT NOT NULL CHECK (min_lag_ms >= 0 AND min_lag_ms <= 600000) DEFAULT 30 , " \ + "lag_num_checks INT NOT NULL CHECK (lag_num_checks >= 1 AND lag_num_checks <= 16) DEFAULT 1 , comment VARCHAR ," \ + "UNIQUE (reader_hostgroup))" + +#define MYHGM_GEN_ADMIN_RUNTIME_SERVERS "SELECT hostgroup_id, hostname, port, gtid_port, CASE status WHEN 0 THEN \"ONLINE\" WHEN 1 THEN \"SHUNNED\" WHEN 2 THEN \"OFFLINE_SOFT\" WHEN 3 THEN \"OFFLINE_HARD\" WHEN 4 THEN \"SHUNNED\" END status, weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment FROM mysql_servers ORDER BY hostgroup_id, hostname, port" + +#define MYHGM_MYSQL_HOSTGROUP_ATTRIBUTES "CREATE TABLE mysql_hostgroup_attributes (hostgroup_id INT NOT NULL PRIMARY KEY , max_num_online_servers INT CHECK (max_num_online_servers>=0 AND max_num_online_servers <= 1000000) NOT NULL DEFAULT 1000000 , autocommit INT CHECK (autocommit IN (-1, 0, 1)) NOT NULL DEFAULT -1 , free_connections_pct INT CHECK (free_connections_pct >= 0 AND free_connections_pct <= 100) NOT NULL DEFAULT 10 , init_connect VARCHAR NOT NULL DEFAULT '' , multiplex INT CHECK (multiplex IN (0, 1)) NOT NULL DEFAULT 1 , connection_warming INT CHECK (connection_warming IN (0, 1)) NOT NULL DEFAULT 0 , throttle_connections_per_sec INT CHECK (throttle_connections_per_sec >= 1 AND throttle_connections_per_sec <= 1000000) NOT NULL DEFAULT 1000000 , ignore_session_variables VARCHAR CHECK (JSON_VALID(ignore_session_variables) OR ignore_session_variables = '') NOT NULL DEFAULT '' , hostgroup_settings VARCHAR CHECK (JSON_VALID(hostgroup_settings) OR hostgroup_settings = '') NOT NULL DEFAULT '' , servers_defaults VARCHAR CHECK (JSON_VALID(servers_defaults) OR servers_defaults = '') NOT NULL DEFAULT '' , comment VARCHAR NOT NULL DEFAULT '')" + + +#define MYHGM_MYSQL_SERVERS_SSL_PARAMS "CREATE TABLE mysql_servers_ssl_params (hostname VARCHAR NOT NULL , port INT CHECK (port >= 0 AND port <= 65535) NOT NULL DEFAULT 3306 , username VARCHAR NOT NULL DEFAULT '' , ssl_ca VARCHAR NOT NULL DEFAULT '' , ssl_cert VARCHAR NOT NULL DEFAULT '' , ssl_key VARCHAR NOT NULL DEFAULT '' , ssl_capath VARCHAR NOT NULL DEFAULT '' , ssl_crl VARCHAR NOT NULL DEFAULT '' , ssl_crlpath VARCHAR NOT NULL DEFAULT '' , ssl_cipher VARCHAR NOT NULL DEFAULT '' , tls_version VARCHAR NOT NULL DEFAULT '' , comment VARCHAR NOT NULL DEFAULT '' , PRIMARY KEY (hostname, port, username) )" + +/* + * @brief Generates the 'runtime_mysql_servers' resultset exposed to other ProxySQL cluster members. + * @details Makes 'SHUNNED' and 'SHUNNED_REPLICATION_LAG' statuses equivalent to 'ONLINE'. 'SHUNNED' states + * are by definition local transitory states, this is why a 'mysql_servers' table reconfiguration isn't + * normally performed when servers are internally imposed with these statuses. This means, that propagating + * this state to other cluster members is undesired behavior, and so it's generating a different checksum, + * due to a server having this particular state, that will result in extra unnecessary fetching operations. + * The query also filters out 'OFFLINE_HARD' servers, 'OFFLINE_HARD' is a local status which is equivalent to + * a server no longer being part of the table (DELETED state). And so, they shouldn't be propagated. + * + * For placing the query into a single line for debugging purposes: + * ``` + * sed 's/^\t\+"//g; s/"\s\\$//g; s/\\"/"/g' /tmp/select.sql | paste -sd '' + * ``` + */ +#define MYHGM_GEN_CLUSTER_ADMIN_RUNTIME_SERVERS \ + "SELECT " \ + "hostgroup_id, hostname, port, gtid_port," \ + "CASE status" \ + " WHEN 0 THEN \"ONLINE\"" \ + " WHEN 1 THEN \"ONLINE\"" \ + " WHEN 2 THEN \"OFFLINE_SOFT\"" \ + " WHEN 3 THEN \"OFFLINE_HARD\"" \ + " WHEN 4 THEN \"ONLINE\" " \ + "END status," \ + "weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment " \ + "FROM mysql_servers " \ + "WHERE status != 3 " \ + "ORDER BY hostgroup_id, hostname, port" \ + +/** + * @brief Generates the 'mysql_servers_v2' resultset exposed to other ProxySQL cluster members. + * @details The generated resultset is used for the checksum computation of the runtime ProxySQL config + * ('mysql_servers_v2' checksum), and it's also forwarded to other cluster members when querying the Admin + * interface with 'CLUSTER_QUERY_MYSQL_SERVERS_V2'. It makes 'SHUNNED' state equivalent to 'ONLINE', and also + * filters out any 'OFFLINE_HARD' entries. This is done because none of the statuses are valid configuration + * statuses, they are local, transient status that ProxySQL uses during operation. + */ +#define MYHGM_GEN_CLUSTER_ADMIN_MYSQL_SERVERS \ + "SELECT " \ + "hostgroup_id, hostname, port, gtid_port, " \ + "CASE" \ + " WHEN status=\"SHUNNED\" THEN \"ONLINE\"" \ + " ELSE status " \ + "END AS status, " \ + "weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment " \ + "FROM main.mysql_servers " \ + "WHERE status != \"OFFLINE_HARD\" " \ + "ORDER BY hostgroup_id, hostname, port" \ + +typedef std::unordered_map umap_mysql_errors; + +class MySrvConnList; +class MySrvC; +class MySrvList; +class MyHGC; + +struct peer_runtime_mysql_servers_t; +struct peer_mysql_servers_v2_t; + +std::string gtid_executed_to_string(gtid_set_t& gtid_executed); +void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed); + +#include "GTID_Server_Data.h" + +/* +class GTID_Server_Data { + public: + char *address; + uint16_t port; + uint16_t mysql_port; + char *data; + size_t len; + size_t size; + size_t pos; + struct ev_io *w; + char uuid_server[64]; + unsigned long long events_read; + gtid_set_t gtid_executed; + bool active; + GTID_Server_Data(struct ev_io *_w, char *_address, uint16_t _port, uint16_t _mysql_port); + void resize(size_t _s); + ~GTID_Server_Data(); + bool readall(); + bool writeout(); + bool read_next_gtid(); + bool gtid_exists(char *gtid_uuid, uint64_t gtid_trxid); + void read_all_gtids(); + void dump(); +}; +*/ + + +class MySrvConnList { + private: + MySrvC *mysrvc; + int find_idx(MySQL_Connection *c) { + //for (unsigned int i=0; ilen; i++) { + MySQL_Connection *conn = NULL; + conn = (MySQL_Connection *)conns->index(i); + if (conn==c) { + return (unsigned int)i; + } + } + return -1; + } + public: + PtrArray *conns; + MySrvConnList(MySrvC *); + ~MySrvConnList(); + void add(MySQL_Connection *); + void remove(MySQL_Connection *c) { + int i = -1; + i = find_idx(c); + assert(i>=0); + conns->remove_index_fast((unsigned int)i); + } + MySQL_Connection *remove(int); + MySQL_Connection * get_random_MyConn(MySQL_Session *sess, bool ff); + void get_random_MyConn_inner_search(unsigned int start, unsigned int end, unsigned int& conn_found_idx, unsigned int& connection_quality_level, unsigned int& number_of_matching_session_variables, const MySQL_Connection * client_conn); + unsigned int conns_length() { return conns->len; } + void drop_all_connections(); + MySQL_Connection *index(unsigned int); +}; + + +class MySrvC { // MySQL Server Container + public: + MyHGC *myhgc; + char *address; + uint16_t port; + uint16_t gtid_port; + uint16_t flags; + int64_t weight; + unsigned int compression; + int64_t max_connections; + unsigned int aws_aurora_current_lag_us; + unsigned int max_replication_lag; + unsigned int max_connections_used; // The maximum number of connections that has been opened + unsigned int connect_OK; + unsigned int connect_ERR; + int cur_replication_lag; + unsigned int cur_replication_lag_count; + // note that these variables are in microsecond, while user defines max latency in millisecond + unsigned int current_latency_us; + unsigned int max_latency_us; + time_t time_last_detected_error; + unsigned int connect_ERR_at_time_last_detected_error; + unsigned long long queries_sent; + unsigned long long queries_gtid_sync; + unsigned long long bytes_sent; + unsigned long long bytes_recv; + bool shunned_automatic; + bool shunned_and_kill_all_connections; // if a serious failure is detected, this will cause all connections to die even if the server is just shunned + int32_t use_ssl; + char *comment; + MySrvConnList *ConnectionsUsed; + MySrvConnList *ConnectionsFree; + /** + * @brief Constructs a new MySQL Server Container. + * @details For 'server_defaults' parameters, if '-1' is supplied, they try to be obtained from + * 'servers_defaults' entry from 'mysql_hostgroup_attributes' when adding the server to it's target + * hostgroup(via 'MySQL_HostGroups_Manager::add'), if not found, value is set with 'mysql_servers' + * defaults. + * @param addr Address of the server, specified either by IP or hostname. + * @param port Server port. + * @param gitd_port If non-zero, enables GTID tracking for the server. + * @param _weight Server weight. 'server_defaults' param, check @details. + * @param _status Initial server status. + * @param _compression Enables compression for server connections. + * @param _max_connections Max server connections. 'server_defaults' param, check @details. + * @param _max_replication_lag If non-zero, enables replication lag checks. + * @param _use_ssl Enables SSL for server connections. 'servers_defaults' param, check @details. + * @param _max_latency_ms Max ping server latency. When exceeded, server gets excluded from conn-pool. + * @param _comment User defined comment. + */ + MySrvC( + char* addr, uint16_t port, uint16_t gitd_port, int64_t _weight, enum MySerStatus _status, unsigned int _compression, + int64_t _max_connections, unsigned int _max_replication_lag, int32_t _use_ssl, unsigned int _max_latency_ms, + char* _comment + ); + ~MySrvC(); + void connect_error(int, bool get_mutex=true); + void shun_and_killall(); + /** + * @brief Update the maximum number of used connections + * @return The maximum number of used connections + */ + unsigned int update_max_connections_used() + { + unsigned int connections_used = ConnectionsUsed->conns_length(); + if (max_connections_used < connections_used) + max_connections_used = connections_used; + return max_connections_used; + } + void set_status(MySerStatus _status); + inline + MySerStatus get_status() const { return status; } +private: + enum MySerStatus status; +}; + +#endif // 0 + +template +class BaseSrvList { // MySQL Server List + private: + HGC *myhgc; + using TypeSrvC = typename std::conditional< + std::is_same_v, MySrvC, PgSQL_SrvC + >::type; + int find_idx(TypeSrvC *); + public: + PtrArray *servers; + unsigned int cnt() { return servers->len; } + BaseSrvList(HGC *); + ~BaseSrvList(); + void add(TypeSrvC *); + void remove(TypeSrvC *); + TypeSrvC * idx(unsigned int i) {return (TypeSrvC *)servers->index(i); } + + friend class PgSQL_SrvList; + friend class PgSQL_HGC; + +}; + + +template +class BaseHGC { // MySQL Host Group Container + public: + unsigned int hid; + std::atomic num_online_servers; + time_t last_log_time_num_online_servers; + unsigned long long current_time_now; + uint32_t new_connections_now; + using TypeSrvList = typename std::conditional< + std::is_same_v, MySrvList, PgSQL_SrvList + >::type; + BaseSrvList *mysrvs; + struct { // this is a series of attributes specific for each hostgroup + char * init_connect; + char * comment; + char * ignore_session_variables_text; // this is the original version (text format) of ignore_session_variables + uint32_t max_num_online_servers; + uint32_t throttle_connections_per_sec; + int32_t monitor_slave_lag_when_null; + int8_t autocommit; + int8_t free_connections_pct; + int8_t handle_warnings; + bool multiplex; + bool connection_warming; + bool configured; // this variable controls if attributes are configured or not. If not configured, they do not apply + bool initialized; // this variable controls if attributes were ever configured or not. Used by reset_attributes() + nlohmann::json * ignore_session_variables_json = NULL; // the JSON format of ignore_session_variables + } attributes; + struct { + int64_t weight; + int64_t max_connections; + int32_t use_ssl; + } servers_defaults; + void reset_attributes(); + inline + bool handle_warnings_enabled() const { + return attributes.configured == true && attributes.handle_warnings != -1 ? attributes.handle_warnings : mysql_thread___handle_warnings; + } + inline + int32_t get_monitor_slave_lag_when_null() const { + return attributes.configured == true && attributes.monitor_slave_lag_when_null != -1 ? attributes.monitor_slave_lag_when_null : mysql_thread___monitor_slave_lag_when_null; + } + BaseHGC(int); + virtual ~BaseHGC(); + using TypeSrvC = typename std::conditional< + std::is_same_v, MySrvC, PgSQL_SrvC + >::type; + using TypeSess = typename std::conditional< + std::is_same_v, MySQL_Session, PgSQL_Session + >::type; + TypeSess *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, TypeSess *sess); + void refresh_online_server_count(); + void log_num_online_server_count_error(); + inline + bool online_servers_within_threshold() const { + if (num_online_servers.load(std::memory_order_relaxed) <= attributes.max_num_online_servers) return true; + return false; + } +}; + +#if 0 +class Group_Replication_Info { + public: + int writer_hostgroup; + int backup_writer_hostgroup; + int reader_hostgroup; + int offline_hostgroup; + int max_writers; + int max_transactions_behind; + char *comment; + bool active; + int writer_is_also_reader; + bool __active; + bool need_converge; // this is set to true on LOAD MYSQL SERVERS TO RUNTIME . This ensure that checks wil take an action + int current_num_writers; + int current_num_backup_writers; + int current_num_readers; + int current_num_offline; + Group_Replication_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); + bool update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); + ~Group_Replication_Info(); +}; + +class Galera_Info { + public: + int writer_hostgroup; + int backup_writer_hostgroup; + int reader_hostgroup; + int offline_hostgroup; + int max_writers; + int max_transactions_behind; + char *comment; + bool active; + int writer_is_also_reader; + bool __active; + bool need_converge; // this is set to true on LOAD MYSQL SERVERS TO RUNTIME . This ensure that checks wil take an action + int current_num_writers; + int current_num_backup_writers; + int current_num_readers; + int current_num_offline; + Galera_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); + bool update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); + ~Galera_Info(); +}; + +class AWS_Aurora_Info { + public: + int writer_hostgroup; + int reader_hostgroup; + int aurora_port; + int max_lag_ms; + int add_lag_ms; + int min_lag_ms; + int lag_num_checks; + int check_interval_ms; + int check_timeout_ms; + int writer_is_also_reader; + int new_reader_weight; + // TODO + // add intermediary status value, for example the last check time + char * domain_name; + char * comment; + bool active; + bool __active; + AWS_Aurora_Info(int w, int r, int _port, char *_end_addr, int maxl, int al, int minl, int lnc, int ci, int ct, bool _a, int wiar, int nrw, char *c); + bool update(int r, int _port, char *_end_addr, int maxl, int al, int minl, int lnc, int ci, int ct, bool _a, int wiar, int nrw, char *c); + ~AWS_Aurora_Info(); +}; + +class MySQLServers_SslParams { + public: + string hostname; + int port; + string username; + string ssl_ca; + string ssl_cert; + string ssl_key; + string ssl_capath; + string ssl_crl; + string ssl_crlpath; + string ssl_cipher; + string tls_version; + string comment; + string MapKey; + MySQLServers_SslParams(string _h, int _p, string _u, + string ca, string cert, string key, string capath, + string crl, string crlpath, string cipher, string tls, + string c) { + hostname = _h; + port = _p; + username = _u; + ssl_ca = ca; + ssl_cert = cert; + ssl_key = key; + ssl_capath = capath; + ssl_crl = crl; + ssl_crlpath = crlpath; + ssl_cipher = cipher; + tls_version = tls; + comment = c; + MapKey = ""; + } + MySQLServers_SslParams(char * _h, int _p, char * _u, + char * ca, char * cert, char * key, char * capath, + char * crl, char * crlpath, char * cipher, char * tls, + char * c) { + hostname = string(_h); + port = _p; + username = string(_u); + ssl_ca = string(ca); + ssl_cert = string(cert); + ssl_key = string(key); + ssl_capath = string(capath); + ssl_crl = string(crl); + ssl_crlpath = string(crlpath); + ssl_cipher = string(cipher); + tls_version = string(tls); + comment = string(c); + MapKey = ""; + } + MySQLServers_SslParams(string _h, int _p, string _u) { + MySQLServers_SslParams(_h, _p, _u, "", "", "", "", "", "", "", "", ""); + } + string getMapKey(const char *del) { + if (MapKey == "") { + MapKey = hostname + string(del) + to_string(port) + string(del) + username; + } + return MapKey; + } +}; + +struct p_hg_counter { + enum metric { + servers_table_version = 0, + server_connections_created, + server_connections_delayed, + server_connections_aborted, + client_connections_created, + client_connections_aborted, + com_autocommit, + com_autocommit_filtered, + com_rollback, + com_rollback_filtered, + com_backend_change_user, + com_backend_init_db, + // TODO: https://github.com/sysown/proxysql/issues/2690 + com_backend_set_names, + com_frontend_init_db, + com_frontend_set_names, + com_frontend_use_db, + com_commit_cnt, + com_commit_cnt_filtered, + selects_for_update__autocommit0, + access_denied_wrong_password, + access_denied_max_connections, + access_denied_max_user_connections, + myhgm_myconnpool_get, + myhgm_myconnpool_get_ok, + myhgm_myconnpool_get_ping, + myhgm_myconnpool_push, + myhgm_myconnpool_reset, + myhgm_myconnpool_destroy, + auto_increment_delay_multiplex, + __size + }; +}; + +struct p_hg_gauge { + enum metric { + server_connections_connected = 0, + client_connections_connected, + __size + }; +}; + +struct p_hg_dyn_counter { + enum metric { + conn_pool_bytes_data_recv = 0, + conn_pool_bytes_data_sent, + connection_pool_conn_err, + connection_pool_conn_ok, + connection_pool_queries, + gtid_executed, + proxysql_mysql_error, + mysql_error, + __size + }; +}; + +enum class p_mysql_error_type { + mysql, + proxysql +}; + +struct p_hg_dyn_gauge { + enum metric { + connection_pool_conn_free = 0, + connection_pool_conn_used, + connection_pool_latency_us, + connection_pool_status, + __size + }; +}; + +struct hg_metrics_map_idx { + enum index { + counters = 0, + gauges, + dyn_counters, + dyn_gauges, + }; +}; + +/** + * @brief Required server info for the read_only Monitoring actions and replication_lag Monitoring actions. + */ +using hostgroupid_t = int; +using hostname_t = std::string; +using address_t = std::string; +using port_t = unsigned int; +using read_only_t = int; +using current_replication_lag = int; +using override_replication_lag = bool; + +using read_only_server_t = std::tuple; +using replication_lag_server_t = std::tuple; + +enum READ_ONLY_SERVER_T { + ROS_HOSTNAME = 0, + ROS_PORT, + ROS_READONLY, + ROS__SIZE +}; + +enum REPLICATION_LAG_SERVER_T { + RLS_HOSTGROUP_ID = 0, + RLS_ADDRESS, + RLS_PORT, + RLS_CURRENT_REPLICATION_LAG, + RLS_OVERRIDE_REPLICATION_LAG, + RLS__SIZE +}; + +/** + * @brief Contains the minimal info for server creation. + */ +struct srv_info_t { + /* @brief Server address */ + string addr; + /* @brief Server port */ + uint16_t port; + /* @brief Server type identifier, used for logging, e.g: 'Aurora AWS', 'GR', etc... */ + string kind; +}; + +/** + * @brief Contains options to be specified during server creation. + */ +struct srv_opts_t { + int64_t weigth; + int64_t max_conns; + int32_t use_ssl; +}; +#endif // 0 + +template +class Base_HostGroups_Manager { + private: + SQLite3DB *admindb; + SQLite3DB *mydb; + pthread_mutex_t readonly_mutex; + std::set read_only_set1; + std::set read_only_set2; + pthread_mutex_t lock; + + PtrArray *MyHostGroups; + std::unordered_mapMyHostGroups_map; + + HGC * MyHGC_find(unsigned int); + HGC * MyHGC_create(unsigned int); + + public: + Base_HostGroups_Manager(); + HGC * MyHGC_lookup(unsigned int); + SQLite3_result * execute_query(char *query, char **error); + + void wrlock(); + void wrunlock(); +#ifdef DEBUG + bool is_locked = false; +#endif + + friend class MySQL_HostGroups_Manager; + friend class PgSQL_HostGroups_Manager; + +}; + +#if 0 +class MySQL_HostGroups_Manager { + private: + SQLite3DB *admindb; + SQLite3DB *mydb; + pthread_mutex_t readonly_mutex; + std::set read_only_set1; + std::set read_only_set2; + pthread_mutex_t lock; + + enum HGM_TABLES { + MYSQL_SERVERS_V2 = 0, + MYSQL_REPLICATION_HOSTGROUPS, + MYSQL_GROUP_REPLICATION_HOSTGROUPS, + MYSQL_GALERA_HOSTGROUPS, + MYSQL_AWS_AURORA_HOSTGROUPS, + MYSQL_HOSTGROUP_ATTRIBUTES, + MYSQL_SERVERS_SSL_PARAMS, + MYSQL_SERVERS, + + __HGM_TABLES_SIZE + }; + + std::array table_resultset_checksum { {0} }; + + class HostGroup_Server_Mapping { + public: + enum Type { + WRITER = 0, + READER = 1, + + __TYPE_SIZE + }; + + struct Node { + MySrvC* srv = NULL; + unsigned int reader_hostgroup_id = -1; + unsigned int writer_hostgroup_id = -1; + //MySerStatus server_status = MYSQL_SERVER_STATUS_OFFLINE_HARD; + }; + + HostGroup_Server_Mapping(MySQL_HostGroups_Manager* hgm) : readonly_flag(1), myHGM(hgm) { } + ~HostGroup_Server_Mapping() = default; + + /** + * @brief Copies all unique nodes from source vector to destination vector. + * @details Copies all unique nodes from source vector to destination vector. The source and destination + * vectors are identified by an input enumeration type, which can be either a reader or a writer. + * During the copying process, the function also adds servers to the HostGroup connection container. + * @param dest_type Input Can be reader or writer + * @param src_type Input Can be reader or writer + */ + void copy_if_not_exists(Type dest_type, Type src_type); + + /** + * @brief Removes node located at the specified index. + * @details Node is removed from vector located at the specified index identified by an input enumeration type. + * Node that was removed is marked as offline in the HostGroup connection container. + * @param dest_type Input Can be reader or writer + * @param index Input Index of node to be removed + */ + void remove(Type type, size_t index); + + /** + * @brief Removes all nodes. + * @details All nodes are removed from vector, identified by an input enumeration type. + * Nodes that are removed is marked as offline in the HostGroup connection container. + * @param type Input Can be reader or writer + */ + void clear(Type type); + + inline + const std::vector& get(Type type) const { + return mapping[type]; + } + + inline + void add(Type type, Node& node) { + mapping[type].push_back(node); + } + + inline + void set_readonly_flag(int val) { + readonly_flag = val; + } + + inline + int get_readonly_flag() const { + return readonly_flag; + } + + private: + unsigned int get_hostgroup_id(Type type, const Node& node) const; + MySrvC* insert_HGM(unsigned int hostgroup_id, const MySrvC* srv); + void remove_HGM(MySrvC* srv); + + std::array, __TYPE_SIZE> mapping; // index 0 contains reader and 1 contains writer hostgroups + int readonly_flag; + MySQL_HostGroups_Manager* myHGM; + }; + + /** + * @brief Used by 'MySQL_Monitor::read_only' to hold a mapping between servers and hostgroups. + * @details The hostgroup mapping holds the MySrvC for each of the hostgroups in which the servers is + * present, distinguishing between 'READER' and 'WRITER' hostgroups. + */ + std::unordered_map> hostgroup_server_mapping; + /** + * @brief Holds the previous computed checksum for 'mysql_servers'. + * @details Used to check if the servers checksums has changed during 'commit', if a change is detected, + * the member 'hostgroup_server_mapping' is required to be regenerated. + * + * This is only updated during 'read_only_action_v2', since the action itself modifies + * 'hostgroup_server_mapping' in case any actions needs to be performed against the servers. + */ + uint64_t hgsm_mysql_servers_checksum = 0; + /** + * @brief Holds the previous checksum for the 'MYSQL_REPLICATION_HOSTGROUPS'. + * @details Used during 'commit' to determine if config has changed for 'MYSQL_REPLICATION_HOSTGROUPS', + * and 'hostgroup_server_mapping' should be rebuild. + */ + uint64_t hgsm_mysql_replication_hostgroups_checksum = 0; + + + PtrArray *MyHostGroups; + std::unordered_mapMyHostGroups_map; + + std::mutex Servers_SSL_Params_map_mutex; + std::unordered_map Servers_SSL_Params_map; + + MyHGC * MyHGC_find(unsigned int); + MyHGC * MyHGC_create(unsigned int); + + void add(MySrvC *, unsigned int); + void purge_mysql_servers_table(); + void generate_mysql_servers_table(int *_onlyhg=NULL); + void generate_mysql_replication_hostgroups_table(); + Galera_Info *get_galera_node_info(int hostgroup); + + /** + * @brief This resultset holds the current values for 'runtime_mysql_servers' computed by either latest + * 'commit' or fetched from another Cluster node. It's also used by ProxySQL_Admin to respond to the + * intercepted query 'CLUSTER_QUERY_RUNTIME_MYSQL_SERVERS'. + * @details This resultset can't right now just contain the value for 'incoming_mysql_servers' as with the + * rest of the intercepted resultset. This is due to 'runtime_mysql_servers' reconfigurations that can be + * triggered by monitoring actions like 'Galera' currently performs. These actions not only trigger status + * changes in the servers, but also re-generate the servers table via 'commit', thus generating a new + * checksum in the process. Because of this potential mismatch, the fetching server wouldn't be able to + * compute the proper checksum for the fetched 'runtime_mysql_servers' config. + * + * As previously stated, these reconfigurations are monitoring actions, they can't be packed or performed + * in a single action, since monitoring data is required, which may not be already present. This makes + * this a convergent, but iterative process, that can't be compressed into a single action. Using other + * nodes 'runtime_mysql_servers' while fetching represents a best effort for avoiding these + * reconfigurations in nodes that already holds the same monitoring conditions. If monitoring + * conditions are not the same, circular fetching is still possible due to the previously described + * scenario. + */ + SQLite3_result* runtime_mysql_servers; + /** + * @brief These resultset holds the latest values for 'incoming_*' tables used to promoted servers to runtime. + * @details All these resultsets are used by 'Cluster' to fetch and promote the same configuration used in the + * node across the whole cluster. For these, the queries: + * - 'CLUSTER_QUERY_MYSQL_REPLICATION_HOSTGROUPS' + * - 'CLUSTER_QUERY_MYSQL_GROUP_REPLICATION_HOSTGROUPS' + * - 'CLUSTER_QUERY_MYSQL_GALERA' + * - 'CLUSTER_QUERY_MYSQL_AWS_AURORA' + * - 'CLUSTER_QUERY_MYSQL_HOSTGROUP_ATTRIBUTES' + * Issued by 'Cluster' are intercepted by 'ProxySQL_Admin' and return the content of these resultsets. + */ + SQLite3_result *incoming_replication_hostgroups; + + void generate_mysql_group_replication_hostgroups_table(); + /** + * @brief Regenerates the resultset used by 'MySQL_Monitor' containing the servers to be monitored. + * @details This function is required to be called after any action that results in the addition of a new + * server that 'MySQL_Monitor' should be aware of for 'group_replication', i.e. a server added to the + * hostgroups present in any entry of 'mysql_group_replication_hostgroups'. E.g: + * - Inside 'generate_mysql_group_replication_hostgroups_table'. + * - Autodiscovery. + * + * NOTE: This is a common pattern for all the clusters monitoring. + */ + void generate_mysql_group_replication_hostgroups_monitor_resultset(); + SQLite3_result *incoming_group_replication_hostgroups; + + pthread_mutex_t Group_Replication_Info_mutex; + std::map Group_Replication_Info_Map; + + void generate_mysql_galera_hostgroups_table(); + SQLite3_result *incoming_galera_hostgroups; + + pthread_mutex_t Galera_Info_mutex; + std::map Galera_Info_Map; + + void generate_mysql_aws_aurora_hostgroups_table(); + SQLite3_result *incoming_aws_aurora_hostgroups; + + pthread_mutex_t AWS_Aurora_Info_mutex; + std::map AWS_Aurora_Info_Map; + + void generate_mysql_hostgroup_attributes_table(); + SQLite3_result *incoming_hostgroup_attributes; + + void generate_mysql_servers_ssl_params_table(); + SQLite3_result *incoming_mysql_servers_ssl_params; + + SQLite3_result* incoming_mysql_servers_v2; + + std::thread *HGCU_thread; + + std::thread *GTID_syncer_thread; + //pthread_t GTID_syncer_thread_id; + //pthread_t HGCU_thread_id; + + char rand_del[8]; + pthread_mutex_t mysql_errors_mutex; + umap_mysql_errors mysql_errors_umap; + + /** + * @brief Update the prometheus "connection_pool" counters. + */ + void p_update_connection_pool(); + /** + * @brief Update the "stats_mysql_gtid_executed" counters. + */ + void p_update_mysql_gtid_executed(); + + void p_update_connection_pool_update_counter( + const std::string& endpoint_id, const std::map& labels, + std::map& m_map, unsigned long long value, p_hg_dyn_counter::metric idx + ); + void p_update_connection_pool_update_gauge( + const std::string& endpoint_id, const std::map& labels, + std::map& m_map, unsigned long long value, p_hg_dyn_gauge::metric idx + ); + + void group_replication_lag_action_set_server_status(MyHGC* myhgc, char* address, int port, int lag_count, bool enable); + + public: + std::mutex galera_set_writer_mutex; + /** + * @brief Mutex used to guard 'mysql_servers_to_monitor' resulset. + */ + std::mutex mysql_servers_to_monitor_mutex; + /** + * @brief Resulset containing the latest 'mysql_servers' present in 'mydb'. + * @details This resulset should be updated via 'update_table_mysql_servers_for_monitor' each time actions + * that modify the 'mysql_servers' table are performed. + */ + SQLite3_result* mysql_servers_to_monitor; + + pthread_rwlock_t gtid_rwlock; + std::unordered_map gtid_map; + struct ev_async * gtid_ev_async; + struct ev_loop * gtid_ev_loop; + struct ev_timer * gtid_ev_timer; + bool gtid_missing_nodes; + struct { + unsigned int servers_table_version; + pthread_mutex_t servers_table_version_lock; + pthread_cond_t servers_table_version_cond; + unsigned long client_connections_aborted; + unsigned long client_connections_created; + int client_connections; + unsigned long server_connections_aborted; + unsigned long server_connections_created; + unsigned long server_connections_delayed; + unsigned long server_connections_connected; + unsigned long myconnpoll_get; + unsigned long myconnpoll_get_ok; + unsigned long myconnpoll_get_ping; + unsigned long myconnpoll_push; + unsigned long myconnpoll_reset; + unsigned long myconnpoll_destroy; + unsigned long long autocommit_cnt; + unsigned long long commit_cnt; + unsigned long long rollback_cnt; + unsigned long long autocommit_cnt_filtered; + unsigned long long commit_cnt_filtered; + unsigned long long rollback_cnt_filtered; + unsigned long long backend_change_user; + unsigned long long backend_init_db; + unsigned long long backend_set_names; + unsigned long long frontend_init_db; + unsigned long long frontend_set_names; + unsigned long long frontend_use_db; + unsigned long long access_denied_wrong_password; + unsigned long long access_denied_max_connections; + unsigned long long access_denied_max_user_connections; + unsigned long long select_for_update_or_equivalent; + unsigned long long auto_increment_delay_multiplex; + + ////////////////////////////////////////////////////// + /// Prometheus Metrics /// + ////////////////////////////////////////////////////// + + /// Prometheus metrics arrays + std::array p_counter_array {}; + std::array p_gauge_array {}; + + // Prometheus dyn_metrics families arrays + std::array*, p_hg_dyn_counter::__size> p_dyn_counter_array {}; + std::array*, p_hg_dyn_gauge::__size> p_dyn_gauge_array {}; + + /// Prometheus connection_pool metrics + std::map p_conn_pool_bytes_data_recv_map {}; + std::map p_conn_pool_bytes_data_sent_map {}; + std::map p_connection_pool_conn_err_map {}; + std::map p_connection_pool_conn_free_map {}; + std::map p_connection_pool_conn_ok_map {}; + std::map p_connection_pool_conn_used_map {}; + std::map p_connection_pool_latency_us_map {}; + std::map p_connection_pool_queries_map {}; + std::map p_connection_pool_status_map {}; + + /// Prometheus gtid_executed metrics + std::map p_gtid_executed_map {}; + + /// Prometheus mysql_error metrics + std::map p_mysql_errors_map {}; + + ////////////////////////////////////////////////////// + } status; + /** + * @brief Update the module prometheus metrics. + */ + void p_update_metrics(); + /** + * @brief Updates the 'mysql_error' counter identified by the 'm_id' parameter, + * or creates a new one in case of not existing. + * + * @param hid The hostgroup identifier. + * @param address The connection address that triggered the error. + * @param port The port of the connection that triggered the error. + * @param errno The error code itself. + */ + void p_update_mysql_error_counter(p_mysql_error_type err_type, unsigned int hid, char* address, uint16_t port, unsigned int code); + + wqueue queue; + // has_gtid_port is set to true if *any* of the servers in mysql_servers has gtid_port enabled + // it is configured during commit() + // NOTE: this variable is currently NOT used, but in future will be able + // to deprecate mysql-default_session_track_gtids because proxysql will + // be automatically able to determine when to enable GTID tracking + std::atomic has_gtid_port; + MySQL_HostGroups_Manager(); + ~MySQL_HostGroups_Manager(); + void init(); + void wrlock(); + void wrunlock(); +#ifdef DEBUG + bool is_locked = false; +#endif + int servers_add(SQLite3_result *resultset); + /** + * @brief Generates a new global checksum for module 'mysql_servers_v2' using the provided hash. + * @param servers_v2_hash The 'raw_checksum' from 'MYHGM_GEN_CLUSTER_ADMIN_MYSQL_SERVERS' or peer node. + * @return Checksum computed using the provided hash, and 'mysql_servers' config tables hashes. + */ + std::string gen_global_mysql_servers_v2_checksum(uint64_t servers_v2_hash); + bool commit(); + bool commit( + const peer_runtime_mysql_servers_t& peer_runtime_mysql_servers, + const peer_mysql_servers_v2_t& peer_mysql_servers_v2, + bool only_commit_runtime_mysql_servers = true, + bool update_version = false + ); + /** + * @brief Extracted from 'commit'. Performs the following actions: + * 1. Re-generates the 'myhgm.mysql_servers' table. + * 2. If supplied 'runtime_mysql_servers' is 'nullptr': + * 1. Gets the contents of the table via 'MYHGM_GEN_CLUSTER_ADMIN_RUNTIME_SERVERS'. + * 2. Save the resultset into 'this->runtime_mysql_servers'. + * 3. If supplied 'runtime_mysql_servers' isn't 'nullptr': + * 1. Updates the 'this->runtime_mysql_servers' with it. + * 4. Updates 'HGM_TABLES::MYSQL_SERVERS' with raw checksum from 'this->runtime_mysql_servers'. + * @param runtime_mysql_servers If not 'nullptr', used to update 'this->runtime_mysql_servers'. + * @return The updated 'MySQL_HostGroups_Manager::runtime_mysql_servers'. + */ + uint64_t commit_update_checksum_from_mysql_servers(SQLite3_result* runtime_mysql_servers = nullptr); + /** + * @brief Analogous to 'commit_generate_mysql_servers_table' but for 'incoming_mysql_servers_v2'. + */ + uint64_t commit_update_checksum_from_mysql_servers_v2(SQLite3_result* incoming_mysql_servers_v2 = nullptr); + /** + * @brief Update all HGM_TABLES checksums and uses them to update the supplied SpookyHash. + * @details Checksums are the checksums for the following tables: + * - mysql_replication_hostgroups + * - mysql_group_replication_hostgroups + * - mysql_galera_hostgroups + * - mysql_aws_aurora_hostgroups + * - mysql_hostgroup_attributes + * + * These checksums are used to compute the global checksum for 'mysql_servers_v2'. + * @param myhash SpookyHash to be updated with all the computed checksums. + * @param init Indicates if the SpookyHash checksum is initialized. + */ + void commit_update_checksums_from_tables(SpookyHash& myhash, bool& init); + /** + * @brief Performs the following actions: + * 1. Gets the current contents of table 'myhgm.TableName', using 'ColumnName' ordering. + * 2. Computes the checksum for that resultset. + * 3. Updates the supplied 'raw_checksum' and the supplied 'SpookyHash' with it. + * @details Stands for 'commit_update_checksum_from_table_1'. + * @param myhash Hash to be updated with the resultset checksum from the selected table. + * @param init If the supplied 'SpookyHash' has already being initialized. + * @param TableName The tablename from which to obtain the resultset for the 'raw_checksum' computation. + * @param ColumnName A column name to use for ordering in the supplied 'TableName'. + * @param raw_checksum A 'raw_checksum' to be updated with the obtained resultset. + */ + void CUCFT1( + SpookyHash& myhash, bool& init, const string& TableName, const string& ColumnName, uint64_t& raw_checksum + ); + /** + * @brief Store the resultset for the 'runtime_mysql_servers' table set that have been loaded to runtime. + * The store configuration is later used by Cluster to propagate current config. + * @param The resulset to be stored replacing the current one. + */ + void save_runtime_mysql_servers(SQLite3_result *); + + /** + * @brief Store the resultset for the 'mysql_servers_v2' table. + * The store configuration is later used by Cluster to propagate current config. + * @param The resulset to be stored replacing the current one. + */ + void save_mysql_servers_v2(SQLite3_result* s); + + /** + * @brief These setters/getter functions store and retrieve the currently hold resultset for the + * 'incoming_*' table set that have been loaded to runtime. The store configuration is later used by + * Cluster to propagate current config. + * @param The resulset to be stored replacing the current one. + */ + + void save_incoming_mysql_table(SQLite3_result *, const string&); + SQLite3_result* get_current_mysql_table(const string& name); + + SQLite3_result * execute_query(char *query, char **error); + + + /** + * @brief Creates a resultset with the current full content of the target table. + * @param string The target table. Valid values are: + * - "mysql_aws_aurora_hostgroups" + * - "mysql_galera_hostgroups" + * - "mysql_group_replication_hostgroups" + * - "mysql_replication_hostgroups" + * - "mysql_hostgroup_attributes" + * - "mysql_servers" + * - "cluster_mysql_servers" + * When targeting 'mysql_servers' table is purged and regenerated. + * @return The generated resultset. + */ + SQLite3_result* dump_table_mysql(const string&); + + /** + * @brief Update the public member resulset 'mysql_servers_to_monitor'. This resulset should contain the latest + * 'mysql_servers' present in 'MySQL_HostGroups_Manager' db, which are not 'OFFLINE_HARD'. The resulset + * fields match the definition of 'monitor_internal.mysql_servers' table. + * @details Several details: + * - Function assumes that 'mysql_servers' table from 'MySQL_HostGroups_Manager' db is ready + * to be consumed, because of this it doesn't perform any of the following operations: + * - Purging 'mysql_servers' table. + * - Regenerating 'mysql_servers' table. + * - Function locks on 'mysql_servers_to_monitor_mutex'. + * @param lock When supplied the function calls 'wrlock()' and 'wrunlock()' functions for accessing the db. + */ + void update_table_mysql_servers_for_monitor(bool lock=false); + + void MyConn_add_to_pool(MySQL_Connection *); + /** + * @brief Creates a new server in the target hostgroup if isn't already present. + * @details If the server is found already in the target hostgroup, no action is taken, unless its status + * is 'OFFLINE_HARD'. In case of finding it as 'OFFLINE_HARD': + * 1. Server hostgroup attributes are reset to known values, so they can be updated. + * 2. Server attributes are updated to either table definition values, or hostgroup 'servers_defaults'. + * 3. Server is bring back as 'ONLINE'. + * @param hid The hostgroup in which the server is to be created (or to bring it back as 'ONLINE'). + * @param srv_info Basic server info to be used during creation. + * @param srv_opts Server creation options. + * @return 0 in case of success, -1 in case of failure. + */ + int create_new_server_in_hg(uint32_t hid, const srv_info_t& srv_info, const srv_opts_t& srv_opts); + /** + * @brief Completely removes server from the target hostgroup if found. + * @details Several actions are taken if server is found: + * - Set the server as 'OFFLINE_HARD'. + * - Drop all current FREE connections to the server. + * - Delete the server from the 'myhgm.mysql_servers' table. + * + * This later step is not required if the caller is already going to perform a full deletion of the + * servers in the target hostgroup. Which is a common operation during table regeneration. + * @param hid Target hostgroup id. + * @param addr Target server address. + * @param port Target server port. + * @return 0 in case of success, -1 in case of failure. + */ + int remove_server_in_hg(uint32_t hid, const string& addr, uint16_t port); + + MySQL_Connection * get_MyConn_from_pool(unsigned int hid, MySQL_Session *sess, bool ff, char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms); + + void drop_all_idle_connections(); + int get_multiple_idle_connections(int, unsigned long long, MySQL_Connection **, int); + SQLite3_result * SQL3_Connection_Pool(bool _reset, int *hid = NULL); + SQLite3_result * SQL3_Free_Connections(); + + void push_MyConn_to_pool(MySQL_Connection *, bool _lock=true); + void push_MyConn_to_pool_array(MySQL_Connection **, unsigned int); + void destroy_MyConn_from_pool(MySQL_Connection *, bool _lock=true); + + void replication_lag_action_inner(MyHGC *, const char*, unsigned int, int, bool); + void replication_lag_action(const std::list& mysql_servers); + void read_only_action(char *hostname, int port, int read_only); + void read_only_action_v2(const std::list& mysql_servers); + unsigned int get_servers_table_version(); + void wait_servers_table_version(unsigned, unsigned); + bool shun_and_killall(char *hostname, int port); + void set_server_current_latency_us(char *hostname, int port, unsigned int _current_latency_us); + unsigned long long Get_Memory_Stats(); + + void add_discovered_servers_to_mysql_servers_and_replication_hostgroups(const vector>& new_servers); + + void update_group_replication_set_offline(char *_hostname, int _port, int _writer_hostgroup, char *error); + void update_group_replication_set_read_only(char *_hostname, int _port, int _writer_hostgroup, char *error); + void update_group_replication_set_writer(char *_hostname, int _port, int _writer_hostgroup); + /** + * @brief Tries to add a new server found during GR autodiscovery to the supplied hostgroup. + * @details For adding the new server, several actions are performed: + * 1. Lookup the target server in the corresponding MyHGC for the supplied hostgroup. + * 2. If server is found, and it's status isn't 'OFFLINE_HARD' do nothing. Otherwise: + * - If server is found as 'OFFLINE_HARD', reset the internal values corresponding to + * 'servers_defaults' values to '-1', update the defaulted values to the ones in its 'MyHGC', lastly + * re-enable the server and log the action. + * - If server isn't found, create it in the corresponding reader hostgroup of the supplied writer + * hostgroup, setting all 'servers_defaults' params as '-1', log the action. + * - After any of the two previous actions, always regenerate servers data structures. + * + * NOTE: Server data structures regeneration requires: + * 1. Purging the 'mysql_servers_table' (Lazy removal of 'OFFLINE_HARD' servers.) + * 2. Regenerate the actual 'myhgm::mysql_servers' table from memory structures. + * 3. Update the 'mysql_servers' resultset used for monitoring. This resultset is used for general + * monitoring actions like 'ping', 'connect'. + * 4. Regenerate the specific resultset for 'Group Replication' monitoring. This resultset is the way to + * communicate back to the main monitoring thread that servers config has changed, and a new thread + * shall be created with the new servers config. This same principle is used for Aurora. + * + * @param _host Server address. + * @param _port Server port. + * @param _wr_hg Writer hostgroup of the cluster being monitored. Autodiscovered servers are always added + * to the reader hostgroup by default, later monitoring actions will re-position the server is required. + */ + void update_group_replication_add_autodiscovered(const std::string& _host, int _port, int _wr_hg); + void converge_group_replication_config(int _writer_hostgroup); + /** + * @brief Set the supplied server as SHUNNED, this function shall be called + * to 'SHUNNED' those servers which replication lag is bigger than: + * - `mysql_thread___monitor_groupreplication_max_transactions_behind_count` + * + * @details The function automatically handles the appropriate operation to + * perform on the supplied server, based on the supplied 'enable' flag and + * in 'monitor_groupreplication_max_transaction_behind_for_read_only' + * variable. In case the value of the variable is: + * + * * '0' or '2': It's required to search the writer hostgroup for + * finding the supplied server. + * * '1' or '2': It's required to search the reader hostgroup for + * finding the supplied server. + * + * @param _hid The writer hostgroup. + * @param address The server address. + * @param port The server port. + * @param lag_counts The computed lag for the sever. + * @param read_only Boolean specifying the read_only flag value of the server. + * @param enable Boolean specifying if the server needs to be disabled / enabled, + * 'true' for enabling the server if it's 'SHUNNED', 'false' for disabling it. + */ + void group_replication_lag_action(int _hid, char *address, unsigned int port, int lag_counts, bool read_only, bool enable); + void update_galera_set_offline(char *_hostname, int _port, int _writer_hostgroup, char *error, bool soft=false); + void update_galera_set_read_only(char *_hostname, int _port, int _writer_hostgroup, char *error); + void update_galera_set_writer(char *_hostname, int _port, int _writer_hostgroup); + void converge_galera_config(int _writer_hostgroup); + + // FIXME : add action functions for AWS Aurora + //void aws_aurora_replication_lag_action(int _whid, int _rhid, char *address, unsigned int port, float current_replication_lag, bool enable, bool verbose=true); + //bool aws_aurora_replication_lag_action(int _whid, int _rhid, char *address, unsigned int port, unsigned int current_replication_lag_us, bool enable, bool is_writer, bool verbose=true); + //void update_aws_aurora_set_writer(int _whid, int _rhid, char *address, unsigned int port, bool verbose=true); + //void update_aws_aurora_set_reader(int _whid, int _rhid, char *_hostname, int _port); + bool aws_aurora_replication_lag_action(int _whid, int _rhid, char *server_id, float current_replication_lag_ms, bool enable, bool is_writer, bool verbose=true); + void update_aws_aurora_set_writer(int _whid, int _rhid, char *server_id, bool verbose=true); + void update_aws_aurora_set_reader(int _whid, int _rhid, char *server_id); + /** + * @brief Updates the resultset and corresponding checksum used by Monitor for AWS Aurora. + * @details This is required to be called when: + * - The 'mysql_aws_aurora_hostgroups' table is regenerated (via 'commit'). + * - When new servers are discovered, and created in already monitored Aurora clusters. + * + * The resultset holds the servers that are present in 'mysql_servers' table, and share hostgroups with + * the **active** clusters specified in 'mysql_aws_aurora_hostgroups'. See query + * 'SELECT_AWS_AURORA_SERVERS_FOR_MONITOR'. + * @param lock Wether if both 'AWS_Aurora_Info_mutex' and 'MySQL_Monitor::aws_aurora_mutex' mutexes should + * be taken or not. + */ + void update_aws_aurora_hosts_monitor_resultset(bool lock=false); + + SQLite3_result * get_stats_mysql_gtid_executed(); + void generate_mysql_gtid_executed_tables(); + bool gtid_exists(MySrvC *mysrvc, char * gtid_uuid, uint64_t gtid_trxid); + + SQLite3_result *SQL3_Get_ConnPool_Stats(); + void increase_reset_counter(); + + void add_mysql_errors(int hostgroup, char *hostname, int port, char *username, char *address, char *schemaname, int err_no, char *last_error); + SQLite3_result *get_mysql_errors(bool); + + void shutdown(); + void unshun_server_all_hostgroups(const char * address, uint16_t port, time_t t, int max_wait_sec, unsigned int *skip_hid); + MySrvC* find_server_in_hg(unsigned int _hid, const std::string& addr, int port); + + MySQLServers_SslParams * get_Server_SSL_Params(char *hostname, int port, char *username); + +private: + void update_hostgroup_manager_mappings(); + uint64_t get_mysql_servers_checksum(SQLite3_result* runtime_mysql_servers = nullptr); + uint64_t get_mysql_servers_v2_checksum(SQLite3_result* incoming_mysql_servers_v2 = nullptr); +}; + +#endif // 0 +#endif // CLASS_BASE_HOSTGROUPS_MANAGER_H diff --git a/include/Base_Session.h b/include/Base_Session.h index 5dc818ed9..2c7a54db5 100644 --- a/include/Base_Session.h +++ b/include/Base_Session.h @@ -121,8 +121,28 @@ class Base_Session { void housekeeping_before_pkts(); virtual void create_new_session_and_reset_connection(DS *_myds) = 0; - - + using TypeConn = typename std::conditional< + std::is_same_v, MySQL_Connection, PgSQL_Connection + >::type; + void update_expired_conns(const std::vector>&); + + void set_unhealthy(); + unsigned int NumActiveTransactions(bool check_savpoint=false); + bool HasOfflineBackends(); + bool SetEventInOfflineBackends(); + /** + * @brief Finds one active transaction in the current backend connections. + * @details Since only one connection is returned, if the session holds multiple backend connections with + * potential transactions, the priority is: + * 1. Connections flagged with 'SERVER_STATUS_IN_TRANS', or 'autocommit=0' in combination with + * 'autocommit_false_is_transaction'. + * 2. Connections with 'autocommit=0' holding a 'SAVEPOINT'. + * 3. Connections with 'unknown transaction status', e.g: connections with errors. + * @param check_savepoint Used to also check for connections holding savepoints. See MySQL bug + * https://bugs.mysql.com/bug.php?id=107875. + * @returns The hostgroup in which the connection was found, -1 in case no connection is found. + */ + int FindOneActiveTransaction(bool check_savepoint=false); }; #endif // CLASS_BASE_SESSION_H diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index f26add749..fb8eef598 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -35,8 +35,8 @@ namespace nlohmann { class json; } //#define STRESSTEST_POOL #endif // DEBUG -#define MHM_PTHREAD_MUTEX +#include "Base_HostGroups_Manager.h" // we have 2 versions of the same tables: with (debug) and without (no debug) checks #ifdef DEBUG @@ -273,68 +273,16 @@ private: enum MySerStatus status; }; -class MySrvList { // MySQL Server List - private: - MyHGC *myhgc; - int find_idx(MySrvC *); +class MySrvList: public BaseSrvList { // MySQL Server List public: - PtrArray *servers; - unsigned int cnt() { return servers->len; } - MySrvList(MyHGC *); - ~MySrvList(); - void add(MySrvC *); - void remove(MySrvC *); - MySrvC * idx(unsigned int i) {return (MySrvC *)servers->index(i); } + MySrvList(MyHGC* hgc) : BaseSrvList(hgc) {} }; -class MyHGC { // MySQL Host Group Container + +class MyHGC: public BaseHGC { public: - unsigned int hid; - std::atomic num_online_servers; - time_t last_log_time_num_online_servers; - unsigned long long current_time_now; - uint32_t new_connections_now; - MySrvList *mysrvs; - struct { // this is a series of attributes specific for each hostgroup - char * init_connect; - char * comment; - char * ignore_session_variables_text; // this is the original version (text format) of ignore_session_variables - uint32_t max_num_online_servers; - uint32_t throttle_connections_per_sec; - int32_t monitor_slave_lag_when_null; - int8_t autocommit; - int8_t free_connections_pct; - int8_t handle_warnings; - bool multiplex; - bool connection_warming; - bool configured; // this variable controls if attributes are configured or not. If not configured, they do not apply - bool initialized; // this variable controls if attributes were ever configured or not. Used by reset_attributes() - nlohmann::json * ignore_session_variables_json = NULL; // the JSON format of ignore_session_variables - } attributes; - struct { - int64_t weight; - int64_t max_connections; - int32_t use_ssl; - } servers_defaults; - void reset_attributes(); - inline - bool handle_warnings_enabled() const { - return attributes.configured == true && attributes.handle_warnings != -1 ? attributes.handle_warnings : mysql_thread___handle_warnings; - } - inline - int32_t get_monitor_slave_lag_when_null() const { - return attributes.configured == true && attributes.monitor_slave_lag_when_null != -1 ? attributes.monitor_slave_lag_when_null : mysql_thread___monitor_slave_lag_when_null; - } - MyHGC(int); - ~MyHGC(); + MyHGC(int _hid) : BaseHGC(_hid) {} MySrvC *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess); - void refresh_online_server_count(); - void log_num_online_server_count_error(); - inline - bool online_servers_within_threshold() const { - if (num_online_servers.load(std::memory_order_relaxed) <= attributes.max_num_online_servers) return true; - return false; - } }; class Group_Replication_Info { @@ -600,19 +548,16 @@ struct srv_opts_t { int32_t use_ssl; }; -class MySQL_HostGroups_Manager { +class MySQL_HostGroups_Manager : public Base_HostGroups_Manager { private: +#if 0 SQLite3DB *admindb; SQLite3DB *mydb; pthread_mutex_t readonly_mutex; std::set read_only_set1; std::set read_only_set2; -#ifdef MHM_PTHREAD_MUTEX pthread_mutex_t lock; -#else - rwlock_t rwlock; -#endif - +#endif // 0 enum HGM_TABLES { MYSQL_SERVERS_V2 = 0, MYSQL_REPLICATION_HOSTGROUPS, @@ -727,15 +672,16 @@ class MySQL_HostGroups_Manager { uint64_t hgsm_mysql_replication_hostgroups_checksum = 0; +#if 0 PtrArray *MyHostGroups; std::unordered_mapMyHostGroups_map; - +#endif // 0 std::mutex Servers_SSL_Params_map_mutex; std::unordered_map Servers_SSL_Params_map; - +#if 0 MyHGC * MyHGC_find(unsigned int); MyHGC * MyHGC_create(unsigned int); - +#endif // 0 void add(MySrvC *, unsigned int); void purge_mysql_servers_table(); void generate_mysql_servers_table(int *_onlyhg=NULL); @@ -952,11 +898,13 @@ class MySQL_HostGroups_Manager { MySQL_HostGroups_Manager(); ~MySQL_HostGroups_Manager(); void init(); +#if 0 void wrlock(); void wrunlock(); #ifdef DEBUG bool is_locked = false; #endif +#endif // 0 int servers_add(SQLite3_result *resultset); /** * @brief Generates a new global checksum for module 'mysql_servers_v2' using the provided hash. @@ -1041,7 +989,7 @@ class MySQL_HostGroups_Manager { void save_incoming_mysql_table(SQLite3_result *, const string&); SQLite3_result* get_current_mysql_table(const string& name); - SQLite3_result * execute_query(char *query, char **error); + //SQLite3_result * execute_query(char *query, char **error); /** * @brief Creates a resultset with the current full content of the target table. * @param string The target table. Valid values are: @@ -1070,7 +1018,6 @@ class MySQL_HostGroups_Manager { * @param lock When supplied the function calls 'wrlock()' and 'wrunlock()' functions for accessing the db. */ void update_table_mysql_servers_for_monitor(bool lock=false); - MyHGC * MyHGC_lookup(unsigned int); void MyConn_add_to_pool(MySQL_Connection *); /** diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index b47f4084a..22d89f258 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -268,7 +268,6 @@ class MySQL_Session: public Base_Session>&); + //void update_expired_conns(const std::vector>&); /** * @brief Performs the final operations after current query has finished to be executed. It updates the session * 'transaction_persistent_hostgroup', and updates the 'MySQL_Data_Stream' and 'MySQL_Connection' before diff --git a/include/PgSQL_HostGroups_Manager.h b/include/PgSQL_HostGroups_Manager.h index 9efd33af2..a907dc204 100644 --- a/include/PgSQL_HostGroups_Manager.h +++ b/include/PgSQL_HostGroups_Manager.h @@ -35,8 +35,8 @@ namespace nlohmann { class json; } //#define STRESSTEST_POOL #endif // DEBUG -#define MHM_PTHREAD_MUTEX +#include "Base_HostGroups_Manager.h" // we have 2 versions of the same tables: with (debug) and without (no debug) checks #ifdef DEBUG @@ -240,122 +240,17 @@ class PgSQL_SrvC { // MySQL Server Container } }; -class PgSQL_SrvList { // MySQL Server List - private: - PgSQL_HGC *myhgc; - int find_idx(PgSQL_SrvC *); - public: - PtrArray *servers; - unsigned int cnt() { return servers->len; } - PgSQL_SrvList(PgSQL_HGC *); - ~PgSQL_SrvList(); - void add(PgSQL_SrvC *); - void remove(PgSQL_SrvC *); - PgSQL_SrvC * idx(unsigned int i) {return (PgSQL_SrvC *)servers->index(i); } -}; - -class PgSQL_HGC { // MySQL Host Group Container - public: - unsigned int hid; - unsigned long long current_time_now; - uint32_t new_connections_now; - PgSQL_SrvList *mysrvs; - struct { // this is a series of attributes specific for each hostgroup - char * init_connect; - char * comment; - char * ignore_session_variables_text; // this is the original version (text format) of ignore_session_variables - uint32_t max_num_online_servers; - uint32_t throttle_connections_per_sec; - int8_t autocommit; - int8_t free_connections_pct; - int8_t handle_warnings; - bool multiplex; - bool connection_warming; - bool configured; // this variable controls if attributes are configured or not. If not configured, they do not apply - bool initialized; // this variable controls if attributes were ever configured or not. Used by reset_attributes() - nlohmann::json * ignore_session_variables_json = NULL; // the JSON format of ignore_session_variables - } attributes; - struct { - int64_t weight; - int64_t max_connections; - int32_t use_ssl; - } servers_defaults; - void reset_attributes(); - inline - bool handle_warnings_enabled() const { - return attributes.configured == true && attributes.handle_warnings != -1 ? attributes.handle_warnings : mysql_thread___handle_warnings; - } - PgSQL_HGC(int); - ~PgSQL_HGC(); - PgSQL_SrvC *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, PgSQL_Session *sess); -}; - -class PgSQL_Group_Replication_Info { +class PgSQL_SrvList: public BaseSrvList { public: - int writer_hostgroup; - int backup_writer_hostgroup; - int reader_hostgroup; - int offline_hostgroup; - int max_writers; - int max_transactions_behind; - char *comment; - bool active; - int writer_is_also_reader; - bool __active; - bool need_converge; // this is set to true on LOAD PgSQL SERVERS TO RUNTIME . This ensure that checks wil take an action - int current_num_writers; - int current_num_backup_writers; - int current_num_readers; - int current_num_offline; - PgSQL_Group_Replication_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); - bool update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); - ~PgSQL_Group_Replication_Info(); + PgSQL_SrvList(PgSQL_HGC* hgc) : BaseSrvList(hgc) {} + friend class PgSQL_HGC; }; -class PgSQL_Galera_Info { - public: - int writer_hostgroup; - int backup_writer_hostgroup; - int reader_hostgroup; - int offline_hostgroup; - int max_writers; - int max_transactions_behind; - char *comment; - bool active; - int writer_is_also_reader; - bool __active; - bool need_converge; // this is set to true on LOAD PgSQL SERVERS TO RUNTIME . This ensure that checks wil take an action - int current_num_writers; - int current_num_backup_writers; - int current_num_readers; - int current_num_offline; - PgSQL_Galera_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); - bool update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c); - ~PgSQL_Galera_Info(); -}; -class PgSQL_AWS_Aurora_Info { +class PgSQL_HGC: public BaseHGC { public: - int writer_hostgroup; - int reader_hostgroup; - int aurora_port; - int max_lag_ms; - int add_lag_ms; - int min_lag_ms; - int lag_num_checks; - int check_interval_ms; - int check_timeout_ms; - int writer_is_also_reader; - int new_reader_weight; - // TODO - // add intermediary status value, for example the last check time - char * domain_name; - char * comment; - bool active; - bool __active; - PgSQL_AWS_Aurora_Info(int w, int r, int _port, char *_end_addr, int maxl, int al, int minl, int lnc, int ci, int ct, bool _a, int wiar, int nrw, char *c); - bool update(int r, int _port, char *_end_addr, int maxl, int al, int minl, int lnc, int ci, int ct, bool _a, int wiar, int nrw, char *c); - ~PgSQL_AWS_Aurora_Info(); + PgSQL_HGC(int _hid) : BaseHGC(_hid) {} + PgSQL_SrvC *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, PgSQL_Session *sess); }; struct PgSQL_p_hg_counter { @@ -490,19 +385,16 @@ struct PgSQL_srv_opts_t { int32_t use_ssl; }; -class PgSQL_HostGroups_Manager { - private: +class PgSQL_HostGroups_Manager : public Base_HostGroups_Manager { +#if 0 SQLite3DB *admindb; SQLite3DB *mydb; pthread_mutex_t readonly_mutex; std::set read_only_set1; std::set read_only_set2; -#ifdef MHM_PTHREAD_MUTEX pthread_mutex_t lock; -#else - rwlock_t rwlock; -#endif - +#endif // 0 + private: enum HGM_TABLES { PgSQL_SERVERS_V2 = 0, PgSQL_REPLICATION_HOSTGROUPS, @@ -615,12 +507,13 @@ class PgSQL_HostGroups_Manager { */ uint64_t hgsm_pgsql_replication_hostgroups_checksum = 0; - +#if 0 PtrArray *MyHostGroups; std::unordered_mapMyHostGroups_map; PgSQL_HGC * MyHGC_find(unsigned int); PgSQL_HGC * MyHGC_create(unsigned int); +#endif // 0 void add(PgSQL_SrvC *, unsigned int); void purge_pgsql_servers_table(); @@ -679,10 +572,6 @@ class PgSQL_HostGroups_Manager { * @brief Update the prometheus "connection_pool" counters. */ void p_update_connection_pool(); - /** - * @brief Update the "stats_pgsql_gtid_executed" counters. - */ - void p_update_pgsql_gtid_executed(); void p_update_connection_pool_update_counter( const std::string& endpoint_id, const std::map& labels, @@ -801,8 +690,8 @@ class PgSQL_HostGroups_Manager { PgSQL_HostGroups_Manager(); ~PgSQL_HostGroups_Manager(); void init(); - void wrlock(); - void wrunlock(); + //void wrlock(); + //void wrunlock(); int servers_add(SQLite3_result *resultset); /** * @brief Generates a new global checksum for module 'pgsql_servers_v2' using the provided hash. @@ -883,7 +772,7 @@ class PgSQL_HostGroups_Manager { void save_incoming_pgsql_table(SQLite3_result *, const string&); SQLite3_result* get_current_pgsql_table(const string& name); - SQLite3_result * execute_query(char *query, char **error); + //SQLite3_result * execute_query(char *query, char **error); /** * @brief Creates a resultset with the current full content of the target table. * @param string The target table. Valid values are: @@ -909,7 +798,6 @@ class PgSQL_HostGroups_Manager { * @param lock When supplied the function calls 'wrlock()' and 'wrunlock()' functions for accessing the db. */ void update_table_pgsql_servers_for_monitor(bool lock=false); - PgSQL_HGC * MyHGC_lookup(unsigned int); void MyConn_add_to_pool(PgSQL_Connection *); /** @@ -962,10 +850,6 @@ class PgSQL_HostGroups_Manager { void set_server_current_latency_us(char *hostname, int port, unsigned int _current_latency_us); unsigned long long Get_Memory_Stats(); - SQLite3_result * get_stats_pgsql_gtid_executed(); - void generate_pgsql_gtid_executed_tables(); - bool gtid_exists(PgSQL_SrvC *mysrvc, char * gtid_uuid, uint64_t gtid_trxid); - SQLite3_result *SQL3_Get_ConnPool_Stats(); void increase_reset_counter(); diff --git a/include/PgSQL_Session.h b/include/PgSQL_Session.h index 08980e87b..91f2f9430 100644 --- a/include/PgSQL_Session.h +++ b/include/PgSQL_Session.h @@ -246,7 +246,6 @@ private: void handler_WCD_SS_MCQ_qpo_OK_msg(PtrSize_t* pkt); void handler_WCD_SS_MCQ_qpo_error_msg(PtrSize_t* pkt); void handler_WCD_SS_MCQ_qpo_LargePacket(PtrSize_t* pkt); - // int handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(PtrSize_t *pkt, bool *lock_hostgroup, unsigned int nTrx, string& nq); public: bool handler_again___status_SETTING_GENERIC_VARIABLE(int* _rc, const char* var_name, const char* var_value, bool no_quote = false, bool set_transaction = false); @@ -365,7 +364,7 @@ public: PgSQL_Session(); ~PgSQL_Session(); - void set_unhealthy(); + //void set_unhealthy(); void set_status(enum session_status e); int handler(); @@ -378,9 +377,9 @@ public: void SQLite3_to_MySQL(SQLite3_result*, char*, int, MySQL_Protocol*, bool in_transaction = false, bool deprecate_eof_active = false) override; void PgSQL_Result_to_PgSQL_wire(PgSQL_Connection* conn, PgSQL_Data_Stream* _myds = NULL); void MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT* stmt, PgSQL_Connection* myconn); - unsigned int NumActiveTransactions(bool check_savpoint = false); - bool HasOfflineBackends(); - bool SetEventInOfflineBackends(); + //unsigned int NumActiveTransactions(bool check_savpoint = false); + //bool HasOfflineBackends(); + //bool SetEventInOfflineBackends(); /** * @brief Finds one active transaction in the current backend connections. * @details Since only one connection is returned, if the session holds multiple backend connections with @@ -393,7 +392,7 @@ public: * https://bugs.mysql.com/bug.php?id=107875. * @returns The hostgroup in which the connection was found, -1 in case no connection is found. */ - int FindOneActiveTransaction(bool check_savepoint = false); + //int FindOneActiveTransaction(bool check_savepoint = false); unsigned long long IdleTime(); //void reset_all_backends(); @@ -401,7 +400,7 @@ public: void Memory_Stats(); void create_new_session_and_reset_connection(PgSQL_Data_Stream* _myds) override; bool handle_command_query_kill(PtrSize_t*); - void update_expired_conns(const std::vector>&); + //void update_expired_conns(const std::vector>&); /** * @brief Performs the final operations after current query has finished to be executed. It updates the session * 'transaction_persistent_hostgroup', and updates the 'PgSQL_Data_Stream' and 'PgSQL_Connection' before diff --git a/lib/Admin_Bootstrap.cpp b/lib/Admin_Bootstrap.cpp index 861f12490..d51e36911 100644 --- a/lib/Admin_Bootstrap.cpp +++ b/lib/Admin_Bootstrap.cpp @@ -617,6 +617,8 @@ bool ProxySQL_Admin::init(const bootstrap_info_t& bootstrap_info) { insert_into_tables_defs(tables_defs_config, "pgsql_ldap_mapping", ADMIN_SQLITE_TABLE_PGSQL_LDAP_MAPPING); insert_into_tables_defs(tables_defs_config, "pgsql_query_rules", ADMIN_SQLITE_TABLE_PGSQL_QUERY_RULES); insert_into_tables_defs(tables_defs_config, "pgsql_query_rules_fast_routing", ADMIN_SQLITE_TABLE_PGSQL_QUERY_RULES_FAST_ROUTING); + insert_into_tables_defs(tables_defs_config, "pgsql_hostgroup_attributes", ADMIN_SQLITE_TABLE_PGSQL_HOSTGROUP_ATTRIBUTES); + insert_into_tables_defs(tables_defs_config, "pgsql_replication_hostgroups", ADMIN_SQLITE_TABLE_PGSQL_REPLICATION_HOSTGROUPS); insert_into_tables_defs(tables_defs_config, "pgsql_firewall_whitelist_users", ADMIN_SQLITE_TABLE_PGSQL_FIREWALL_WHITELIST_USERS); insert_into_tables_defs(tables_defs_config, "pgsql_firewall_whitelist_rules", ADMIN_SQLITE_TABLE_PGSQL_FIREWALL_WHITELIST_RULES); insert_into_tables_defs(tables_defs_config, "pgsql_firewall_whitelist_sqli_fingerprints", ADMIN_SQLITE_TABLE_PGSQL_FIREWALL_WHITELIST_SQLI_FINGERPRINTS); diff --git a/lib/Admin_Handler.cpp b/lib/Admin_Handler.cpp index b00075383..d0f055e4d 100644 --- a/lib/Admin_Handler.cpp +++ b/lib/Admin_Handler.cpp @@ -3469,6 +3469,21 @@ void admin_session_handler(Client_Session sess, void *_pa, PtrSize_t *pkt) { goto __run_query; } + if (query_no_space_length == strlen("SHOW PGSQL VARIABLES") && !strncasecmp("SHOW PGSQL VARIABLES", query_no_space, query_no_space_length)) { + l_free(query_length, query); + query = l_strdup("SELECT variable_name AS Variable_name, variable_value AS Value FROM global_variables WHERE variable_name LIKE 'pgsql-\%' ORDER BY variable_name"); + query_length = strlen(query) + 1; + goto __run_query; + } + + /*if (query_no_space_length == strlen("SHOW PGSQL STATUS") && !strncasecmp("SHOW PGSQL STATUS", query_no_space, query_no_space_length)) { + l_free(query_length, query); + query = l_strdup("SELECT Variable_Name AS Variable_name, Variable_Value AS Value FROM stats_pgsql_global ORDER BY variable_name"); + query_length = strlen(query) + 1; + GloAdmin->stats___mysql_global(); + goto __run_query; + }*/ + strA=(char *)"SHOW CREATE TABLE "; strB=(char *)"SELECT name AS 'table' , REPLACE(REPLACE(sql,' , ', X'2C0A20202020'),'CREATE TABLE %s (','CREATE TABLE %s ('||X'0A20202020') AS 'Create Table' FROM %s.sqlite_master WHERE type='table' AND name='%s'"; strAl=strlen(strA); diff --git a/lib/BaseHGC.cpp b/lib/BaseHGC.cpp new file mode 100644 index 000000000..54b0d4ca5 --- /dev/null +++ b/lib/BaseHGC.cpp @@ -0,0 +1,131 @@ +#include "../deps/json/json.hpp" +using json = nlohmann::json; +#define PROXYJSON + +#include "Base_HostGroups_Manager.h" + + +template BaseHGC::BaseHGC(int); +template BaseHGC::~BaseHGC(); +template void BaseHGC::log_num_online_server_count_error(); +template void BaseHGC::reset_attributes(); +template void BaseHGC::refresh_online_server_count(); +template BaseHGC::BaseHGC(int); +template BaseHGC::~BaseHGC(); +template void BaseHGC::log_num_online_server_count_error(); +template void BaseHGC::reset_attributes(); +template void BaseHGC::refresh_online_server_count(); + +template +using TypeSrvC = typename std::conditional< + std::is_same_v, MySrvC, PgSQL_SrvC +>::type; + +template +using TypeSess = typename std::conditional< + std::is_same_v, MySQL_Session, PgSQL_Session +>::type; + + +#include "MySQL_HostGroups_Manager.h" + + +#ifdef TEST_AURORA +if constexpr (std::is_same_v) { +static unsigned long long array_mysrvc_total = 0; +static unsigned long long array_mysrvc_cands = 0; +} +#endif // TEST_AURORA + +extern MySQL_Threads_Handler *GloMTH; + + +template +BaseHGC::BaseHGC(int _hid) { + hid=_hid; + if constexpr (std::is_same_v) { + mysrvs=new MySrvList(static_cast(this)); + } else if constexpr (std::is_same_v) { + mysrvs=new PgSQL_SrvList(static_cast(this)); + } else { + assert(0); + } + current_time_now = 0; + new_connections_now = 0; + attributes.initialized = false; + reset_attributes(); + // Uninitialized server defaults. Should later be initialized via 'mysql_hostgroup_attributes'. + servers_defaults.weight = -1; + servers_defaults.max_connections = -1; + servers_defaults.use_ssl = -1; + num_online_servers.store(0, std::memory_order_relaxed);; + last_log_time_num_online_servers = 0; +} + + +template +void BaseHGC::reset_attributes() { + if (attributes.initialized == false) { + attributes.init_connect = NULL; + attributes.comment = NULL; + attributes.ignore_session_variables_text = NULL; + } + attributes.initialized = true; + attributes.configured = false; + attributes.max_num_online_servers = 1000000; + attributes.throttle_connections_per_sec = 1000000; + attributes.autocommit = -1; + attributes.free_connections_pct = 10; + attributes.handle_warnings = -1; + attributes.monitor_slave_lag_when_null = -1; + attributes.multiplex = true; + attributes.connection_warming = false; + free(attributes.init_connect); + attributes.init_connect = NULL; + free(attributes.comment); + attributes.comment = NULL; + free(attributes.ignore_session_variables_text); + attributes.ignore_session_variables_text = NULL; + if (attributes.ignore_session_variables_json) { + delete attributes.ignore_session_variables_json; + attributes.ignore_session_variables_json = NULL; + } +} + +template +BaseHGC::~BaseHGC() { + reset_attributes(); // free all memory + delete mysrvs; +} + +template +void BaseHGC::refresh_online_server_count() { + if (__sync_fetch_and_add(&glovars.shutdown, 0) != 0) + return; +#ifdef DEBUG + assert(MyHGM->is_locked); +#endif + unsigned int online_servers_count = 0; + if constexpr (std::is_same_v) { // FIXME: this logic for now is enabled only for MySQL + for (unsigned int i = 0; i < mysrvs->servers->len; i++) { + TypeSrvC* mysrvc = (TypeSrvC*)mysrvs->servers->index(i); + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { + online_servers_count++; + } + } + } + num_online_servers.store(online_servers_count, std::memory_order_relaxed); +} + +template +void BaseHGC::log_num_online_server_count_error() { + const time_t curtime = time(NULL); + // if this is the first time the method is called or if more than 10 seconds have passed since the last log + if (last_log_time_num_online_servers == 0 || + ((curtime - last_log_time_num_online_servers) > 10)) { + last_log_time_num_online_servers = curtime; + proxy_error( + "Number of online servers detected in a hostgroup exceeds the configured maximum online servers. hostgroup:%u, num_online_servers:%u, max_online_servers:%u\n", + hid, num_online_servers.load(std::memory_order_relaxed), attributes.max_num_online_servers); + } +} diff --git a/lib/BaseSrvList.cpp b/lib/BaseSrvList.cpp new file mode 100644 index 000000000..991861192 --- /dev/null +++ b/lib/BaseSrvList.cpp @@ -0,0 +1,72 @@ +#include "Base_HostGroups_Manager.h" + +template +using TypeSrvC = typename std::conditional< + std::is_same_v, MySrvC, PgSQL_SrvC +>::type; + +template BaseSrvList::BaseSrvList(MyHGC*); +template BaseSrvList::~BaseSrvList(); +template void BaseSrvList::add(MySrvC*); + +template BaseSrvList::BaseSrvList(PgSQL_HGC*); +template BaseSrvList::~BaseSrvList(); +template void BaseSrvList::add(PgSQL_SrvC*); + + +template +BaseSrvList::BaseSrvList(HGC *_myhgc) { + myhgc=_myhgc; + servers=new PtrArray(); +} + +template +void BaseSrvList::add(TypeSrvC *s) { + if (s->myhgc==NULL) { + s->myhgc=myhgc; + } + servers->add(s); + if constexpr (std::is_same_v) { + myhgc->refresh_online_server_count(); + } else if constexpr (std::is_same_v) { + //myhgc->refresh_online_server_count(); FIXME: not implemented + } else { + assert(0); + } +} + + +template +int BaseSrvList::find_idx(TypeSrvC *s) { + for (unsigned int i=0; ilen; i++) { + TypeSrvC *mysrv=(TypeSrvC *)servers->index(i); + if (mysrv==s) { + return (unsigned int)i; + } + } + return -1; +} + +template +void BaseSrvList::remove(TypeSrvC *s) { + int i=find_idx(s); + assert(i>=0); + servers->remove_index_fast((unsigned int)i); + if constexpr (std::is_same_v) { + myhgc->refresh_online_server_count(); + } else if constexpr (std::is_same_v) { + //myhgc->refresh_online_server_count(); FIXME: not implemented + } else { + assert(0); + } +} + +template +BaseSrvList::~BaseSrvList() { + myhgc=NULL; + while (servers->len) { + TypeSrvC *mysrvc=(TypeSrvC *)servers->remove_index_fast(0); + delete mysrvc; + } + delete servers; +} diff --git a/lib/Base_HostGroups_Manager.cpp b/lib/Base_HostGroups_Manager.cpp new file mode 100644 index 000000000..4a27e7888 --- /dev/null +++ b/lib/Base_HostGroups_Manager.cpp @@ -0,0 +1,4797 @@ +#include "../deps/json/json.hpp" +using json = nlohmann::json; +#define PROXYJSON + +#include "MySQL_HostGroups_Manager.h" +#include "proxysql.h" +#include "cpp.h" + +#include "MySQL_PreparedStatement.h" +#include "MySQL_Data_Stream.h" + +#include +#include +#include + +#include "prometheus/counter.h" +#include "prometheus/detail/builder.h" +#include "prometheus/family.h" +#include "prometheus/gauge.h" + +#include "prometheus_helpers.h" +#include "proxysql_utils.h" + +#define char_malloc (char *)malloc +#define itostr(__s, __i) { __s=char_malloc(32); sprintf(__s, "%lld", __i); } + +#include "thread.h" +#include "wqueue.h" + +#include "ev.h" + +#include +#include +#include + +using std::function; + +#include "Base_HostGroups_Manager.h" + + +template Base_HostGroups_Manager::Base_HostGroups_Manager(); +template MyHGC * Base_HostGroups_Manager::MyHGC_find(unsigned int); +template MyHGC * Base_HostGroups_Manager::MyHGC_create(unsigned int); +template MyHGC * Base_HostGroups_Manager::MyHGC_lookup(unsigned int); +template void Base_HostGroups_Manager::wrlock(); +template void Base_HostGroups_Manager::wrunlock(); + +template Base_HostGroups_Manager::Base_HostGroups_Manager(); +template PgSQL_HGC * Base_HostGroups_Manager::MyHGC_find(unsigned int); +template PgSQL_HGC * Base_HostGroups_Manager::MyHGC_create(unsigned int); +template PgSQL_HGC * Base_HostGroups_Manager::MyHGC_lookup(unsigned int); +template void Base_HostGroups_Manager::wrlock(); +template void Base_HostGroups_Manager::wrunlock(); + +template SQLite3_result * Base_HostGroups_Manager::execute_query(char*, char**); + +#if 0 +#define SAFE_SQLITE3_STEP(_stmt) do {\ + do {\ + rc=(*proxy_sqlite3_step)(_stmt);\ + if (rc!=SQLITE_DONE) {\ + assert(rc==SQLITE_LOCKED);\ + usleep(100);\ + }\ + } while (rc!=SQLITE_DONE);\ +} while (0) + +extern ProxySQL_Admin *GloAdmin; + +extern MySQL_Threads_Handler *GloMTH; + +extern MySQL_Monitor *GloMyMon; + +class MySrvConnList; +class MySrvC; +class MySrvList; +class MyHGC; + +const int MYSQL_ERRORS_STATS_FIELD_NUM = 11; + + +static int wait_for_mysql(MYSQL *mysql, int status) { + struct pollfd pfd; + int timeout, res; + + pfd.fd = mysql_get_socket(mysql); + pfd.events = + (status & MYSQL_WAIT_READ ? POLLIN : 0) | + (status & MYSQL_WAIT_WRITE ? POLLOUT : 0) | + (status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0); + timeout = 1; + res = poll(&pfd, 1, timeout); + if (res == 0) + return MYSQL_WAIT_TIMEOUT | status; + else if (res < 0) + return MYSQL_WAIT_TIMEOUT; + else { + int status = 0; + if (pfd.revents & POLLIN) status |= MYSQL_WAIT_READ; + if (pfd.revents & POLLOUT) status |= MYSQL_WAIT_WRITE; + if (pfd.revents & POLLPRI) status |= MYSQL_WAIT_EXCEPT; + return status; + } +} + +/** + * @brief Helper function used to try to extract a value from the JSON field 'servers_defaults'. + * + * @param j JSON object constructed from 'servers_defaults' field. + * @param hid Hostgroup for which the 'servers_defaults' is defined in 'mysql_hostgroup_attributes'. Used for + * error logging. + * @param key The key for the value to be extracted. + * @param val_check A validation function, checks if the value is within a expected range. + * + * @return The value extracted from the supplied JSON. In case of error '-1', and error cause is logged. + */ +template ::value, bool>::type = true> +T j_get_srv_default_int_val( + const json& j, uint32_t hid, const string& key, const function& val_check +) { + if (j.find(key) != j.end()) { + const json::value_t val_type = j[key].type(); + const char* type_name = j[key].type_name(); + + if (val_type == json::value_t::number_integer || val_type == json::value_t::number_unsigned) { + T val = j[key].get(); + + if (val_check(val)) { + return val; + } else { + proxy_error( + "Invalid value %ld supplied for 'mysql_hostgroup_attributes.servers_defaults.%s' for hostgroup %d." + " Value NOT UPDATED.\n", + static_cast(val), key.c_str(), hid + ); + } + } else { + proxy_error( + "Invalid type '%s'(%hhu) supplied for 'mysql_hostgroup_attributes.servers_defaults.%s' for hostgroup %d." + " Value NOT UPDATED.\n", + type_name, static_cast(val_type), key.c_str(), hid + ); + } + } + + return static_cast(-1); +} + + +//static void * HGCU_thread_run() { +static void * HGCU_thread_run() { + PtrArray *conn_array=new PtrArray(); + while(1) { + MySQL_Connection *myconn= NULL; + myconn = (MySQL_Connection *)MyHGM->queue.remove(); + if (myconn==NULL) { + // intentionally exit immediately + delete conn_array; + return NULL; + } + conn_array->add(myconn); + while (MyHGM->queue.size()) { + myconn=(MySQL_Connection *)MyHGM->queue.remove(); + if (myconn==NULL) { + delete conn_array; + return NULL; + } + conn_array->add(myconn); + } + unsigned int l=conn_array->len; + int *errs=(int *)malloc(sizeof(int)*l); + int *statuses=(int *)malloc(sizeof(int)*l); + my_bool *ret=(my_bool *)malloc(sizeof(my_bool)*l); + int i; + for (i=0;i<(int)l;i++) { + myconn->reset(); + MyHGM->increase_reset_counter(); + myconn=(MySQL_Connection *)conn_array->index(i); + if (myconn->mysql->net.pvio && myconn->mysql->net.fd && myconn->mysql->net.buff) { + MySQL_Connection_userinfo *userinfo = myconn->userinfo; + char *auth_password = NULL; + if (userinfo->password) { + if (userinfo->password[0]=='*') { // we don't have the real password, let's pass sha1 + auth_password=userinfo->sha1_pass; + } else { + auth_password=userinfo->password; + } + } + //async_exit_status = mysql_change_user_start(&ret_bool,mysql,_ui->username, auth_password, _ui->schemaname); + // we first reset the charset to a default one. + // this to solve the problem described here: + // https://github.com/sysown/proxysql/pull/3249#issuecomment-761887970 + if (myconn->mysql->charset->nr >= 255) + mysql_options(myconn->mysql, MYSQL_SET_CHARSET_NAME, myconn->mysql->charset->csname); + statuses[i]=mysql_change_user_start(&ret[i], myconn->mysql, myconn->userinfo->username, auth_password, myconn->userinfo->schemaname); + if (myconn->mysql->net.pvio==NULL || myconn->mysql->net.fd==0 || myconn->mysql->net.buff==NULL) { + statuses[i]=0; ret[i]=1; + } + } else { + statuses[i]=0; + ret[i]=1; + } + } + for (i=0;i<(int)conn_array->len;i++) { + if (statuses[i]==0) { + myconn=(MySQL_Connection *)conn_array->remove_index_fast(i); + if (!ret[i]) { + MyHGM->push_MyConn_to_pool(myconn); + } else { + myconn->send_quit=false; + MyHGM->destroy_MyConn_from_pool(myconn); + } + statuses[i]=statuses[conn_array->len]; + ret[i]=ret[conn_array->len]; + i--; + } + } + unsigned long long now=monotonic_time(); + while (conn_array->len && ((monotonic_time() - now) < 1000000)) { + usleep(50); + for (i=0;i<(int)conn_array->len;i++) { + myconn=(MySQL_Connection *)conn_array->index(i); + if (myconn->mysql->net.pvio && myconn->mysql->net.fd && myconn->mysql->net.buff) { + statuses[i]=wait_for_mysql(myconn->mysql, statuses[i]); + if (myconn->mysql->net.pvio && myconn->mysql->net.fd && myconn->mysql->net.buff) { + if ((statuses[i] & MYSQL_WAIT_TIMEOUT) == 0) { + statuses[i]=mysql_change_user_cont(&ret[i], myconn->mysql, statuses[i]); + if (myconn->mysql->net.pvio==NULL || myconn->mysql->net.fd==0 || myconn->mysql->net.buff==NULL ) { + statuses[i]=0; ret[i]=1; + } + } + } else { + statuses[i]=0; ret[i]=1; + } + } else { + statuses[i]=0; ret[i]=1; + } + } + for (i=0;i<(int)conn_array->len;i++) { + if (statuses[i]==0) { + myconn=(MySQL_Connection *)conn_array->remove_index_fast(i); + if (!ret[i]) { + myconn->reset(); + MyHGM->push_MyConn_to_pool(myconn); + } else { + myconn->send_quit=false; + MyHGM->destroy_MyConn_from_pool(myconn); + } + statuses[i]=statuses[conn_array->len]; + ret[i]=ret[conn_array->len]; + i--; + } + } + } + while (conn_array->len) { + // we reached here, and there are still connections + myconn=(MySQL_Connection *)conn_array->remove_index_fast(0); + myconn->send_quit=false; + MyHGM->destroy_MyConn_from_pool(myconn); + } + free(statuses); + free(errs); + free(ret); + } + delete conn_array; +} + + +using metric_name = std::string; +using metric_help = std::string; +using metric_tags = std::map; + +using hg_counter_tuple = + std::tuple< + p_hg_counter::metric, + metric_name, + metric_help, + metric_tags + >; + +using hg_gauge_tuple = + std::tuple< + p_hg_gauge::metric, + metric_name, + metric_help, + metric_tags + >; + +using hg_dyn_counter_tuple = + std::tuple< + p_hg_dyn_counter::metric, + metric_name, + metric_help, + metric_tags + >; + +using hg_dyn_gauge_tuple = + std::tuple< + p_hg_dyn_gauge::metric, + metric_name, + metric_help, + metric_tags + >; + +using hg_counter_vector = std::vector; +using hg_gauge_vector = std::vector; +using hg_dyn_counter_vector = std::vector; +using hg_dyn_gauge_vector = std::vector; + +/** + * @brief Metrics map holding the metrics for the 'MySQL_HostGroups_Manager' module. + * + * @note Many metrics in this map, share a common "id name", because + * they differ only by label, because of this, HELP is shared between + * them. For better visual identification of this groups they are + * sepparated using a line separator comment. + */ +const std::tuple< + hg_counter_vector, + hg_gauge_vector, + hg_dyn_counter_vector, + hg_dyn_gauge_vector +> +hg_metrics_map = std::make_tuple( + hg_counter_vector { + std::make_tuple ( + p_hg_counter::servers_table_version, + "proxysql_servers_table_version_total", + "Number of times the \"servers_table\" have been modified.", + metric_tags {} + ), + + // ==================================================================== + std::make_tuple ( + p_hg_counter::server_connections_created, + "proxysql_server_connections_total", + "Total number of server connections (created|delayed|aborted).", + metric_tags { + { "status", "created" } + } + ), + std::make_tuple ( + p_hg_counter::server_connections_delayed, + "proxysql_server_connections_total", + "Total number of server connections (created|delayed|aborted).", + metric_tags { + { "status", "delayed" } + } + ), + std::make_tuple ( + p_hg_counter::server_connections_aborted, + "proxysql_server_connections_total", + "Total number of server connections (created|delayed|aborted).", + metric_tags { + { "status", "aborted" } + } + ), + // ==================================================================== + + // ==================================================================== + std::make_tuple ( + p_hg_counter::client_connections_created, + "proxysql_client_connections_total", + "Total number of client connections created.", + metric_tags { + { "status", "created" } + } + ), + std::make_tuple ( + p_hg_counter::client_connections_aborted, + "proxysql_client_connections_total", + "Total number of client failed connections (or closed improperly).", + metric_tags { + { "status", "aborted" } + } + ), + // ==================================================================== + + std::make_tuple ( + p_hg_counter::com_autocommit, + "proxysql_com_autocommit_total", + "Total queries autocommited.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_autocommit_filtered, + "proxysql_com_autocommit_filtered_total", + "Total queries filtered autocommit.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_rollback, + "proxysql_com_rollback_total", + "Total queries rollbacked.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_rollback_filtered, + "proxysql_com_rollback_filtered_total", + "Total queries filtered rollbacked.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_backend_change_user, + "proxysql_com_backend_change_user_total", + "Total CHANGE_USER queries backend.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_backend_init_db, + "proxysql_com_backend_init_db_total", + "Total queries backend INIT DB.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_backend_set_names, + "proxysql_com_backend_set_names_total", + "Total queries backend SET NAMES.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_frontend_init_db, + "proxysql_com_frontend_init_db_total", + "Total INIT DB queries frontend.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_frontend_set_names, + "proxysql_com_frontend_set_names_total", + "Total SET NAMES frontend queries.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_frontend_use_db, + "proxysql_com_frontend_use_db_total", + "Total USE DB queries frontend.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_commit_cnt, + "proxysql_com_commit_cnt_total", + "Total queries commit.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::com_commit_cnt_filtered, + "proxysql_com_commit_cnt_filtered_total", + "Total queries commit filtered.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::selects_for_update__autocommit0, + "proxysql_selects_for_update__autocommit0_total", + "Total queries that are SELECT for update or equivalent.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::access_denied_wrong_password, + "proxysql_access_denied_wrong_password_total", + "Total access denied \"wrong password\".", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::access_denied_max_connections, + "proxysql_access_denied_max_connections_total", + "Total access denied \"max connections\".", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::access_denied_max_user_connections, + "proxysql_access_denied_max_user_connections_total", + "Total access denied \"max user connections\".", + metric_tags {} + ), + + // ==================================================================== + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_get, + "proxysql_myhgm_myconnpool_get_total", + "The number of requests made to the connection pool.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_get_ok, + "proxysql_myhgm_myconnpool_get_ok_total", + "The number of successful requests to the connection pool (i.e. where a connection was available).", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_get_ping, + "proxysql_myhgm_myconnpool_get_ping_total", + "The number of connections that were taken from the pool to run a ping to keep them alive.", + metric_tags {} + ), + // ==================================================================== + + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_push, + "proxysql_myhgm_myconnpool_push_total", + "The number of connections returned to the connection pool.", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_reset, + "proxysql_myhgm_myconnpool_reset_total", + "The number of connections that have been reset / re-initialized using \"COM_CHANGE_USER\"", + metric_tags {} + ), + std::make_tuple ( + p_hg_counter::myhgm_myconnpool_destroy, + "proxysql_myhgm_myconnpool_destroy_total", + "The number of connections considered unhealthy and therefore closed.", + metric_tags {} + ), + + // ==================================================================== + + std::make_tuple ( + p_hg_counter::auto_increment_delay_multiplex, + "proxysql_myhgm_auto_increment_multiplex_total", + "The number of times that 'auto_increment_delay_multiplex' has been triggered.", + metric_tags {} + ), + }, + // prometheus gauges + hg_gauge_vector { + std::make_tuple ( + p_hg_gauge::server_connections_connected, + "proxysql_server_connections_connected", + "Backend connections that are currently connected.", + metric_tags {} + ), + std::make_tuple ( + p_hg_gauge::client_connections_connected, + "proxysql_client_connections_connected", + "Client connections that are currently connected.", + metric_tags {} + ) + }, + // prometheus dynamic counters + hg_dyn_counter_vector { + // connection_pool + // ==================================================================== + + // ==================================================================== + std::make_tuple ( + p_hg_dyn_counter::conn_pool_bytes_data_recv, + "proxysql_connpool_data_bytes_total", + "Amount of data (sent|recv) from the backend, excluding metadata.", + metric_tags { + { "traffic_flow", "recv" } + } + ), + std::make_tuple ( + p_hg_dyn_counter::conn_pool_bytes_data_sent, + "proxysql_connpool_data_bytes_total", + "Amount of data (sent|recv) from the backend, excluding metadata.", + metric_tags { + { "traffic_flow", "sent" } + } + ), + // ==================================================================== + + // ==================================================================== + std::make_tuple ( + p_hg_dyn_counter::connection_pool_conn_err, + "proxysql_connpool_conns_total", + "How many connections have been tried to be established.", + metric_tags { + { "status", "err" } + } + ), + std::make_tuple ( + p_hg_dyn_counter::connection_pool_conn_ok, + "proxysql_connpool_conns_total", + "How many connections have been tried to be established.", + metric_tags { + { "status", "ok" } + } + ), + // ==================================================================== + + std::make_tuple ( + p_hg_dyn_counter::connection_pool_queries, + "proxysql_connpool_conns_queries_total", + "The number of queries routed towards this particular backend server.", + metric_tags {} + ), + // gtid + std::make_tuple ( + p_hg_dyn_counter::gtid_executed, + "proxysql_gtid_executed_total", + "Tracks the number of executed gtid per host and port.", + metric_tags {} + ), + // mysql_error + std::make_tuple ( + p_hg_dyn_counter::proxysql_mysql_error, + "proxysql_mysql_error_total", + "Tracks the mysql errors generated by proxysql.", + metric_tags {} + ), + std::make_tuple ( + p_hg_dyn_counter::mysql_error, + "mysql_error_total", + "Tracks the mysql errors encountered.", + metric_tags {} + ) + }, + // prometheus dynamic gauges + hg_dyn_gauge_vector { + std::make_tuple ( + p_hg_dyn_gauge::connection_pool_conn_free, + "proxysql_connpool_conns", + "How many backend connections are currently (free|used).", + metric_tags { + { "status", "free" } + } + ), + std::make_tuple ( + p_hg_dyn_gauge::connection_pool_conn_used, + "proxysql_connpool_conns", + "How many backend connections are currently (free|used).", + metric_tags { + { "status", "used" } + } + ), + std::make_tuple ( + p_hg_dyn_gauge::connection_pool_latency_us, + "proxysql_connpool_conns_latency_us", + "The currently ping time in microseconds, as reported from Monitor.", + metric_tags {} + ), + std::make_tuple ( + p_hg_dyn_gauge::connection_pool_status, + "proxysql_connpool_conns_status", + "The status of the backend server (1 - ONLINE, 2 - SHUNNED, 3 - OFFLINE_SOFT, 4 - OFFLINE_HARD).", + metric_tags {} + ) + } +); +#endif // 0 + +template +Base_HostGroups_Manager::Base_HostGroups_Manager() { + pthread_mutex_init(&readonly_mutex, NULL); + pthread_mutex_init(&lock, NULL); + admindb=NULL; // initialized only if needed + mydb=new SQLite3DB(); +} + +#if 0 +MySQL_HostGroups_Manager::MySQL_HostGroups_Manager() { + status.client_connections=0; + status.client_connections_aborted=0; + status.client_connections_created=0; + status.server_connections_connected=0; + status.server_connections_aborted=0; + status.server_connections_created=0; + status.server_connections_delayed=0; + status.servers_table_version=0; + pthread_mutex_init(&status.servers_table_version_lock, NULL); + pthread_cond_init(&status.servers_table_version_cond, NULL); + status.myconnpoll_get=0; + status.myconnpoll_get_ok=0; + status.myconnpoll_get_ping=0; + status.myconnpoll_push=0; + status.myconnpoll_destroy=0; + status.myconnpoll_reset=0; + status.autocommit_cnt=0; + status.commit_cnt=0; + status.rollback_cnt=0; + status.autocommit_cnt_filtered=0; + status.commit_cnt_filtered=0; + status.rollback_cnt_filtered=0; + status.backend_change_user=0; + status.backend_init_db=0; + status.backend_set_names=0; + status.frontend_init_db=0; + status.frontend_set_names=0; + status.frontend_use_db=0; + status.access_denied_wrong_password=0; + status.access_denied_max_connections=0; + status.access_denied_max_user_connections=0; + status.select_for_update_or_equivalent=0; + status.auto_increment_delay_multiplex=0; + pthread_mutex_init(&readonly_mutex, NULL); + pthread_mutex_init(&Group_Replication_Info_mutex, NULL); + pthread_mutex_init(&Galera_Info_mutex, NULL); + pthread_mutex_init(&AWS_Aurora_Info_mutex, NULL); + pthread_mutex_init(&lock, NULL); + admindb=NULL; // initialized only if needed + mydb=new SQLite3DB(); +#ifdef DEBUG + mydb->open((char *)"file:mem_mydb?mode=memory&cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); +#else + mydb->open((char *)"file:mem_mydb?mode=memory", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); +#endif /* DEBUG */ + mydb->execute(MYHGM_MYSQL_SERVERS); + mydb->execute(MYHGM_MYSQL_SERVERS_INCOMING); + mydb->execute(MYHGM_MYSQL_REPLICATION_HOSTGROUPS); + mydb->execute(MYHGM_MYSQL_GROUP_REPLICATION_HOSTGROUPS); + mydb->execute(MYHGM_MYSQL_GALERA_HOSTGROUPS); + mydb->execute(MYHGM_MYSQL_AWS_AURORA_HOSTGROUPS); + mydb->execute(MYHGM_MYSQL_HOSTGROUP_ATTRIBUTES); + mydb->execute(MYHGM_MYSQL_SERVERS_SSL_PARAMS); + mydb->execute("CREATE INDEX IF NOT EXISTS idx_mysql_servers_hostname_port ON mysql_servers (hostname,port)"); + MyHostGroups=new PtrArray(); + runtime_mysql_servers=NULL; + incoming_replication_hostgroups=NULL; + incoming_group_replication_hostgroups=NULL; + incoming_galera_hostgroups=NULL; + incoming_aws_aurora_hostgroups = NULL; + incoming_hostgroup_attributes = NULL; + incoming_mysql_servers_ssl_params = NULL; + incoming_mysql_servers_v2 = NULL; + pthread_rwlock_init(>id_rwlock, NULL); + gtid_missing_nodes = false; + gtid_ev_loop=NULL; + gtid_ev_timer=NULL; + gtid_ev_async = (struct ev_async *)malloc(sizeof(struct ev_async)); + mysql_servers_to_monitor = NULL; + + { + static const char alphanum[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + rand_del[0] = '-'; + for (int i = 1; i < 6; i++) { + rand_del[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + rand_del[6] = '-'; + rand_del[7] = 0; + } + pthread_mutex_init(&mysql_errors_mutex, NULL); + + // Initialize prometheus metrics + init_prometheus_counter_array(hg_metrics_map, this->status.p_counter_array); + init_prometheus_gauge_array(hg_metrics_map, this->status.p_gauge_array); + init_prometheus_dyn_counter_array(hg_metrics_map, this->status.p_dyn_counter_array); + init_prometheus_dyn_gauge_array(hg_metrics_map, this->status.p_dyn_gauge_array); + + pthread_mutex_init(&mysql_errors_mutex, NULL); +} + +void MySQL_HostGroups_Manager::init() { + //conn_reset_queue = NULL; + //conn_reset_queue = new wqueue(); + HGCU_thread = new std::thread(&HGCU_thread_run); + //pthread_create(&HGCU_thread_id, NULL, HGCU_thread_run , NULL); + + // gtid initialization; + GTID_syncer_thread = new std::thread(>ID_syncer_run); + + //pthread_create(>ID_syncer_thread_id, NULL, GTID_syncer_run , NULL); +} + +void MySQL_HostGroups_Manager::shutdown() { + queue.add(NULL); + HGCU_thread->join(); + delete HGCU_thread; + ev_async_send(gtid_ev_loop, gtid_ev_async); + GTID_syncer_thread->join(); + delete GTID_syncer_thread; +} + +MySQL_HostGroups_Manager::~MySQL_HostGroups_Manager() { + while (MyHostGroups->len) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->remove_index_fast(0); + delete myhgc; + } + delete MyHostGroups; + delete mydb; + if (admindb) { + delete admindb; + } + for (auto info : AWS_Aurora_Info_Map) + delete info.second; + free(gtid_ev_async); + if (gtid_ev_loop) + ev_loop_destroy(gtid_ev_loop); + if (gtid_ev_timer) + free(gtid_ev_timer); + pthread_mutex_destroy(&lock); +} + +#endif // 0 + +// wrlock() is only required during commit() +template +void Base_HostGroups_Manager::wrlock() { + pthread_mutex_lock(&lock); +#ifdef DEBUG + is_locked = true; +#endif +} + +#if 0 +void MySQL_HostGroups_Manager::p_update_mysql_error_counter(p_mysql_error_type err_type, unsigned int hid, char* address, uint16_t port, unsigned int code) { + p_hg_dyn_counter::metric metric = p_hg_dyn_counter::mysql_error; + if (err_type == p_mysql_error_type::proxysql) { + metric = p_hg_dyn_counter::proxysql_mysql_error; + } + + std::string s_hostgroup = std::to_string(hid); + std::string s_address = std::string(address); + std::string s_port = std::to_string(port); + // TODO: Create switch here to classify error codes + std::string s_code = std::to_string(code); + std::string metric_id = s_hostgroup + ":" + address + ":" + s_port + ":" + s_code; + std::map metric_labels { + { "hostgroup", s_hostgroup }, + { "address", address }, + { "port", s_port }, + { "code", s_code } + }; + + pthread_mutex_lock(&mysql_errors_mutex); + + p_inc_map_counter( + status.p_mysql_errors_map, + status.p_dyn_counter_array[metric], + metric_id, + metric_labels + ); + + pthread_mutex_unlock(&mysql_errors_mutex); +} +#endif // 0 + +template +void Base_HostGroups_Manager::wrunlock() { +#ifdef DEBUG + is_locked = false; +#endif + pthread_mutex_unlock(&lock); +} + +#if 0 +void MySQL_HostGroups_Manager::wait_servers_table_version(unsigned v, unsigned w) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + //ts.tv_sec += w; + unsigned int i = 0; + int rc = 0; + pthread_mutex_lock(&status.servers_table_version_lock); + while ((rc == 0 || rc == ETIMEDOUT) && (i < w) && (__sync_fetch_and_add(&glovars.shutdown,0)==0) && (__sync_fetch_and_add(&status.servers_table_version,0) < v)) { + i++; + ts.tv_sec += 1; + rc = pthread_cond_timedwait( &status.servers_table_version_cond, &status.servers_table_version_lock, &ts); + } + pthread_mutex_unlock(&status.servers_table_version_lock); +} + +unsigned int MySQL_HostGroups_Manager::get_servers_table_version() { + return __sync_fetch_and_add(&status.servers_table_version,0); +} + +// we always assume that the calling thread has acquired a rdlock() +int MySQL_HostGroups_Manager::servers_add(SQLite3_result *resultset) { + if (resultset==NULL) { + return 0; + } + int rc; + mydb->execute("DELETE FROM mysql_servers_incoming"); + sqlite3_stmt *statement1=NULL; + sqlite3_stmt *statement32=NULL; + //sqlite3 *mydb3=mydb->get_db(); + char *query1=(char *)"INSERT INTO mysql_servers_incoming VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"; + std::string query32s = "INSERT INTO mysql_servers_incoming VALUES " + generate_multi_rows_query(32,12); + char *query32 = (char *)query32s.c_str(); + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query1, -1, &statement1, 0); + rc = mydb->prepare_v2(query1, &statement1); + ASSERT_SQLITE_OK(rc, mydb); + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query32, -1, &statement32, 0); + rc = mydb->prepare_v2(query32, &statement32); + ASSERT_SQLITE_OK(rc, mydb); + MySerStatus status1=MYSQL_SERVER_STATUS_ONLINE; + int row_idx=0; + int max_bulk_row_idx=resultset->rows_count/32; + max_bulk_row_idx=max_bulk_row_idx*32; + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *r1=*it; + status1=MYSQL_SERVER_STATUS_ONLINE; + if (strcasecmp(r1->fields[4],"ONLINE")) { + if (!strcasecmp(r1->fields[4],"SHUNNED")) { + status1=MYSQL_SERVER_STATUS_SHUNNED; + } else { + if (!strcasecmp(r1->fields[4],"OFFLINE_SOFT")) { + status1=MYSQL_SERVER_STATUS_OFFLINE_SOFT; + } else { + if (!strcasecmp(r1->fields[4],"OFFLINE_HARD")) { + status1=MYSQL_SERVER_STATUS_OFFLINE_HARD; + } + } + } + } + int idx=row_idx%32; + if (row_idxfields[0])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*12)+2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+3, atoi(r1->fields[2])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+4, atoi(r1->fields[3])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+5, atoi(r1->fields[5])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+6, status1); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+7, atoi(r1->fields[6])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+8, atoi(r1->fields[7])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+9, atoi(r1->fields[8])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+10, atoi(r1->fields[9])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*12)+11, atoi(r1->fields[10])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*12)+12, r1->fields[11], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + if (idx==31) { + SAFE_SQLITE3_STEP2(statement32); + rc=(*proxy_sqlite3_clear_bindings)(statement32); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement32); ASSERT_SQLITE_OK(rc, mydb); + } + } else { // single row + rc=(*proxy_sqlite3_bind_int64)(statement1, 1, atoi(r1->fields[0])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement1, 2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 3, atoi(r1->fields[2])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 4, atoi(r1->fields[3])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 5, atoi(r1->fields[5])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 6, status1); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 7, atoi(r1->fields[6])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 8, atoi(r1->fields[7])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 9, atoi(r1->fields[8])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 10, atoi(r1->fields[9])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 11, atoi(r1->fields[10])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement1, 12, r1->fields[11], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + SAFE_SQLITE3_STEP2(statement1); + rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, mydb); + } + row_idx++; + } + (*proxy_sqlite3_finalize)(statement1); + (*proxy_sqlite3_finalize)(statement32); + return 0; +} +#endif // 0 + +/** + * @brief Execute a SQL query and retrieve the resultset. + * + * This function executes a SQL query using the provided query string and returns the resultset obtained from the + * database operation. It also provides an optional error parameter to capture any error messages encountered during + * query execution. + * + * @param query A pointer to a null-terminated string containing the SQL query to be executed. + * @param error A pointer to a char pointer where any error message encountered during query execution will be stored. + * Pass nullptr if error handling is not required. + * @return A pointer to a SQLite3_result object representing the resultset obtained from the query execution. This + * pointer may be nullptr if the query execution fails or returns an empty result. + */ +template +SQLite3_result * Base_HostGroups_Manager::execute_query(char *query, char **error) { + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + wrlock(); + mydb->execute_statement(query, error , &cols , &affected_rows , &resultset); + wrunlock(); + return resultset; +} + +#if 0 +/** + * @brief Calculate and update the checksum for a specified table in the database. + * + * This function calculates the checksum for a specified table in the database using the provided SpookyHash object. + * The checksum is computed based on the table's contents, sorted by the specified column name. If the initialization + * flag is false, the SpookyHash object is initialized with predefined parameters. The calculated checksum is stored + * in the raw_checksum parameter. + * + * @param myhash A reference to the SpookyHash object used for calculating the checksum. + * @param init A reference to a boolean flag indicating whether the SpookyHash object has been initialized. + * @param TableName The name of the table for which the checksum is to be calculated. + * @param ColumnName The name of the column to be used for sorting the table before calculating the checksum. + * @param raw_checksum A reference to a uint64_t variable where the calculated checksum will be stored. + */ +void MySQL_HostGroups_Manager::CUCFT1( + SpookyHash& myhash, bool& init, const string& TableName, const string& ColumnName, uint64_t& raw_checksum +) { + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + string query = "SELECT * FROM " + TableName + " ORDER BY " + ColumnName; + mydb->execute_statement(query.c_str(), &error , &cols , &affected_rows , &resultset); + if (resultset) { + if (resultset->rows_count) { + if (init == false) { + init = true; + myhash.Init(19,3); + } + uint64_t hash1_ = resultset->raw_checksum(); + raw_checksum = hash1_; + myhash.Update(&hash1_, sizeof(hash1_)); + proxy_info("Checksum for table %s is 0x%lX\n", TableName.c_str(), hash1_); + } + delete resultset; + } else { + proxy_info("Checksum for table %s is 0x%lX\n", TableName.c_str(), (long unsigned int)0); + } +} + +/** + * @brief Compute and update checksum values for specified tables. + * + * This function computes checksum values for specified tables by executing checksum calculation queries for each table. + * It updates the checksum values in the `table_resultset_checksum` array. + * + * @param myhash A reference to a SpookyHash object used for computing the checksums. + * @param init A reference to a boolean flag indicating whether the checksum computation has been initialized. + * @note This function resets the current checksum values for all tables except MYSQL_SERVERS and MYSQL_SERVERS_V2 + * before recomputing the checksums. + * @note The computed checksum values are stored in the `table_resultset_checksum` array. + */ +void MySQL_HostGroups_Manager::commit_update_checksums_from_tables(SpookyHash& myhash, bool& init) { + // Always reset the current table values before recomputing + for (size_t i = 0; i < table_resultset_checksum.size(); i++) { + if (i != HGM_TABLES::MYSQL_SERVERS && i != HGM_TABLES::MYSQL_SERVERS_V2) { + table_resultset_checksum[i] = 0; + } + } + + CUCFT1(myhash,init,"mysql_replication_hostgroups","writer_hostgroup", table_resultset_checksum[HGM_TABLES::MYSQL_REPLICATION_HOSTGROUPS]); + CUCFT1(myhash,init,"mysql_group_replication_hostgroups","writer_hostgroup", table_resultset_checksum[HGM_TABLES::MYSQL_GROUP_REPLICATION_HOSTGROUPS]); + CUCFT1(myhash,init,"mysql_galera_hostgroups","writer_hostgroup", table_resultset_checksum[HGM_TABLES::MYSQL_GALERA_HOSTGROUPS]); + CUCFT1(myhash,init,"mysql_aws_aurora_hostgroups","writer_hostgroup", table_resultset_checksum[HGM_TABLES::MYSQL_AWS_AURORA_HOSTGROUPS]); + CUCFT1(myhash,init,"mysql_hostgroup_attributes","hostgroup_id", table_resultset_checksum[HGM_TABLES::MYSQL_HOSTGROUP_ATTRIBUTES]); + CUCFT1(myhash,init,"mysql_servers_ssl_params","hostname,port,username", table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS_SSL_PARAMS]); +} + +/** + * @brief This code updates the 'hostgroup_server_mapping' table with the most recent mysql_servers and mysql_replication_hostgroups + * records while utilizing checksums to prevent unnecessary updates. + * + * IMPORTANT: Make sure wrlock() is called before calling this method. + * +*/ +void MySQL_HostGroups_Manager::update_hostgroup_manager_mappings() { + + if (hgsm_mysql_servers_checksum != table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] || + hgsm_mysql_replication_hostgroups_checksum != table_resultset_checksum[HGM_TABLES::MYSQL_REPLICATION_HOSTGROUPS]) + { + proxy_info("Rebuilding 'Hostgroup_Manager_Mapping' due to checksums change - mysql_servers { old: 0x%lX, new: 0x%lX }, mysql_replication_hostgroups { old:0x%lX, new:0x%lX }\n", + hgsm_mysql_servers_checksum, table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS], + hgsm_mysql_replication_hostgroups_checksum, table_resultset_checksum[HGM_TABLES::MYSQL_REPLICATION_HOSTGROUPS]); + + char* error = NULL; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = NULL; + + hostgroup_server_mapping.clear(); + + const char* query = "SELECT DISTINCT hostname, port, '1' is_writer, status, reader_hostgroup, writer_hostgroup, mem_pointer FROM mysql_replication_hostgroups JOIN mysql_servers ON hostgroup_id=writer_hostgroup WHERE status<>3 \ + UNION \ + SELECT DISTINCT hostname, port, '0' is_writer, status, reader_hostgroup, writer_hostgroup, mem_pointer FROM mysql_replication_hostgroups JOIN mysql_servers ON hostgroup_id=reader_hostgroup WHERE status<>3 \ + ORDER BY hostname, port"; + + mydb->execute_statement(query, &error, &cols, &affected_rows, &resultset); + + if (resultset && resultset->rows_count) { + std::string fetched_server_id; + HostGroup_Server_Mapping* fetched_server_mapping = NULL; + + for (std::vector::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) { + SQLite3_row* r = *it; + + const std::string& server_id = std::string(r->fields[0]) + ":::" + r->fields[1]; + + if (fetched_server_mapping == NULL || server_id != fetched_server_id) { + + auto itr = hostgroup_server_mapping.find(server_id); + + if (itr == hostgroup_server_mapping.end()) { + std::unique_ptr server_mapping(new HostGroup_Server_Mapping(this)); + fetched_server_mapping = server_mapping.get(); + hostgroup_server_mapping.insert( std::pair> { + server_id, std::move(server_mapping) + } ); + } else { + fetched_server_mapping = itr->second.get(); + } + + fetched_server_id = server_id; + } + + HostGroup_Server_Mapping::Node node; + //node.server_status = static_cast(atoi(r->fields[3])); + node.reader_hostgroup_id = atoi(r->fields[4]); + node.writer_hostgroup_id = atoi(r->fields[5]); + node.srv = reinterpret_cast(atoll(r->fields[6])); + + HostGroup_Server_Mapping::Type type = (r->fields[2] && r->fields[2][0] == '1') ? HostGroup_Server_Mapping::Type::WRITER : HostGroup_Server_Mapping::Type::READER; + fetched_server_mapping->add(type, node); + } + } + delete resultset; + + hgsm_mysql_servers_checksum = table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS]; + hgsm_mysql_replication_hostgroups_checksum = table_resultset_checksum[HGM_TABLES::MYSQL_REPLICATION_HOSTGROUPS]; + } +} + +/** + * @brief Generates a resultset holding the current Admin 'runtime_mysql_servers' as reported by Admin. + * @details Requires caller to hold the mutex 'MySQL_HostGroups_Manager::wrlock'. + * @param mydb The db in which to perform the query, typically 'MySQL_HostGroups_Manager::mydb'. + * @return An SQLite3 resultset for the query 'MYHGM_GEN_ADMIN_RUNTIME_SERVERS'. + */ +unique_ptr get_admin_runtime_mysql_servers(SQLite3DB* mydb) { + char* error = nullptr; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = nullptr; + + mydb->execute_statement(MYHGM_GEN_CLUSTER_ADMIN_RUNTIME_SERVERS, &error, &cols, &affected_rows, &resultset); + + if (error) { + proxy_error("SQLite3 query generating 'runtime_mysql_servers' resultset failed with error '%s'\n", error); + assert(0); + } + + return unique_ptr(resultset); +} + +/** + * @brief Generates a resultset with holding the current 'mysql_servers_v2' table. + * @details Requires caller to hold the mutex 'ProxySQL_Admin::mysql_servers_wrlock'. + * @return A resulset holding 'mysql_servers_v2'. + */ +unique_ptr get_mysql_servers_v2() { + char* error = nullptr; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = nullptr; + + if (GloAdmin && GloAdmin->admindb) { + GloAdmin->admindb->execute_statement( + MYHGM_GEN_CLUSTER_ADMIN_MYSQL_SERVERS, &error, &cols, &affected_rows, &resultset + ); + } + + return unique_ptr(resultset); +} + +static void update_glovars_checksum_with_peers( + ProxySQL_Checksum_Value& module_checksum, + const string& new_checksum, + const string& peer_checksum_value, + time_t new_epoch, + time_t peer_checksum_epoch, + bool update_version +) { + module_checksum.set_checksum(const_cast(new_checksum.c_str())); + + if (update_version) + module_checksum.version++; + + bool computed_checksum_matches = + peer_checksum_value != "" && module_checksum.checksum == peer_checksum_value; + + if (peer_checksum_epoch != 0 && computed_checksum_matches) { + module_checksum.epoch = peer_checksum_epoch; + } else { + module_checksum.epoch = new_epoch; + } +} + +/** + * @brief Updates the global 'mysql_servers' module checksum. + * @details If the new computed checksum matches the supplied 'cluster_checksum', the epoch used for the + * checksum is the supplied epoch instead of current time. This way we ensure the preservation of the + * checksum and epoch fetched from the ProxySQL cluster peer node. + * + * IMPORTANT: This function also generates a new 'global_checksum'. This is because everytime + * 'runtime_mysql_servers' change, updating the global checksum is unconditional. + * @param new_checksum The new computed checksum for 'runtime_mysql_servers'. + * @param peer_checksum A checksum fetched from another ProxySQL cluster node, holds the checksum value + * and its epoch. Should be empty if no remote checksum is being considered. + * @param epoch The epoch to be preserved in case the supplied 'peer_checksum' matches the new computed + * checksum. + */ +static void update_glovars_mysql_servers_checksum( + const string& new_checksum, + const runtime_mysql_servers_checksum_t& peer_checksum = {}, + bool update_version = false +) { + time_t new_epoch = time(NULL); + + update_glovars_checksum_with_peers( + GloVars.checksums_values.mysql_servers, + new_checksum, + peer_checksum.value, + new_epoch, + peer_checksum.epoch, + update_version + ); + + GloVars.checksums_values.updates_cnt++; + GloVars.generate_global_checksum(); + GloVars.epoch_version = new_epoch; +} + +/** + * @brief Updates the global 'mysql_servers_v2' module checksum. + * @details Unlike 'update_glovars_mysql_servers_checksum' this function doesn't generate a new + * 'global_checksum'. It's caller responsibility to ensure that 'global_checksum' is updated. + * @param new_checksum The new computed checksum for 'mysql_servers_v2'. + * @param peer_checksum A checksum fetched from another ProxySQL cluster node, holds the checksum value + * and its epoch. Should be empty if no remote checksum is being considered. + * @param epoch The epoch to be preserved in case the supplied 'peer_checksum' matches the new computed + * checksum. + */ +static void update_glovars_mysql_servers_v2_checksum( + const string& new_checksum, + const mysql_servers_v2_checksum_t& peer_checksum = {}, + bool update_version = false +) { + time_t new_epoch = time(NULL); + + update_glovars_checksum_with_peers( + GloVars.checksums_values.mysql_servers_v2, + new_checksum, + peer_checksum.value, + new_epoch, + peer_checksum.epoch, + update_version + ); +} + +/** + * @brief Commit and update checksum from the MySQL servers. + * + * This function commits updates and calculates the checksum from the MySQL servers. It performs the following steps: + * 1. Deletes existing data from the 'mysql_servers' table. + * 2. Generates a new 'mysql_servers' table. + * 3. Saves the runtime MySQL servers data obtained from the provided result set or from the database if the result set is null. + * 4. Calculates the checksum of the runtime MySQL servers data and updates the checksum value in the 'table_resultset_checksum' array. + * + * @param runtime_mysql_servers A pointer to the result set containing runtime MySQL servers data. + * @return The raw checksum value calculated from the runtime MySQL servers data. + */ +uint64_t MySQL_HostGroups_Manager::commit_update_checksum_from_mysql_servers(SQLite3_result* runtime_mysql_servers) { + mydb->execute("DELETE FROM mysql_servers"); + generate_mysql_servers_table(); + + if (runtime_mysql_servers == nullptr) { + unique_ptr resultset { get_admin_runtime_mysql_servers(mydb) }; + save_runtime_mysql_servers(resultset.release()); + } else { + save_runtime_mysql_servers(runtime_mysql_servers); + } + + uint64_t raw_checksum = this->runtime_mysql_servers ? this->runtime_mysql_servers->raw_checksum() : 0; + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] = raw_checksum; + + return raw_checksum; +} + +/** + * @brief Commit and update checksum from the MySQL servers V2. + * + * This function commits updates and calculates the checksum from the MySQL servers V2 data. It performs the following steps: + * 1. Saves the provided MySQL servers V2 data if not null, or retrieves and saves the data from the database. + * 2. Calculates the checksum of the MySQL servers V2 data and updates the checksum value in the 'table_resultset_checksum' array. + * + * @param mysql_servers_v2 A pointer to the result set containing MySQL servers V2 data. + * @return The raw checksum value calculated from the MySQL servers V2 data. + */ +uint64_t MySQL_HostGroups_Manager::commit_update_checksum_from_mysql_servers_v2(SQLite3_result* mysql_servers_v2) { + if (mysql_servers_v2 == nullptr) { + unique_ptr resultset { get_mysql_servers_v2() }; + save_mysql_servers_v2(resultset.release()); + } else { + save_mysql_servers_v2(mysql_servers_v2); + } + + uint64_t raw_checksum = this->incoming_mysql_servers_v2 ? this->incoming_mysql_servers_v2->raw_checksum() : 0; + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS_V2] = raw_checksum; + + return raw_checksum; +} + +std::string MySQL_HostGroups_Manager::gen_global_mysql_servers_v2_checksum(uint64_t servers_v2_hash) { + bool init = false; + SpookyHash global_hash {}; + + if (servers_v2_hash != 0) { + if (init == false) { + init = true; + global_hash.Init(19, 3); + } + + global_hash.Update(&servers_v2_hash, sizeof(servers_v2_hash)); + } + + commit_update_checksums_from_tables(global_hash, init); + + uint64_t hash_1 = 0, hash_2 = 0; + if (init) { + global_hash.Final(&hash_1,&hash_2); + } + + string mysrvs_checksum { get_checksum_from_hash(hash_1) }; + return mysrvs_checksum; +} + +bool MySQL_HostGroups_Manager::commit() { + return commit({},{}); +} + +bool MySQL_HostGroups_Manager::commit( + const peer_runtime_mysql_servers_t& peer_runtime_mysql_servers, + const peer_mysql_servers_v2_t& peer_mysql_servers_v2, + bool only_commit_runtime_mysql_servers, + bool update_version +) { + // if only_commit_runtime_mysql_servers is true, mysql_servers_v2 resultset will not be entertained and will cause memory leak. + if (only_commit_runtime_mysql_servers) { + proxy_info("Generating runtime mysql servers records only.\n"); + } else { + proxy_info("Generating runtime mysql servers and mysql servers v2 records.\n"); + } + + unsigned long long curtime1=monotonic_time(); + wrlock(); + // purge table + purge_mysql_servers_table(); + // if any server has gtid_port enabled, use_gtid is set to true + // and then has_gtid_port is set too + bool use_gtid = false; + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers\n"); + mydb->execute("DELETE FROM mysql_servers"); + generate_mysql_servers_table(); + + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + if (GloMTH->variables.hostgroup_manager_verbose) { + mydb->execute_statement((char *)"SELECT * FROM mysql_servers_incoming", &error , &cols , &affected_rows , &resultset); + if (error) { + proxy_error("Error on read from mysql_servers_incoming : %s\n", error); + } else { + if (resultset) { + proxy_info("Dumping mysql_servers_incoming\n"); + resultset->dump_to_stderr(); + } + } + if (resultset) { delete resultset; resultset=NULL; } + } + char *query=NULL; + query=(char *)"SELECT mem_pointer, t1.hostgroup_id, t1.hostname, t1.port FROM mysql_servers t1 LEFT OUTER JOIN mysql_servers_incoming t2 ON (t1.hostgroup_id=t2.hostgroup_id AND t1.hostname=t2.hostname AND t1.port=t2.port) WHERE t2.hostgroup_id IS NULL"; + mydb->execute_statement(query, &error , &cols , &affected_rows , &resultset); + if (error) { + proxy_error("Error on %s : %s\n", query, error); + } else { + if (GloMTH->variables.hostgroup_manager_verbose) { + proxy_info("Dumping mysql_servers LEFT JOIN mysql_servers_incoming\n"); + resultset->dump_to_stderr(); + } + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *r=*it; + long long ptr=atoll(r->fields[0]); + proxy_warning("Removed server at address %lld, hostgroup %s, address %s port %s. Setting status OFFLINE HARD and immediately dropping all free connections. Used connections will be dropped when trying to use them\n", ptr, r->fields[1], r->fields[2], r->fields[3]); + MySrvC *mysrvc=(MySrvC *)ptr; + mysrvc->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); + mysrvc->ConnectionsFree->drop_all_connections(); + char *q1=(char *)"DELETE FROM mysql_servers WHERE mem_pointer=%lld"; + char *q2=(char *)malloc(strlen(q1)+32); + sprintf(q2,q1,ptr); + mydb->execute(q2); + free(q2); + } + } + if (resultset) { delete resultset; resultset=NULL; } + + // This seems unnecessary. Removed as part of issue #829 + //mydb->execute("DELETE FROM mysql_servers"); + //generate_mysql_servers_table(); + + mydb->execute("INSERT OR IGNORE INTO mysql_servers(hostgroup_id, hostname, port, gtid_port, weight, status, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment) SELECT hostgroup_id, hostname, port, gtid_port, weight, status, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment FROM mysql_servers_incoming"); + + // SELECT FROM mysql_servers whatever is not identical in mysql_servers_incoming, or where mem_pointer=0 (where there is no pointer yet) + query=(char *)"SELECT t1.*, t2.gtid_port, t2.weight, t2.status, t2.compression, t2.max_connections, t2.max_replication_lag, t2.use_ssl, t2.max_latency_ms, t2.comment FROM mysql_servers t1 JOIN mysql_servers_incoming t2 ON (t1.hostgroup_id=t2.hostgroup_id AND t1.hostname=t2.hostname AND t1.port=t2.port) WHERE mem_pointer=0 OR t1.gtid_port<>t2.gtid_port OR t1.weight<>t2.weight OR t1.status<>t2.status OR t1.compression<>t2.compression OR t1.max_connections<>t2.max_connections OR t1.max_replication_lag<>t2.max_replication_lag OR t1.use_ssl<>t2.use_ssl OR t1.max_latency_ms<>t2.max_latency_ms or t1.comment<>t2.comment"; + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "%s\n", query); + mydb->execute_statement(query, &error , &cols , &affected_rows , &resultset); + if (error) { + proxy_error("Error on %s : %s\n", query, error); + } else { + + if (GloMTH->variables.hostgroup_manager_verbose) { + proxy_info("Dumping mysql_servers JOIN mysql_servers_incoming\n"); + resultset->dump_to_stderr(); + } + // optimization #829 + int rc; + sqlite3_stmt *statement1=NULL; + sqlite3_stmt *statement2=NULL; + //sqlite3 *mydb3=mydb->get_db(); + char *query1=(char *)"UPDATE mysql_servers SET mem_pointer = ?1 WHERE hostgroup_id = ?2 AND hostname = ?3 AND port = ?4"; + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query1, -1, &statement1, 0); + rc = mydb->prepare_v2(query1, &statement1); + ASSERT_SQLITE_OK(rc, mydb); + char *query2=(char *)"UPDATE mysql_servers SET weight = ?1 , status = ?2 , compression = ?3 , max_connections = ?4 , max_replication_lag = ?5 , use_ssl = ?6 , max_latency_ms = ?7 , comment = ?8 , gtid_port = ?9 WHERE hostgroup_id = ?10 AND hostname = ?11 AND port = ?12"; + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query2, -1, &statement2, 0); + rc = mydb->prepare_v2(query2, &statement2); + ASSERT_SQLITE_OK(rc, mydb); + + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *r=*it; + long long ptr=atoll(r->fields[12]); // increase this index every time a new column is added + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d , weight=%d, status=%d, mem_pointer=%llu, hostgroup=%d, compression=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), atoi(r->fields[5]), ptr, atoi(r->fields[0]), atoi(r->fields[6])); + //fprintf(stderr,"%lld\n", ptr); + if (ptr==0) { + if (GloMTH->variables.hostgroup_manager_verbose) { + proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%d, status=%d\n", atoi(r->fields[0]), r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), atoi(r->fields[5])); + } + MySrvC *mysrvc=new MySrvC(r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), (MySerStatus)atoi(r->fields[5]), atoi(r->fields[6]), atoi(r->fields[7]), atoi(r->fields[8]), atoi(r->fields[9]), atoi(r->fields[10]), r->fields[11]); // add new fields here if adding more columns in mysql_servers + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%d, status=%d, mem_ptr=%p into hostgroup=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), atoi(r->fields[5]), mysrvc, atoi(r->fields[0])); + add(mysrvc,atoi(r->fields[0])); + ptr=(uintptr_t)mysrvc; + rc=(*proxy_sqlite3_bind_int64)(statement1, 1, ptr); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 2, atoi(r->fields[0])); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement1, 3, r->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 4, atoi(r->fields[2])); ASSERT_SQLITE_OK(rc, mydb); + SAFE_SQLITE3_STEP2(statement1); + rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, mydb); + if (mysrvc->gtid_port) { + // this server has gtid_port configured, we set use_gtid + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 6, "Server %u:%s:%d has gtid_port enabled, setting use_gitd=true if not already set\n", mysrvc->myhgc->hid , mysrvc->address, mysrvc->port); + use_gtid = true; + } + } else { + bool run_update=false; + MySrvC *mysrvc=(MySrvC *)ptr; + // carefully increase the 2nd index by 1 for every new column added + if (atoi(r->fields[3])!=atoi(r->fields[13])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing gtid_port for server %u:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]) , mysrvc->gtid_port , atoi(r->fields[13])); + mysrvc->gtid_port=atoi(r->fields[13]); + } + + if (atoi(r->fields[4])!=atoi(r->fields[14])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Changing weight for server %d:%s:%d (%s:%d) from %d (%ld) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]) , mysrvc->weight , atoi(r->fields[14])); + mysrvc->weight=atoi(r->fields[14]); + } + if (atoi(r->fields[5])!=atoi(r->fields[15])) { + bool change_server_status = true; + if (GloMTH->variables.evaluate_replication_lag_on_servers_load == 1) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG && // currently server is shunned due to replication lag + (MySerStatus)atoi(r->fields[15]) == MYSQL_SERVER_STATUS_ONLINE) { // new server status is online + if (mysrvc->cur_replication_lag != -2) { // Master server? Seconds_Behind_Master column is not present + const unsigned int new_max_repl_lag = atoi(r->fields[18]); + if (mysrvc->cur_replication_lag < 0 || + (new_max_repl_lag > 0 && + ((unsigned int)mysrvc->cur_replication_lag > new_max_repl_lag))) { // we check if current replication lag is greater than new max_replication_lag + change_server_status = false; + } + } + } + } + if (change_server_status == true) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing status for server %d:%s:%d (%s:%d) from %d (%d) to %d\n", mysrvc->myhgc->hid, mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[5]), (int)mysrvc->get_status(), atoi(r->fields[15])); + mysrvc->set_status((MySerStatus)atoi(r->fields[15])); + } + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED) { + mysrvc->shunned_automatic=false; + } + } + if (atoi(r->fields[6])!=atoi(r->fields[16])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing compression for server %d:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[6]) , mysrvc->compression , atoi(r->fields[16])); + mysrvc->compression=atoi(r->fields[16]); + } + if (atoi(r->fields[7])!=atoi(r->fields[17])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing max_connections for server %d:%s:%d (%s:%d) from %d (%ld) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[7]) , mysrvc->max_connections , atoi(r->fields[17])); + mysrvc->max_connections=atoi(r->fields[17]); + } + if (atoi(r->fields[8])!=atoi(r->fields[18])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing max_replication_lag for server %u:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[8]) , mysrvc->max_replication_lag , atoi(r->fields[18])); + mysrvc->max_replication_lag=atoi(r->fields[18]); + if (mysrvc->max_replication_lag == 0) { // we just changed it to 0 + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + // the server is currently shunned due to replication lag + // but we reset max_replication_lag to 0 + // therefore we immediately reset the status too + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + } + } + } + if (atoi(r->fields[9])!=atoi(r->fields[19])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing use_ssl for server %d:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[9]) , mysrvc->use_ssl , atoi(r->fields[19])); + mysrvc->use_ssl=atoi(r->fields[19]); + } + if (atoi(r->fields[10])!=atoi(r->fields[20])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing max_latency_ms for server %d:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[10]) , mysrvc->max_latency_us/1000 , atoi(r->fields[20])); + mysrvc->max_latency_us=1000*atoi(r->fields[20]); + } + if (strcmp(r->fields[11],r->fields[21])) { + if (GloMTH->variables.hostgroup_manager_verbose) + proxy_info("Changing comment for server %d:%s:%d (%s:%d) from '%s' to '%s'\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), r->fields[11], r->fields[21]); + free(mysrvc->comment); + mysrvc->comment=strdup(r->fields[21]); + } + if (run_update) { + rc=(*proxy_sqlite3_bind_int64)(statement2, 1, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 2, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 3, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 4, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 5, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 6, mysrvc->use_ssl); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 7, mysrvc->max_latency_us/1000); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement2, 8, mysrvc->comment, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 9, mysrvc->gtid_port); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 10, mysrvc->myhgc->hid); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement2, 11, mysrvc->address, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 12, mysrvc->port); ASSERT_SQLITE_OK(rc, mydb); + SAFE_SQLITE3_STEP2(statement2); + rc=(*proxy_sqlite3_clear_bindings)(statement2); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement2); ASSERT_SQLITE_OK(rc, mydb); + } + if (mysrvc->gtid_port) { + // this server has gtid_port configured, we set use_gtid + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 6, "Server %u:%s:%d has gtid_port enabled, setting use_gitd=true if not already set\n", mysrvc->myhgc->hid , mysrvc->address, mysrvc->port); + use_gtid = true; + } + } + } + (*proxy_sqlite3_finalize)(statement1); + (*proxy_sqlite3_finalize)(statement2); + } + if (use_gtid) { + has_gtid_port = true; + } else { + has_gtid_port = false; + } + if (resultset) { delete resultset; resultset=NULL; } + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers_incoming\n"); + mydb->execute("DELETE FROM mysql_servers_incoming"); + + string global_checksum_v2 {}; + if (only_commit_runtime_mysql_servers == false) { + // replication + if (incoming_replication_hostgroups) { // this IF is extremely important, otherwise replication hostgroups may disappear + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_replication_hostgroups\n"); + mydb->execute("DELETE FROM mysql_replication_hostgroups"); + generate_mysql_replication_hostgroups_table(); + } + + // group replication + if (incoming_group_replication_hostgroups) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_group_replication_hostgroups\n"); + mydb->execute("DELETE FROM mysql_group_replication_hostgroups"); + generate_mysql_group_replication_hostgroups_table(); + } + + // galera + if (incoming_galera_hostgroups) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_galera_hostgroups\n"); + mydb->execute("DELETE FROM mysql_galera_hostgroups"); + generate_mysql_galera_hostgroups_table(); + } + + // AWS Aurora + if (incoming_aws_aurora_hostgroups) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_aws_aurora_hostgroups\n"); + mydb->execute("DELETE FROM mysql_aws_aurora_hostgroups"); + generate_mysql_aws_aurora_hostgroups_table(); + } + + // hostgroup attributes + if (incoming_hostgroup_attributes) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_hostgroup_attributes\n"); + mydb->execute("DELETE FROM mysql_hostgroup_attributes"); + generate_mysql_hostgroup_attributes_table(); + } + + // SSL params + if (incoming_mysql_servers_ssl_params) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers_ssl_params\n"); + mydb->execute("DELETE FROM mysql_servers_ssl_params"); + generate_mysql_servers_ssl_params_table(); + } + + uint64_t new_hash = commit_update_checksum_from_mysql_servers_v2(peer_mysql_servers_v2.resultset); + + { + const string new_checksum { get_checksum_from_hash(new_hash) }; + proxy_info("Checksum for table %s is %s\n", "mysql_servers_v2", new_checksum.c_str()); + } + + global_checksum_v2 = gen_global_mysql_servers_v2_checksum(new_hash); + proxy_info("New computed global checksum for 'mysql_servers_v2' is '%s'\n", global_checksum_v2.c_str()); + } + + // Update 'mysql_servers' and global checksums + { + uint64_t new_hash = commit_update_checksum_from_mysql_servers(peer_runtime_mysql_servers.resultset); + const string new_checksum { get_checksum_from_hash(new_hash) }; + proxy_info("Checksum for table %s is %s\n", "mysql_servers", new_checksum.c_str()); + + pthread_mutex_lock(&GloVars.checksum_mutex); + if (only_commit_runtime_mysql_servers == false) { + update_glovars_mysql_servers_v2_checksum(global_checksum_v2, peer_mysql_servers_v2.checksum, true); + } + update_glovars_mysql_servers_checksum(new_checksum, peer_runtime_mysql_servers.checksum, update_version); + pthread_mutex_unlock(&GloVars.checksum_mutex); + } + + // fill Hostgroup_Manager_Mapping with latest records + update_hostgroup_manager_mappings(); + + + ev_async_send(gtid_ev_loop, gtid_ev_async); + + __sync_fetch_and_add(&status.servers_table_version,1); + + // We completely reset read_only_set1. It will generated (completely) again in read_only_action() + // Note: read_only_set1 will be regenerated all at once + read_only_set1.erase(read_only_set1.begin(), read_only_set1.end()); + // We completely reset read_only_set2. It will be again written in read_only_action() + // Note: read_only_set2 will be regenerated one server at the time + read_only_set2.erase(read_only_set2.begin(), read_only_set2.end()); + + this->status.p_counter_array[p_hg_counter::servers_table_version]->Increment(); + pthread_cond_broadcast(&status.servers_table_version_cond); + pthread_mutex_unlock(&status.servers_table_version_lock); + + // NOTE: In order to guarantee the latest generated version, this should be kept after all the + // calls to 'generate_mysql_servers'. + update_table_mysql_servers_for_monitor(false); + + wrunlock(); + unsigned long long curtime2=monotonic_time(); + curtime1 = curtime1/1000; + curtime2 = curtime2/1000; + proxy_info("MySQL_HostGroups_Manager::commit() locked for %llums\n", curtime2-curtime1); + + if (GloMTH) { + GloMTH->signal_all_threads(1); + } + + return true; +} + +/** + * @brief Calculate the checksum for the runtime mysql_servers record, after excluding all the rows + * with the status OFFLINE_HARD from the result set + * + * @details The runtime mysql_servers is now considered as a distinct module and have a separate checksum calculation. + * This is because the records in the runtime module may differ from those in the admin mysql_servers module, which + * can cause synchronization issues within the cluster. + * + * @param runtime_mysql_servers resultset of runtime mysql_servers or can be a nullptr. +*/ +uint64_t MySQL_HostGroups_Manager::get_mysql_servers_checksum(SQLite3_result* runtime_mysql_servers) { + + //Note: GloVars.checksum_mutex needs to be locked + SQLite3_result* resultset = nullptr; + + if (runtime_mysql_servers == nullptr) { + char* error = NULL; + int cols = 0; + int affected_rows = 0; + + mydb->execute_statement(MYHGM_GEN_CLUSTER_ADMIN_RUNTIME_SERVERS, &error, &cols, &affected_rows, &resultset); + + if (resultset) { + save_runtime_mysql_servers(resultset); + } else { + proxy_info("Checksum for table %s is 0x%lX\n", "mysql_servers", (long unsigned int)0); + } + } else { + resultset = runtime_mysql_servers; + save_runtime_mysql_servers(runtime_mysql_servers); + } + + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] = resultset != nullptr ? resultset->raw_checksum() : 0; + proxy_info("Checksum for table %s is 0x%lX\n", "mysql_servers", table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS]); + + return table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS]; +} + + +/** + * @brief Purge the MySQL servers table by removing offline hard servers with no active connections. + * + * This function iterates through each host group in the host groups manager and examines each server within the host group. + * For each server that is marked as offline hard and has no active connections (both used and free), it removes the server from the host group. + * After removing the server, it deletes the server object to free up memory. + * This process ensures that offline hard servers with no connections are properly removed from the MySQL servers table. + */ +void MySQL_HostGroups_Manager::purge_mysql_servers_table() { + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + MySrvC *mysrvc=NULL; + for (unsigned int j=0; jmysrvs->servers->len; j++) { + mysrvc=myhgc->mysrvs->idx(j); + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + if (mysrvc->ConnectionsUsed->conns_length()==0 && mysrvc->ConnectionsFree->conns_length()==0) { + // no more connections for OFFLINE_HARD server, removing it + mysrvc=(MySrvC *)myhgc->mysrvs->servers->remove_index_fast(j); + // already being refreshed in MySrvC destructor + //myhgc->refresh_online_server_count(); + j--; + delete mysrvc; + } + } + } + } +} + + + +void MySQL_HostGroups_Manager::generate_mysql_servers_table(int *_onlyhg) { + int rc; + sqlite3_stmt *statement1=NULL; + sqlite3_stmt *statement32=NULL; + + PtrArray *lst=new PtrArray(); + //sqlite3 *mydb3=mydb->get_db(); + char *query1=(char *)"INSERT INTO mysql_servers VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)"; + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query1, -1, &statement1, 0); + rc = mydb->prepare_v2(query1, &statement1); + ASSERT_SQLITE_OK(rc, mydb); + std::string query32s = "INSERT INTO mysql_servers VALUES " + generate_multi_rows_query(32,13); + char *query32 = (char *)query32s.c_str(); + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query32, -1, &statement32, 0); + rc = mydb->prepare_v2(query32, &statement32); + ASSERT_SQLITE_OK(rc, mydb); + + if (mysql_thread___hostgroup_manager_verbose) { + if (_onlyhg==NULL) { + proxy_info("Dumping current MySQL Servers structures for hostgroup ALL\n"); + } else { + int hidonly=*_onlyhg; + proxy_info("Dumping current MySQL Servers structures for hostgroup %d\n", hidonly); + } + } + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + if (_onlyhg) { + int hidonly=*_onlyhg; + if (myhgc->hid!=(unsigned int)hidonly) { + // skipping this HG + continue; + } + } + MySrvC *mysrvc=NULL; + for (unsigned int j=0; jmysrvs->servers->len; j++) { + mysrvc=myhgc->mysrvs->idx(j); + if (mysql_thread___hostgroup_manager_verbose) { + char *st; + switch ((int)mysrvc->get_status()) { + case 0: + st=(char *)"ONLINE"; + break; + case 2: + st=(char *)"OFFLINE_SOFT"; + break; + case 3: + st=(char *)"OFFLINE_HARD"; + break; + default: + case 1: + case 4: + st=(char *)"SHUNNED"; + break; + } + fprintf(stderr,"HID: %d , address: %s , port: %d , gtid_port: %d , weight: %ld , status: %s , max_connections: %ld , max_replication_lag: %u , use_ssl: %u , max_latency_ms: %u , comment: %s\n", mysrvc->myhgc->hid, mysrvc->address, mysrvc->port, mysrvc->gtid_port, mysrvc->weight, st, mysrvc->max_connections, mysrvc->max_replication_lag, mysrvc->use_ssl, mysrvc->max_latency_us*1000, mysrvc->comment); + } + lst->add(mysrvc); + if (lst->len==32) { + while (lst->len) { + int i=lst->len; + i--; + MySrvC *mysrvc=(MySrvC *)lst->remove_index_fast(0); + uintptr_t ptr=(uintptr_t)mysrvc; + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+1, mysrvc->myhgc->hid); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement32, (i*13)+2, mysrvc->address, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+3, mysrvc->port); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+4, mysrvc->gtid_port); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+5, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+6, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+7, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+8, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+9, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+10, mysrvc->use_ssl); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+11, mysrvc->max_latency_us/1000); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement32, (i*13)+12, mysrvc->comment, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+13, ptr); ASSERT_SQLITE_OK(rc, mydb); + } + SAFE_SQLITE3_STEP2(statement32); + rc=(*proxy_sqlite3_clear_bindings)(statement32); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement32); ASSERT_SQLITE_OK(rc, mydb); + } + } + } + while (lst->len) { + MySrvC *mysrvc=(MySrvC *)lst->remove_index_fast(0); + uintptr_t ptr=(uintptr_t)mysrvc; + rc=(*proxy_sqlite3_bind_int64)(statement1, 1, mysrvc->myhgc->hid); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement1, 2, mysrvc->address, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 3, mysrvc->port); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 4, mysrvc->gtid_port); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 5, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 6, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 7, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 8, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 9, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 10, mysrvc->use_ssl); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 11, mysrvc->max_latency_us/1000); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement1, 12, mysrvc->comment, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 13, ptr); ASSERT_SQLITE_OK(rc, mydb); + + SAFE_SQLITE3_STEP2(statement1); + rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, mydb); + } + (*proxy_sqlite3_finalize)(statement1); + (*proxy_sqlite3_finalize)(statement32); + if (mysql_thread___hostgroup_manager_verbose) { + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + if (_onlyhg==NULL) { + mydb->execute_statement((char *)"SELECT hostgroup_id hid, hostname, port, gtid_port gtid, weight, status, compression cmp, max_connections max_conns, max_replication_lag max_lag, use_ssl ssl, max_latency_ms max_lat, comment, mem_pointer FROM mysql_servers", &error , &cols , &affected_rows , &resultset); + } else { + int hidonly=*_onlyhg; + char *q1 = (char *)malloc(256); + sprintf(q1,"SELECT hostgroup_id hid, hostname, port, gtid_port gtid, weight, status, compression cmp, max_connections max_conns, max_replication_lag max_lag, use_ssl ssl, max_latency_ms max_lat, comment, mem_pointer FROM mysql_servers WHERE hostgroup_id=%d" , hidonly); + mydb->execute_statement(q1, &error , &cols , &affected_rows , &resultset); + free(q1); + } + if (error) { + proxy_error("Error on read from mysql_servers : %s\n", error); + } else { + if (resultset) { + if (_onlyhg==NULL) { + proxy_info("Dumping mysql_servers: ALL\n"); + } else { + int hidonly=*_onlyhg; + proxy_info("Dumping mysql_servers: HG %d\n", hidonly); + } + resultset->dump_to_stderr(); + } + } + if (resultset) { delete resultset; resultset=NULL; } + } + delete lst; +} + +/** + * @brief Generate the mysql_replication_hostgroups table based on incoming data. + * + * This function populates the mysql_replication_hostgroups table in the host groups manager database + * using the incoming replication hostgroups data. It iterates through each row of the incoming data, + * constructs an SQL INSERT query to insert the data into the table, and executes the query. + * If verbose mode is enabled, it logs information about each row being processed. + * + * @note This function assumes that the incoming_replication_hostgroups member variable is not NULL. + * If it is NULL, the function returns without performing any action. + */ +void MySQL_HostGroups_Manager::generate_mysql_replication_hostgroups_table() { + if (incoming_replication_hostgroups==NULL) + return; + if (mysql_thread___hostgroup_manager_verbose) { + proxy_info("New mysql_replication_hostgroups table\n"); + } + for (std::vector::iterator it = incoming_replication_hostgroups->rows.begin() ; it != incoming_replication_hostgroups->rows.end(); ++it) { + SQLite3_row *r=*it; + char *o=NULL; + int comment_length=0; // #issue #643 + //if (r->fields[3]) { // comment is not null + o=escape_string_single_quotes(r->fields[3],false); + comment_length=strlen(o); + //} + char *query=(char *)malloc(256+comment_length); + //if (r->fields[3]) { // comment is not null + sprintf(query,"INSERT INTO mysql_replication_hostgroups VALUES(%s,%s,'%s','%s')",r->fields[0], r->fields[1], r->fields[2], o); + if (o!=r->fields[3]) { // there was a copy + free(o); + } + //} else { + //sprintf(query,"INSERT INTO mysql_replication_hostgroups VALUES(%s,%s,NULL)",r->fields[0],r->fields[1]); + //} + mydb->execute(query); + if (mysql_thread___hostgroup_manager_verbose) { + fprintf(stderr,"writer_hostgroup: %s , reader_hostgroup: %s, check_type %s, comment: %s\n", r->fields[0],r->fields[1], r->fields[2], r->fields[3]); + } + free(query); + } + incoming_replication_hostgroups=NULL; +} + + +void MySQL_HostGroups_Manager::update_table_mysql_servers_for_monitor(bool lock) { + if (lock) { + wrlock(); + } + + std::lock_guard mysql_servers_lock(this->mysql_servers_to_monitor_mutex); + + char* error = NULL; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = NULL; + const char* query = "SELECT hostname, port, status, use_ssl FROM mysql_servers WHERE status != 3 GROUP BY hostname, port"; + + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "%s\n", query); + mydb->execute_statement(query, &error , &cols , &affected_rows , &resultset); + + if (error != nullptr) { + proxy_error("Error on read from mysql_servers : %s\n", error); + } else { + if (resultset != nullptr) { + delete this->mysql_servers_to_monitor; + this->mysql_servers_to_monitor = resultset; + } + } + + if (lock) { + wrunlock(); + } + + MySQL_Monitor::trigger_dns_cache_update(); +} + +/** + * @brief Dump data from a specified MySQL table. + * + * This function retrieves data from the specified MySQL table and returns it as a result set. + * The table name determines the SQL query to be executed to fetch the data. If the table is + * one of the predefined tables with special handling (e.g., mysql_servers), additional actions + * such as purging and generating the table may be performed before fetching the data. + * + * @param name The name of the MySQL table from which to dump data. + * @return A SQLite3_result pointer representing the result set containing the dumped data. + * The caller is responsible for managing the memory of the result set. + * @note If the provided table name is not recognized, the function assertion fails. + */ +SQLite3_result * MySQL_HostGroups_Manager::dump_table_mysql(const string& name) { + char * query = (char *)""; + if (name == "mysql_aws_aurora_hostgroups") { + query=(char *)"SELECT writer_hostgroup,reader_hostgroup,active,aurora_port,domain_name,max_lag_ms," + "check_interval_ms,check_timeout_ms,writer_is_also_reader,new_reader_weight,add_lag_ms,min_lag_ms,lag_num_checks,comment FROM mysql_aws_aurora_hostgroups"; + } else if (name == "mysql_galera_hostgroups") { + query=(char *)"SELECT writer_hostgroup,backup_writer_hostgroup,reader_hostgroup,offline_hostgroup,active,max_writers,writer_is_also_reader,max_transactions_behind,comment FROM mysql_galera_hostgroups"; + } else if (name == "mysql_group_replication_hostgroups") { + query=(char *)"SELECT writer_hostgroup,backup_writer_hostgroup,reader_hostgroup,offline_hostgroup,active,max_writers,writer_is_also_reader,max_transactions_behind,comment FROM mysql_group_replication_hostgroups"; + } else if (name == "mysql_replication_hostgroups") { + query=(char *)"SELECT writer_hostgroup, reader_hostgroup, check_type, comment FROM mysql_replication_hostgroups"; + } else if (name == "mysql_hostgroup_attributes") { + query=(char *)"SELECT hostgroup_id, max_num_online_servers, autocommit, free_connections_pct, init_connect, multiplex, connection_warming, throttle_connections_per_sec, ignore_session_variables, hostgroup_settings, servers_defaults, comment FROM mysql_hostgroup_attributes ORDER BY hostgroup_id"; + } else if (name == "mysql_servers_ssl_params") { + query=(char *)"SELECT hostname, port, username, ssl_ca, ssl_cert, ssl_key, ssl_capath, ssl_crl, ssl_crlpath, ssl_cipher, tls_version, comment FROM mysql_servers_ssl_params ORDER BY hostname, port, username"; + } else if (name == "mysql_servers") { + query = (char *)MYHGM_GEN_ADMIN_RUNTIME_SERVERS; + } else if (name == "cluster_mysql_servers") { + query = (char *)MYHGM_GEN_CLUSTER_ADMIN_RUNTIME_SERVERS; + } else { + assert(0); + } + wrlock(); + if (name == "mysql_servers") { + purge_mysql_servers_table(); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers\n"); + mydb->execute("DELETE FROM mysql_servers"); + generate_mysql_servers_table(); + } + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "%s\n", query); + mydb->execute_statement(query, &error , &cols , &affected_rows , &resultset); + wrunlock(); + return resultset; +} + +#endif // 0 +/** + * @brief Create a new MySQL host group container. + * + * This function creates a new instance of the MySQL host group container (`MyHGC`) with + * the specified host group ID and returns a pointer to it. + * + * @param _hid The host group ID for the new container. + * @return A pointer to the newly created `MyHGC` instance. + */ +template +HGC * Base_HostGroups_Manager::MyHGC_create(unsigned int _hid) { + HGC *myhgc=new HGC(_hid); + return myhgc; +} + +/** + * @brief Find a MySQL host group container by host group ID. + * + * This function searches for a MySQL host group container with the specified host group ID + * in the list of host groups. If found, it returns a pointer to the container; otherwise, + * it returns a null pointer. + * + * @param _hid The host group ID to search for. + * @return A pointer to the found `MyHGC` instance if found; otherwise, a null pointer. + */ +template +HGC * Base_HostGroups_Manager::MyHGC_find(unsigned int _hid) { + if (MyHostGroups->len < 100) { + // for few HGs, we use the legacy search + for (unsigned int i=0; ilen; i++) { + HGC *myhgc=(HGC *)MyHostGroups->index(i); + if (myhgc->hid==_hid) { + return myhgc; + } + } + } else { + // for a large number of HGs, we use the unordered_map + // this search is slower for a small number of HGs, therefore we use + // it only for large number of HGs + typename std::unordered_map::const_iterator it = MyHostGroups_map.find(_hid); + if (it != MyHostGroups_map.end()) { + HGC *myhgc = it->second; + return myhgc; + } + } + return NULL; +} + +/** + * @brief Lookup or create a MySQL host group container by host group ID. + * + * This function looks up a MySQL host group container with the specified host group ID. If + * found, it returns a pointer to the existing container; otherwise, it creates a new container + * with the specified host group ID, adds it to the list of host groups, and returns a pointer + * to it. + * + * @param _hid The host group ID to lookup or create. + * @return A pointer to the found or newly created `MyHGC` instance. + * @note The function assertion fails if a newly created container is not found. + */ +template +HGC * Base_HostGroups_Manager::MyHGC_lookup(unsigned int _hid) { + HGC *myhgc=NULL; + myhgc=MyHGC_find(_hid); + if (myhgc==NULL) { + myhgc=MyHGC_create(_hid); + } else { + return myhgc; + } + assert(myhgc); + MyHostGroups->add(myhgc); + MyHostGroups_map.emplace(_hid,myhgc); + return myhgc; +} + +#if 0 +void MySQL_HostGroups_Manager::increase_reset_counter() { + wrlock(); + status.myconnpoll_reset++; + wrunlock(); +} + +/** + * @brief Pushes a MySQL_Connection back to the connection pool. + * + * This method is responsible for returning a MySQL_Connection object back to its associated connection pool + * after it has been used. It performs various checks and optimizations before deciding whether to return + * the connection to the pool or destroy it. + * + * @param c The MySQL_Connection object to be pushed back to the pool. + * @param _lock Boolean flag indicating whether to acquire a lock before performing the operation. Default is true. + * + * @note The method assumes that the provided MySQL_Connection object has a valid parent server (MySrvC). + * If the parent server is not valid, unexpected behavior may occur. + * + * @note The method also assumes that the global thread handler (GloMTH) is available and initialized properly. + * If the global thread handler is not initialized, certain checks may fail, leading to unexpected behavior. + */ +void MySQL_HostGroups_Manager::push_MyConn_to_pool(MySQL_Connection *c, bool _lock) { + // Ensure that the provided connection has a valid parent server + assert(c->parent); + + MySrvC *mysrvc = nullptr; // Pointer to the parent server object + + // Acquire a lock if specified + if (_lock) + wrlock(); + + // Reset the auto-increment delay token associated with the connection + c->auto_increment_delay_token = 0; + + // Increment the counter tracking the number of connections pushed back to the pool + status.myconnpoll_push++; + + // Obtain a pointer to the parent server (MySrvC) + mysrvc = static_cast(c->parent); + + // Log debug information about the connection being returned to the pool + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); + + // Remove the connection from the list of used connections for the parent server + mysrvc->ConnectionsUsed->remove(c); + + // If the global thread handler (GloMTH) is not available, skip further processing + if (GloMTH == nullptr) { + goto __exit_push_MyConn_to_pool; + } + + // If the largest query length exceeds the threshold, destroy the connection + if (c->largest_query_length > (unsigned int)GloMTH->variables.threshold_query_length) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d . largest_query_length = %lu\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status(), c->largest_query_length); + delete c; + goto __exit_push_MyConn_to_pool; + } + + // If the server is online and the connection is in the idle state + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { + if (c->async_state_machine==ASYNC_IDLE) { + if (GloMTH == NULL) { goto __exit_push_MyConn_to_pool; } + if (c->local_stmts->get_num_backend_stmts() > (unsigned int)GloMTH->variables.max_stmts_per_connection) { // Check if the connection has too many prepared statements + // Log debug information about destroying the connection due to too many prepared statements + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d because has too many prepared statements\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); +// delete c; + mysrvc->ConnectionsUsed->add(c); // Add the connection back to the list of used connections + destroy_MyConn_from_pool(c, false); // Destroy the connection from the pool + } else { + c->optimize(); // Optimize the connection + mysrvc->ConnectionsFree->add(c); // Add the connection to the list of free connections + } + } else { + // Log debug information about destroying the connection + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); + delete c; // Destroy the connection + } + } else { + // Log debug information about destroying the connection + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); + delete c; // Destroy the connection + } + +// Exit point for releasing the lock +__exit_push_MyConn_to_pool: + if (_lock) + wrunlock(); // Release the lock if acquired +} + +/** + * @brief Pushes an array of MySQL_Connection objects back to the connection pool. + * + * This method is responsible for returning an array of MySQL_Connection objects back to their associated + * connection pool after they have been used. It iterates through the array and calls the push_MyConn_to_pool + * method for each connection without acquiring a lock for each individual push operation. + * + * @param ca An array of MySQL_Connection pointers representing the connections to be pushed back to the pool. + * @param cnt The number of connections in the array. + * + * @note This method assumes that the array of connections is valid and does not contain any nullptr entries. + * Unexpected behavior may occur if the array contains invalid pointers. + */ +void MySQL_HostGroups_Manager::push_MyConn_to_pool_array(MySQL_Connection **ca, unsigned int cnt) { + unsigned int i=0; // Index variable for iterating through the array + MySQL_Connection *c = nullptr; // Pointer to hold the current connection from the array + c=ca[i]; + + // Acquire a write lock to perform the operations atomically + wrlock(); + + // Iterate through the array of connections + while (ivariables.hostgroup_manager_verbose >= 3) { + char buf[64]; + if (skip_hid == NULL) { + sprintf(buf,"NULL"); + } else { + sprintf(buf,"%u", *skip_hid); + } + proxy_info("Calling unshun_server_all_hostgroups() for server %s:%d . Arguments: %lu , %d , %s\n" , address, port, t, max_wait_sec, buf); + } + 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->get_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 + ) { + if (GloMTH->variables.hostgroup_manager_verbose >= 3) { + proxy_info("Unshunning server %d:%s:%d . time_last_detected_error=%lu\n", mysrvc->myhgc->hid, address, port, mysrvc->time_last_detected_error); + } + mysrvc->set_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; + } + } + } + } + } + } +} + +/** + * @brief Retrieves a MySQL_Connection from the connection pool for a given hostgroup. + * + * This method is responsible for retrieving a MySQL_Connection from the connection pool associated + * with the specified hostgroup. It selects a random MySQL server within the hostgroup based on various + * criteria such as GTID information, maximum lag, and session attributes. If a suitable connection is found, + * it is marked as used and returned to the caller. + * + * @param _hid The ID of the hostgroup from which to retrieve the connection. + * @param sess A pointer to the MySQL_Session object associated with the connection. + * @param ff A boolean flag indicating whether to prioritize failover connections. + * @param gtid_uuid The GTID UUID used for GTID-based routing. + * @param gtid_trxid The GTID transaction ID used for GTID-based routing. + * @param max_lag_ms The maximum allowed lag in milliseconds. + * + * @return A pointer to the retrieved MySQL_Connection object if successful, or nullptr if no suitable connection + * is available in the pool. + * + * @note This method locks the connection pool to ensure thread safety during access. It releases the lock once + * the operation is completed. + */ +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 = nullptr; // Pointer to hold the retrieved MySQL_Connection + + // Acquire a write lock to access the connection pool + wrlock(); + + // Increment the counter for connection pool retrieval attempts + status.myconnpoll_get++; + + // Look up the hostgroup by ID and retrieve a random MySQL server from it based on specified criteria + MyHGC *myhgc=MyHGC_lookup(_hid); + MySrvC *mysrvc = NULL; +#ifdef TEST_AURORA + for (int i=0; i<10; i++) +#endif // TEST_AURORA + mysrvc = myhgc->get_random_MySrvC(gtid_uuid, gtid_trxid, max_lag_ms, sess); + if (mysrvc) { // a MySrvC exists. If not, we return NULL = no targets + // Attempt to get a random MySQL_Connection from the server's free connection pool + conn=mysrvc->ConnectionsFree->get_random_MyConn(sess, ff); + + // If a connection is obtained, mark it as used and update connection pool statistics + if (conn) { + mysrvc->ConnectionsUsed->add(conn); + status.myconnpoll_get_ok++; + mysrvc->update_max_connections_used(); + } + } + + // Release the write lock after accessing the connection pool + wrunlock(); + + // Debug message indicating the retrieved MySQL_Connection and its server details + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, (conn ? conn->parent->address : "") , (conn ? conn->parent->port : 0 )); + + // Return the retrieved MySQL_Connection (or nullptr if none available) + return conn; +} + +void MySQL_HostGroups_Manager::destroy_MyConn_from_pool(MySQL_Connection *c, bool _lock) { + bool to_del=true; // the default, legacy behavior + MySrvC *mysrvc=(MySrvC *)c->parent; + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE && c->send_quit && queue.size() < __sync_fetch_and_add(&GloMTH->variables.connpoll_reset_queue_length, 0)) { + if (c->async_state_machine==ASYNC_IDLE) { + // overall, the backend seems healthy and so it is the connection. Try to reset it + int myerr=mysql_errno(c->mysql); + if (myerr >= 2000 && myerr < 3000) { + // client library error . We must not try to save the connection + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Not trying to reset MySQL_Connection %p, server %s:%d . Error code %d\n", c, mysrvc->address, mysrvc->port, myerr); + } else { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Trying to reset MySQL_Connection %p, server %s:%d\n", c, mysrvc->address, mysrvc->port); + to_del=false; + queue.add(c); + } + } else { + // the connection seems health, but we are trying to destroy it + // probably because there is a long running query + // therefore we will try to kill the connection + if (mysql_thread___kill_backend_connection_when_disconnect) { + int myerr=mysql_errno(c->mysql); + switch (myerr) { + case 1231: + break; + default: + if (c->mysql->thread_id) { + MySQL_Connection_userinfo *ui=c->userinfo; + char *auth_password=NULL; + if (ui->password) { + if (ui->password[0]=='*') { // we don't have the real password, let's pass sha1 + auth_password=ui->sha1_pass; + } else { + auth_password=ui->password; + } + } + KillArgs *ka = new KillArgs(ui->username, auth_password, c->parent->address, c->parent->port, c->parent->myhgc->hid, c->mysql->thread_id, KILL_CONNECTION, c->parent->use_ssl, NULL, c->connected_host_details.ip); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize (&attr, 256*1024); + pthread_t pt; + if (pthread_create(&pt, &attr, &kill_query_thread, ka) != 0) { + // LCOV_EXCL_START + proxy_error("Thread creation\n"); + assert(0); + // LCOV_EXCL_STOP + } + } + break; + } + } + } + } + if (to_del) { + // we lock only this part of the code because we need to remove the connection from ConnectionsUsed + if (_lock) { + wrlock(); + } + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d\n", c, mysrvc->address, mysrvc->port); + mysrvc->ConnectionsUsed->remove(c); + status.myconnpoll_destroy++; + if (_lock) { + wrunlock(); + } + delete c; + } +} + +inline double get_prometheus_counter_val( + std::map& counter_map, const std::string& endpoint_id +) { + const auto& counter_entry = counter_map.find(endpoint_id); + double current_val = 0; + + if (counter_entry != counter_map.end()) { + current_val = counter_entry->second->Value(); + } + + return current_val; +} + +void reset_hg_attrs_server_defaults(MySrvC* mysrvc) { + mysrvc->weight = -1; + mysrvc->max_connections = -1; + mysrvc->use_ssl = -1; +} + +void update_hg_attrs_server_defaults(MySrvC* mysrvc, MyHGC* myhgc) { + if (mysrvc->weight == -1) { + if (myhgc->servers_defaults.weight != -1) { + mysrvc->weight = myhgc->servers_defaults.weight; + } else { + // Same harcoded default as in 'CREATE TABLE mysql_servers ...' + mysrvc->weight = 1; + } + } + if (mysrvc->max_connections == -1) { + if (myhgc->servers_defaults.max_connections != -1) { + mysrvc->max_connections = myhgc->servers_defaults.max_connections; + } else { + // Same harcoded default as in 'CREATE TABLE mysql_servers ...' + mysrvc->max_connections = 1000; + } + } + if (mysrvc->use_ssl == -1) { + if (myhgc->servers_defaults.use_ssl != -1) { + mysrvc->use_ssl = myhgc->servers_defaults.use_ssl; + } else { + // Same harcoded default as in 'CREATE TABLE mysql_servers ...' + mysrvc->use_ssl = 0; + } + } +} + +/** + * @brief Adds a MySQL server connection (MySrvC) to the specified hostgroup. + * + * This method adds a MySQL server connection (MySrvC) to the hostgroup identified by the given hostgroup ID (_hid). + * It performs necessary updates to the server metrics and attributes associated with the hostgroup. Additionally, it + * updates the endpoint metrics for the server based on its address and port. + * + * @param mysrvc A pointer to the MySQL server connection (MySrvC) to be added to the hostgroup. + * @param _hid The ID of the hostgroup to which the server connection is being added. + * + * @note The method updates various metrics and attributes associated with the server and hostgroup. It also ensures + * that endpoint metrics are updated to reflect the addition of the server to the hostgroup. + */ +void MySQL_HostGroups_Manager::add(MySrvC *mysrvc, unsigned int _hid) { + + // Debug message indicating the addition of the MySQL server connection to the hostgroup + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Adding MySrvC %p (%s:%d) for hostgroup %d\n", mysrvc, mysrvc->address, mysrvc->port, _hid); + + // Construct the endpoint ID using the hostgroup ID, server address, and port + std::string endpoint_id { std::to_string(_hid) + ":" + string { mysrvc->address } + ":" + std::to_string(mysrvc->port) }; + + // Since metrics for servers are stored per-endpoint; the metrics for a particular endpoint can live longer than the + // 'MySrvC' itself. For example, a failover or a server config change could remove the server from a particular + // hostgroup, and a subsequent one bring it back to the original hostgroup. For this reason, everytime a 'mysrvc' is + // created and added to a particular hostgroup, we update the endpoint metrics for it. + + // Update server metrics based on endpoint ID + mysrvc->bytes_recv = get_prometheus_counter_val(this->status.p_conn_pool_bytes_data_recv_map, endpoint_id); + mysrvc->bytes_sent = get_prometheus_counter_val(this->status.p_conn_pool_bytes_data_sent_map, endpoint_id); + mysrvc->connect_ERR = get_prometheus_counter_val(this->status.p_connection_pool_conn_err_map, endpoint_id); + mysrvc->connect_OK = get_prometheus_counter_val(this->status.p_connection_pool_conn_ok_map, endpoint_id); + mysrvc->queries_sent = get_prometheus_counter_val(this->status.p_connection_pool_queries_map, endpoint_id); + + // Lookup the hostgroup by ID and add the server connection to it + MyHGC *myhgc=MyHGC_lookup(_hid); + + // Update server defaults with hostgroup attributes + update_hg_attrs_server_defaults(mysrvc, myhgc); + + // Add the server to the hostgroup's servers list + myhgc->mysrvs->add(mysrvc); +} + +void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, const char *address, unsigned int port, + int current_replication_lag, bool override_repl_lag) { + + if (current_replication_lag == -1 && override_repl_lag == true) { + current_replication_lag = myhgc->get_monitor_slave_lag_when_null(); + override_repl_lag = false; + proxy_error("Replication lag on server %s:%d is NULL, using value %d\n", address, port, current_replication_lag); + } + + for (int j=0; j<(int)myhgc->mysrvs->cnt(); j++) { + MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); + if (strcmp(mysrvc->address,address)==0 && mysrvc->port==port) { + mysrvc->cur_replication_lag = current_replication_lag; + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { + if ( +// (current_replication_lag==-1 ) +// || + ( + current_replication_lag >= 0 && + mysrvc->max_replication_lag > 0 && // see issue #4018 + (current_replication_lag > (int)mysrvc->max_replication_lag) + ) + ) { + // always increase the counter + mysrvc->cur_replication_lag_count += 1; + if (mysrvc->cur_replication_lag_count >= (unsigned int)mysql_thread___monitor_replication_lag_count) { + proxy_warning("Shunning server %s:%d from HG %u with replication lag of %d second, count number: '%d'\n", address, port, myhgc->hid, current_replication_lag, mysrvc->cur_replication_lag_count); + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG); + } else { + proxy_info( + "Not shunning server %s:%d from HG %u with replication lag of %d second, count number: '%d' < replication_lag_count: '%d'\n", + address, + port, + myhgc->hid, + current_replication_lag, + mysrvc->cur_replication_lag_count, + mysql_thread___monitor_replication_lag_count + ); + } + } else { + mysrvc->cur_replication_lag_count = 0; + } + } else { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if ( + (/*current_replication_lag >= 0 &&*/override_repl_lag == false && + (current_replication_lag <= (int)mysrvc->max_replication_lag)) + || + (current_replication_lag==-2 && override_repl_lag == true) // see issue 959 + ) { + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + proxy_warning("Re-enabling server %s:%d from HG %u with replication lag of %d second\n", address, port, myhgc->hid, current_replication_lag); + mysrvc->cur_replication_lag_count = 0; + } + } + } + return; + } + } +} + +void MySQL_HostGroups_Manager::replication_lag_action(const std::list& mysql_servers) { + + //this method does not use admin table, so this lock is not needed. + //GloAdmin->mysql_servers_wrlock(); + unsigned long long curtime1 = monotonic_time(); + wrlock(); + + for (const auto& server : mysql_servers) { + + const int hid = std::get(server); + const std::string& address = std::get(server); + const unsigned int port = std::get(server); + const int current_replication_lag = std::get(server); + const bool override_repl_lag = std::get(server); + + if (mysql_thread___monitor_replication_lag_group_by_host == false) { + // legacy check. 1 check per server per hostgroup + MyHGC *myhgc = MyHGC_find(hid); + replication_lag_action_inner(myhgc,address.c_str(),port,current_replication_lag,override_repl_lag); + } + else { + // only 1 check per server, no matter the hostgroup + // all hostgroups must be searched + for (unsigned int i=0; ilen; i++) { + MyHGC*myhgc=(MyHGC*)MyHostGroups->index(i); + replication_lag_action_inner(myhgc,address.c_str(),port,current_replication_lag,override_repl_lag); + } + } + } + + wrunlock(); + //GloAdmin->mysql_servers_wrunlock(); + + unsigned long long curtime2 = monotonic_time(); + curtime1 = curtime1 / 1000; + curtime2 = curtime2 / 1000; + proxy_debug(PROXY_DEBUG_MONITOR, 7, "MySQL_HostGroups_Manager::replication_lag_action() locked for %llums (server count:%ld)\n", curtime2 - curtime1, mysql_servers.size()); +} + +void MySQL_HostGroups_Manager::drop_all_idle_connections() { + // NOTE: the caller should hold wrlock + int i, j; + for (i=0; i<(int)MyHostGroups->len; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { + MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); + //__sync_fetch_and_sub(&status.server_connections_connected, mysrvc->ConnectionsFree->conns->len); + mysrvc->ConnectionsFree->drop_all_connections(); + } + + // Drop idle connections if beyond max_connection + while (mysrvc->ConnectionsFree->conns_length() && mysrvc->ConnectionsUsed->conns_length()+mysrvc->ConnectionsFree->conns_length() > mysrvc->max_connections) { + MySQL_Connection *conn=mysrvc->ConnectionsFree->remove(0); + delete conn; + } + + //PtrArray *pa=mysrvc->ConnectionsFree->conns; + MySrvConnList *mscl=mysrvc->ConnectionsFree; + int free_connections_pct = mysql_thread___free_connections_pct; + if (mysrvc->myhgc->attributes.configured == true) { + // mysql_hostgroup_attributes takes priority + free_connections_pct = mysrvc->myhgc->attributes.free_connections_pct; + } + while (mscl->conns_length() > free_connections_pct*mysrvc->max_connections/100) { + MySQL_Connection *mc=mscl->remove(0); + delete mc; + } + + // drop all connections with life exceeding mysql-connection_max_age + if (mysql_thread___connection_max_age_ms) { + unsigned long long curtime=monotonic_time(); + int i=0; + for (i=0; i<(int)mscl->conns_length() ; i++) { + MySQL_Connection *mc=mscl->index(i); + unsigned long long intv = mysql_thread___connection_max_age_ms; + intv *= 1000; + if (curtime > mc->creation_time + intv) { + mc=mscl->remove(0); + delete mc; + i--; + } + } + } + + } + } +} + +/* + * Prepares at most num_conn idle connections in the given hostgroup for + * pinging. When -1 is passed as a hostgroup, all hostgroups are examined. + * + * The resulting idle connections are returned in conn_list. Note that not all + * currently idle connections will be returned (some might be purged). + * + * Connections are purged according to 2 criteria: + * - whenever the maximal number of connections for a server is hit, free + * connections will be purged + * - also, idle connections that cause the number of free connections to rise + * above a certain percentage of the maximal number of connections will be + * dropped as well + */ +int MySQL_HostGroups_Manager::get_multiple_idle_connections(int _hid, unsigned long long _max_last_time_used, MySQL_Connection **conn_list, int num_conn) { + wrlock(); + drop_all_idle_connections(); + int num_conn_current=0; + int j,k; + MyHGC* myhgc = NULL; + // Multimap holding the required info for accesing the oldest idle connections found. + std::multimap> oldest_idle_connections {}; + + 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; + MySrvConnList *mscl=mysrvc->ConnectionsFree; + for (k=0; k<(int)mscl->conns_length(); k++) { + MySQL_Connection *mc=mscl->index(k); + // If the connection is idle ... + if (mc->last_time_used && mc->last_time_used < _max_last_time_used) { + if ((int)oldest_idle_connections.size() < num_conn) { + oldest_idle_connections.insert({mc->last_time_used, { mysrvc, k }}); + } else if (num_conn != 0) { + auto last_elem_it = std::prev(oldest_idle_connections.end()); + + if (mc->last_time_used < last_elem_it->first) { + oldest_idle_connections.erase(last_elem_it); + oldest_idle_connections.insert({mc->last_time_used, { mysrvc, k }}); + } + } + } + } + } + } + + // In order to extract the found connections, the following actions must be performed: + // + // 1. Filter the found connections by 'MySrvC'. + // 2. Order by indexes on 'ConnectionsFree' in desc order. + // 3. Move the conns from 'ConnectionsFree' into 'ConnectionsUsed'. + std::unordered_map> mysrvcs_conns_idxs {}; + + // 1. Filter the connections by 'MySrvC'. + // + // We extract this for being able to later iterate through the obtained 'MySrvC' using the conn indexes. + for (const auto& conn_info : oldest_idle_connections) { + MySrvC* mysrvc = conn_info.second.first; + int32_t mc_idx = conn_info.second.second; + auto mysrcv_it = mysrvcs_conns_idxs.find(mysrvc); + + if (mysrcv_it == mysrvcs_conns_idxs.end()) { + mysrvcs_conns_idxs.insert({ mysrvc, { mc_idx }}); + } else { + mysrcv_it->second.push_back(mc_idx); + } + } + + // 2. Order by indexes on FreeConns in desc order. + // + // Since the conns are stored in 'ConnectionsFree', which holds the conns in a 'PtrArray', and we plan + // to remove multiple connections using the pre-stored indexes. We need to reorder the indexes in 'desc' + // order, otherwise we could be trashing the array while consuming it. See 'PtrArray::remove_index_fast'. + for (auto& mysrvc_conns_idxs : mysrvcs_conns_idxs) { + std::sort(std::begin(mysrvc_conns_idxs.second), std::end(mysrvc_conns_idxs.second), std::greater()); + } + + // 3. Move the conns from 'ConnectionsFree' into 'ConnectionsUsed'. + for (auto& conn_info : mysrvcs_conns_idxs) { + MySrvC* mysrvc = conn_info.first; + + for (const int conn_idx : conn_info.second) { + MySrvConnList* mscl = mysrvc->ConnectionsFree; + MySQL_Connection* mc = mscl->remove(conn_idx); + mysrvc->ConnectionsUsed->add(mc); + + conn_list[num_conn_current] = mc; + num_conn_current++; + + // Left here as a safeguard + if (num_conn_current >= num_conn) { + goto __exit_get_multiple_idle_connections; + } + } + } + +__exit_get_multiple_idle_connections: + status.myconnpoll_get_ping+=num_conn_current; + wrunlock(); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning %d idle connections\n", num_conn_current); + return num_conn_current; +} + +void MySQL_HostGroups_Manager::save_incoming_mysql_table(SQLite3_result *s, const string& name) { + SQLite3_result ** inc = NULL; + if (name == "mysql_aws_aurora_hostgroups") { + inc = &incoming_aws_aurora_hostgroups; + } else if (name == "mysql_galera_hostgroups") { + inc = &incoming_galera_hostgroups; + } else if (name == "mysql_group_replication_hostgroups") { + inc = &incoming_group_replication_hostgroups; + } else if (name == "mysql_replication_hostgroups") { + inc = &incoming_replication_hostgroups; + } else if (name == "mysql_hostgroup_attributes") { + inc = &incoming_hostgroup_attributes; + } else if (name == "mysql_servers_ssl_params") { + inc = &incoming_mysql_servers_ssl_params; + } else { + assert(0); + } + if (*inc != nullptr) { + delete *inc; + *inc = nullptr; + } + *inc = s; +} + +void MySQL_HostGroups_Manager::save_runtime_mysql_servers(SQLite3_result *s) { + if (runtime_mysql_servers) { + delete runtime_mysql_servers; + runtime_mysql_servers = nullptr; + } + runtime_mysql_servers=s; +} + +void MySQL_HostGroups_Manager::save_mysql_servers_v2(SQLite3_result* s) { + if (incoming_mysql_servers_v2) { + delete incoming_mysql_servers_v2; + incoming_mysql_servers_v2 = nullptr; + } + incoming_mysql_servers_v2 = s; +} + +/** + * @brief Retrieves the current SQLite3 result set associated with the specified MySQL table name. + * + * This method retrieves the current SQLite3 result set corresponding to the specified MySQL table name. + * The method is used to obtain the result set for various MySQL tables, such as hostgroups, replication configurations, + * SSL parameters, and runtime server information. + * + * @param name The name of the MySQL table for which the current SQLite3 result set is to be retrieved. + * Supported table names include: + * - "mysql_aws_aurora_hostgroups" + * - "mysql_galera_hostgroups" + * - "mysql_group_replication_hostgroups" + * - "mysql_replication_hostgroups" + * - "mysql_hostgroup_attributes" + * - "mysql_servers_ssl_params" + * - "cluster_mysql_servers" + * - "mysql_servers_v2" + * + * @return A pointer to the current SQLite3 result set associated with the specified MySQL table name. + * If the table name is not recognized or no result set is available for the specified table, NULL is returned. + * + * @note The method assumes that the result sets are stored in class member variables, and it returns the pointer to + * the appropriate result set based on the provided table name. If the table name is not recognized, an assertion + * failure occurs, indicating an invalid table name. + */ +SQLite3_result* MySQL_HostGroups_Manager::get_current_mysql_table(const string& name) { + if (name == "mysql_aws_aurora_hostgroups") { + return this->incoming_aws_aurora_hostgroups; + } else if (name == "mysql_galera_hostgroups") { + return this->incoming_galera_hostgroups; + } else if (name == "mysql_group_replication_hostgroups") { + return this->incoming_group_replication_hostgroups; + } else if (name == "mysql_replication_hostgroups") { + return this->incoming_replication_hostgroups; + } else if (name == "mysql_hostgroup_attributes") { + return this->incoming_hostgroup_attributes; + } else if (name == "mysql_servers_ssl_params") { + return this->incoming_mysql_servers_ssl_params; + } else if (name == "cluster_mysql_servers") { + return this->runtime_mysql_servers; + } else if (name == "mysql_servers_v2") { + return this->incoming_mysql_servers_v2; + } else { + assert(0); // Assertion failure for unrecognized table name + } + return NULL; +} + + + +SQLite3_result * MySQL_HostGroups_Manager::SQL3_Free_Connections() { + const int colnum=13; + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 4, "Dumping Free Connections in Pool\n"); + SQLite3_result *result=new SQLite3_result(colnum); + result->add_column_definition(SQLITE_TEXT,"fd"); + result->add_column_definition(SQLITE_TEXT,"hostgroup"); + result->add_column_definition(SQLITE_TEXT,"srv_host"); + result->add_column_definition(SQLITE_TEXT,"srv_port"); + result->add_column_definition(SQLITE_TEXT,"user"); + result->add_column_definition(SQLITE_TEXT,"schema"); + result->add_column_definition(SQLITE_TEXT,"init_connect"); + result->add_column_definition(SQLITE_TEXT,"time_zone"); + result->add_column_definition(SQLITE_TEXT,"sql_mode"); + result->add_column_definition(SQLITE_TEXT,"autocommit"); + result->add_column_definition(SQLITE_TEXT,"idle_ms"); + result->add_column_definition(SQLITE_TEXT,"statistics"); + result->add_column_definition(SQLITE_TEXT,"mysql_info"); + unsigned long long curtime = monotonic_time(); + wrlock(); + int i,j, k, l; + for (i=0; i<(int)MyHostGroups->len; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { + MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); + mysrvc->ConnectionsFree->drop_all_connections(); + } + // drop idle connections if beyond max_connection + while (mysrvc->ConnectionsFree->conns_length() && mysrvc->ConnectionsUsed->conns_length()+mysrvc->ConnectionsFree->conns_length() > mysrvc->max_connections) { + //MySQL_Connection *conn=(MySQL_Connection *)mysrvc->ConnectionsFree->conns->remove_index_fast(0); + MySQL_Connection *conn=mysrvc->ConnectionsFree->remove(0); + delete conn; + } + char buf[1024]; + for (l=0; l < (int) mysrvc->ConnectionsFree->conns_length(); l++) { + char **pta=(char **)malloc(sizeof(char *)*colnum); + MySQL_Connection *conn = mysrvc->ConnectionsFree->index(l); + sprintf(buf,"%d", conn->fd); + pta[0]=strdup(buf); + sprintf(buf,"%d", (int)myhgc->hid); + pta[1]=strdup(buf); + pta[2]=strdup(mysrvc->address); + sprintf(buf,"%d", mysrvc->port); + pta[3]=strdup(buf); + pta[4] = strdup(conn->userinfo->username); + pta[5] = strdup(conn->userinfo->schemaname); + pta[6] = NULL; + if (conn->options.init_connect) { + pta[6] = strdup(conn->options.init_connect); + } + pta[7] = NULL; + if (conn->variables[SQL_TIME_ZONE].value) { + pta[7] = strdup(conn->variables[SQL_TIME_ZONE].value); + } + pta[8] = NULL; + if (conn->variables[SQL_SQL_MODE].value) { + pta[8] = strdup(conn->variables[SQL_SQL_MODE].value); + } + sprintf(buf,"%d", conn->options.autocommit); + pta[9]=strdup(buf); + sprintf(buf,"%llu", (curtime-conn->last_time_used)/1000); + pta[10]=strdup(buf); + { + json j; + char buff[32]; + sprintf(buff,"%p",conn); + j["address"] = buff; + uint64_t age_ms = (curtime - conn->creation_time)/1000; + j["age_ms"] = age_ms; + j["bytes_recv"] = conn->bytes_info.bytes_recv; + j["bytes_sent"] = conn->bytes_info.bytes_sent; + j["myconnpoll_get"] = conn->statuses.myconnpoll_get; + j["myconnpoll_put"] = conn->statuses.myconnpoll_put; + j["questions"] = conn->statuses.questions; + string s = j.dump(); + pta[11] = strdup(s.c_str()); + } + { + MYSQL *_my = conn->mysql; + json j; + char buff[32]; + sprintf(buff,"%p",_my); + j["address"] = buff; + j["host"] = _my->host; + j["host_info"] = _my->host_info; + j["port"] = _my->port; + j["server_version"] = _my->server_version; + j["user"] = _my->user; + j["unix_socket"] = (_my->unix_socket ? _my->unix_socket : ""); + j["db"] = (_my->db ? _my->db : ""); + j["affected_rows"] = _my->affected_rows; + j["insert_id"] = _my->insert_id; + j["thread_id"] = _my->thread_id; + j["server_status"] = _my->server_status; + j["charset"] = _my->charset->nr; + j["charset_name"] = _my->charset->csname; + + j["options"]["charset_name"] = ( _my->options.charset_name ? _my->options.charset_name : "" ); + j["options"]["use_ssl"] = _my->options.use_ssl; + j["client_flag"]["client_found_rows"] = (_my->client_flag & CLIENT_FOUND_ROWS ? 1 : 0); + j["client_flag"]["client_multi_statements"] = (_my->client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0); + j["client_flag"]["client_multi_results"] = (_my->client_flag & CLIENT_MULTI_RESULTS ? 1 : 0); + j["net"]["last_errno"] = _my->net.last_errno; + j["net"]["fd"] = _my->net.fd; + j["net"]["max_packet_size"] = _my->net.max_packet_size; + j["net"]["sqlstate"] = _my->net.sqlstate; + string s = j.dump(); + pta[12] = strdup(s.c_str()); + } + result->add_row(pta); + for (k=0; k& labels, std::map& m_map, unsigned long long value, p_hg_dyn_counter::metric idx +) { + const auto& counter_id = m_map.find(endpoint_id); + if (counter_id != m_map.end()) { + const auto& cur_val = counter_id->second->Value(); + counter_id->second->Increment(value - cur_val); + } else { + auto& new_counter = status.p_dyn_counter_array[idx]; + m_map.insert( + { + endpoint_id, + std::addressof(new_counter->Add(labels)) + } + ); + } +} + +void MySQL_HostGroups_Manager::p_update_connection_pool_update_gauge( + const std::string& endpoint_id, const std::map& labels, + std::map& m_map, unsigned long long value, p_hg_dyn_gauge::metric idx +) { + const auto& counter_id = m_map.find(endpoint_id); + if (counter_id != m_map.end()) { + counter_id->second->Set(value); + } else { + auto& new_counter = status.p_dyn_gauge_array[idx]; + m_map.insert( + { + endpoint_id, + std::addressof(new_counter->Add(labels)) + } + ); + } +} + +void MySQL_HostGroups_Manager::p_update_connection_pool() { + std::vector cur_servers_ids {}; + wrlock(); + for (int i = 0; i < static_cast(MyHostGroups->len); i++) { + MyHGC *myhgc = static_cast(MyHostGroups->index(i)); + for (int j = 0; j < static_cast(myhgc->mysrvs->cnt()); j++) { + MySrvC *mysrvc = static_cast(myhgc->mysrvs->servers->index(j)); + std::string endpoint_addr = mysrvc->address; + std::string endpoint_port = std::to_string(mysrvc->port); + std::string hostgroup_id = std::to_string(myhgc->hid); + std::string endpoint_id = hostgroup_id + ":" + endpoint_addr + ":" + endpoint_port; + const std::map common_labels { + {"endpoint", endpoint_addr + ":" + endpoint_port}, + {"hostgroup", hostgroup_id } + }; + cur_servers_ids.push_back(endpoint_id); + + // proxysql_connection_pool_bytes_data_recv metric + std::map recv_pool_bytes_labels = common_labels; + recv_pool_bytes_labels.insert({"traffic_flow", "recv"}); + p_update_connection_pool_update_counter(endpoint_id, recv_pool_bytes_labels, + status.p_conn_pool_bytes_data_recv_map, mysrvc->bytes_recv, p_hg_dyn_counter::conn_pool_bytes_data_recv); + + // proxysql_connection_pool_bytes_data_sent metric + std::map sent_pool_bytes_labels = common_labels; + sent_pool_bytes_labels.insert({"traffic_flow", "sent"}); + p_update_connection_pool_update_counter(endpoint_id, sent_pool_bytes_labels, + status.p_conn_pool_bytes_data_sent_map, mysrvc->bytes_sent, p_hg_dyn_counter::conn_pool_bytes_data_sent); + + // proxysql_connection_pool_conn_err metric + std::map pool_conn_err_labels = common_labels; + pool_conn_err_labels.insert({"status", "err"}); + p_update_connection_pool_update_counter(endpoint_id, pool_conn_err_labels, + status.p_connection_pool_conn_err_map, mysrvc->connect_ERR, p_hg_dyn_counter::connection_pool_conn_err); + + // proxysql_connection_pool_conn_ok metric + std::map pool_conn_ok_labels = common_labels; + pool_conn_ok_labels.insert({"status", "ok"}); + p_update_connection_pool_update_counter(endpoint_id, pool_conn_ok_labels, + status.p_connection_pool_conn_ok_map, mysrvc->connect_OK, p_hg_dyn_counter::connection_pool_conn_ok); + + // proxysql_connection_pool_conn_free metric + std::map pool_conn_free_labels = common_labels; + pool_conn_free_labels.insert({"status", "free"}); + p_update_connection_pool_update_gauge(endpoint_id, pool_conn_free_labels, + status.p_connection_pool_conn_free_map, mysrvc->ConnectionsFree->conns_length(), p_hg_dyn_gauge::connection_pool_conn_free); + + // proxysql_connection_pool_conn_used metric + std::map pool_conn_used_labels = common_labels; + pool_conn_used_labels.insert({"status", "used"}); + p_update_connection_pool_update_gauge(endpoint_id, pool_conn_used_labels, + status.p_connection_pool_conn_used_map, mysrvc->ConnectionsUsed->conns_length(), p_hg_dyn_gauge::connection_pool_conn_used); + + // proxysql_connection_pool_latency_us metric + p_update_connection_pool_update_gauge(endpoint_id, common_labels, + status.p_connection_pool_latency_us_map, mysrvc->current_latency_us, p_hg_dyn_gauge::connection_pool_latency_us); + + // proxysql_connection_pool_queries metric + p_update_connection_pool_update_counter(endpoint_id, common_labels, + status.p_connection_pool_queries_map, mysrvc->queries_sent, p_hg_dyn_counter::connection_pool_queries); + + // proxysql_connection_pool_status metric + p_update_connection_pool_update_gauge(endpoint_id, common_labels, + status.p_connection_pool_status_map, ((int)mysrvc->get_status()) + 1, p_hg_dyn_gauge::connection_pool_status); + } + } + + // Remove the non-present servers for the gauge metrics + vector missing_server_keys {}; + + for (const auto& key : status.p_connection_pool_status_map) { + if (std::find(cur_servers_ids.begin(), cur_servers_ids.end(), key.first) == cur_servers_ids.end()) { + missing_server_keys.push_back(key.first); + } + } + + for (const auto& key : missing_server_keys) { + auto gauge = status.p_connection_pool_status_map[key]; + status.p_dyn_gauge_array[p_hg_dyn_gauge::connection_pool_status]->Remove(gauge); + status.p_connection_pool_status_map.erase(key); + + gauge = status.p_connection_pool_conn_free_map[key]; + status.p_dyn_gauge_array[p_hg_dyn_gauge::connection_pool_conn_free]->Remove(gauge); + status.p_connection_pool_conn_free_map.erase(key); + + gauge = status.p_connection_pool_conn_used_map[key]; + status.p_dyn_gauge_array[p_hg_dyn_gauge::connection_pool_conn_used]->Remove(gauge); + status.p_connection_pool_conn_used_map.erase(key); + + gauge = status.p_connection_pool_latency_us_map[key]; + status.p_dyn_gauge_array[p_hg_dyn_gauge::connection_pool_latency_us]->Remove(gauge); + status.p_connection_pool_latency_us_map.erase(key); + } + + wrunlock(); +} + +SQLite3_result * MySQL_HostGroups_Manager::SQL3_Connection_Pool(bool _reset, int *hid) { + const int colnum=14; + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 4, "Dumping Connection Pool\n"); + SQLite3_result *result=new SQLite3_result(colnum); + result->add_column_definition(SQLITE_TEXT,"hostgroup"); + result->add_column_definition(SQLITE_TEXT,"srv_host"); + result->add_column_definition(SQLITE_TEXT,"srv_port"); + result->add_column_definition(SQLITE_TEXT,"status"); + result->add_column_definition(SQLITE_TEXT,"ConnUsed"); + result->add_column_definition(SQLITE_TEXT,"ConnFree"); + result->add_column_definition(SQLITE_TEXT,"ConnOK"); + result->add_column_definition(SQLITE_TEXT,"ConnERR"); + result->add_column_definition(SQLITE_TEXT,"MaxConnUsed"); + result->add_column_definition(SQLITE_TEXT,"Queries"); + result->add_column_definition(SQLITE_TEXT,"Queries_GTID_sync"); + result->add_column_definition(SQLITE_TEXT,"Bytes_sent"); + result->add_column_definition(SQLITE_TEXT,"Bytes_recv"); + result->add_column_definition(SQLITE_TEXT,"Latency_us"); + wrlock(); + int i,j, k; + for (i=0; i<(int)MyHostGroups->len; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { + MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); + if (hid == NULL) { + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); + //__sync_fetch_and_sub(&status.server_connections_connected, mysrvc->ConnectionsFree->conns->len); + mysrvc->ConnectionsFree->drop_all_connections(); + } + // drop idle connections if beyond max_connection + while (mysrvc->ConnectionsFree->conns_length() && mysrvc->ConnectionsUsed->conns_length()+mysrvc->ConnectionsFree->conns_length() > mysrvc->max_connections) { + //MySQL_Connection *conn=(MySQL_Connection *)mysrvc->ConnectionsFree->conns->remove_index_fast(0); + MySQL_Connection *conn=mysrvc->ConnectionsFree->remove(0); + delete conn; + //__sync_fetch_and_sub(&status.server_connections_connected, 1); + } + } else { + if (*hid != (int)myhgc->hid) { + continue; + } + } + char buf[1024]; + char **pta=(char **)malloc(sizeof(char *)*colnum); + sprintf(buf,"%d", (int)myhgc->hid); + pta[0]=strdup(buf); + pta[1]=strdup(mysrvc->address); + sprintf(buf,"%d", mysrvc->port); + pta[2]=strdup(buf); + switch ((int)mysrvc->get_status()) { + case 0: + pta[3]=strdup("ONLINE"); + break; + case 1: + pta[3]=strdup("SHUNNED"); + break; + case 2: + pta[3]=strdup("OFFLINE_SOFT"); + break; + case 3: + pta[3]=strdup("OFFLINE_HARD"); + break; + case 4: + pta[3]=strdup("SHUNNED_REPLICATION_LAG"); + break; + default: + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } + sprintf(buf,"%u", mysrvc->ConnectionsUsed->conns_length()); + pta[4]=strdup(buf); + sprintf(buf,"%u", mysrvc->ConnectionsFree->conns_length()); + pta[5]=strdup(buf); + sprintf(buf,"%u", mysrvc->connect_OK); + pta[6]=strdup(buf); + if (_reset) { + mysrvc->connect_OK=0; + } + sprintf(buf,"%u", mysrvc->connect_ERR); + pta[7]=strdup(buf); + if (_reset) { + mysrvc->connect_ERR=0; + } + sprintf(buf,"%u", mysrvc->max_connections_used); + pta[8]=strdup(buf); + if (_reset) { + mysrvc->max_connections_used=0; + } + sprintf(buf,"%llu", mysrvc->queries_sent); + pta[9]=strdup(buf); + if (_reset) { + mysrvc->queries_sent=0; + } + sprintf(buf,"%llu", mysrvc->queries_gtid_sync); + pta[10]=strdup(buf); + if (_reset) { + mysrvc->queries_gtid_sync=0; + } + sprintf(buf,"%llu", mysrvc->bytes_sent); + pta[11]=strdup(buf); + if (_reset) { + mysrvc->bytes_sent=0; + } + sprintf(buf,"%llu", mysrvc->bytes_recv); + pta[12]=strdup(buf); + if (_reset) { + mysrvc->bytes_recv=0; + } + sprintf(buf,"%u", mysrvc->current_latency_us); + pta[13]=strdup(buf); + result->add_row(pta); + for (k=0; k3"; + mydb->execute_statement((char *)q1, &error , &cols , &affected_rows , &res_set1); + for (std::vector::iterator it = res_set1->rows.begin() ; it != res_set1->rows.end(); ++it) { + SQLite3_row *r=*it; + std::string s = r->fields[0]; + s += ":::"; + s += r->fields[1]; + read_only_set1.insert(s); + } + proxy_info("Regenerating read_only_set1 with %lu servers\n", read_only_set1.size()); + if (read_only_set1.empty()) { + // to avoid regenerating this set always with 0 entries, we generate a fake entry + read_only_set1.insert("----:::----"); + } + delete res_set1; + } + wrunlock(); + std::string ser = hostname; + ser += ":::"; + ser += std::to_string(port); + std::set::iterator it; + it = read_only_set1.find(ser); + if (it != read_only_set1.end()) { + num_rows=1; + } + + if (admindb==NULL) { // we initialize admindb only if needed + admindb=new SQLite3DB(); + admindb->open((char *)"file:mem_admindb?mode=memory&cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + } + + switch (read_only) { + case 0: + if (num_rows==0) { + // the server has read_only=0 , but we can't find any writer, so we perform a swap + GloAdmin->mysql_servers_wrlock(); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 phase 1 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->save_mysql_servers_runtime_to_database(false); // SAVE MYSQL SERVERS FROM RUNTIME + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 phase 2 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + sprintf(query,Q2A,hostname,port); + admindb->execute(query); + sprintf(query,Q2B,hostname,port); + admindb->execute(query); + if (mysql_thread___monitor_writer_is_also_reader) { + sprintf(query,Q3A,hostname,port); + } else { + sprintf(query,Q3B,hostname,port); + } + admindb->execute(query); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 phase 3 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->load_mysql_servers_to_runtime(); // LOAD MYSQL SERVERS TO RUNTIME + GloAdmin->mysql_servers_wrunlock(); + } else { + // there is a server in writer hostgroup, let check the status of present and not present hosts + bool act=false; + wrlock(); + std::set::iterator it; + // read_only_set2 acts as a cache + // if the server was RO=0 on the previous check and no action was needed, + // it will be here + it = read_only_set2.find(ser); + if (it != read_only_set2.end()) { + // the server was already detected as RO=0 + // no action required + } else { + // it is the first time that we detect RO on this server + sprintf(query,Q1B,hostname,port,hostname,port,hostname,port); + mydb->execute_statement(query, &error , &cols , &affected_rows , &resultset); + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *r=*it; + int status=MYSQL_SERVER_STATUS_OFFLINE_HARD; // default status, even for missing + if (r->fields[1]) { // has status + status=atoi(r->fields[1]); + } + if (status==MYSQL_SERVER_STATUS_OFFLINE_HARD) { + act=true; + } + } + if (act == false) { + // no action required, therefore we write in read_only_set2 + proxy_info("read_only_action() detected RO=0 on server %s:%d for the first time after commit(), but no need to reconfigure\n", hostname, port); + read_only_set2.insert(ser); + } + } + wrunlock(); + if (act==true) { // there are servers either missing, or with stats=OFFLINE_HARD + GloAdmin->mysql_servers_wrlock(); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 , rows=%d , phase 1 : Dumping mysql_servers for %s:%d\n", num_rows, hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->save_mysql_servers_runtime_to_database(false); // SAVE MYSQL SERVERS FROM RUNTIME + sprintf(query,Q2A,hostname,port); + admindb->execute(query); + sprintf(query,Q2B,hostname,port); + admindb->execute(query); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 , rows=%d , phase 2 : Dumping mysql_servers for %s:%d\n", num_rows, hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + if (mysql_thread___monitor_writer_is_also_reader) { + sprintf(query,Q3A,hostname,port); + } else { + sprintf(query,Q3B,hostname,port); + } + admindb->execute(query); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=0 , rows=%d , phase 3 : Dumping mysql_servers for %s:%d\n", num_rows, hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->load_mysql_servers_to_runtime(); // LOAD MYSQL SERVERS TO RUNTIME + GloAdmin->mysql_servers_wrunlock(); + } + } + break; + case 1: + if (num_rows) { + // the server has read_only=1 , but we find it as writer, so we perform a swap + GloAdmin->mysql_servers_wrlock(); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=1 phase 1 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->save_mysql_servers_runtime_to_database(false); // SAVE MYSQL SERVERS FROM RUNTIME + sprintf(query,Q4,hostname,port); + admindb->execute(query); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=1 phase 2 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + sprintf(query,Q5,hostname,port); + admindb->execute(query); + if (GloMTH->variables.hostgroup_manager_verbose) { + char *error2=NULL; + int cols2=0; + int affected_rows2=0; + SQLite3_result *resultset2=NULL; + char * query2 = NULL; + char *q = (char *)"SELECT * FROM mysql_servers WHERE hostname=\"%s\" AND port=%d"; + query2 = (char *)malloc(strlen(q)+strlen(hostname)+32); + sprintf(query2,q,hostname,port); + admindb->execute_statement(query2, &error2 , &cols2 , &affected_rows2 , &resultset2); + if (error2) { + proxy_error("Error on read from mysql_servers : %s\n", error2); + } else { + if (resultset2) { + proxy_info("read_only_action RO=1 phase 3 : Dumping mysql_servers for %s:%d\n", hostname, port); + resultset2->dump_to_stderr(); + } + } + if (resultset2) { delete resultset2; resultset2=NULL; } + free(query2); + } + GloAdmin->load_mysql_servers_to_runtime(); // LOAD MYSQL SERVERS TO RUNTIME + GloAdmin->mysql_servers_wrunlock(); + } + break; + default: + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } + + pthread_mutex_unlock(&readonly_mutex); + if (resultset) { + delete resultset; + } + free(query); +} + +/** + * @brief New implementation of the read_only_action method that does not depend on the admin table. + * The method checks each server in the provided list and adjusts the servers according to their corresponding read_only value. + * If any change has occured, checksum is calculated. + * + * @param mysql_servers List of servers having hostname, port and read only value. + * + */ +void MySQL_HostGroups_Manager::read_only_action_v2(const std::list& mysql_servers) { + + bool update_mysql_servers_table = false; + + unsigned long long curtime1 = monotonic_time(); + wrlock(); + for (const auto& server : mysql_servers) { + bool is_writer = false; + const std::string& hostname = std::get(server); + const int port = std::get(server); + const int read_only = std::get(server); + const std::string& srv_id = hostname + ":::" + std::to_string(port); + + auto itr = hostgroup_server_mapping.find(srv_id); + + if (itr == hostgroup_server_mapping.end()) { + proxy_warning("Server %s:%d not found\n", hostname.c_str(), port); + continue; + } + + HostGroup_Server_Mapping* host_server_mapping = itr->second.get(); + + if (!host_server_mapping) + assert(0); + + const std::vector& writer_map = host_server_mapping->get(HostGroup_Server_Mapping::Type::WRITER); + + is_writer = !writer_map.empty(); + + if (read_only == 0) { + if (is_writer == false) { + // the server has read_only=0 (writer), but we can't find any writer, + // so we copy all reader nodes to writer + proxy_info("Server '%s:%d' found with 'read_only=0', but not found as writer\n", hostname.c_str(), port); + proxy_debug(PROXY_DEBUG_MONITOR, 5, "Server '%s:%d' found with 'read_only=0', but not found as writer\n", hostname.c_str(), port); + host_server_mapping->copy_if_not_exists(HostGroup_Server_Mapping::Type::WRITER, HostGroup_Server_Mapping::Type::READER); + + if (mysql_thread___monitor_writer_is_also_reader == false) { + // remove node from reader + host_server_mapping->clear(HostGroup_Server_Mapping::Type::READER); + } + + update_mysql_servers_table = true; + proxy_info("Regenerating table 'mysql_servers' due to actions on server '%s:%d'\n", hostname.c_str(), port); + } else { + bool act = false; + + // if the server was RO=0 on the previous check then no action is needed + if (host_server_mapping->get_readonly_flag() != 0) { + // it is the first time that we detect RO on this server + const std::vector& reader_map = host_server_mapping->get(HostGroup_Server_Mapping::Type::READER); + + for (const auto& reader_node : reader_map) { + for (const auto& writer_node : writer_map) { + + if (reader_node.writer_hostgroup_id == writer_node.writer_hostgroup_id) { + goto __writer_found; + } + } + act = true; + break; + __writer_found: + continue; + } + + if (act == false) { + // no action required, therefore we set readonly_flag to 0 + proxy_info("read_only_action_v2() detected RO=0 on server %s:%d for the first time after commit(), but no need to reconfigure\n", hostname.c_str(), port); + host_server_mapping->set_readonly_flag(0); + } + } else { + // the server was already detected as RO=0 + // no action required + } + + if (act == true) { // there are servers either missing, or with stats=OFFLINE_HARD + + proxy_info("Server '%s:%d' with 'read_only=0' found missing at some 'writer_hostgroup'\n", hostname.c_str(), port); + proxy_debug(PROXY_DEBUG_MONITOR, 5, "Server '%s:%d' with 'read_only=0' found missing at some 'writer_hostgroup'\n", hostname.c_str(), port); + + // copy all reader nodes to writer + host_server_mapping->copy_if_not_exists(HostGroup_Server_Mapping::Type::WRITER, HostGroup_Server_Mapping::Type::READER); + + if (mysql_thread___monitor_writer_is_also_reader == false) { + // remove node from reader + host_server_mapping->clear(HostGroup_Server_Mapping::Type::READER); + } + + update_mysql_servers_table = true; + proxy_info("Regenerating table 'mysql_servers' due to actions on server '%s:%d'\n", hostname.c_str(), port); + } + } + } else if (read_only == 1) { + if (is_writer) { + // the server has read_only=1 (reader), but we find it as writer, so we copy all writer nodes to reader (previous reader nodes will be reused) + proxy_info("Server '%s:%d' found with 'read_only=1', but not found as reader\n", hostname.c_str(), port); + proxy_debug(PROXY_DEBUG_MONITOR, 5, "Server '%s:%d' found with 'read_only=1', but not found as reader\n", hostname.c_str(), port); + host_server_mapping->copy_if_not_exists(HostGroup_Server_Mapping::Type::READER, HostGroup_Server_Mapping::Type::WRITER); + + // clearing all writer nodes + host_server_mapping->clear(HostGroup_Server_Mapping::Type::WRITER); + + update_mysql_servers_table = true; + proxy_info("Regenerating table 'mysql_servers' due to actions on server '%s:%d'\n", hostname.c_str(), port); + } + } else { + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } + } + + if (update_mysql_servers_table) { + purge_mysql_servers_table(); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers\n"); + mydb->execute("DELETE FROM mysql_servers"); + generate_mysql_servers_table(); + + // Update the global checksums after 'mysql_servers' regeneration + { + unique_ptr resultset { get_admin_runtime_mysql_servers(mydb) }; + uint64_t raw_checksum = resultset ? resultset->raw_checksum() : 0; + + // This is required to be updated to avoid extra rebuilding member 'hostgroup_server_mapping' + // during 'commit'. For extra details see 'hgsm_mysql_servers_checksum' @details. + hgsm_mysql_servers_checksum = raw_checksum; + + string mysrvs_checksum { get_checksum_from_hash(raw_checksum) }; + save_runtime_mysql_servers(resultset.release()); + proxy_info("Checksum for table %s is %s\n", "mysql_servers", mysrvs_checksum.c_str()); + + pthread_mutex_lock(&GloVars.checksum_mutex); + update_glovars_mysql_servers_checksum(mysrvs_checksum); + pthread_mutex_unlock(&GloVars.checksum_mutex); + } + } + wrunlock(); + unsigned long long curtime2 = monotonic_time(); + curtime1 = curtime1 / 1000; + curtime2 = curtime2 / 1000; + proxy_debug(PROXY_DEBUG_MONITOR, 7, "MySQL_HostGroups_Manager::read_only_action_v2() locked for %llums (server count:%ld)\n", curtime2 - curtime1, mysql_servers.size()); +} + +// shun_and_killall +// this function is called only from MySQL_Monitor::monitor_ping() +// it temporary disables a host that is not responding to pings, and mark the host in a way that when used the connection will be dropped +// return true if the status was changed +bool MySQL_HostGroups_Manager::shun_and_killall(char *hostname, int port) { + time_t t = time(NULL); + bool ret = false; + wrlock(); + MySrvC *mysrvc=NULL; + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + unsigned int j; + unsigned int l=myhgc->mysrvs->cnt(); + if (l) { + for (j=0; jmysrvs->idx(j); + if (mysrvc->port==port && strcmp(mysrvc->address,hostname)==0) { + switch ((MySerStatus)mysrvc->get_status()) { + case MYSQL_SERVER_STATUS_SHUNNED: + if (mysrvc->shunned_automatic==false) { + break; + } + case MYSQL_SERVER_STATUS_ONLINE: + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { + ret = true; + } + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED); + case MYSQL_SERVER_STATUS_OFFLINE_SOFT: + mysrvc->shunned_automatic=true; + mysrvc->shunned_and_kill_all_connections=true; + mysrvc->ConnectionsFree->drop_all_connections(); + break; + default: + break; + } + // if Monitor is enabled and mysql-monitor_ping_interval is + // set too high, ProxySQL will unshun hosts that are not + // available. For this reason time_last_detected_error will + // be tuned in the future + if (mysql_thread___monitor_enabled) { + int a = mysql_thread___shun_recovery_time_sec; + int b = mysql_thread___monitor_ping_interval; + b = b/1000; + if (b > a) { + t = t + (b - a); + } + } + mysrvc->time_last_detected_error = t; + } + } + } + } + wrunlock(); + return ret; +} + +// set_server_current_latency_us +// this function is called only from MySQL_Monitor::monitor_ping() +// it set the average latency for a host in the last 3 pings +// the connection pool will use this information to evaluate or exclude a specific hosts +// note that this variable is in microsecond, while user defines it in millisecond +void MySQL_HostGroups_Manager::set_server_current_latency_us(char *hostname, int port, unsigned int _current_latency_us) { + wrlock(); + MySrvC *mysrvc=NULL; + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + unsigned int j; + unsigned int l=myhgc->mysrvs->cnt(); + if (l) { + for (j=0; jmysrvs->idx(j); + if (mysrvc->port==port && strcmp(mysrvc->address,hostname)==0) { + mysrvc->current_latency_us=_current_latency_us; + } + } + } + } + wrunlock(); +} + +void MySQL_HostGroups_Manager::p_update_metrics() { + p_update_counter(status.p_counter_array[p_hg_counter::servers_table_version], status.servers_table_version); + // Update *server_connections* related metrics + status.p_gauge_array[p_hg_gauge::server_connections_connected]->Set(status.server_connections_connected); + p_update_counter(status.p_counter_array[p_hg_counter::server_connections_aborted], status.server_connections_aborted); + p_update_counter(status.p_counter_array[p_hg_counter::server_connections_created], status.server_connections_created); + p_update_counter(status.p_counter_array[p_hg_counter::server_connections_delayed], status.server_connections_delayed); + + // Update *client_connections* related metrics + p_update_counter(status.p_counter_array[p_hg_counter::client_connections_created], status.client_connections_created); + p_update_counter(status.p_counter_array[p_hg_counter::client_connections_aborted], status.client_connections_aborted); + status.p_gauge_array[p_hg_gauge::client_connections_connected]->Set(status.client_connections); + + // Update *acess_denied* related metrics + p_update_counter(status.p_counter_array[p_hg_counter::access_denied_wrong_password], status.access_denied_wrong_password); + p_update_counter(status.p_counter_array[p_hg_counter::access_denied_max_connections], status.access_denied_max_connections); + p_update_counter(status.p_counter_array[p_hg_counter::access_denied_max_user_connections], status.access_denied_max_user_connections); + + p_update_counter(status.p_counter_array[p_hg_counter::selects_for_update__autocommit0], status.select_for_update_or_equivalent); + + // Update *com_* related metrics + p_update_counter(status.p_counter_array[p_hg_counter::com_autocommit], status.autocommit_cnt); + p_update_counter(status.p_counter_array[p_hg_counter::com_autocommit_filtered], status.autocommit_cnt_filtered); + p_update_counter(status.p_counter_array[p_hg_counter::com_commit_cnt], status.commit_cnt); + p_update_counter(status.p_counter_array[p_hg_counter::com_commit_cnt_filtered], status.commit_cnt_filtered); + p_update_counter(status.p_counter_array[p_hg_counter::com_rollback], status.rollback_cnt); + p_update_counter(status.p_counter_array[p_hg_counter::com_rollback_filtered], status.rollback_cnt_filtered); + p_update_counter(status.p_counter_array[p_hg_counter::com_backend_init_db], status.backend_init_db); + p_update_counter(status.p_counter_array[p_hg_counter::com_backend_change_user], status.backend_change_user); + p_update_counter(status.p_counter_array[p_hg_counter::com_backend_set_names], status.backend_set_names); + p_update_counter(status.p_counter_array[p_hg_counter::com_frontend_init_db], status.frontend_init_db); + p_update_counter(status.p_counter_array[p_hg_counter::com_frontend_set_names], status.frontend_set_names); + p_update_counter(status.p_counter_array[p_hg_counter::com_frontend_use_db], status.frontend_use_db); + + // Update *myconnpoll* related metrics + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_get], status.myconnpoll_get); + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_get_ok], status.myconnpoll_get_ok); + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_get_ping], status.myconnpoll_get_ping); + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_push], status.myconnpoll_push); + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_reset], status.myconnpoll_reset); + p_update_counter(status.p_counter_array[p_hg_counter::myhgm_myconnpool_destroy], status.myconnpoll_destroy); + + p_update_counter(status.p_counter_array[p_hg_counter::auto_increment_delay_multiplex], status.auto_increment_delay_multiplex); + + // Update the *connection_pool* metrics + this->p_update_connection_pool(); + // Update the *gtid_executed* metrics + this->p_update_mysql_gtid_executed(); +} + +SQLite3_result * MySQL_HostGroups_Manager::SQL3_Get_ConnPool_Stats() { + const int colnum=2; + char buf[256]; + char **pta=(char **)malloc(sizeof(char *)*colnum); + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 4, "Dumping MySQL Global Status\n"); + SQLite3_result *result=new SQLite3_result(colnum); + result->add_column_definition(SQLITE_TEXT,"Variable_Name"); + result->add_column_definition(SQLITE_TEXT,"Variable_Value"); + wrlock(); + // NOTE: as there is no string copy, we do NOT free pta[0] and pta[1] + { + pta[0]=(char *)"MyHGM_myconnpoll_get"; + sprintf(buf,"%lu",status.myconnpoll_get); + pta[1]=buf; + result->add_row(pta); + } + { + pta[0]=(char *)"MyHGM_myconnpoll_get_ok"; + sprintf(buf,"%lu",status.myconnpoll_get_ok); + pta[1]=buf; + result->add_row(pta); + } + { + pta[0]=(char *)"MyHGM_myconnpoll_push"; + sprintf(buf,"%lu",status.myconnpoll_push); + pta[1]=buf; + result->add_row(pta); + } + { + pta[0]=(char *)"MyHGM_myconnpoll_destroy"; + sprintf(buf,"%lu",status.myconnpoll_destroy); + pta[1]=buf; + result->add_row(pta); + } + { + pta[0]=(char *)"MyHGM_myconnpoll_reset"; + sprintf(buf,"%lu",status.myconnpoll_reset); + pta[1]=buf; + result->add_row(pta); + } + wrunlock(); + free(pta); + return result; +} + +/** + * @brief Retrieves memory usage statistics for the MySQL host groups manager. + * + * This method calculates the total memory usage of the MySQL host groups manager, including memory allocated for + * host groups, server connections, and MySQL connections. It iterates over all host groups and their associated + * server connections to compute the memory usage. + * + * @return The total memory usage of the MySQL host groups manager in bytes. + */ +unsigned long long MySQL_HostGroups_Manager::Get_Memory_Stats() { + // Initialize the memory size counter + unsigned long long intsize=0; + // Acquire write lock to ensure thread safety during memory calculation + wrlock(); + MySrvC *mysrvc=NULL; // Pointer to a MySQL server connection + + // Iterate over all hostgroups + for (unsigned int i=0; ilen; i++) { + // Add memory size for the hostgroup object + intsize+=sizeof(MyHGC); + // Get the hostgroup object + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + unsigned int j,k; + // Get the number of server connections in the hostgroup + unsigned int l=myhgc->mysrvs->cnt(); + // Iterate over all server connections in the hostgroup + if (l) { + for (j=0; jmysrvs->idx(j); + // Calculate memory usage for each connection in the "ConnectionsFree" list + intsize+=((mysrvc->ConnectionsUsed->conns_length())*sizeof(MySQL_Connection *)); + for (k=0; kConnectionsFree->conns_length(); k++) { + // Get a MySQL connection + MySQL_Connection *myconn=mysrvc->ConnectionsFree->index(k); + // Add memory size for MySQL connection object and MYSQL struct + intsize+=sizeof(MySQL_Connection)+sizeof(MYSQL); + // Add memory size for the MySQL packet buffer + intsize+=myconn->mysql->net.max_packet; + // Add memory size for the default stack size of the asynchronous context + intsize+=(4096*15); // ASYNC_CONTEXT_DEFAULT_STACK_SIZE + // Add memory size for result set object if present + if (myconn->MyRS) { + intsize+=myconn->MyRS->current_size(); + } + } + intsize+=((mysrvc->ConnectionsUsed->conns_length())*sizeof(MySQL_Connection *)); + } + } + } + // Release the write lock + wrunlock(); + // Return the total memory usage + return intsize; +} + +Group_Replication_Info::Group_Replication_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c) { + comment=NULL; + if (c) { + comment=strdup(c); + } + writer_hostgroup=w; + backup_writer_hostgroup=b; + reader_hostgroup=r; + offline_hostgroup=o; + max_writers=mw; + max_transactions_behind=mtb; + active=_a; + writer_is_also_reader=_w; + current_num_writers=0; + current_num_backup_writers=0; + current_num_readers=0; + current_num_offline=0; + __active=true; + need_converge=true; +} + +Group_Replication_Info::~Group_Replication_Info() { + if (comment) { + free(comment); + comment=NULL; + } +} + +bool Group_Replication_Info::update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c) { + bool ret=false; + __active=true; + if (backup_writer_hostgroup!=b) { + backup_writer_hostgroup=b; + ret=true; + } + if (reader_hostgroup!=r) { + reader_hostgroup=r; + ret=true; + } + if (offline_hostgroup!=o) { + offline_hostgroup=o; + ret=true; + } + if (max_writers!=mw) { + max_writers=mw; + ret=true; + } + if (max_transactions_behind!=mtb) { + max_transactions_behind=mtb; + ret=true; + } + if (active!=_a) { + active=_a; + ret=true; + } + if (writer_is_also_reader!=_w) { + writer_is_also_reader=_w; + ret=true; + } + // for comment we don't change return value + if (comment) { + if (c) { + if (strcmp(comment,c)) { + free(comment); + comment=strdup(c); + } + } else { + free(comment); + comment=NULL; + } + } else { + if (c) { + comment=strdup(c); + } + } + return ret; +} + +class MySQL_Errors_stats { + public: + int hostgroup; + char *hostname; + int port; + char *username; + char *client_address; + char *schemaname; + int err_no; + char *last_error; + time_t first_seen; + time_t last_seen; + unsigned long long count_star; + MySQL_Errors_stats(int hostgroup_, char *hostname_, int port_, char *username_, char *address_, char *schemaname_, int err_no_, char *last_error_, time_t tn) { + hostgroup = hostgroup_; + if (hostname_) { + hostname = strdup(hostname_); + } else { + hostname = strdup((char *)""); + } + port = port_; + if (username_) { + username = strdup(username_); + } else { + username = strdup((char *)""); + } + if (address_) { + client_address = strdup(address_); + } else { + client_address = strdup((char *)""); + } + if (schemaname_) { + schemaname = strdup(schemaname_); + } else { + schemaname = strdup((char *)""); + } + err_no = err_no_; + if (last_error_) { + last_error = strdup(last_error_); + } else { + last_error = strdup((char *)""); + } + last_seen = tn; + first_seen = tn; + count_star = 1; + } + ~MySQL_Errors_stats() { + if (hostname) { + free(hostname); + hostname=NULL; + } + if (username) { + free(username); + username=NULL; + } + if (client_address) { + free(client_address); + client_address=NULL; + } + if (schemaname) { + free(schemaname); + schemaname=NULL; + } + if (last_error) { + free(last_error); + last_error=NULL; + } + } + char **get_row() { + char buf[128]; + char **pta=(char **)malloc(sizeof(char *)*MYSQL_ERRORS_STATS_FIELD_NUM); + sprintf(buf,"%d",hostgroup); + pta[0]=strdup(buf); + assert(hostname); + pta[1]=strdup(hostname); + sprintf(buf,"%d",port); + pta[2]=strdup(buf); + assert(username); + pta[3]=strdup(username); + assert(client_address); + pta[4]=strdup(client_address); + assert(schemaname); + pta[5]=strdup(schemaname); + sprintf(buf,"%d",err_no); + pta[6]=strdup(buf); + + sprintf(buf,"%llu",count_star); + pta[7]=strdup(buf); + + sprintf(buf,"%ld", first_seen); + pta[8]=strdup(buf); + + sprintf(buf,"%ld", last_seen); + pta[9]=strdup(buf); + + assert(last_error); + pta[10]=strdup(last_error); + return pta; + } + void add_time(unsigned long long n, char *le) { + count_star++; + if (first_seen==0) { + first_seen=n; + } + last_seen=n; + if (strcmp(last_error,le)){ + free(last_error); + last_error=strdup(le); + } + } + void free_row(char **pta) { + int i; + for (i=0;i::iterator it; + pthread_mutex_lock(&mysql_errors_mutex); + + it=mysql_errors_umap.find(hash1); + + if (it != mysql_errors_umap.end()) { + // found + mes=(MySQL_Errors_stats *)it->second; + mes->add_time(tn, last_error); +/* + mes->last_seen = tn; + if (strcmp(mes->last_error,last_error)) { + free(mes->last_error); + mes->last_error = strdup(last_error); + mes->count_star++; + } +*/ + } else { + mes = new MySQL_Errors_stats(hostgroup, hostname, port, username, address, schemaname, err_no, last_error, tn); + mysql_errors_umap.insert(std::make_pair(hash1,(void *)mes)); + } + pthread_mutex_unlock(&mysql_errors_mutex); +} + +SQLite3_result * MySQL_HostGroups_Manager::get_mysql_errors(bool reset) { + SQLite3_result *result=new SQLite3_result(MYSQL_ERRORS_STATS_FIELD_NUM); + pthread_mutex_lock(&mysql_errors_mutex); + result->add_column_definition(SQLITE_TEXT,"hid"); + result->add_column_definition(SQLITE_TEXT,"hostname"); + result->add_column_definition(SQLITE_TEXT,"port"); + result->add_column_definition(SQLITE_TEXT,"username"); + result->add_column_definition(SQLITE_TEXT,"client_address"); + result->add_column_definition(SQLITE_TEXT,"schemaname"); + result->add_column_definition(SQLITE_TEXT,"err_no"); + result->add_column_definition(SQLITE_TEXT,"count_star"); + result->add_column_definition(SQLITE_TEXT,"first_seen"); + result->add_column_definition(SQLITE_TEXT,"last_seen"); + result->add_column_definition(SQLITE_TEXT,"last_error"); + for (std::unordered_map::iterator it=mysql_errors_umap.begin(); it!=mysql_errors_umap.end(); ++it) { + MySQL_Errors_stats *mes=(MySQL_Errors_stats *)it->second; + char **pta=mes->get_row(); + result->add_row(pta); + mes->free_row(pta); + if (reset) { + delete mes; + } + } + if (reset) { + mysql_errors_umap.erase(mysql_errors_umap.begin(),mysql_errors_umap.end()); + } + pthread_mutex_unlock(&mysql_errors_mutex); + return result; +} + +/** + * @brief Initializes the supplied 'MyHGC' with the specified 'hostgroup_settings'. + * @details Input verification is performed in the supplied 'hostgroup_settings'. It's expected to be a valid + * JSON that may contain the following fields: + * - handle_warnings: Value must be >= 0. + * + * In case input verification fails for a field, supplied 'MyHGC' is NOT updated for that field. An error + * message is logged specifying the source of the error. + * + * @param hostgroup_settings String containing a JSON defined in 'mysql_hostgroup_attributes'. + * @param myhgc The 'MyHGC' of the target hostgroup of the supplied 'hostgroup_settings'. + */ +void init_myhgc_hostgroup_settings(const char* hostgroup_settings, MyHGC* myhgc) { + const uint32_t hid = myhgc->hid; + + if (hostgroup_settings[0] != '\0') { + try { + nlohmann::json j = nlohmann::json::parse(hostgroup_settings); + + const auto handle_warnings_check = [](int8_t handle_warnings) -> bool { return handle_warnings == 0 || handle_warnings == 1; }; + const int8_t handle_warnings = j_get_srv_default_int_val(j, hid, "handle_warnings", handle_warnings_check); + myhgc->attributes.handle_warnings = handle_warnings; + + const auto monitor_slave_lag_when_null_check = [](int32_t monitor_slave_lag_when_null) -> bool + { return (monitor_slave_lag_when_null >= 0 && monitor_slave_lag_when_null <= 604800); }; + const int32_t monitor_slave_lag_when_null = j_get_srv_default_int_val(j, hid, "monitor_slave_lag_when_null", monitor_slave_lag_when_null_check); + myhgc->attributes.monitor_slave_lag_when_null = monitor_slave_lag_when_null; + } + catch (const json::exception& e) { + proxy_error( + "JSON parsing for 'mysql_hostgroup_attributes.hostgroup_settings' for hostgroup %d failed with exception `%s`.\n", + hid, e.what() + ); + } + } +} + +/** + * @brief Initializes the supplied 'MyHGC' with the specified 'servers_defaults'. + * @details Input verification is performed in the supplied 'server_defaults'. It's expected to be a valid + * JSON that may contain the following fields: + * - weight: Must be an unsigned integer >= 0. + * - max_connections: Must be an unsigned integer >= 0. + * - use_ssl: Must be a integer with either value 0 or 1. + * + * In case input verification fails for a field, supplied 'MyHGC' is NOT updated for that field. An error + * message is logged specifying the source of the error. + * + * @param servers_defaults String containing a JSON defined in 'mysql_hostgroup_attributes'. + * @param myhgc The 'MyHGC' of the target hostgroup of the supplied 'servers_defaults'. + */ +void init_myhgc_servers_defaults(char* servers_defaults, MyHGC* myhgc) { + uint32_t hid = myhgc->hid; + + if (strcmp(servers_defaults, "") != 0) { + try { + nlohmann::json j = nlohmann::json::parse(servers_defaults); + + const auto weight_check = [] (int64_t weight) -> bool { return weight >= 0; }; + int64_t weight = j_get_srv_default_int_val(j, hid, "weight", weight_check); + + myhgc->servers_defaults.weight = weight; + + const auto max_conns_check = [] (int64_t max_conns) -> bool { return max_conns >= 0; }; + int64_t max_conns = j_get_srv_default_int_val(j, hid, "max_connections", max_conns_check); + + myhgc->servers_defaults.max_connections = max_conns; + + const auto use_ssl_check = [] (int32_t use_ssl) -> bool { return use_ssl == 0 || use_ssl == 1; }; + int32_t use_ssl = j_get_srv_default_int_val(j, hid, "use_ssl", use_ssl_check); + + myhgc->servers_defaults.use_ssl = use_ssl; + } catch (const json::exception& e) { + proxy_error( + "JSON parsing for 'mysql_hostgroup_attributes.servers_defaults' for hostgroup %d failed with exception `%s`.\n", + hid, e.what() + ); + } + } +} + +void MySQL_HostGroups_Manager::generate_mysql_hostgroup_attributes_table() { + if (incoming_hostgroup_attributes==NULL) { + return; + } + int rc; + sqlite3_stmt *statement=NULL; + + const char * query=(const char *)"INSERT INTO mysql_hostgroup_attributes ( " + "hostgroup_id, max_num_online_servers, autocommit, free_connections_pct, " + "init_connect, multiplex, connection_warming, throttle_connections_per_sec, " + "ignore_session_variables, hostgroup_settings, servers_defaults, comment) VALUES " + "(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"; + + //rc=(*proxy_sqlite3_prepare_v2)(mydb3, query, -1, &statement, 0); + rc = mydb->prepare_v2(query, &statement); + ASSERT_SQLITE_OK(rc, mydb); + proxy_info("New mysql_hostgroup_attributes table\n"); + bool current_configured[MyHostGroups->len]; + // set configured = false to all + // in this way later we can known which HG were updated + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + current_configured[i] = myhgc->attributes.configured; + myhgc->attributes.configured = false; + } + + /** + * @brief We iterate the whole resultset incoming_hostgroup_attributes and configure + * both the hostgroup in memory, but also pupulate table mysql_hostgroup_attributes + * connection errors. + * @details for each row in incoming_hostgroup_attributes: + * 1. it finds (or create) the hostgroup + * 2. it writes the in mysql_hostgroup_attributes + * 3. it finds (or create) the attributes of the hostgroup + */ + for (std::vector::iterator it = incoming_hostgroup_attributes->rows.begin() ; it != incoming_hostgroup_attributes->rows.end(); ++it) { + SQLite3_row *r=*it; + unsigned int hid = (unsigned int)atoi(r->fields[0]); + MyHGC *myhgc = MyHGC_lookup(hid); // note: MyHGC_lookup() will create the HG if doesn't exist! + int max_num_online_servers = atoi(r->fields[1]); + int autocommit = atoi(r->fields[2]); + int free_connections_pct = atoi(r->fields[3]); + char * init_connect = r->fields[4]; + int multiplex = atoi(r->fields[5]); + int connection_warming = atoi(r->fields[6]); + int throttle_connections_per_sec = atoi(r->fields[7]); + char * ignore_session_variables = r->fields[8]; + char * hostgroup_settings = r->fields[9]; + char * servers_defaults = r->fields[10]; + char * comment = r->fields[11]; + proxy_info("Loading MySQL Hostgroup Attributes info for (%d,%d,%d,%d,\"%s\",%d,%d,%d,\"%s\",\"%s\",\"%s\",\"%s\")\n", + hid, max_num_online_servers, autocommit, free_connections_pct, + init_connect, multiplex, connection_warming, throttle_connections_per_sec, + ignore_session_variables, hostgroup_settings, servers_defaults, comment + ); + rc=(*proxy_sqlite3_bind_int64)(statement, 1, hid); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 2, max_num_online_servers); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 3, autocommit); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 4, free_connections_pct); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement, 5, init_connect, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 6, multiplex); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 7, connection_warming); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement, 8, throttle_connections_per_sec); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement, 9, ignore_session_variables, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement, 10, hostgroup_settings, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement, 11, servers_defaults, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_text)(statement, 12, comment, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); + SAFE_SQLITE3_STEP2(statement); + rc=(*proxy_sqlite3_clear_bindings)(statement); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement); ASSERT_SQLITE_OK(rc, mydb); + myhgc->attributes.configured = true; + myhgc->attributes.max_num_online_servers = max_num_online_servers; + myhgc->attributes.autocommit = autocommit; + myhgc->attributes.free_connections_pct = free_connections_pct; + myhgc->attributes.multiplex = multiplex; + myhgc->attributes.connection_warming = connection_warming; + myhgc->attributes.throttle_connections_per_sec = throttle_connections_per_sec; + if (myhgc->attributes.init_connect != NULL) + free(myhgc->attributes.init_connect); + myhgc->attributes.init_connect = strdup(init_connect); + if (myhgc->attributes.comment != NULL) + free(myhgc->attributes.comment); + myhgc->attributes.comment = strdup(comment); + // for ignore_session_variables we store 2 versions: + // 1. the text + // 2. the JSON + // Because calling JSON functions is expensive, we first verify if it changes + if (myhgc->attributes.ignore_session_variables_text == NULL) { + myhgc->attributes.ignore_session_variables_text = strdup(ignore_session_variables); + if (strlen(ignore_session_variables) != 0) { // only if there is a valid JSON + if (myhgc->attributes.ignore_session_variables_json != nullptr) { delete myhgc->attributes.ignore_session_variables_json; } + myhgc->attributes.ignore_session_variables_json = new json(json::parse(ignore_session_variables)); + } + } else { + if (strcmp(myhgc->attributes.ignore_session_variables_text, ignore_session_variables) != 0) { + free(myhgc->attributes.ignore_session_variables_text); + myhgc->attributes.ignore_session_variables_text = strdup(ignore_session_variables); + if (strlen(ignore_session_variables) != 0) { // only if there is a valid JSON + if (myhgc->attributes.ignore_session_variables_json != nullptr) { delete myhgc->attributes.ignore_session_variables_json; } + myhgc->attributes.ignore_session_variables_json = new json(json::parse(ignore_session_variables)); + } + // TODO: assign the variables + } + } + init_myhgc_hostgroup_settings(hostgroup_settings, myhgc); + init_myhgc_servers_defaults(servers_defaults, myhgc); + } + for (unsigned int i=0; ilen; i++) { + MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); + if (myhgc->attributes.configured == false) { + if (current_configured[i] == true) { + // if configured == false and previously it was configured == true , reset to defaults + proxy_info("Resetting hostgroup attributes for hostgroup %u\n", myhgc->hid); + myhgc->reset_attributes(); + } + } + } + + (*proxy_sqlite3_finalize)(statement); + delete incoming_hostgroup_attributes; + incoming_hostgroup_attributes=NULL; +} + +void MySQL_HostGroups_Manager::generate_mysql_servers_ssl_params_table() { + if (incoming_mysql_servers_ssl_params==NULL) { + return; + } + int rc; + sqlite3_stmt *statement=NULL; + + const char * query = (const char *)"INSERT INTO mysql_servers_ssl_params (" + "hostname, port, username, ssl_ca, ssl_cert, ssl_key, ssl_capath, " + "ssl_crl, ssl_crlpath, ssl_cipher, tls_version, comment) VALUES " + "(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"; + + rc = mydb->prepare_v2(query, &statement); + ASSERT_SQLITE_OK(rc, mydb); + proxy_info("New mysql_servers_ssl_params table\n"); + std::lock_guard lock(Servers_SSL_Params_map_mutex); + Servers_SSL_Params_map.clear(); + + for (std::vector::iterator it = incoming_mysql_servers_ssl_params->rows.begin() ; it != incoming_mysql_servers_ssl_params->rows.end(); ++it) { + SQLite3_row *r=*it; + proxy_info("Loading MySQL Server SSL Params for (%s,%s,%s)\n", + r->fields[0], r->fields[1], r->fields[2] + ); + + rc=(*proxy_sqlite3_bind_text)(statement, 1, r->fields[0] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // hostname + rc=(*proxy_sqlite3_bind_int64)(statement, 2, atoi(r->fields[1])); ASSERT_SQLITE_OK(rc, mydb); // port + rc=(*proxy_sqlite3_bind_text)(statement, 3, r->fields[2] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // username + rc=(*proxy_sqlite3_bind_text)(statement, 4, r->fields[3] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_ca + rc=(*proxy_sqlite3_bind_text)(statement, 5, r->fields[4] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_cert + rc=(*proxy_sqlite3_bind_text)(statement, 6, r->fields[5] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_key + rc=(*proxy_sqlite3_bind_text)(statement, 7, r->fields[6] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_capath + rc=(*proxy_sqlite3_bind_text)(statement, 8, r->fields[7] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_crl + rc=(*proxy_sqlite3_bind_text)(statement, 9, r->fields[8] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_crlpath + rc=(*proxy_sqlite3_bind_text)(statement, 10, r->fields[9] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // ssl_cipher + rc=(*proxy_sqlite3_bind_text)(statement, 11, r->fields[10] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // tls_version + rc=(*proxy_sqlite3_bind_text)(statement, 12, r->fields[11] , -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, mydb); // comment + + SAFE_SQLITE3_STEP2(statement); + rc=(*proxy_sqlite3_clear_bindings)(statement); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_reset)(statement); ASSERT_SQLITE_OK(rc, mydb); + + MySQLServers_SslParams MSSP( + r->fields[0], atoi(r->fields[1]), r->fields[2], + r->fields[3], r->fields[4], r->fields[5], + r->fields[6], r->fields[7], r->fields[8], + r->fields[9], r->fields[10], r->fields[11] + ); + string MapKey = MSSP.getMapKey(rand_del); + Servers_SSL_Params_map.emplace(MapKey, MSSP); + } + (*proxy_sqlite3_finalize)(statement); + delete incoming_mysql_servers_ssl_params; + incoming_mysql_servers_ssl_params=NULL; +} + +int MySQL_HostGroups_Manager::create_new_server_in_hg( + uint32_t hid, const srv_info_t& srv_info, const srv_opts_t& srv_opts +) { + int32_t res = -1; + MySrvC* mysrvc = find_server_in_hg(hid, srv_info.addr, srv_info.port); + + if (mysrvc == nullptr) { + char* c_hostname { const_cast(srv_info.addr.c_str()) }; + MySrvC* mysrvc = new MySrvC( + c_hostname, srv_info.port, 0, srv_opts.weigth, MYSQL_SERVER_STATUS_ONLINE, 0, srv_opts.max_conns, 0, + srv_opts.use_ssl, 0, const_cast("") + ); + add(mysrvc,hid); + proxy_info( + "Adding new discovered %s node %s:%d with: hostgroup=%d, weight=%ld, max_connections=%ld, use_ssl=%d\n", + srv_info.kind.c_str(), c_hostname, srv_info.port, hid, mysrvc->weight, mysrvc->max_connections, + mysrvc->use_ssl + ); + + res = 0; + } else { + // If the server is found as 'OFFLINE_HARD' we reset the 'MySrvC' values corresponding with the + // 'servers_defaults' (as in a new 'MySrvC' creation). We then later update these values with the + // 'servers_defaults' attributes from its corresponding 'MyHGC'. This way we ensure uniform behavior + // of new servers, and 'OFFLINE_HARD' ones when a user update 'servers_defaults' values, and reloads + // the servers to runtime. + if (mysrvc && mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + reset_hg_attrs_server_defaults(mysrvc); + update_hg_attrs_server_defaults(mysrvc, mysrvc->myhgc); + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + + proxy_info( + "Found healthy previously discovered %s node %s:%d as 'OFFLINE_HARD', setting back as 'ONLINE' with:" + " hostgroup=%d, weight=%ld, max_connections=%ld, use_ssl=%d\n", + srv_info.kind.c_str(), srv_info.addr.c_str(), srv_info.port, hid, mysrvc->weight, + mysrvc->max_connections, mysrvc->use_ssl + ); + + res = 0; + } + } + + return res; +} + +int MySQL_HostGroups_Manager::remove_server_in_hg(uint32_t hid, const string& addr, uint16_t port) { + MySrvC* mysrvc = find_server_in_hg(hid, addr, port); + if (mysrvc == nullptr) { + return -1; + } + + uint64_t mysrvc_addr = reinterpret_cast(mysrvc); + + proxy_warning( + "Removed server at address %ld, hostgroup %d, address %s port %d." + " Setting status OFFLINE HARD and immediately dropping all free connections." + " Used connections will be dropped when trying to use them\n", + mysrvc_addr, hid, mysrvc->address, mysrvc->port + ); + + // Set the server status + mysrvc->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); + mysrvc->ConnectionsFree->drop_all_connections(); + + // TODO-NOTE: This is only required in case the caller isn't going to perform: + // - Full deletion of servers in the target 'hid'. + // - Table regeneration for the servers in the target 'hid'. + // This is a very common pattern when further operations have been performed over the + // servers, e.g. a set of servers additions and deletions over the target hostgroups. + // //////////////////////////////////////////////////////////////////////// + + // Remove the server from the table + const string del_srv_query { "DELETE FROM mysql_servers WHERE mem_pointer=" + std::to_string(mysrvc_addr) }; + mydb->execute(del_srv_query.c_str()); + + // //////////////////////////////////////////////////////////////////////// + + return 0; +} + +void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::copy_if_not_exists(Type dest_type, Type src_type) { + + assert(dest_type != src_type); + + const std::vector& src_nodes = mapping[src_type]; + + if (src_nodes.empty()) return; + + std::vector& dest_nodes = mapping[dest_type]; + std::list append; + + for (const auto& src_node : src_nodes) { + + for (const auto& dest_node : dest_nodes) { + + if (src_node.reader_hostgroup_id == dest_node.reader_hostgroup_id && + src_node.writer_hostgroup_id == dest_node.writer_hostgroup_id) { + goto __skip; + } + } + + append.push_back(src_node); + + __skip: + continue; + } + + if (append.empty()) { + return; + } + + if (dest_nodes.capacity() < (dest_nodes.size() + append.size())) + dest_nodes.reserve(dest_nodes.size() + append.size()); + + for (auto& node : append) { + + if (node.srv->get_status() == MYSQL_SERVER_STATUS_SHUNNED || + node.srv->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + // Status updated from "*SHUNNED" to "ONLINE" as "read_only" value was successfully + // retrieved from the backend server, indicating server is now online. + node.srv->set_status(MYSQL_SERVER_STATUS_ONLINE); + } + + MySrvC* new_srv = insert_HGM(get_hostgroup_id(dest_type, node), node.srv); + + if (!new_srv) assert(0); + + node.srv = new_srv; + dest_nodes.push_back(node); + } +} + +void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::remove(Type type, size_t index) { + + std::vector& nodes = mapping[type]; + + // ensure that we're not attempting to access out of the bounds of the container. + assert(index < nodes.size()); + + remove_HGM(nodes[index].srv); + + //Swap the element with the back element, except in the case when we're the last element. + if (index + 1 != nodes.size()) + std::swap(nodes[index], nodes.back()); + + //Pop the back of the container, deleting our old element. + nodes.pop_back(); +} + +void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::clear(Type type) { + + for (const auto& node : mapping[type]) { + remove_HGM(node.srv); + } + + mapping[type].clear(); +} + +unsigned int MySQL_HostGroups_Manager::HostGroup_Server_Mapping::get_hostgroup_id(Type type, const Node& node) const { + + if (type == Type::WRITER) + return node.writer_hostgroup_id; + else if (type == Type::READER) + return node.reader_hostgroup_id; + else + assert(0); +} + +MySrvC* MySQL_HostGroups_Manager::HostGroup_Server_Mapping::insert_HGM(unsigned int hostgroup_id, const MySrvC* srv) { + + MyHGC* myhgc = myHGM->MyHGC_lookup(hostgroup_id); + + if (!myhgc) + return NULL; + + MySrvC* ret_srv = NULL; + + for (uint32_t j = 0; j < myhgc->mysrvs->cnt(); j++) { + MySrvC* mysrvc = static_cast(myhgc->mysrvs->servers->index(j)); + if (strcmp(mysrvc->address, srv->address) == 0 && mysrvc->port == srv->port) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + + mysrvc->gtid_port = srv->gtid_port; + mysrvc->weight = srv->weight; + mysrvc->compression = srv->compression; + mysrvc->max_connections = srv->max_connections; + mysrvc->max_replication_lag = srv->max_replication_lag; + mysrvc->use_ssl = srv->use_ssl; + mysrvc->max_latency_us = srv->max_latency_us; + mysrvc->comment = strdup(srv->comment); + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + + if (GloMTH->variables.hostgroup_manager_verbose) { + proxy_info( + "Found server node in Host Group Container %s:%d as 'OFFLINE_HARD', setting back as 'ONLINE' with:" + " hostgroup_id=%d, gtid_port=%d, weight=%ld, compression=%d, max_connections=%ld, use_ssl=%d," + " max_replication_lag=%d, max_latency_ms=%d, comment=%s\n", + mysrvc->address, mysrvc->port, hostgroup_id, mysrvc->gtid_port, mysrvc->weight, mysrvc->compression, + mysrvc->max_connections, mysrvc->use_ssl, mysrvc->max_replication_lag, (mysrvc->max_latency_us / 1000), + mysrvc->comment + ); + } + ret_srv = mysrvc; + break; + } + } + } + + if (!ret_srv) { + if (GloMTH->variables.hostgroup_manager_verbose) { + proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%ld, status=%d\n", hostgroup_id, srv->address, srv->port, srv->gtid_port, srv->weight, (int)srv->get_status()); + } + + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%ld, status=%d, mem_ptr=%p into hostgroup=%d\n", srv->address, srv->port, srv->weight, (int)srv->get_status(), srv, hostgroup_id); + + ret_srv = new MySrvC(srv->address, srv->port, srv->gtid_port, srv->weight, srv->get_status(), srv->compression, + srv->max_connections, srv->max_replication_lag, srv->use_ssl, (srv->max_latency_us / 1000), srv->comment); + + myhgc->mysrvs->add(ret_srv); + } + + return ret_srv; +} + +void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::remove_HGM(MySrvC* srv) { + proxy_warning("Removed server at address %p, hostgroup %d, address %s port %d. Setting status OFFLINE HARD and immediately dropping all free connections. Used connections will be dropped when trying to use them\n", (void*)srv, srv->myhgc->hid, srv->address, srv->port); + srv->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); + srv->ConnectionsFree->drop_all_connections(); +} + +MySQLServers_SslParams * MySQL_HostGroups_Manager::get_Server_SSL_Params(char *hostname, int port, char *username) { + string MapKey = string(hostname) + string(rand_del) + to_string(port) + string(rand_del) + string(username); + std::lock_guard lock(Servers_SSL_Params_map_mutex); + auto it = Servers_SSL_Params_map.find(MapKey); + if (it != Servers_SSL_Params_map.end()) { + MySQLServers_SslParams * MSSP = new MySQLServers_SslParams(it->second); + return MSSP; + } else { + MapKey = string(hostname) + string(rand_del) + to_string(port) + string(rand_del) + ""; // search for empty username + it = Servers_SSL_Params_map.find(MapKey); + if (it != Servers_SSL_Params_map.end()) { + MySQLServers_SslParams * MSSP = new MySQLServers_SslParams(it->second); + return MSSP; + } + } + return NULL; +} + +/** +* @brief Updates replication hostgroups by adding autodiscovered mysql servers. +* @details Adds each server from 'new_servers' to the 'runtime_mysql_servers' table. +* We then rebuild the 'mysql_servers' table as well as the internal 'hostname_hostgroup_mapping'. +* @param new_servers A vector of tuples where each tuple contains the values needed to add each new server. +*/ +void MySQL_HostGroups_Manager::add_discovered_servers_to_mysql_servers_and_replication_hostgroups( + const vector>& new_servers +) { + int added_new_server = -1; + + GloAdmin->mysql_servers_wrlock(); + wrlock(); + + // Add the discovered server with default values + for (const tuple& s : new_servers) { + string host = std::get<0>(s); + uint16_t port = std::get<1>(s); + long int hostgroup_id = std::get<2>(s); + + srv_info_t srv_info { host.c_str(), port, "AWS RDS" }; + srv_opts_t srv_opts { -1, -1, -1 }; + + added_new_server = create_new_server_in_hg(hostgroup_id, srv_info, srv_opts); + } + + // If servers were added, perform necessary updates to internal structures + if (added_new_server > -1) { + purge_mysql_servers_table(); + mydb->execute("DELETE FROM mysql_servers"); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "DELETE FROM mysql_servers\n"); + generate_mysql_servers_table(); + + // Update the global checksums after 'mysql_servers' regeneration + { + unique_ptr resultset { get_admin_runtime_mysql_servers(mydb) }; + string mysrvs_checksum { get_checksum_from_hash(resultset ? resultset->raw_checksum() : 0) }; + save_runtime_mysql_servers(resultset.release()); + + // Update the runtime_mysql_servers checksum with the new checksum + uint64_t raw_checksum = this->runtime_mysql_servers ? this->runtime_mysql_servers->raw_checksum() : 0; + table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS] = raw_checksum; + + // This is required for preserving coherence in the checksums, otherwise they would be inconsistent with `commit` generated checksums + SpookyHash rep_hgs_hash {}; + bool init = false; + uint64_t servers_v2_hash = table_resultset_checksum[HGM_TABLES::MYSQL_SERVERS_V2]; + + if (servers_v2_hash) { + if (init == false) { + init = true; + rep_hgs_hash.Init(19, 3); + } + + rep_hgs_hash.Update(&servers_v2_hash, sizeof(servers_v2_hash)); + } + + CUCFT1( + rep_hgs_hash, init, "mysql_replication_hostgroups", "writer_hostgroup", + table_resultset_checksum[HGM_TABLES::MYSQL_REPLICATION_HOSTGROUPS] + ); + + proxy_info("Checksum for table %s is %s\n", "mysql_servers", mysrvs_checksum.c_str()); + + pthread_mutex_lock(&GloVars.checksum_mutex); + update_glovars_mysql_servers_checksum(mysrvs_checksum); + pthread_mutex_unlock(&GloVars.checksum_mutex); + } + + update_table_mysql_servers_for_monitor(false); + update_hostgroup_manager_mappings(); + } + + wrunlock(); + GloAdmin->mysql_servers_wrunlock(); +} +#endif // 0 diff --git a/lib/Base_Session.cpp b/lib/Base_Session.cpp index 26c7aa91f..c32e5ccdb 100644 --- a/lib/Base_Session.cpp +++ b/lib/Base_Session.cpp @@ -48,6 +48,25 @@ template bool Base_Session::housekeeping_before_pkts(); template void Base_Session::housekeeping_before_pkts(); + +template void Base_Session::update_expired_conns(std::vector, std::allocator > > const&); +template void Base_Session::update_expired_conns(std::vector, std::allocator > > const&); + +template unsigned int Base_Session::NumActiveTransactions(bool); +template unsigned int Base_Session::NumActiveTransactions(bool); + +template void Base_Session::set_unhealthy(); +template void Base_Session::set_unhealthy(); + +template int Base_Session::FindOneActiveTransaction(bool); +template int Base_Session::FindOneActiveTransaction(bool); + +template bool Base_Session::HasOfflineBackends(); +template bool Base_Session::HasOfflineBackends(); + +template bool Base_Session::SetEventInOfflineBackends(); +template bool Base_Session::SetEventInOfflineBackends(); + template Base_Session::Base_Session() { }; @@ -500,3 +519,154 @@ void Base_Session::housekeeping_before_pkts() { } } } + +/** + * @brief Update expired connections based on specified checks. + * + * This function iterates through the list of backends and their connections + * to determine if any connections have expired based on the provided checks. + * If a connection is found to be expired, its hostgroup ID is added to the + * list of expired connections for further processing. + * + * @param checks A vector of function objects representing checks to determine if a connection has expired. + */ +template +using TypeConn = typename std::conditional< + std::is_same_v, MySQL_Connection, PgSQL_Connection +>::type; + +template +void Base_Session::update_expired_conns(const vector>& checks) { + for (uint32_t i = 0; i < mybes->len; i++) { // iterate through the list of backends + B * mybe = static_cast(mybes->index(i)); + DS * myds = mybe != nullptr ? mybe->server_myds : nullptr; + + + TypeConn * myconn = myds != nullptr ? myds->myconn : nullptr; + + //! it performs a series of checks to determine if it has expired + if (myconn != nullptr) { + const bool is_active_transaction = myconn->IsActiveTransaction(); + const bool multiplex_disabled = myconn->MultiplexDisabled(false); + const bool is_idle = myconn->async_state_machine == ASYNC_IDLE; + + // Make sure the connection is reusable before performing any check + if (myconn->reusable == true && is_active_transaction == false && multiplex_disabled == false && is_idle) { + for (const function& check : checks) { + if (check(myconn)) { + // If a connection is found to be expired based on the provided checks, + // its hostgroup ID is added to the list of expired connections (hgs_expired_conns) + // for further processing. + this->hgs_expired_conns.push_back(mybe->hostgroup_id); + break; + } + } + } + } + } +} + + +template +void Base_Session::set_unhealthy() { + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess:%p\n", this); + healthy=0; +} + + +template +unsigned int Base_Session::NumActiveTransactions(bool check_savepoint) { + unsigned int ret=0; + if (mybes==0) return ret; + B *_mybe; + unsigned int i; + for (i=0; i < mybes->len; i++) { + _mybe=(B *)mybes->index(i); + if (_mybe->server_myds) { + if (_mybe->server_myds->myconn) { + if (_mybe->server_myds->myconn->IsActiveTransaction()) { + ret++; + } else { + // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due + // to MySQL bug https://bugs.mysql.com/bug.php?id=107875 related to + // SAVEPOINT and autocommit=0 + if (check_savepoint) { + if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { + ret++; + } + } + } + } + } + } + return ret; +} + +template +bool Base_Session::HasOfflineBackends() { + bool ret=false; + if (mybes==0) return ret; + B * _mybe; + unsigned int i; + for (i=0; i < mybes->len; i++) { + _mybe=(B *)mybes->index(i); + if (_mybe->server_myds) + if (_mybe->server_myds->myconn) + if (_mybe->server_myds->myconn->IsServerOffline()) { + ret=true; + return ret; + } + } + return ret; +} + +template +bool Base_Session::SetEventInOfflineBackends() { + bool ret=false; + if (mybes==0) return ret; + B * _mybe; + unsigned int i; + for (i = 0; i < mybes->len; i++) { + _mybe = (B *) mybes->index(i); + if (_mybe->server_myds) + if (_mybe->server_myds->myconn) + if (_mybe->server_myds->myconn->IsServerOffline()) { + _mybe->server_myds->revents |= POLLIN; + ret = true; + } + } + return ret; +} + + +template +int Base_Session::FindOneActiveTransaction(bool check_savepoint) { + int ret=-1; + if (mybes==0) return ret; + B * _mybe; + unsigned int i; + for (i=0; i < mybes->len; i++) { + _mybe = (B *) mybes->index(i); + if (_mybe->server_myds) { + if (_mybe->server_myds->myconn) { + if (_mybe->server_myds->myconn->IsKnownActiveTransaction()) { + return (int)_mybe->server_myds->myconn->parent->myhgc->hid; + } + else if (_mybe->server_myds->myconn->IsActiveTransaction()) { + ret = (int)_mybe->server_myds->myconn->parent->myhgc->hid; + } + else { + // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due + // to MySQL bug https://bugs.mysql.com/bug.php?id=107875 related to + // SAVEPOINT and autocommit=0 + if (check_savepoint) { + if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { + return (int)_mybe->server_myds->myconn->parent->myhgc->hid; + } + } + } + } + } + } + return ret; +} diff --git a/lib/Makefile b/lib/Makefile index 3237c3b26..4858ce3bc 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -138,8 +138,9 @@ default: libproxysql.a _OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo SpookyV2.oo MySQL_Authentication.oo gen_utils.oo sqlite3db.oo mysql_connection.oo MySQL_HostGroups_Manager.oo mysql_data_stream.oo MySQL_Thread.oo MySQL_Session.oo MySQL_Protocol.oo mysql_backend.oo Query_Processor.oo ProxySQL_Admin.oo ProxySQL_Config.oo ProxySQL_Restapi.oo MySQL_Monitor.oo MySQL_Logger.oo thread.oo MySQL_PreparedStatement.oo ProxySQL_Cluster.oo ClickHouse_Authentication.oo ClickHouse_Server.oo ProxySQL_Statistics.oo Chart_bundle_js.oo ProxySQL_HTTP_Server.oo ProxySQL_RESTAPI_Server.oo font-awesome.min.css.oo main-bundle.min.css.oo set_parser.oo MySQL_Variables.oo c_tokenizer.oo proxysql_utils.oo proxysql_coredump.oo proxysql_sslkeylog.oo \ sha256crypt.oo \ + BaseSrvList.oo BaseHGC.oo Base_HostGroups_Manager.oo \ QP_rule_text.oo QP_query_digest_stats.oo \ - GTID_Server_Data.oo MyHGC.oo MySrvConnList.oo MySrvList.oo MySrvC.oo \ + GTID_Server_Data.oo MyHGC.oo MySrvConnList.oo MySrvC.oo \ MySQL_encode.oo MySQL_ResultSet.oo \ ProxySQL_Admin_Tests.oo ProxySQL_Admin_Tests2.oo ProxySQL_Admin_Scheduler.oo ProxySQL_Admin_Disk_Upgrade.oo ProxySQL_Admin_Stats.oo \ Admin_Handler.oo Admin_FlushVariables.oo Admin_Bootstrap.oo \ diff --git a/lib/MyHGC.cpp b/lib/MyHGC.cpp index 0318291ff..8af6956d1 100644 --- a/lib/MyHGC.cpp +++ b/lib/MyHGC.cpp @@ -11,54 +11,6 @@ static unsigned long long array_mysrvc_cands = 0; extern MySQL_Threads_Handler *GloMTH; -MyHGC::MyHGC(int _hid) { - hid=_hid; - mysrvs=new MySrvList(this); - current_time_now = 0; - new_connections_now = 0; - attributes.initialized = false; - reset_attributes(); - // Uninitialized server defaults. Should later be initialized via 'mysql_hostgroup_attributes'. - servers_defaults.weight = -1; - servers_defaults.max_connections = -1; - servers_defaults.use_ssl = -1; - num_online_servers.store(0, std::memory_order_relaxed);; - last_log_time_num_online_servers = 0; -} - -void MyHGC::reset_attributes() { - if (attributes.initialized == false) { - attributes.init_connect = NULL; - attributes.comment = NULL; - attributes.ignore_session_variables_text = NULL; - } - attributes.initialized = true; - attributes.configured = false; - attributes.max_num_online_servers = 1000000; - attributes.throttle_connections_per_sec = 1000000; - attributes.autocommit = -1; - attributes.free_connections_pct = 10; - attributes.handle_warnings = -1; - attributes.monitor_slave_lag_when_null = -1; - attributes.multiplex = true; - attributes.connection_warming = false; - free(attributes.init_connect); - attributes.init_connect = NULL; - free(attributes.comment); - attributes.comment = NULL; - free(attributes.ignore_session_variables_text); - attributes.ignore_session_variables_text = NULL; - if (attributes.ignore_session_variables_json) { - delete attributes.ignore_session_variables_json; - attributes.ignore_session_variables_json = NULL; - } -} - -MyHGC::~MyHGC() { - reset_attributes(); // free all memory - delete mysrvs; -} - MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess) { MySrvC *mysrvc=NULL; unsigned int j; @@ -400,31 +352,3 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ #endif // TEST_AURORA return NULL; // if we reach here, we couldn't find any target } - -void MyHGC::refresh_online_server_count() { - if (__sync_fetch_and_add(&glovars.shutdown, 0) != 0) - return; -#ifdef DEBUG - assert(MyHGM->is_locked); -#endif - unsigned int online_servers_count = 0; - for (unsigned int i = 0; i < mysrvs->servers->len; i++) { - MySrvC* mysrvc = (MySrvC*)mysrvs->servers->index(i); - if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { - online_servers_count++; - } - } - num_online_servers.store(online_servers_count, std::memory_order_relaxed); -} - -void MyHGC::log_num_online_server_count_error() { - const time_t curtime = time(NULL); - // if this is the first time the method is called or if more than 10 seconds have passed since the last log - if (last_log_time_num_online_servers == 0 || - ((curtime - last_log_time_num_online_servers) > 10)) { - last_log_time_num_online_servers = curtime; - proxy_error( - "Number of online servers detected in a hostgroup exceeds the configured maximum online servers. hostgroup:%u, num_online_servers:%u, max_online_servers:%u\n", - hid, num_online_servers.load(std::memory_order_relaxed), attributes.max_num_online_servers); - } -} diff --git a/lib/MySQL_HostGroups_Manager.cpp b/lib/MySQL_HostGroups_Manager.cpp index 268b9bcfc..4f7925959 100644 --- a/lib/MySQL_HostGroups_Manager.cpp +++ b/lib/MySQL_HostGroups_Manager.cpp @@ -657,17 +657,17 @@ MySQL_HostGroups_Manager::MySQL_HostGroups_Manager() { status.access_denied_max_user_connections=0; status.select_for_update_or_equivalent=0; status.auto_increment_delay_multiplex=0; +#if 0 pthread_mutex_init(&readonly_mutex, NULL); +#endif // 0 pthread_mutex_init(&Group_Replication_Info_mutex, NULL); pthread_mutex_init(&Galera_Info_mutex, NULL); pthread_mutex_init(&AWS_Aurora_Info_mutex, NULL); -#ifdef MHM_PTHREAD_MUTEX +#if 0 pthread_mutex_init(&lock, NULL); -#else - spinlock_rwlock_init(&rwlock); -#endif admindb=NULL; // initialized only if needed mydb=new SQLite3DB(); +#endif // 0 #ifdef DEBUG mydb->open((char *)"file:mem_mydb?mode=memory&cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); #else @@ -756,21 +756,7 @@ MySQL_HostGroups_Manager::~MySQL_HostGroups_Manager() { ev_loop_destroy(gtid_ev_loop); if (gtid_ev_timer) free(gtid_ev_timer); -#ifdef MHM_PTHREAD_MUTEX pthread_mutex_destroy(&lock); -#endif -} - -// wrlock() is only required during commit() -void MySQL_HostGroups_Manager::wrlock() { -#ifdef MHM_PTHREAD_MUTEX - pthread_mutex_lock(&lock); -#else - spin_wrlock(&rwlock); -#endif -#ifdef DEBUG - is_locked = true; -#endif } void MySQL_HostGroups_Manager::p_update_mysql_error_counter(p_mysql_error_type err_type, unsigned int hid, char* address, uint16_t port, unsigned int code) { @@ -804,18 +790,6 @@ void MySQL_HostGroups_Manager::p_update_mysql_error_counter(p_mysql_error_type e pthread_mutex_unlock(&mysql_errors_mutex); } -void MySQL_HostGroups_Manager::wrunlock() { -#ifdef DEBUG - is_locked = false; -#endif -#ifdef MHM_PTHREAD_MUTEX - pthread_mutex_unlock(&lock); -#else - spin_wrunlock(&rwlock); -#endif -} - - void MySQL_HostGroups_Manager::wait_servers_table_version(unsigned v, unsigned w) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -917,29 +891,6 @@ int MySQL_HostGroups_Manager::servers_add(SQLite3_result *resultset) { return 0; } -/** - * @brief Execute a SQL query and retrieve the resultset. - * - * This function executes a SQL query using the provided query string and returns the resultset obtained from the - * database operation. It also provides an optional error parameter to capture any error messages encountered during - * query execution. - * - * @param query A pointer to a null-terminated string containing the SQL query to be executed. - * @param error A pointer to a char pointer where any error message encountered during query execution will be stored. - * Pass nullptr if error handling is not required. - * @return A pointer to a SQLite3_result object representing the resultset obtained from the query execution. This - * pointer may be nullptr if the query execution fails or returns an empty result. - */ -SQLite3_result * MySQL_HostGroups_Manager::execute_query(char *query, char **error) { - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - wrlock(); - mydb->execute_statement(query, error , &cols , &affected_rows , &resultset); - wrunlock(); - return resultset; -} - /** * @brief Calculate and update the checksum for a specified table in the database. * @@ -2279,78 +2230,6 @@ SQLite3_result * MySQL_HostGroups_Manager::dump_table_mysql(const string& name) return resultset; } -/** - * @brief Create a new MySQL host group container. - * - * This function creates a new instance of the MySQL host group container (`MyHGC`) with - * the specified host group ID and returns a pointer to it. - * - * @param _hid The host group ID for the new container. - * @return A pointer to the newly created `MyHGC` instance. - */ -MyHGC * MySQL_HostGroups_Manager::MyHGC_create(unsigned int _hid) { - MyHGC *myhgc=new MyHGC(_hid); - return myhgc; -} - -/** - * @brief Find a MySQL host group container by host group ID. - * - * This function searches for a MySQL host group container with the specified host group ID - * in the list of host groups. If found, it returns a pointer to the container; otherwise, - * it returns a null pointer. - * - * @param _hid The host group ID to search for. - * @return A pointer to the found `MyHGC` instance if found; otherwise, a null pointer. - */ -MyHGC * MySQL_HostGroups_Manager::MyHGC_find(unsigned int _hid) { - if (MyHostGroups->len < 100) { - // for few HGs, we use the legacy search - for (unsigned int i=0; ilen; i++) { - MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); - if (myhgc->hid==_hid) { - return myhgc; - } - } - } else { - // for a large number of HGs, we use the unordered_map - // this search is slower for a small number of HGs, therefore we use - // it only for large number of HGs - std::unordered_map::const_iterator it = MyHostGroups_map.find(_hid); - if (it != MyHostGroups_map.end()) { - MyHGC *myhgc = it->second; - return myhgc; - } - } - return NULL; -} - -/** - * @brief Lookup or create a MySQL host group container by host group ID. - * - * This function looks up a MySQL host group container with the specified host group ID. If - * found, it returns a pointer to the existing container; otherwise, it creates a new container - * with the specified host group ID, adds it to the list of host groups, and returns a pointer - * to it. - * - * @param _hid The host group ID to lookup or create. - * @return A pointer to the found or newly created `MyHGC` instance. - * @note The function assertion fails if a newly created container is not found. - */ -MyHGC * MySQL_HostGroups_Manager::MyHGC_lookup(unsigned int _hid) { - MyHGC *myhgc=NULL; - myhgc=MyHGC_find(_hid); - if (myhgc==NULL) { - myhgc=MyHGC_create(_hid); - } else { - return myhgc; - } - assert(myhgc); - MyHostGroups->add(myhgc); - MyHostGroups_map.emplace(_hid,myhgc); - return myhgc; -} - void MySQL_HostGroups_Manager::increase_reset_counter() { wrlock(); status.myconnpoll_reset++; diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 4433f0fea..41d116620 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -783,45 +783,6 @@ MySQL_Session::~MySQL_Session() { } } - -/** - * @brief Update expired connections based on specified checks. - * - * This function iterates through the list of backends and their connections - * to determine if any connections have expired based on the provided checks. - * If a connection is found to be expired, its hostgroup ID is added to the - * list of expired connections for further processing. - * - * @param checks A vector of function objects representing checks to determine if a connection has expired. - */ -void MySQL_Session::update_expired_conns(const vector>& checks) { - for (uint32_t i = 0; i < mybes->len; i++) { // iterate through the list of backends - MySQL_Backend* mybe = static_cast(mybes->index(i)); - MySQL_Data_Stream* myds = mybe != nullptr ? mybe->server_myds : nullptr; - MySQL_Connection* myconn = myds != nullptr ? myds->myconn : nullptr; - - //! it performs a series of checks to determine if it has expired - if (myconn != nullptr) { - const bool is_active_transaction = myconn->IsActiveTransaction(); - const bool multiplex_disabled = myconn->MultiplexDisabled(false); - const bool is_idle = myconn->async_state_machine == ASYNC_IDLE; - - // Make sure the connection is reusable before performing any check - if (myconn->reusable==true && is_active_transaction==false && multiplex_disabled==false && is_idle) { - for (const function& check : checks) { - if (check(myconn)) { - // If a connection is found to be expired based on the provided checks, - // its hostgroup ID is added to the list of expired connections (hgs_expired_conns) - // for further processing. - this->hgs_expired_conns.push_back(mybe->hostgroup_id); - break; - } - } - } - } - } -} - /** * @brief Handles COMMIT or ROLLBACK commands received from the client. * @@ -5861,84 +5822,6 @@ void MySQL_Session::handler_WCD_SS_MCQ_qpo_LargePacket(PtrSize_t *pkt) { l_free(pkt->size,pkt->ptr); } -/* -// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo -// returned values: -// 0 : no action -// 1 : return false -// 2 : return true -int MySQL_Session::handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(PtrSize_t *pkt, bool *lock_hostgroup, unsigned int nTrx, string& nq) { - re2::RE2::Options *opt2=new re2::RE2::Options(RE2::Quiet); - opt2->set_case_sensitive(false); - char *pattern=(char *)"(?: *)SET *(?:|SESSION +|@@|@@session.)SQL_LOG_BIN *(?:|:)= *(\\d+) *(?:(|;|-- .*|#.*))$"; - re2::RE2 *re=new RE2(pattern, *opt2); - int i; - int rc=RE2::PartialMatch(nq, *re, &i); - delete re; - delete opt2; - if (rc && ( i==0 || i==1) ) { - //fprintf(stderr,"sql_log_bin=%d\n", i); - if (i == 1) { - if (!mysql_variables.client_set_value(this, SQL_SQL_LOG_BIN, "1")) - return 1; - } - else if (i == 0) { - if (!mysql_variables.client_set_value(this, SQL_SQL_LOG_BIN, "0")) - return 1; - } - -#ifdef DEBUG - proxy_info("Setting SQL_LOG_BIN to %d\n", i); -#endif -#ifdef DEBUG - { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Setting SQL_LOG_BIN to %d for query: %s\n", i, nqn.c_str()); - } -#endif - // we recompute command_type instead of taking it from the calling function - unsigned char command_type=*((unsigned char *)pkt->ptr+sizeof(mysql_hdr)); - if (command_type == _MYSQL_COM_QUERY) { - client_myds->DSS=STATE_QUERY_SENT_NET; - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT; - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,setStatus,0,NULL); - client_myds->DSS=STATE_SLEEP; - status=WAITING_CLIENT_DATA; - RequestEnd(NULL); - l_free(pkt->size,pkt->ptr); - return 2; - } - } else { - int kq = 0; - kq = strncmp((const char *)CurrentQuery.QueryPointer, (const char *)"SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;" , CurrentQuery.QueryLength); -#ifdef DEBUG - { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Setting SQL_LOG_BIN to %d for query: %s\n", i, nqn.c_str()); - } -#endif - if (kq == 0) { - client_myds->DSS=STATE_QUERY_SENT_NET; - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT; - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,setStatus,0,NULL); - client_myds->DSS=STATE_SLEEP; - status=WAITING_CLIENT_DATA; - RequestEnd(NULL); - l_free(pkt->size,pkt->ptr); - return 2; - } else { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_error("Unable to parse query. If correct, report it as a bug: %s\n", nqn.c_str()); - unable_to_parse_set_statement(lock_hostgroup); - return 1; - } - } - return 0; -} -*/ - bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t *pkt, bool *lock_hostgroup, ps_type prepare_stmt_type) { /* lock_hostgroup: @@ -6072,15 +5955,6 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C RE2::GlobalReplace(&nq,(char *)"(?U)/\\*.*\\*/",(char *)""); // remove trailing space and semicolon if present. See issue#4380 nq.erase(nq.find_last_not_of(" ;") + 1); -/* - // we do not threat SET SQL_LOG_BIN as a special case - if (match_regexes && match_regexes[0]->match(dig)) { - int rc = handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(pkt, lock_hostgroup, nTrx, nq); - if (rc == 1) return false; - if (rc == 2) return true; - // if rc == 0 , continue as normal - } -*/ if ( ( match_regexes && (match_regexes[1]->match(dig)) @@ -7481,102 +7355,6 @@ void MySQL_Session::SQLite3_to_MySQL(SQLite3_result *result, char *error, int af } } -void MySQL_Session::set_unhealthy() { - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess:%p\n", this); - healthy=0; -} - - -unsigned int MySQL_Session::NumActiveTransactions(bool check_savepoint) { - unsigned int ret=0; - if (mybes==0) return ret; - MySQL_Backend *_mybe; - unsigned int i; - for (i=0; i < mybes->len; i++) { - _mybe=(MySQL_Backend *)mybes->index(i); - if (_mybe->server_myds) { - if (_mybe->server_myds->myconn) { - if (_mybe->server_myds->myconn->IsActiveTransaction()) { - ret++; - } else { - // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due - // to MySQL bug https://bugs.mysql.com/bug.php?id=107875 related to - // SAVEPOINT and autocommit=0 - if (check_savepoint) { - if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { - ret++; - } - } - } - } - } - } - return ret; -} - -bool MySQL_Session::HasOfflineBackends() { - bool ret=false; - if (mybes==0) return ret; - MySQL_Backend *_mybe; - unsigned int i; - for (i=0; i < mybes->len; i++) { - _mybe=(MySQL_Backend *)mybes->index(i); - if (_mybe->server_myds) - if (_mybe->server_myds->myconn) - if (_mybe->server_myds->myconn->IsServerOffline()) { - ret=true; - return ret; - } - } - return ret; -} - -bool MySQL_Session::SetEventInOfflineBackends() { - bool ret=false; - if (mybes==0) return ret; - MySQL_Backend *_mybe; - unsigned int i; - for (i=0; i < mybes->len; i++) { - _mybe=(MySQL_Backend *)mybes->index(i); - if (_mybe->server_myds) - if (_mybe->server_myds->myconn) - if (_mybe->server_myds->myconn->IsServerOffline()) { - _mybe->server_myds->revents|=POLLIN; - ret = true; - } - } - return ret; -} - -int MySQL_Session::FindOneActiveTransaction(bool check_savepoint) { - int ret=-1; - if (mybes==0) return ret; - MySQL_Backend *_mybe; - unsigned int i; - for (i=0; i < mybes->len; i++) { - _mybe=(MySQL_Backend *)mybes->index(i); - if (_mybe->server_myds) { - if (_mybe->server_myds->myconn) { - if (_mybe->server_myds->myconn->IsKnownActiveTransaction()) { - return (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } else if (_mybe->server_myds->myconn->IsActiveTransaction()) { - ret = (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } else { - // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due - // to MySQL bug https://bugs.mysql.com/bug.php?id=107875 related to - // SAVEPOINT and autocommit=0 - if (check_savepoint) { - if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { - return (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } - } - } - } - } - } - return ret; -} - unsigned long long MySQL_Session::IdleTime() { unsigned long long ret = 0; if (client_myds==0) return 0; @@ -7592,7 +7370,6 @@ unsigned long long MySQL_Session::IdleTime() { } - // this is called either from RequestEnd(), or at the end of executing // prepared statements void MySQL_Session::LogQuery(MySQL_Data_Stream *myds) { diff --git a/lib/MySrvList.cpp b/lib/MySrvList.cpp deleted file mode 100644 index ab39ef22e..000000000 --- a/lib/MySrvList.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "MySQL_HostGroups_Manager.h" - -class MySrvConnList; -class MySrvC; -class MySrvList; -class MyHGC; - -MySrvList::MySrvList(MyHGC *_myhgc) { - myhgc=_myhgc; - servers=new PtrArray(); -} - -void MySrvList::add(MySrvC *s) { - if (s->myhgc==NULL) { - s->myhgc=myhgc; - } - servers->add(s); - myhgc->refresh_online_server_count(); -} - - -int MySrvList::find_idx(MySrvC *s) { - for (unsigned int i=0; ilen; i++) { - MySrvC *mysrv=(MySrvC *)servers->index(i); - if (mysrv==s) { - return (unsigned int)i; - } - } - return -1; -} - -void MySrvList::remove(MySrvC *s) { - int i=find_idx(s); - assert(i>=0); - servers->remove_index_fast((unsigned int)i); - myhgc->refresh_online_server_count(); -} - -MySrvList::~MySrvList() { - myhgc=NULL; - while (servers->len) { - MySrvC *mysrvc=(MySrvC *)servers->remove_index_fast(0); - delete mysrvc; - } - delete servers; -} diff --git a/lib/PgSQL_HostGroups_Manager.cpp b/lib/PgSQL_HostGroups_Manager.cpp index 1ea3172e9..2c2d12f72 100644 --- a/lib/PgSQL_HostGroups_Manager.cpp +++ b/lib/PgSQL_HostGroups_Manager.cpp @@ -69,39 +69,6 @@ static pthread_mutex_t ev_loop_mutex; const int PgSQL_ERRORS_STATS_FIELD_NUM = 11; -#if 0 -static std::string gtid_executed_to_string(gtid_set_t & gtid_executed); -static void addGtid(const gtid_t & gtid, gtid_set_t & gtid_executed); - -static void gtid_async_cb(struct ev_loop *loop, struct ev_async *watcher, int revents) { - if (glovars.shutdown) { - ev_break(loop); - } - pthread_mutex_lock(&ev_loop_mutex); - PgHGM->gtid_missing_nodes = false; - PgHGM->generate_pgsql_gtid_executed_tables(); - pthread_mutex_unlock(&ev_loop_mutex); - return; -} - -static void gtid_timer_cb (struct ev_loop *loop, struct ev_timer *timer, int revents) { - if (GloMTH == nullptr) { return; } - ev_timer_stop(loop, timer); - ev_timer_set(timer, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); - if (glovars.shutdown) { - ev_break(loop); - } - if (PgHGM->gtid_missing_nodes) { - pthread_mutex_lock(&ev_loop_mutex); - PgHGM->gtid_missing_nodes = false; - PgHGM->generate_pgsql_gtid_executed_tables(); - pthread_mutex_unlock(&ev_loop_mutex); - } - ev_timer_start(loop, timer); - return; -} -#endif // 0 - static int wait_for_pgsql(MYSQL *mysql, int status) { struct pollfd pfd; int timeout, res; @@ -169,435 +136,6 @@ T PgSQL_j_get_srv_default_int_val( return static_cast(-1); } -#if 0 -static void reader_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - pthread_mutex_lock(&ev_loop_mutex); - if (revents & EV_READ) { - PgSQL_GTID_Server_Data *sd = (PgSQL_GTID_Server_Data *)w->data; - bool rc = true; - rc = sd->readall(); - if (rc == false) { - //delete sd; - std::string s1 = sd->address; - s1.append(":"); - s1.append(std::to_string(sd->pgsql_port)); - PgHGM->gtid_missing_nodes = true; - proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->pgsql_port); - std::unordered_map ::iterator it2; - it2 = PgHGM->gtid_map.find(s1); - if (it2 != PgHGM->gtid_map.end()) { - //PgHGM->gtid_map.erase(it2); - it2->second = NULL; - delete sd; - } - ev_io_stop(PgHGM->gtid_ev_loop, w); - free(w); - } else { - sd->dump(); - } - } - pthread_mutex_unlock(&ev_loop_mutex); -} - -static void connect_cb(EV_P_ ev_io *w, int revents) { - pthread_mutex_lock(&ev_loop_mutex); - struct ev_io * c = w; - if (revents & EV_WRITE) { - int optval = 0; - socklen_t optlen = sizeof(optval); - if ((getsockopt(w->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) || - (optval != 0)) { - /* Connection failed; try the next address in the list. */ - //int errnum = optval ? optval : errno; - ev_io_stop(PgHGM->gtid_ev_loop, w); - close(w->fd); - PgHGM->gtid_missing_nodes = true; - PgSQL_GTID_Server_Data * custom_data = (PgSQL_GTID_Server_Data *)w->data; - PgSQL_GTID_Server_Data *sd = custom_data; - std::string s1 = sd->address; - s1.append(":"); - s1.append(std::to_string(sd->pgsql_port)); - proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->pgsql_port); - std::unordered_map ::iterator it2; - it2 = PgHGM->gtid_map.find(s1); - if (it2 != PgHGM->gtid_map.end()) { - //PgHGM->gtid_map.erase(it2); - it2->second = NULL; - delete sd; - } - //delete custom_data; - free(c); - } else { - ev_io_stop(PgHGM->gtid_ev_loop, w); - int fd=w->fd; - struct ev_io * new_w = (struct ev_io*) malloc(sizeof(struct ev_io)); - new_w->data = w->data; - PgSQL_GTID_Server_Data * custom_data = (PgSQL_GTID_Server_Data *)new_w->data; - custom_data->w = new_w; - free(w); - ev_io_init(new_w, reader_cb, fd, EV_READ); - ev_io_start(PgHGM->gtid_ev_loop, new_w); - } - } - pthread_mutex_unlock(&ev_loop_mutex); -} - -static struct ev_io * new_connector(char *address, uint16_t gtid_port, uint16_t pgsql_port) { - //struct sockaddr_in a; - int s; - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - perror("socket"); - close(s); - return NULL; - } -/* - memset(&a, 0, sizeof(a)); - a.sin_port = htons(gtid_port); - a.sin_family = AF_INET; - if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) { - perror("bad IP address format"); - close(s); - return NULL; - } -*/ - ioctl_FIONBIO(s,1); - - struct addrinfo hints; - struct addrinfo *res = NULL; - memset(&hints, 0, sizeof(hints)); - hints.ai_protocol= IPPROTO_TCP; - hints.ai_family= AF_UNSPEC; - hints.ai_socktype= SOCK_STREAM; - - char str_port[NI_MAXSERV+1]; - sprintf(str_port,"%d", gtid_port); - int gai_rc = getaddrinfo(address, str_port, &hints, &res); - if (gai_rc) { - freeaddrinfo(res); - //exit here - return NULL; - } - - //int status = connect(s, (struct sockaddr *) &a, sizeof(a)); - int status = connect(s, res->ai_addr, res->ai_addrlen); - if ((status == 0) || ((status == -1) && (errno == EINPROGRESS))) { - struct ev_io *c = (struct ev_io *)malloc(sizeof(struct ev_io)); - if (c) { - ev_io_init(c, connect_cb, s, EV_WRITE); - PgSQL_GTID_Server_Data * custom_data = new PgSQL_GTID_Server_Data(c, address, gtid_port, pgsql_port); - c->data = (void *)custom_data; - return c; - } - /* else error */ - } - return NULL; -} - - - -PgSQL_GTID_Server_Data::PgSQL_GTID_Server_Data(struct ev_io *_w, char *_address, uint16_t _port, uint16_t _pgsql_port) { - active = true; - w = _w; - size = 1024; // 1KB buffer - data = (char *)malloc(size); - memset(uuid_server, 0, sizeof(uuid_server)); - pos = 0; - len = 0; - address = strdup(_address); - port = _port; - pgsql_port = _pgsql_port; - events_read = 0; -} - -void PgSQL_GTID_Server_Data::resize(size_t _s) { - char *data_ = (char *)malloc(_s); - memcpy(data_, data, (_s > size ? size : _s)); - size = _s; - free(data); - data = data_; -} - -PgSQL_GTID_Server_Data::~PgSQL_GTID_Server_Data() { - free(address); - free(data); -} - -bool PgSQL_GTID_Server_Data::readall() { - bool ret = true; - if (size == len) { - // buffer is full, expand - resize(len*2); - } - int rc = 0; - rc = read(w->fd,data+len,size-len); - if (rc > 0) { - len += rc; - } else { - int myerr = errno; - proxy_error("Read returned %d bytes, error %d\n", rc, myerr); - if ( - (rc == 0) || - (rc==-1 && myerr != EINTR && myerr != EAGAIN) - ) { - ret = false; - } - } - return ret; -} - - -bool PgSQL_GTID_Server_Data::gtid_exists(char *gtid_uuid, uint64_t gtid_trxid) { - std::string s = gtid_uuid; - auto it = gtid_executed.find(s); -// fprintf(stderr,"Checking if server %s:%d has GTID %s:%lu ... ", address, port, gtid_uuid, gtid_trxid); - if (it == gtid_executed.end()) { -// fprintf(stderr,"NO\n"); - return false; - } - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { - if ((int64_t)gtid_trxid >= itr->first && (int64_t)gtid_trxid <= itr->second) { -// fprintf(stderr,"YES\n"); - return true; - } - } -// fprintf(stderr,"NO\n"); - return false; -} - -void PgSQL_GTID_Server_Data::read_all_gtids() { - while (read_next_gtid()) { - } - } - -void PgSQL_GTID_Server_Data::dump() { - if (len==0) { - return; - } - read_all_gtids(); - //int rc = write(1,data+pos,len-pos); - fflush(stdout); - ///pos += rc; - if (pos >= len/2) { - memmove(data,data+pos,len-pos); - len = len-pos; - pos = 0; - } -} - -bool PgSQL_GTID_Server_Data::writeout() { - bool ret = true; - if (len==0) { - return ret; - } - int rc = 0; - rc = write(w->fd,data+pos,len-pos); - if (rc > 0) { - pos += rc; - if (pos >= len/2) { - memmove(data,data+pos,len-pos); - len = len-pos; - pos = 0; - } - } - return ret; -} - -bool PgSQL_GTID_Server_Data::read_next_gtid() { - if (len==0) { - return false; - } - void *nlp = NULL; - nlp = memchr(data+pos,'\n',len-pos); - if (nlp == NULL) { - return false; - } - int l = (char *)nlp - (data+pos); - char rec_msg[80]; - if (strncmp(data+pos,(char *)"ST=",3)==0) { - // we are reading the bootstrap - char *bs = (char *)malloc(l+1-3); // length + 1 (null byte) - 3 (header) - memcpy(bs, data+pos+3, l-3); - bs[l-3] = '\0'; - char *saveptr1=NULL; - char *saveptr2=NULL; - //char *saveptr3=NULL; - char *token = NULL; - char *subtoken = NULL; - //char *subtoken2 = NULL; - char *str1 = NULL; - char *str2 = NULL; - //char *str3 = NULL; - for (str1 = bs; ; str1 = NULL) { - token = strtok_r(str1, ",", &saveptr1); - if (token == NULL) { - break; - } - int j = 0; - for (str2 = token; ; str2 = NULL) { - subtoken = strtok_r(str2, ":", &saveptr2); - if (subtoken == NULL) { - break; - } - j++; - if (j%2 == 1) { // we are reading the uuid - char *p = uuid_server; - for (unsigned int k=0; kfirst; - s.insert(8,"-"); - s.insert(13,"-"); - s.insert(18,"-"); - s.insert(23,"-"); - s = s + ":"; - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { - std::string s2 = s; - s2 = s2 + std::to_string(itr->first); - s2 = s2 + "-"; - s2 = s2 + std::to_string(itr->second); - s2 = s2 + ","; - gtid_set = gtid_set + s2; - } - } - // Extract latest comma only in case 'gtid_executed' isn't empty - if (gtid_set.empty() == false) { - gtid_set.pop_back(); - } - return gtid_set; -} - - - -static void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed) { - auto it = gtid_executed.find(gtid.first); - if (it == gtid_executed.end()) - { - gtid_executed[gtid.first].emplace_back(gtid.second, gtid.second); - return; - } - - bool flag = true; - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) - { - if (gtid.second >= itr->first && gtid.second <= itr->second) - return; - if (gtid.second + 1 == itr->first) - { - --itr->first; - flag = false; - break; - } - else if (gtid.second == itr->second + 1) - { - ++itr->second; - flag = false; - break; - } - else if (gtid.second < itr->first) - { - it->second.emplace(itr, gtid.second, gtid.second); - return; - } - } - - if (flag) - it->second.emplace_back(gtid.second, gtid.second); - - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) - { - auto next_itr = std::next(itr); - if (next_itr != it->second.end() && itr->second + 1 == next_itr->first) - { - itr->second = next_itr->second; - it->second.erase(next_itr); - break; - } - } -} - -static void * GTID_syncer_run() { - //struct ev_loop * gtid_ev_loop; - //gtid_ev_loop = NULL; - PgHGM->gtid_ev_loop = ev_loop_new (EVBACKEND_POLL | EVFLAG_NOENV); - if (PgHGM->gtid_ev_loop == NULL) { - proxy_error("could not initialise GTID sync loop\n"); - exit(EXIT_FAILURE); - } - //ev_async_init(gtid_ev_async, gtid_async_cb); - //ev_async_start(gtid_ev_loop, gtid_ev_async); - PgHGM->gtid_ev_timer = (struct ev_timer *)malloc(sizeof(struct ev_timer)); - ev_async_init(PgHGM->gtid_ev_async, gtid_async_cb); - ev_async_start(PgHGM->gtid_ev_loop, PgHGM->gtid_ev_async); - //ev_timer_init(PgHGM->gtid_ev_timer, gtid_timer_cb, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); - ev_timer_init(PgHGM->gtid_ev_timer, gtid_timer_cb, 3, 0); - ev_timer_start(PgHGM->gtid_ev_loop, PgHGM->gtid_ev_timer); - //ev_ref(gtid_ev_loop); - ev_run(PgHGM->gtid_ev_loop, 0); - //sleep(1000); - return NULL; -} -#endif // 0 - PgSQL_Connection *PgSQL_SrvConnList::index(unsigned int _k) { return (PgSQL_Connection *)conns->index(_k); } @@ -606,12 +144,6 @@ PgSQL_Connection * PgSQL_SrvConnList::remove(int _k) { return (PgSQL_Connection *)conns->remove_index_fast(_k); } -/* -unsigned int PgSQL_SrvConnList::conns_length() { - return conns->len; -} -*/ - PgSQL_SrvConnList::PgSQL_SrvConnList(PgSQL_SrvC *_mysrvc) { mysrvc=_mysrvc; conns=new PtrArray(); @@ -630,35 +162,6 @@ PgSQL_SrvConnList::~PgSQL_SrvConnList() { delete conns; } -PgSQL_SrvList::PgSQL_SrvList(PgSQL_HGC *_myhgc) { - myhgc=_myhgc; - servers=new PtrArray(); -} - -void PgSQL_SrvList::add(PgSQL_SrvC *s) { - if (s->myhgc==NULL) { - s->myhgc=myhgc; - } - servers->add(s); -} - - -int PgSQL_SrvList::find_idx(PgSQL_SrvC *s) { - for (unsigned int i=0; ilen; i++) { - PgSQL_SrvC *mysrv=(PgSQL_SrvC *)servers->index(i); - if (mysrv==s) { - return (unsigned int)i; - } - } - return -1; -} - -void PgSQL_SrvList::remove(PgSQL_SrvC *s) { - int i=find_idx(s); - assert(i>=0); - servers->remove_index_fast((unsigned int)i); -} - void PgSQL_SrvConnList::drop_all_connections() { proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Dropping all connections (%u total) on PgSQL_SrvConnList %p for server %s:%d , hostgroup=%d , status=%d\n", conns_length(), this, mysrvc->address, mysrvc->port, mysrvc->myhgc->hid, mysrvc->status); while (conns_length()) { @@ -805,61 +308,6 @@ PgSQL_SrvC::~PgSQL_SrvC() { delete ConnectionsFree; } -PgSQL_SrvList::~PgSQL_SrvList() { - myhgc=NULL; - while (servers->len) { - PgSQL_SrvC *mysrvc=(PgSQL_SrvC *)servers->remove_index_fast(0); - delete mysrvc; - } - delete servers; -} - - -PgSQL_HGC::PgSQL_HGC(int _hid) { - hid=_hid; - mysrvs=new PgSQL_SrvList(this); - current_time_now = 0; - new_connections_now = 0; - attributes.initialized = false; - reset_attributes(); - // Uninitialized server defaults. Should later be initialized via 'pgsql_hostgroup_attributes'. - servers_defaults.weight = -1; - servers_defaults.max_connections = -1; - servers_defaults.use_ssl = -1; -} - -void PgSQL_HGC::reset_attributes() { - if (attributes.initialized == false) { - attributes.init_connect = NULL; - attributes.comment = NULL; - attributes.ignore_session_variables_text = NULL; - } - attributes.initialized = true; - attributes.configured = false; - attributes.max_num_online_servers = 1000000; - attributes.throttle_connections_per_sec = 1000000; - attributes.autocommit = -1; - attributes.free_connections_pct = 10; - attributes.handle_warnings = -1; - attributes.multiplex = true; - attributes.connection_warming = false; - free(attributes.init_connect); - attributes.init_connect = NULL; - free(attributes.comment); - attributes.comment = NULL; - free(attributes.ignore_session_variables_text); - attributes.ignore_session_variables_text = NULL; - if (attributes.ignore_session_variables_json) { - delete attributes.ignore_session_variables_json; - attributes.ignore_session_variables_json = NULL; - } -} - -PgSQL_HGC::~PgSQL_HGC() { - reset_attributes(); // free all memory - delete mysrvs; -} - using metric_name = std::string; using metric_help = std::string; using metric_tags = std::map; @@ -1269,14 +717,12 @@ PgSQL_HostGroups_Manager::PgSQL_HostGroups_Manager() { status.access_denied_max_user_connections=0; status.select_for_update_or_equivalent=0; status.auto_increment_delay_multiplex=0; +#if 0 pthread_mutex_init(&readonly_mutex, NULL); -#ifdef MHM_PTHREAD_MUTEX pthread_mutex_init(&lock, NULL); -#else - spinlock_rwlock_init(&rwlock); -#endif admindb=NULL; // initialized only if needed mydb=new SQLite3DB(); +#endif // 0 #ifdef DEBUG mydb->open((char *)"file:mem_mydb?mode=memory&cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); #else @@ -1350,18 +796,7 @@ PgSQL_HostGroups_Manager::~PgSQL_HostGroups_Manager() { ev_loop_destroy(gtid_ev_loop); if (gtid_ev_timer) free(gtid_ev_timer); -#ifdef MHM_PTHREAD_MUTEX pthread_mutex_destroy(&lock); -#endif -} - -// wrlock() is only required during commit() -void PgSQL_HostGroups_Manager::wrlock() { -#ifdef MHM_PTHREAD_MUTEX - pthread_mutex_lock(&lock); -#else - spin_wrlock(&rwlock); -#endif } void PgSQL_HostGroups_Manager::p_update_pgsql_error_counter(p_pgsql_error_type err_type, unsigned int hid, char* address, uint16_t port, unsigned int code) { @@ -1395,15 +830,6 @@ void PgSQL_HostGroups_Manager::p_update_pgsql_error_counter(p_pgsql_error_type e pthread_mutex_unlock(&pgsql_errors_mutex); } -void PgSQL_HostGroups_Manager::wrunlock() { -#ifdef MHM_PTHREAD_MUTEX - pthread_mutex_unlock(&lock); -#else - spin_wrunlock(&rwlock); -#endif -} - - void PgSQL_HostGroups_Manager::wait_servers_table_version(unsigned v, unsigned w) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -1505,16 +931,6 @@ int PgSQL_HostGroups_Manager::servers_add(SQLite3_result *resultset) { return 0; } -SQLite3_result * PgSQL_HostGroups_Manager::execute_query(char *query, char **error) { - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - wrlock(); - mydb->execute_statement(query, error , &cols , &affected_rows , &resultset); - wrunlock(); - return resultset; -} - void PgSQL_HostGroups_Manager::CUCFT1( SpookyHash& myhash, bool& init, const string& TableName, const string& ColumnName, uint64_t& raw_checksum ) { @@ -2143,108 +1559,6 @@ uint64_t PgSQL_HostGroups_Manager::get_pgsql_servers_checksum(SQLite3_result* ru return table_resultset_checksum[HGM_TABLES::PgSQL_SERVERS]; } -bool PgSQL_HostGroups_Manager::gtid_exists(PgSQL_SrvC *mysrvc, char * gtid_uuid, uint64_t gtid_trxid) { - bool ret = false; -#if 0 - pthread_rwlock_rdlock(>id_rwlock); - std::string s1 = mysrvc->address; - s1.append(":"); - s1.append(std::to_string(mysrvc->port)); - std::unordered_map ::iterator it2; - it2 = gtid_map.find(s1); - PgSQL_GTID_Server_Data *gtid_is=NULL; - if (it2!=gtid_map.end()) { - gtid_is=it2->second; - if (gtid_is) { - if (gtid_is->active == true) { - ret = gtid_is->gtid_exists(gtid_uuid,gtid_trxid); - } - } - } - //proxy_info("Checking if server %s has GTID %s:%lu . %s\n", s1.c_str(), gtid_uuid, gtid_trxid, (ret ? "YES" : "NO")); - pthread_rwlock_unlock(>id_rwlock); -#endif // 0 - return ret; -} - -void PgSQL_HostGroups_Manager::generate_pgsql_gtid_executed_tables() { -#if 0 - pthread_rwlock_wrlock(>id_rwlock); - // first, set them all as active = false - std::unordered_map::iterator it = gtid_map.begin(); - while(it != gtid_map.end()) { - PgSQL_GTID_Server_Data * gtid_si = it->second; - if (gtid_si) { - gtid_si->active = false; - } - it++; - } - - // NOTE: We are required to lock while iterating over 'MyHostGroups'. Otherwise race conditions could take place, - // e.g. servers could be purged by 'purge_pgsql_servers_table' and invalid memory be accessed. - wrlock(); - for (unsigned int i=0; ilen; i++) { - PgSQL_HGC *myhgc=(PgSQL_HGC *)MyHostGroups->index(i); - PgSQL_SrvC *mysrvc=NULL; - for (unsigned int j=0; jmysrvs->servers->len; j++) { - mysrvc=myhgc->mysrvs->idx(j); - if (mysrvc->gtid_port) { - std::string s1 = mysrvc->address; - s1.append(":"); - s1.append(std::to_string(mysrvc->port)); - std::unordered_map ::iterator it2; - it2 = gtid_map.find(s1); - PgSQL_GTID_Server_Data *gtid_is=NULL; - if (it2!=gtid_map.end()) { - gtid_is=it2->second; - if (gtid_is == NULL) { - gtid_map.erase(it2); - } - } - if (gtid_is) { - gtid_is->active = true; - } else if (mysrvc->status != MYSQL_SERVER_STATUS_OFFLINE_HARD) { - // we didn't find it. Create it - /* - struct ev_io *watcher = (struct ev_io *)malloc(sizeof(struct ev_io)); - gtid_is = new PgSQL_GTID_Server_Data(watcher, mysrvc->address, mysrvc->port, mysrvc->gtid_port); - gtid_map.emplace(s1,gtid_is); - */ - struct ev_io * c = NULL; - c = new_connector(mysrvc->address, mysrvc->gtid_port, mysrvc->port); - if (c) { - gtid_is = (PgSQL_GTID_Server_Data *)c->data; - gtid_map.emplace(s1,gtid_is); - //pthread_mutex_lock(&ev_loop_mutex); - ev_io_start(PgHGM->gtid_ev_loop,c); - //pthread_mutex_unlock(&ev_loop_mutex); - } - } - } - } - } - wrunlock(); - std::vector to_remove; - it = gtid_map.begin(); - while(it != gtid_map.end()) { - PgSQL_GTID_Server_Data * gtid_si = it->second; - if (gtid_si && gtid_si->active == false) { - to_remove.push_back(it->first); - } - it++; - } - for (std::vector::iterator it3=to_remove.begin(); it3!=to_remove.end(); ++it3) { - it = gtid_map.find(*it3); - PgSQL_GTID_Server_Data * gtid_si = it->second; - ev_io_stop(PgHGM->gtid_ev_loop, gtid_si->w); - close(gtid_si->w->fd); - free(gtid_si->w); - gtid_map.erase(*it3); - } - pthread_rwlock_unlock(>id_rwlock); -#endif // 0 -} - void PgSQL_HostGroups_Manager::purge_pgsql_servers_table() { for (unsigned int i=0; ilen; i++) { PgSQL_HGC *myhgc=(PgSQL_HGC *)MyHostGroups->index(i); @@ -2498,48 +1812,6 @@ SQLite3_result * PgSQL_HostGroups_Manager::dump_table_pgsql(const string& name) return resultset; } - -PgSQL_HGC * PgSQL_HostGroups_Manager::MyHGC_create(unsigned int _hid) { - PgSQL_HGC *myhgc=new PgSQL_HGC(_hid); - return myhgc; -} - -PgSQL_HGC * PgSQL_HostGroups_Manager::MyHGC_find(unsigned int _hid) { - if (MyHostGroups->len < 100) { - // for few HGs, we use the legacy search - for (unsigned int i=0; ilen; i++) { - PgSQL_HGC *myhgc=(PgSQL_HGC *)MyHostGroups->index(i); - if (myhgc->hid==_hid) { - return myhgc; - } - } - } else { - // for a large number of HGs, we use the unordered_map - // this search is slower for a small number of HGs, therefore we use - // it only for large number of HGs - std::unordered_map::const_iterator it = MyHostGroups_map.find(_hid); - if (it != MyHostGroups_map.end()) { - PgSQL_HGC *myhgc = it->second; - return myhgc; - } - } - return NULL; -} - -PgSQL_HGC * PgSQL_HostGroups_Manager::MyHGC_lookup(unsigned int _hid) { - PgSQL_HGC *myhgc=NULL; - myhgc=MyHGC_find(_hid); - if (myhgc==NULL) { - myhgc=MyHGC_create(_hid); - } else { - return myhgc; - } - assert(myhgc); - MyHostGroups->add(myhgc); - MyHostGroups_map.emplace(_hid,myhgc); - return myhgc; -} - void PgSQL_HostGroups_Manager::increase_reset_counter() { wrlock(); status.myconnpoll_reset++; @@ -2630,12 +1902,14 @@ PgSQL_SrvC *PgSQL_HGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, if (mysrvc->ConnectionsUsed->conns_length() < mysrvc->max_connections) { // consider this server only if didn't reach max_connections if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : pgsql_thread___default_max_latency_ms *1000 ) ) { // consider the host only if not too far if (gtid_trxid) { +#if 0 if (PgHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { sum+=mysrvc->weight; TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); mysrvcCandidates[num_candidates]=mysrvc; num_candidates++; } +#endif // 0 } else { if (max_lag_ms >= 0) { if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { @@ -2698,12 +1972,14 @@ PgSQL_SrvC *PgSQL_HGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, // if a server is taken back online, consider it immediately if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : pgsql_thread___default_max_latency_ms *1000 ) ) { // consider the host only if not too far if (gtid_trxid) { +#if 0 if (PgHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { sum+=mysrvc->weight; TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); mysrvcCandidates[num_candidates]=mysrvc; num_candidates++; } +#endif // 0 } else { if (max_lag_ms >= 0) { if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { @@ -2783,12 +2059,14 @@ PgSQL_SrvC *PgSQL_HGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, // if a server is taken back online, consider it immediately if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : pgsql_thread___default_max_latency_ms *1000 ) ) { // consider the host only if not too far if (gtid_trxid) { +#if 0 if (PgHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { sum+=mysrvc->weight; TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); mysrvcCandidates[num_candidates]=mysrvc; num_candidates++; } +#endif // 0 } else { if (max_lag_ms >= 0) { if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { @@ -4659,8 +3937,6 @@ void PgSQL_HostGroups_Manager::p_update_metrics() { // Update the *connection_pool* metrics this->p_update_connection_pool(); - // Update the *gtid_executed* metrics - this->p_update_pgsql_gtid_executed(); } SQLite3_result * PgSQL_HostGroups_Manager::SQL3_Get_ConnPool_Stats() { @@ -4741,185 +4017,6 @@ unsigned long long PgSQL_HostGroups_Manager::Get_Memory_Stats() { return intsize; } -PgSQL_Group_Replication_Info::PgSQL_Group_Replication_Info(int w, int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c) { - comment=NULL; - if (c) { - comment=strdup(c); - } - writer_hostgroup=w; - backup_writer_hostgroup=b; - reader_hostgroup=r; - offline_hostgroup=o; - max_writers=mw; - max_transactions_behind=mtb; - active=_a; - writer_is_also_reader=_w; - current_num_writers=0; - current_num_backup_writers=0; - current_num_readers=0; - current_num_offline=0; - __active=true; - need_converge=true; -} - -PgSQL_Group_Replication_Info::~PgSQL_Group_Replication_Info() { - if (comment) { - free(comment); - comment=NULL; - } -} - -bool PgSQL_Group_Replication_Info::update(int b, int r, int o, int mw, int mtb, bool _a, int _w, char *c) { - bool ret=false; - __active=true; - if (backup_writer_hostgroup!=b) { - backup_writer_hostgroup=b; - ret=true; - } - if (reader_hostgroup!=r) { - reader_hostgroup=r; - ret=true; - } - if (offline_hostgroup!=o) { - offline_hostgroup=o; - ret=true; - } - if (max_writers!=mw) { - max_writers=mw; - ret=true; - } - if (max_transactions_behind!=mtb) { - max_transactions_behind=mtb; - ret=true; - } - if (active!=_a) { - active=_a; - ret=true; - } - if (writer_is_also_reader!=_w) { - writer_is_also_reader=_w; - ret=true; - } - // for comment we don't change return value - if (comment) { - if (c) { - if (strcmp(comment,c)) { - free(comment); - comment=strdup(c); - } - } else { - free(comment); - comment=NULL; - } - } else { - if (c) { - comment=strdup(c); - } - } - return ret; -} - -void PgSQL_HostGroups_Manager::p_update_pgsql_gtid_executed() { - pthread_rwlock_wrlock(>id_rwlock); - - std::unordered_map::iterator it = gtid_map.begin(); - while(it != gtid_map.end()) { - PgSQL_GTID_Server_Data* gtid_si = it->second; - std::string address {}; - std::string port {}; - std::string endpoint_id {}; - - if (gtid_si) { - address = std::string(gtid_si->address); - port = std::to_string(gtid_si->pgsql_port); - } else { - std::string s = it->first; - std::size_t found = s.find_last_of(":"); - address = s.substr(0, found); - port = s.substr(found + 1); - } - endpoint_id = address + ":" + port; - - const auto& gitd_id_counter = this->status.p_gtid_executed_map.find(endpoint_id); - prometheus::Counter* gtid_counter = nullptr; - - if (gitd_id_counter == this->status.p_gtid_executed_map.end()) { - auto& gitd_counter = - this->status.p_dyn_counter_array[PgSQL_p_hg_dyn_counter::gtid_executed]; - - gtid_counter = std::addressof(gitd_counter->Add({ - { "hostname", address }, - { "port", port }, - })); - - this->status.p_gtid_executed_map.insert( - { - endpoint_id, - gtid_counter - } - ); - } else { - gtid_counter = gitd_id_counter->second; - } - - if (gtid_si) { - const auto& cur_executed_gtid = gtid_counter->Value(); - gtid_counter->Increment(gtid_si->events_read - cur_executed_gtid); - } - - it++; - } - - pthread_rwlock_unlock(>id_rwlock); -} - -SQLite3_result * PgSQL_HostGroups_Manager::get_stats_pgsql_gtid_executed() { - const int colnum = 4; - SQLite3_result * result = new SQLite3_result(colnum); - result->add_column_definition(SQLITE_TEXT,"hostname"); - result->add_column_definition(SQLITE_TEXT,"port"); - result->add_column_definition(SQLITE_TEXT,"gtid_executed"); - result->add_column_definition(SQLITE_TEXT,"events"); - int k; - pthread_rwlock_wrlock(>id_rwlock); - std::unordered_map::iterator it = gtid_map.begin(); - while(it != gtid_map.end()) { - PgSQL_GTID_Server_Data * gtid_si = it->second; - char buf[64]; - char **pta=(char **)malloc(sizeof(char *)*colnum); - if (gtid_si) { - pta[0]=strdup(gtid_si->address); - sprintf(buf,"%d", (int)gtid_si->pgsql_port); - pta[1]=strdup(buf); - //sprintf(buf,"%d", mysrvc->port); - string s1 = gtid_executed_to_string(gtid_si->gtid_executed); - pta[2]=strdup(s1.c_str()); - sprintf(buf,"%llu", gtid_si->events_read); - pta[3]=strdup(buf); - } else { - std::string s = it->first; - std::size_t found=s.find_last_of(":"); - std::string host=s.substr(0,found); - std::string port=s.substr(found+1); - pta[0]=strdup(host.c_str()); - pta[1]=strdup(port.c_str()); - pta[2]=strdup((char *)"NULL"); - pta[3]=strdup((char *)"0"); - } - result->add_row(pta); - for (k=0; k>& checks) { - for (uint32_t i = 0; i < mybes->len; i++) { - PgSQL_Backend* mybe = static_cast(mybes->index(i)); - PgSQL_Data_Stream* myds = mybe != nullptr ? mybe->server_myds : nullptr; - PgSQL_Connection* myconn = myds != nullptr ? myds->myconn : nullptr; - - if (myconn != nullptr) { - const bool is_active_transaction = myconn->IsActiveTransaction(); - const bool multiplex_disabled = myconn->MultiplexDisabled(false); - const bool is_idle = myconn->async_state_machine == ASYNC_IDLE; - - // Make sure the connection is reusable before performing any check - if (myconn->reusable == true && is_active_transaction == false && multiplex_disabled == false && is_idle) { - for (const function& check : checks) { - if (check(myconn)) { - this->hgs_expired_conns.push_back(mybe->hostgroup_id); - break; - } - } - } - } - } -} - bool PgSQL_Session::handler_CommitRollback(PtrSize_t* pkt) { if (pkt->size <= 5) { return false; } char c = ((char*)pkt->ptr)[5]; @@ -5565,84 +5541,6 @@ void PgSQL_Session::handler_WCD_SS_MCQ_qpo_LargePacket(PtrSize_t* pkt) { l_free(pkt->size, pkt->ptr); } -/* -// this function as inline in handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo -// returned values: -// 0 : no action -// 1 : return false -// 2 : return true -int PgSQL_Session::handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(PtrSize_t *pkt, bool *lock_hostgroup, unsigned int nTrx, string& nq) { - re2::RE2::Options *opt2=new re2::RE2::Options(RE2::Quiet); - opt2->set_case_sensitive(false); - char *pattern=(char *)"(?: *)SET *(?:|SESSION +|@@|@@session.)SQL_LOG_BIN *(?:|:)= *(\\d+) *(?:(|;|-- .*|#.*))$"; - re2::RE2 *re=new RE2(pattern, *opt2); - int i; - int rc=RE2::PartialMatch(nq, *re, &i); - delete re; - delete opt2; - if (rc && ( i==0 || i==1) ) { - //fprintf(stderr,"sql_log_bin=%d\n", i); - if (i == 1) { - if (!pgsql_variables.client_set_value(this, SQL_SQL_LOG_BIN, "1")) - return 1; - } - else if (i == 0) { - if (!pgsql_variables.client_set_value(this, SQL_SQL_LOG_BIN, "0")) - return 1; - } - -#ifdef DEBUG - proxy_info("Setting SQL_LOG_BIN to %d\n", i); -#endif -#ifdef DEBUG - { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Setting SQL_LOG_BIN to %d for query: %s\n", i, nqn.c_str()); - } -#endif - // we recompute command_type instead of taking it from the calling function - unsigned char command_type=*((unsigned char *)pkt->ptr+sizeof(mysql_hdr)); - if (command_type == _MYSQL_COM_QUERY) { - client_myds->DSS=STATE_QUERY_SENT_NET; - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT; - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,setStatus,0,NULL); - client_myds->DSS=STATE_SLEEP; - status=WAITING_CLIENT_DATA; - RequestEnd(NULL); - l_free(pkt->size,pkt->ptr); - return 2; - } - } else { - int kq = 0; - kq = strncmp((const char *)CurrentQuery.QueryPointer, (const char *)"SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;" , CurrentQuery.QueryLength); -#ifdef DEBUG - { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Setting SQL_LOG_BIN to %d for query: %s\n", i, nqn.c_str()); - } -#endif - if (kq == 0) { - client_myds->DSS=STATE_QUERY_SENT_NET; - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (autocommit) setStatus |= SERVER_STATUS_AUTOCOMMIT; - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,setStatus,0,NULL); - client_myds->DSS=STATE_SLEEP; - status=WAITING_CLIENT_DATA; - RequestEnd(NULL); - l_free(pkt->size,pkt->ptr); - return 2; - } else { - string nqn = string((char *)CurrentQuery.QueryPointer,CurrentQuery.QueryLength); - proxy_error("Unable to parse query. If correct, report it as a bug: %s\n", nqn.c_str()); - unable_to_parse_set_statement(lock_hostgroup); - return 1; - } - } - return 0; -} -*/ - bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t* pkt, bool* lock_hostgroup, PgSQL_ps_type prepare_stmt_type) { /* lock_hostgroup: @@ -5777,15 +5675,6 @@ bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C RE2::GlobalReplace(&nq, (char*)"(?U)/\\*.*\\*/", (char*)""); // remove trailing space and semicolon if present. See issue#4380 nq.erase(nq.find_last_not_of(" ;") + 1); - /* - // we do not threat SET SQL_LOG_BIN as a special case - if (match_regexes && match_regexes[0]->match(dig)) { - int rc = handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(pkt, lock_hostgroup, nTrx, nq); - if (rc == 1) return false; - if (rc == 2) return true; - // if rc == 0 , continue as normal - } - */ if ( ( match_regexes && (match_regexes[1]->match(dig)) @@ -7283,105 +7172,6 @@ void PgSQL_Session::SQLite3_to_MySQL(SQLite3_result* result, char* error, int af } } -void PgSQL_Session::set_unhealthy() { - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Sess:%p\n", this); - healthy = 0; -} - - -unsigned int PgSQL_Session::NumActiveTransactions(bool check_savepoint) { - unsigned int ret = 0; - if (mybes == 0) return ret; - PgSQL_Backend* _mybe; - unsigned int i; - for (i = 0; i < mybes->len; i++) { - _mybe = (PgSQL_Backend*)mybes->index(i); - if (_mybe->server_myds) { - if (_mybe->server_myds->myconn) { - if (_mybe->server_myds->myconn->IsActiveTransaction()) { - ret++; - } - else { - // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due - // to MySQL bug https://bugs.pgsql.com/bug.php?id=107875 related to - // SAVEPOINT and autocommit=0 - if (check_savepoint) { - if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { - ret++; - } - } - } - } - } - } - return ret; -} - -bool PgSQL_Session::HasOfflineBackends() { - bool ret = false; - if (mybes == 0) return ret; - PgSQL_Backend* _mybe; - unsigned int i; - for (i = 0; i < mybes->len; i++) { - _mybe = (PgSQL_Backend*)mybes->index(i); - if (_mybe->server_myds) - if (_mybe->server_myds->myconn) - if (_mybe->server_myds->myconn->IsServerOffline()) { - ret = true; - return ret; - } - } - return ret; -} - -bool PgSQL_Session::SetEventInOfflineBackends() { - bool ret = false; - if (mybes == 0) return ret; - PgSQL_Backend* _mybe; - unsigned int i; - for (i = 0; i < mybes->len; i++) { - _mybe = (PgSQL_Backend*)mybes->index(i); - if (_mybe->server_myds) - if (_mybe->server_myds->myconn) - if (_mybe->server_myds->myconn->IsServerOffline()) { - _mybe->server_myds->revents |= POLLIN; - ret = true; - } - } - return ret; -} - -int PgSQL_Session::FindOneActiveTransaction(bool check_savepoint) { - int ret = -1; - if (mybes == 0) return ret; - PgSQL_Backend* _mybe; - unsigned int i; - for (i = 0; i < mybes->len; i++) { - _mybe = (PgSQL_Backend*)mybes->index(i); - if (_mybe->server_myds) { - if (_mybe->server_myds->myconn) { - if (_mybe->server_myds->myconn->IsKnownActiveTransaction()) { - return (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } - else if (_mybe->server_myds->myconn->IsActiveTransaction()) { - ret = (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } - else { - // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due - // to MySQL bug https://bugs.pgsql.com/bug.php?id=107875 related to - // SAVEPOINT and autocommit=0 - if (check_savepoint) { - if (_mybe->server_myds->myconn->AutocommitFalse_AndSavepoint() == true) { - return (int)_mybe->server_myds->myconn->parent->myhgc->hid; - } - } - } - } - } - } - return ret; -} - unsigned long long PgSQL_Session::IdleTime() { unsigned long long ret = 0; if (client_myds == 0) return 0; diff --git a/lib/PgSQL_Thread.cpp b/lib/PgSQL_Thread.cpp index 9cfa8a582..968c29353 100644 --- a/lib/PgSQL_Thread.cpp +++ b/lib/PgSQL_Thread.cpp @@ -5193,17 +5193,17 @@ PgSQL_Connection* PgSQL_Thread::get_MyConn_local(unsigned int _hid, PgSQL_Sessio if (it != parents.end()) { // we didn't exclude this server (yet?) bool gtid_found = false; +#if 0 gtid_found = PgHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid); +#endif // 0 if (gtid_found) { // this server has the correct GTID c = (PgSQL_Connection*)cached_connections->remove_index_fast(i); return c; - } - else { + } else { parents.push_back(mysrvc); // stop evaluating this server } } - } - else { // gtid_is not used + } else { // gtid_is not used if (max_lag_ms >= 0) { if ((unsigned int)max_lag_ms < (c->parent->aws_aurora_current_lag_us / 1000)) { status_variables.stvar[st_var_aws_aurora_replicas_skipped_during_query]++; diff --git a/test/tap/tests/admin_various_commands-t.cpp b/test/tap/tests/admin_various_commands-t.cpp index cbc2ea3c9..003356fc1 100644 --- a/test/tap/tests/admin_various_commands-t.cpp +++ b/test/tap/tests/admin_various_commands-t.cpp @@ -79,6 +79,7 @@ int main() { { 1 , "show VARIABLES" }, { 1 , "show ALL variables" }, { 1 , "show MYSQL variables" }, + { 1 , "SHOW PGSQL VARIABLES" }, { 1 , "SHOW admin VARIABLES" }, { 3 , "sHoW DATABASES" }, { 3 , "sHoW SCHEMAS" },