diff --git a/include/proxysql_glovars.hpp b/include/proxysql_glovars.hpp index c5b52b2db1..806802c668 100644 --- a/include/proxysql_glovars.hpp +++ b/include/proxysql_glovars.hpp @@ -87,8 +87,50 @@ class ProxySQL_GlobalVariables { unsigned long long start_time; bool gdbg; bool nostart; + /** + * @brief Disable/Enable the MySQL Monitor module. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. + */ bool my_monitor; + /** + * @brief Disable/Enable the PostgreSQL Monitor module. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. + */ bool pg_monitor; + /** + * @brief Disable/Enable the MySQL Workers module. This disables ProxySQL capability for handling + * MySQL traffic to be routed to the MySQL backend servers. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. Disabling this module doesn't affect MySQL + * Monitoring. + */ + bool mysql_workers; + /** + * @brief Disable/Enable the PostgreSQL Workers module. This disables ProxySQL capability for handling + * PostgreSQL traffic to be routed to the PostgreSQL backend servers. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. Disabling this module doesn't affect PostgreSQL + * Monitoring. + */ + bool pgsql_workers; + /** + * @brief Disable/Enable MySQL Admin module. This disables access, via MySQL protocol, to + * ProxySQL Administration interface. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. It's important to notice that Administrative access + * remains possible via PostgreSQL Admin interface, if enabled. + */ + bool mysql_admin; + /** + * @brief Disable/Enable PostgreSQL Admin module. This disables access, via PostgreSQL + * protocol, to ProxySQL Administration interface. + * @details Meant to be configured as an startup switch. Possible to change it's value only via a + * command line switch or via config file option. It's important to notice that Administrative access + * remains possible via the MySQL Admin interface, if enabled. + */ + bool pgsql_admin; bool version_check; #ifdef SO_REUSEPORT bool reuseport; diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index e299d3dab8..09e8e1cf55 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -2073,8 +2073,10 @@ bool MySQL_Threads_Handler::set_variable(char *name, const char *value) { // thi } } if (!strcasecmp(name,"threads")) { - unsigned int intv=atoi(value); - if ((num_threads==0 || num_threads==intv || mysql_threads==NULL) && intv > 0 && intv < 256) { + const uint32_t intv { !GloVars.global.mysql_workers ? uint32_t(0) : atoi(value) }; + const bool valid_val { (intv > 0 && intv < 256) || (!GloVars.global.mysql_workers && intv == 0) }; + + if ((num_threads==0 || num_threads==intv || mysql_threads==NULL) && valid_val) { num_threads=intv; this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(intv); return true; @@ -2428,7 +2430,7 @@ void MySQL_Threads_Handler::init(unsigned int num, size_t stack) { num_threads=num; this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(num); } else { - if (num_threads==0) { + if (num_threads==0 && GloVars.global.mysql_workers) { num_threads=DEFAULT_NUM_THREADS; //default this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(DEFAULT_NUM_THREADS); } @@ -2483,7 +2485,7 @@ proxysql_mysql_thread_t * MySQL_Threads_Handler::create_thread(unsigned int tn, if (GloVars.set_thread_name == true) { char thr_name[16]; snprintf(thr_name, sizeof(thr_name), "MySQLIdle%d", tn); - pthread_setname_np(mysql_threads[tn].thread_id, thr_name); + pthread_setname_np(mysql_threads_idles[tn].thread_id, thr_name); } } #endif // defined(__linux__) || defined(__FreeBSD__) diff --git a/lib/PgSQL_Thread.cpp b/lib/PgSQL_Thread.cpp index d1084d557d..3f25b04d2b 100644 --- a/lib/PgSQL_Thread.cpp +++ b/lib/PgSQL_Thread.cpp @@ -1962,8 +1962,10 @@ bool PgSQL_Threads_Handler::set_variable(char* name, const char* value) { // thi } } if (!strcasecmp(name, "threads")) { - unsigned int intv = atoi(value); - if ((num_threads == 0 || num_threads == intv || pgsql_threads == NULL) && intv > 0 && intv < 256) { + const uint32_t intv { !GloVars.global.pgsql_workers ? uint32_t(0) : atoi(value) }; + const bool valid_val { (intv > 0 && intv < 256) || (!GloVars.global.pgsql_workers && intv == 0) }; + + if ((num_threads == 0 || num_threads == intv || pgsql_threads == NULL) && valid_val) { num_threads = intv; //this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(intv); return true; @@ -2277,7 +2279,7 @@ void PgSQL_Threads_Handler::init(unsigned int num, size_t stack) { //this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(num); } else { - if (num_threads == 0) { + if (num_threads==0 && GloVars.global.pgsql_workers) { num_threads = DEFAULT_NUM_THREADS; //default //this->status_variables.p_gauge_array[p_th_gauge::mysql_thread_workers]->Set(DEFAULT_NUM_THREADS); } diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index daebe79ced..bc785dcc29 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -2248,9 +2248,6 @@ void * admin_main_loop(void *arg) { __sync_fetch_and_add(&admin_load_main_,1); while (glovars.shutdown==0 && *shutdown==0) { - //int *client; - //int client_t; - //socklen_t addr_size = sizeof(addr); pthread_t child; size_t stacks; unsigned long long curtime=monotonic_time(); @@ -2283,13 +2280,9 @@ void * admin_main_loop(void *arg) { passarg->addr_size = sizeof(custom_sockaddr); memset(passarg->addr, 0, sizeof(custom_sockaddr)); passarg->client_t = accept(fds[i].fd, (struct sockaddr*)passarg->addr, &passarg->addr_size); -// printf("Connected: %s:%d sock=%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), client_t); pthread_attr_getstacksize (&attr, &stacks); -// printf("Default stack size = %d\n", stacks); pthread_mutex_lock (&sock_mutex); - //client=(int *)malloc(sizeof(int)); - //*client= client_t; - //if ( pthread_create(&child, &attr, child_func[callback_func[i]], client) != 0 ) { + if ( pthread_create(&child, &attr, child_func[callback_func[i]], passarg) != 0 ) { // LCOV_EXCL_START perror("pthread_create"); @@ -2315,12 +2308,15 @@ void * admin_main_loop(void *arg) { if (resultset) { SQLite3_result * resultset2 = NULL; - // In debug, run the code to generate metrics so that it can be tested even if the web interface plugin isn't loaded. - #ifdef DEBUG - if (true) { - #else - if (GloVars.web_interface_plugin) { - #endif + // In debug, run the code to generate metrics so that it can be tested even if + // the 'web_interface_plugin' isn't loaded. + if ( + #ifdef DEBUG + true + #else + GloVars.web_interface_plugin + #endif + ) { resultset2 = MyHGM->SQL3_Connection_Pool(false); } GloProxyStats->MyHGM_Handler_sets(resultset, resultset2); @@ -2378,7 +2374,7 @@ void * admin_main_loop(void *arg) { nfds++; unsigned int j; i=0; j=0; - for (j=0; jifaces->len; j++) { + for (j=0; j < S_amll.ifaces_mysql->ifaces->len && GloVars.global.mysql_admin; j++) { char *add=NULL; char *port=NULL; char *sn=(char *)S_amll.ifaces_mysql->ifaces->index(j); bool is_ipv6 = false; char *h = NULL; @@ -2402,7 +2398,7 @@ void * admin_main_loop(void *arg) { #else int s = ( atoi(port) ? listen_on_port(add, atoi(port), 128) : listen_on_unix(add, 128)); #endif - //if (s>0) { fds[nfds].fd=s; fds[nfds].events=POLLIN; fds[nfds].revents=0; callback_func[nfds]=0; socket_names[nfds]=strdup(sn); nfds++; } + if (s > 0) { fds[nfds].fd = s; fds[nfds].events = POLLIN; @@ -2418,7 +2414,7 @@ void * admin_main_loop(void *arg) { } i = 0; j = 0; - for (; j < S_amll.ifaces_pgsql->ifaces->len; j++) { + for (; j < S_amll.ifaces_pgsql->ifaces->len && GloVars.global.pgsql_admin; j++) { char* add = NULL; char* port = NULL; char* sn = (char*)S_amll.ifaces_pgsql->ifaces->index(j); bool is_ipv6 = false; char* h = NULL; @@ -2443,7 +2439,7 @@ void * admin_main_loop(void *arg) { #else int s = (atoi(port) ? listen_on_port(add, atoi(port), 128) : listen_on_unix(add, 128)); #endif - //if (s>0) { fds[nfds].fd=s; fds[nfds].events=POLLIN; fds[nfds].revents=0; callback_func[nfds]=0; socket_names[nfds]=strdup(sn); nfds++; } + if (s > 0) { fds[nfds].fd = s; fds[nfds].events = POLLIN; @@ -2461,7 +2457,7 @@ void * admin_main_loop(void *arg) { } } - //if (__sync_add_and_fetch(shutdown,0)==0) __sync_add_and_fetch(shutdown,1); + for (i=0; iadd((const char *)"",0,0,0,(const char *)"Starts only the admin service",(const char *)"-n",(const char *)"--no-start"); opt->add((const char *)"",0,0,0,(const char *)"Do not start Monitor Module",(const char *)"-M",(const char *)"--no-monitor"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start MySQL Monitor Module",(const char *)"--mysql-monitor"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start PgSQL Monitor Module",(const char *)"--pgsql-monitor"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start MySQL Worker Threads",(const char *)"--mysql-workers"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start PgSQL Worker Threads",(const char *)"--pgsql-workers"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start MySQL Admin Module",(const char *)"--mysql-admin"); + opt->add((const char *)"",0,1,0,(const char *)"Do not start PgSQL Admin Module",(const char *)"--pgsql-admin"); opt->add((const char *)"",0,0,0,(const char *)"Run in foreground",(const char *)"-f",(const char *)"--foreground"); #ifdef SO_REUSEPORT opt->add((const char *)"",0,0,0,(const char *)"Use SO_REUSEPORT",(const char *)"-r",(const char *)"--reuseport"); @@ -490,6 +500,60 @@ void ProxySQL_GlobalVariables::process_opts_post() { global.pg_monitor=false; } + if (opt->isSet("--mysql-monitor")) { + string val {}; + opt->get("--mysql-monitor")->getString(val); + + if (val == "false" || val == "0") { + global.my_monitor = false; + } + } + + if (opt->isSet("--pgsql-monitor")) { + string val {}; + opt->get("--pgsql-monitor")->getString(val); + + if (val == "false" || val == "0") { + global.pg_monitor = false; + } + } + + if (opt->isSet("--mysql-workers")) { + string val {}; + opt->get("--mysql-workers")->getString(val); + + if (val == "false" || val == "0") { + global.mysql_workers = false; + } + } + + if (opt->isSet("--pgsql-workers")) { + string val {}; + opt->get("--pgsql-workers")->getString(val); + + if (val == "false" || val == "0") { + global.pgsql_workers = false; + } + } + + if (opt->isSet("--mysql-admin")) { + string val {}; + opt->get("--mysql-admin")->getString(val); + + if (val == "false" || val == "0") { + global.mysql_admin = false; + } + } + + if (opt->isSet("--pgsql-admin")) { + string val {}; + opt->get("--pgsql-admin")->getString(val); + + if (val == "false" || val == "0") { + global.pgsql_admin = false; + } + } + #ifdef SO_REUSEPORT { struct utsname unameData; diff --git a/src/main.cpp b/src/main.cpp index 52ee9ef4a6..9f89249bd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -674,10 +674,21 @@ void* unified_query_cache_purge_thread(void *arg) { return NULL; } -/*void* pgsql_shared_query_cache_funct(void* arg) { - GloPgQC->purgeHash_thread(NULL); - return NULL; -}*/ +template +void update_global_variable(const string& name, T& var) { + const Setting& root { GloVars.confFile->cfg.getRoot() }; + + if (root.exists(name)==true) { + T new_val {}; + bool rc { root.lookupValue(name, new_val) }; + + if (rc == true) { + var = new_val; + } else { + proxy_error("The config file is configured with an invalid '%s'\n", name.c_str()); + } + } +} void ProxySQL_Main_process_global_variables(int argc, const char **argv) { GloVars.errorlog = NULL; @@ -733,27 +744,17 @@ void ProxySQL_Main_process_global_variables(int argc, const char **argv) { } } } + // if cluster_sync_interfaces is true, interfaces variables are synced too - if (root.exists("cluster_sync_interfaces")==true) { - bool value_bool; - bool rc; - rc=root.lookupValue("cluster_sync_interfaces", value_bool); - if (rc==true) { - GloVars.cluster_sync_interfaces=value_bool; - } else { - proxy_error("The config file is configured with an invalid cluster_sync_interfaces\n"); - } - } - if (root.exists("set_thread_name")==true) { - bool value_bool; - bool rc; - rc=root.lookupValue("set_thread_name", value_bool); - if (rc==true) { - GloVars.set_thread_name=value_bool; - } else { - proxy_error("The config file is configured with an invalid set_thread_name\n"); - } - } + update_global_variable("cluster_sync_interfaces", GloVars.cluster_sync_interfaces); + update_global_variable("set_thread_name", GloVars.set_thread_name); + update_global_variable("mysql-workers", GloVars.global.mysql_workers); + update_global_variable("pgsql-workers", GloVars.global.pgsql_workers); + update_global_variable("mysql-admin", GloVars.global.mysql_admin); + update_global_variable("pgsql-admin", GloVars.global.pgsql_admin); + update_global_variable("mysql-monitor", GloVars.global.my_monitor); + update_global_variable("pgsql-monitor", GloVars.global.pg_monitor); + if (root.exists("pidfile")==true) { string pidfile_path; bool rc; @@ -888,6 +889,20 @@ void ProxySQL_Main_process_global_variables(int argc, const char **argv) { GloVars.confFile->ReadGlobals(); GloVars.process_opts_post(); + + // Coherence check on global variables status + if (!GloVars.global.mysql_admin && !GloVars.global.pgsql_admin) { + proxy_info("All Admin interfaces, MySQL and PostgreSQL, disabled by config\n"); + } + if (!GloVars.global.mysql_workers && !GloVars.global.pgsql_workers) { + proxy_info("All worker threads, MySQL and PostgreSQL, disabled by config\n"); + } + if (!GloVars.global.my_monitor && !GloVars.global.pg_monitor) { + proxy_info("All Monitoring, MySQL and PostgreSQL, disabled by config\n"); + } + if (!GloVars.global.pgsql_workers && !GloVars.global.pgsql_admin && !GloVars.global.pg_monitor) { + proxy_info("PostgreSQL support fully disabled by config\n"); + } } void ProxySQL_Main_init_main_modules() { @@ -943,6 +958,41 @@ void ProxySQL_Main_init_Admin_module(const bootstrap_info_t& bootstrap_info) { GloAdmin = new ProxySQL_Admin(); GloAdmin->init(bootstrap_info); GloAdmin->print_version(); + + // Synchronize monitor enabled variables with global settings + // + // The CLI arguments --mysql-monitor and --pgsql-monitor correctly set the internal + // global variables GloVars.global.my_monitor and GloVars.global.pg_monitor, which + // control whether monitor threads are started. However, these internal variables + // are not automatically synchronized with the admin interface variables + // mysql-monitor_enabled and pgsql-monitor_enabled that users can query via + // SELECT variable_value FROM global_variables WHERE variable_name='mysql-monitor_enabled'. + // + // Without this synchronization, the admin interface would incorrectly show + // mysql-monitor_enabled=true and pgsql-monitor_enabled=true even when the + // monitor modules are disabled via CLI arguments, breaking user expectations + // and automated testing that relies on these admin interface variables. + // + // This code ensures that the admin interface variables accurately reflect the + // actual monitor module state as controlled by CLI arguments. + { + char query[256]; + // Set mysql-monitor_enabled based on global.my_monitor + snprintf(query, sizeof(query), + "INSERT OR REPLACE INTO global_variables VALUES('mysql-monitor_enabled','%s')", + GloVars.global.my_monitor ? "true" : "false"); + GloAdmin->admindb->execute(query); + + // Set pgsql-monitor_enabled based on global.pg_monitor + snprintf(query, sizeof(query), + "INSERT OR REPLACE INTO global_variables VALUES('pgsql-monitor_enabled','%s')", + GloVars.global.pg_monitor ? "true" : "false"); + GloAdmin->admindb->execute(query); + + proxy_info("Monitor variables synchronized: mysql-monitor_enabled=%s, pgsql-monitor_enabled=%s\n", + GloVars.global.my_monitor ? "true" : "false", + GloVars.global.pg_monitor ? "true" : "false"); + } if (binary_sha1) { proxy_info("ProxySQL SHA1 checksum: %s\n", binary_sha1); } @@ -988,7 +1038,7 @@ void ProxySQL_Main_init_MySQL_Threads_Handler_module() { proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n"); } #endif // IDLE_THREADS - for (i=0; inum_threads; i++) { + for (i=0; i < GloMTH->num_threads && GloVars.global.mysql_workers; i++) { GloMTH->create_thread(i,mysql_worker_thread_func, false); #ifdef IDLE_THREADS if (GloVars.global.idle_threads) { @@ -1012,7 +1062,7 @@ void ProxySQL_Main_init_PgSQL_Threads_Handler_module() { proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n"); } #endif // IDLE_THREADS - for (i = 0; i < GloPTH->num_threads; i++) { + for (i = 0; i < GloPTH->num_threads && GloVars.global.pgsql_workers; i++) { GloPTH->create_thread(i, pgsql_worker_thread_func, false); #ifdef IDLE_THREADS if (GloVars.global.idle_threads) { diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index f5223ba2ba..3a8df8b597 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -7,6 +7,8 @@ "admin_various_commands3-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql91-g1","mysql91-gr-g1","mysql92-g1","mysql92-gr-g1","mysql93-g1","mysql93-gr-g1" ], "admin_various_commands-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql91-g1","mysql91-gr-g1","mysql92-g1","mysql92-gr-g1","mysql93-g1","mysql93-gr-g1" ], "basic-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql91-g1","mysql91-gr-g1","mysql92-g1","mysql92-gr-g1","mysql93-g1","mysql93-gr-g1" ], + "reg_test_4960_modules_startup-t" : [ "default-g1" ], + "reg_test_4960_monitor_modules-t" : [ "default-g1" ], "charset_unsigned_int-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "clickhouse_php_conn-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "envvars-t" : [ "default-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1","mysql84-g1","mysql84-gr-g1","mysql90-g1","mysql90-gr-g1","mysql91-g1","mysql91-gr-g1","mysql92-g1","mysql92-gr-g1","mysql93-g1","mysql93-gr-g1" ], diff --git a/test/tap/tests/reg_test_4960_modules_startup-t.cpp b/test/tap/tests/reg_test_4960_modules_startup-t.cpp new file mode 100644 index 0000000000..dbf3333663 --- /dev/null +++ b/test/tap/tests/reg_test_4960_modules_startup-t.cpp @@ -0,0 +1,718 @@ +/** + * @file reg_test_4960_modules_startup-t.cpp + * @brief TAP test for verifying module enable/disable functionality introduced in PR #4960. + * + * This test verifies that ProxySQL can start correctly with various combinations of + * MySQL/PostgreSQL worker, admin, and monitor modules enabled or disabled via both + * command line arguments and configuration file settings. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mysql.h" +#include "mysqld_error.h" + +#include "proxysql_utils.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::string; +using std::vector; + +struct TestCase { + string name; + vector cli_args; + string config_content; + int mysql_admin_port; + int pgsql_admin_port; + int mysql_worker_port; + int pgsql_worker_port; + bool should_start; + bool mysql_admin_expected; + bool pgsql_admin_expected; + bool mysql_worker_expected; + bool pgsql_worker_expected; +}; + +int launch_proxysql_instance(const TestCase& test_case, const CommandLine& cl, int& proxy_pid) { + const string test_datadir = string { cl.workdir } + "reg_test_4960_node_" + test_case.name; + const string test_config_file = test_datadir + "/proxysql.cfg"; + const string test_log_file = test_datadir + "/proxysql.log"; + + diag(" Creating test environment:"); + diag(" Datadir: %s", test_datadir.c_str()); + diag(" Config file: %s", test_config_file.c_str()); + + // Clean up existing datadir if it exists + string cleanup_cmd = "rm -rf " + test_datadir; + int cleanup_result = system(cleanup_cmd.c_str()); + (void)cleanup_result; // Suppress unused warning + + // Create test datadir + string mkdir_cmd = "mkdir -p " + test_datadir; + int mkdir_result = system(mkdir_cmd.c_str()); + (void)mkdir_result; // Suppress unused warning + + // Create config file + std::ofstream config_file(test_config_file); + config_file << test_case.config_content; + config_file.close(); + + diag(" Config file contents:"); + // Show config file contents (with proper indentation) + std::istringstream config_stream(test_case.config_content); + string config_line; + while (std::getline(config_stream, config_line)) { + diag(" %s", config_line.c_str()); + } + + // Launch ProxySQL using the same pattern as reg_test_3847_admin_lock-t.cpp + std::thread launch_proxy([&cl, &test_case, &test_config_file, &test_log_file, &proxy_pid] (int& err_code) -> void { + to_opts_t wexecvp_opts {}; + wexecvp_opts.poll_to_us = 100 * 1000; + wexecvp_opts.waitpid_delay_us = 500 * 1000; + wexecvp_opts.timeout_us = 20000 * 1000; // 20s timeout + wexecvp_opts.sigkill_to_us = 3000 * 1000; + + const string proxysql_path { string { getenv("WORKSPACE") } + "/src/proxysql" }; + vector proxy_args = { "-f", "-c", test_config_file.c_str() }; + + // Add test-specific CLI arguments + for (const auto& arg : test_case.cli_args) { + proxy_args.push_back(arg); + } + + // Build and display the full command for manual testing (with timeout) + string full_command = "timeout 30 " + proxysql_path; + for (const auto& arg : proxy_args) { + full_command += " " + string(arg); + } + diag(" Command to execute manually:"); + diag(" %s", full_command.c_str()); + + string s_stdout {}; + string s_stderr {}; + + diag(" Starting ProxySQL (with 30s timeout)..."); + int w_res = wexecvp(proxysql_path, proxy_args, wexecvp_opts, s_stdout, s_stderr); + if (w_res != EXIT_SUCCESS) { + diag("'wexecvp' failed with error: %d for test case: %s", w_res, test_case.name.c_str()); + diag("Command: %s", proxysql_path.c_str()); + for (size_t i = 0; i < proxy_args.size(); i++) { + diag(" arg[%zu]: %s", i, proxy_args[i]); + } + if (!s_stderr.empty()) { + diag("stderr: %s", s_stderr.c_str()); + } + } + + // Write process output to log file + try { + std::ofstream os_logfile { test_log_file, std::ios::out }; + os_logfile << s_stderr; + } catch (const std::exception& ex) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, ex.what()); + } + + err_code = w_res; + }, std::ref(proxy_pid)); + + launch_proxy.detach(); + + // Wait for startup + diag(" Waiting for ProxySQL to start (3 seconds)..."); + sleep(3); + diag(" ProxySQL startup wait completed"); + + return EXIT_SUCCESS; +} + +bool check_port_listening(int port, int timeout = 2) { + for (int i = 0; i < timeout; i++) { + string cmd = "nc -z 127.0.0.1 " + std::to_string(port) + " 2>/dev/null"; + int result = system(cmd.c_str()); + diag("DEBUG: nc -z 127.0.0.1 %d returned %d", port, result); + if (result == 0) { + diag("DEBUG: Port %d is listening", port); + return true; + } else if (result != 0 && i == 0) { + // Check if nc command exists (only on first attempt to avoid spam) + string check_cmd = "which nc >/dev/null 2>&1"; + if (system(check_cmd.c_str()) != 0) { + diag("ERROR: 'nc' (netcat) command not found. Please install netcat to run this test."); + diag("On Ubuntu/Debian: sudo apt-get install netcat-openbsd"); + diag("On CentOS/RHEL: sudo yum install nc"); + diag("On Fedora: sudo dnf install nmap-ncat"); + exit(EXIT_FAILURE); + } + diag("DEBUG: Port %d is NOT listening (result=%d)", port, result); + } + } + diag("DEBUG: Port %d timeout completed, returning false", port); + return false; +} + +int run_test_case(const TestCase& test_case, const CommandLine& cl) { + int proxy_pid = -1; + int result = EXIT_SUCCESS; + + diag("Running test case: %s", test_case.name.c_str()); + diag(" Expected MySQL admin: %s (port %d)", + test_case.mysql_admin_expected ? "YES" : "NO", test_case.mysql_admin_port); + diag(" Expected PgSQL admin: %s (port %d)", + test_case.pgsql_admin_expected ? "YES" : "NO", test_case.pgsql_admin_port); + diag(" Expected MySQL worker: %s (port %d)", + test_case.mysql_worker_expected ? "YES" : "NO", test_case.mysql_worker_port); + diag(" Expected PgSQL worker: %s (port %d)", + test_case.pgsql_worker_expected ? "YES" : "NO", test_case.pgsql_worker_port); + + // Display CLI arguments if any + if (!test_case.cli_args.empty()) { + diag(" CLI arguments:"); + for (size_t i = 0; i < test_case.cli_args.size(); i++) { + diag(" %s", test_case.cli_args[i]); + } + } + + // CLEANUP FIRST: Kill any existing ProxySQL processes that might be listening on our ports + diag(" Pre-test cleanup: killing any existing ProxySQL processes on test ports..."); + string cleanup_mysql_admin = "fuser -k " + std::to_string(test_case.mysql_admin_port) + "/tcp 2>/dev/null || true"; + string cleanup_pgsql_admin = "fuser -k " + std::to_string(test_case.pgsql_admin_port) + "/tcp 2>/dev/null || true"; + string cleanup_mysql_worker = "fuser -k " + std::to_string(test_case.mysql_worker_port) + "/tcp 2>/dev/null || true"; + string cleanup_pgsql_worker = "fuser -k " + std::to_string(test_case.pgsql_worker_port) + "/tcp 2>/dev/null || true"; + + int result1 = system(cleanup_mysql_admin.c_str()); + int result2 = system(cleanup_pgsql_admin.c_str()); + int result3 = system(cleanup_mysql_worker.c_str()); + int result4 = system(cleanup_pgsql_worker.c_str()); + (void)result1; (void)result2; (void)result3; (void)result4; // Suppress unused warnings + + // Also kill any remaining ProxySQL processes from previous test cases + string kill_all_cmd = "pkill -f \"proxysql.*reg_test_4960_node_\" 2>/dev/null || true"; + int kill_result = system(kill_all_cmd.c_str()); + (void)kill_result; // Suppress unused warning + sleep(2); // Give time for cleanup to complete + + diag(" Pre-test cleanup completed"); + + // Launch ProxySQL instance + if (launch_proxysql_instance(test_case, cl, proxy_pid) != EXIT_SUCCESS) { + diag("Failed to launch ProxySQL for test case: %s", test_case.name.c_str()); + return EXIT_FAILURE; + } + + diag(" Checking admin and worker interfaces..."); + + // Check if admin interfaces are listening as expected + if (test_case.mysql_admin_expected) { + diag(" Checking MySQL admin interface on port %d (should be listening)...", test_case.mysql_admin_port); + if (!check_port_listening(test_case.mysql_admin_port)) { + diag(" ❌ MySQL admin interface NOT listening on port %d for test: %s", + test_case.mysql_admin_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ MySQL admin interface IS listening on port %d", test_case.mysql_admin_port); + } + } else { + diag(" Checking MySQL admin interface on port %d (should NOT be listening)...", test_case.mysql_admin_port); + if (check_port_listening(test_case.mysql_admin_port, 1)) { + diag(" ❌ MySQL admin interface unexpectedly listening on port %d for test: %s", + test_case.mysql_admin_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ MySQL admin interface correctly NOT listening on port %d", test_case.mysql_admin_port); + } + } + + if (test_case.pgsql_admin_expected) { + diag(" Checking PgSQL admin interface on port %d (should be listening)...", test_case.pgsql_admin_port); + if (!check_port_listening(test_case.pgsql_admin_port)) { + diag(" ❌ PgSQL admin interface NOT listening on port %d for test: %s", + test_case.pgsql_admin_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ PgSQL admin interface IS listening on port %d", test_case.pgsql_admin_port); + } + } else { + diag(" Checking PgSQL admin interface on port %d (should NOT be listening)...", test_case.pgsql_admin_port); + if (check_port_listening(test_case.pgsql_admin_port, 1)) { + diag(" ❌ PgSQL admin interface unexpectedly listening on port %d for test: %s", + test_case.pgsql_admin_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ PgSQL admin interface correctly NOT listening on port %d", test_case.pgsql_admin_port); + } + } + + // Check if worker interfaces are listening as expected + if (test_case.mysql_worker_expected) { + diag(" Checking MySQL worker interface on port %d (should be listening)...", test_case.mysql_worker_port); + if (!check_port_listening(test_case.mysql_worker_port)) { + diag(" ❌ MySQL worker interface NOT listening on port %d for test: %s", + test_case.mysql_worker_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ MySQL worker interface IS listening on port %d", test_case.mysql_worker_port); + } + } else { + diag(" Checking MySQL worker interface on port %d (should NOT be listening)...", test_case.mysql_worker_port); + if (check_port_listening(test_case.mysql_worker_port, 1)) { + diag(" ❌ MySQL worker interface unexpectedly listening on port %d for test: %s", + test_case.mysql_worker_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ MySQL worker interface correctly NOT listening on port %d", test_case.mysql_worker_port); + } + } + + if (test_case.pgsql_worker_expected) { + diag(" Checking PgSQL worker interface on port %d (should be listening)...", test_case.pgsql_worker_port); + if (!check_port_listening(test_case.pgsql_worker_port)) { + diag(" ❌ PgSQL worker interface NOT listening on port %d for test: %s", + test_case.pgsql_worker_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ PgSQL worker interface IS listening on port %d", test_case.pgsql_worker_port); + } + } else { + diag(" Checking PgSQL worker interface on port %d (should NOT be listening)...", test_case.pgsql_worker_port); + if (check_port_listening(test_case.pgsql_worker_port, 1)) { + diag(" ❌ PgSQL worker interface unexpectedly listening on port %d for test: %s", + test_case.pgsql_worker_port, test_case.name.c_str()); + result = EXIT_FAILURE; + } else { + diag(" ✅ PgSQL worker interface correctly NOT listening on port %d", test_case.pgsql_worker_port); + } + } + + // Force kill any remaining ProxySQL processes to ensure cleanup + diag(" Force killing any remaining ProxySQL processes..."); + string kill_cmd = "pkill -f \"proxysql.*" + string { cl.workdir } + "reg_test_4960_node_" + test_case.name + "\" 2>/dev/null || true"; + int force_kill_result = system(kill_cmd.c_str()); + (void)force_kill_result; // Suppress unused warning + sleep(1); + + // Additional cleanup - kill by port if needed + if (proxy_pid > 0) { + diag(" Ensuring ProxySQL (PID: %d) is terminated...", proxy_pid); + kill(proxy_pid, SIGKILL); // Use SIGKILL to ensure termination + sleep(1); + int status; + waitpid(proxy_pid, &status, WNOHANG); // Non-blocking wait + } + + // Additional post-test cleanup for safety + int post_result1 = system(cleanup_mysql_admin.c_str()); + int post_result2 = system(cleanup_pgsql_admin.c_str()); + int post_result3 = system(cleanup_mysql_worker.c_str()); + int post_result4 = system(cleanup_pgsql_worker.c_str()); + (void)post_result1; (void)post_result2; (void)post_result3; (void)post_result4; // Suppress unused warnings + + diag(" Post-test cleanup completed"); + + return result; +} + +int main(int argc, char** argv) { + CommandLine cl; + + const char* WORKSPACE = getenv("WORKSPACE"); + + if (cl.getEnv() || WORKSPACE == nullptr) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + // Check for required system tools upfront + diag("Checking for required system tools..."); + int nc_check_result = system("which nc >/dev/null 2>&1"); + (void)nc_check_result; // Suppress unused result warning + if (nc_check_result != 0) { + diag("ERROR: 'nc' (netcat) command not found. Please install netcat to run this test."); + diag("On Ubuntu/Debian: sudo apt-get install netcat-openbsd"); + diag("On CentOS/RHEL: sudo yum install nc"); + diag("On Fedora: sudo dnf install nmap-ncat"); + plan(0); // Skip all tests + return exit_status(); + } + diag("Required tools found."); + + // Define test cases for all 16 combinations of 4 boolean variables: + // mysql-workers, pgsql-workers, mysql-admin, pgsql-admin + vector test_cases = { + // 0000: all disabled + { + "0000_all_disabled", + {"--mysql-workers", "false", "--pgsql-workers", "false", "--mysql-admin", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0000_all_disabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13750\"\n" + " pgsql_ifaces=\"127.0.0.1:13751\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13752\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13753\"\n" + "}\n", + 13750, 13751, 13752, 13753, true, false, false, false, false + }, + + // 0001: only pgsql-admin enabled + { + "0001_pgsql_admin_only", + {"--mysql-workers", "false", "--pgsql-workers", "false", "--mysql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0001_pgsql_admin_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13754\"\n" + " pgsql_ifaces=\"127.0.0.1:13755\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13756\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13757\"\n" + "}\n", + 13754, 13755, 13756, 13757, true, false, true, false, false + }, + + // 0010: only mysql-admin enabled + { + "0010_mysql_admin_only", + {"--mysql-workers", "false", "--pgsql-workers", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0010_mysql_admin_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13758\"\n" + " pgsql_ifaces=\"127.0.0.1:13759\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13760\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13761\"\n" + "}\n", + 13758, 13759, 13760, 13761, true, true, false, false, false + }, + + // 0011: mysql-admin + pgsql-admin enabled + { + "0011_admin_only", + {"--mysql-workers", "false", "--pgsql-workers", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0011_admin_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13762\"\n" + " pgsql_ifaces=\"127.0.0.1:13763\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13764\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13765\"\n" + "}\n", + 13762, 13763, 13764, 13765, true, true, true, false, false + }, + + // 0100: only pgsql-workers enabled + { + "0100_pgsql_workers_only", + {"--mysql-workers", "false", "--mysql-admin", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0100_pgsql_workers_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13766\"\n" + " pgsql_ifaces=\"127.0.0.1:13767\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13768\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13769\"\n" + "}\n", + 13766, 13767, 13768, 13769, true, false, false, false, true + }, + + // 0101: pgsql-workers + pgsql-admin enabled + { + "0101_pgsql_workers_admin", + {"--mysql-workers", "false", "--mysql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0101_pgsql_workers_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13770\"\n" + " pgsql_ifaces=\"127.0.0.1:13771\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13772\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13773\"\n" + "}\n", + 13770, 13771, 13772, 13773, true, false, true, false, true + }, + + // 0110: pgsql-workers + mysql-admin enabled + { + "0110_pgsql_workers_mysql_admin", + {"--mysql-workers", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0110_pgsql_workers_mysql_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13774\"\n" + " pgsql_ifaces=\"127.0.0.1:13775\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13776\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13777\"\n" + "}\n", + 13774, 13775, 13776, 13777, true, true, false, false, true + }, + + // 0111: pgsql-workers + mysql-admin + pgsql-admin enabled + { + "0111_pgsql_workers_all_admin", + {"--mysql-workers", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_0111_pgsql_workers_all_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13778\"\n" + " pgsql_ifaces=\"127.0.0.1:13779\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13780\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13781\"\n" + "}\n", + 13778, 13779, 13780, 13781, true, true, true, false, true + }, + + // 1000: only mysql-workers enabled + { + "1000_mysql_workers_only", + {"--pgsql-workers", "false", "--mysql-admin", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1000_mysql_workers_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13782\"\n" + " pgsql_ifaces=\"127.0.0.1:13783\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13784\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13785\"\n" + "}\n", + 13782, 13783, 13784, 13785, true, false, false, true, false + }, + + // 1001: mysql-workers + pgsql-admin enabled + { + "1001_mysql_workers_pgsql_admin", + {"--pgsql-workers", "false", "--mysql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1001_mysql_workers_pgsql_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13786\"\n" + " pgsql_ifaces=\"127.0.0.1:13787\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13788\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13789\"\n" + "}\n", + 13786, 13787, 13788, 13789, true, false, true, true, false + }, + + // 1010: mysql-workers + mysql-admin enabled + { + "1010_mysql_workers_admin", + {"--pgsql-workers", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1010_mysql_workers_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13790\"\n" + " pgsql_ifaces=\"127.0.0.1:13791\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13792\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13793\"\n" + "}\n", + 13790, 13791, 13792, 13793, true, true, false, true, false + }, + + // 1011: mysql-workers + mysql-admin + pgsql-admin enabled + { + "1011_mysql_workers_all_admin", + {"--pgsql-workers", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1011_mysql_workers_all_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13794\"\n" + " pgsql_ifaces=\"127.0.0.1:13795\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13796\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13797\"\n" + "}\n", + 13794, 13795, 13796, 13797, true, true, true, true, false + }, + + // 1100: both workers enabled, no admin + { + "1100_workers_only", + {"--mysql-admin", "false", "--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1100_workers_only\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13798\"\n" + " pgsql_ifaces=\"127.0.0.1:13799\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13800\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13801\"\n" + "}\n", + 13798, 13799, 13800, 13801, true, false, false, true, true + }, + + // 1101: both workers + pgsql-admin enabled + { + "1101_workers_mysql_pgsql_admin", + {"--mysql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1101_workers_mysql_pgsql_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13802\"\n" + " pgsql_ifaces=\"127.0.0.1:13803\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13804\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13805\"\n" + "}\n", + 13802, 13803, 13804, 13805, true, false, true, true, true + }, + + // 1110: both workers + mysql-admin enabled + { + "1110_workers_mysql_admin", + {"--pgsql-admin", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1110_workers_mysql_admin\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13806\"\n" + " pgsql_ifaces=\"127.0.0.1:13807\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13808\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13809\"\n" + "}\n", + 13806, 13807, 13808, 13809, true, true, false, true, true + }, + + // 1111: all enabled (default) + { + "1111_all_enabled", + {}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_node_1111_all_enabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:13810\"\n" + " pgsql_ifaces=\"127.0.0.1:13811\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13812\"\n" + "}\n\n" + "pgsql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:13813\"\n" + "}\n", + 13810, 13811, 13812, 13813, true, true, true, true, true + } + }; + + plan(test_cases.size()); + + // Run all test cases + for (const auto& test_case : test_cases) { + diag("============================================================"); + int result = run_test_case(test_case, cl); + ok(result == EXIT_SUCCESS, "Test case '%s' %s", test_case.name.c_str(), + result == EXIT_SUCCESS ? "passed" : "failed"); + } + diag("============================================================"); + + return exit_status(); +} \ No newline at end of file diff --git a/test/tap/tests/reg_test_4960_monitor_modules-t.cpp b/test/tap/tests/reg_test_4960_monitor_modules-t.cpp new file mode 100644 index 0000000000..381563fa72 --- /dev/null +++ b/test/tap/tests/reg_test_4960_monitor_modules-t.cpp @@ -0,0 +1,322 @@ +/** + * @file reg_test_4960_monitor_modules-t.cpp + * @brief TAP test for verifying monitor module enable/disable functionality from PR #4960. + * + * This test verifies that MySQL and PostgreSQL monitor modules can be enabled/disabled + * via CLI arguments and that their status is correctly reflected in the global_variables table. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mysql.h" +#include "mysqld_error.h" + +#include "proxysql_utils.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::string; +using std::vector; + +struct MonitorTestCase { + string name; + vector cli_args; + string config_content; + int mysql_admin_port; + int mysql_worker_port; + bool mysql_monitor_expected; + bool pgsql_monitor_expected; +}; + +int connect_to_proxysql_admin(int port, MYSQL*& mysql) { + mysql = mysql_init(NULL); + if (!mysql) { + diag("MySQL initialization failed"); + return -1; + } + + // Set connection timeout + unsigned int timeout = 5; + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout); + mysql_options(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout); + + // Connect to ProxySQL admin + if (!mysql_real_connect(mysql, "127.0.0.1", "admin", "admin", NULL, port, NULL, 0)) { + diag("Failed to connect to ProxySQL admin on port %d: %s", port, mysql_error(mysql)); + mysql_close(mysql); + mysql = NULL; + return -1; + } + + return 0; +} + +int check_monitor_status(MYSQL* mysql, const string& monitor_name, bool expected_enabled) { + string query = "SELECT variable_value FROM global_variables WHERE variable_name = '" + monitor_name + "'"; + + int query_result = mysql_query(mysql, query.c_str()); + if (query_result != 0) { + diag("Failed to execute query for %s: %s", monitor_name.c_str(), mysql_error(mysql)); + return -1; + } + + MYSQL_RES* result = mysql_store_result(mysql); + if (!result) { + diag("Failed to store result for %s: %s", monitor_name.c_str(), mysql_error(mysql)); + return -1; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + diag("No result found for %s", monitor_name.c_str()); + mysql_free_result(result); + return -1; + } + + string variable_value = row[0] ? row[0] : ""; + bool actual_enabled = (variable_value == "true"); + + mysql_free_result(result); + + if (actual_enabled != expected_enabled) { + diag("Monitor status mismatch for %s: expected %s, got %s", + monitor_name.c_str(), + expected_enabled ? "true" : "false", + actual_enabled ? "true" : "false"); + return 1; + } + + return 0; +} + +int launch_proxysql_instance(const MonitorTestCase& test_case, const CommandLine& cl) { + const string test_datadir = string { cl.workdir } + "reg_test_4960_monitor_" + test_case.name; + const string test_config_file = test_datadir + "/proxysql.cfg"; + + // Clean up existing datadir if it exists + string cleanup_cmd = "rm -rf " + test_datadir; + int cleanup_result = system(cleanup_cmd.c_str()); + (void)cleanup_result; + + // Create test datadir + string mkdir_cmd = "mkdir -p " + test_datadir; + int mkdir_result = system(mkdir_cmd.c_str()); + (void)mkdir_result; + + // Create config file + std::ofstream config_file(test_config_file); + config_file << test_case.config_content; + config_file.close(); + + // Build command to start ProxySQL + const string proxysql_path { string { getenv("WORKSPACE") } + "/src/proxysql" }; + string cmd = proxysql_path + " -f -c " + test_config_file; + + // Add CLI arguments + for (const auto& arg : test_case.cli_args) { + cmd += " " + string(arg); + } + + // Create log file path + const string log_file = test_datadir + "/proxysql.log"; + + // Start ProxySQL in background with output redirected to log file + diag(" Starting ProxySQL with output redirected to %s", log_file.c_str()); + string full_cmd = cmd + " > " + log_file + " 2>&1 &"; + int start_result = system(full_cmd.c_str()); + (void)start_result; + + // Wait a bit for startup + sleep(5); + + return EXIT_SUCCESS; +} + +int run_monitor_test_case(const MonitorTestCase& test_case, const CommandLine& cl) { + int result = EXIT_SUCCESS; + + diag("Running monitor test case: %s", test_case.name.c_str()); + diag(" Expected MySQL monitor: %s", test_case.mysql_monitor_expected ? "YES" : "NO"); + diag(" Expected PgSQL monitor: %s", test_case.pgsql_monitor_expected ? "YES" : "NO"); + + // Display CLI arguments if any + if (!test_case.cli_args.empty()) { + diag(" CLI arguments:"); + for (size_t i = 0; i < test_case.cli_args.size(); i++) { + diag(" %s", test_case.cli_args[i]); + } + } + + // Launch ProxySQL instance + if (launch_proxysql_instance(test_case, cl) != EXIT_SUCCESS) { + diag("Failed to launch ProxySQL for test case: %s", test_case.name.c_str()); + return EXIT_FAILURE; + } + + // Wait for ProxySQL to be ready using the standard approach + diag(" Waiting for ProxySQL admin interface to be ready..."); + conn_opts_t conn_opts {}; + conn_opts.host = "127.0.0.1"; + conn_opts.port = test_case.mysql_admin_port; + conn_opts.user = "admin"; + conn_opts.pass = "admin"; + + MYSQL* mysql = wait_for_proxysql(conn_opts, 15); + if (mysql == nullptr) { + diag(" ❌ Failed to connect to ProxySQL admin interface after 15 seconds"); + result = EXIT_FAILURE; + } else { + diag(" ✅ Connected to admin interface"); + + // Check MySQL monitor status + diag(" Checking MySQL monitor status..."); + int mysql_result = check_monitor_status(mysql, "mysql-monitor_enabled", test_case.mysql_monitor_expected); + if (mysql_result == 0) { + diag(" ✅ MySQL monitor status correct"); + } else if (mysql_result == 1) { + diag(" ❌ MySQL monitor status incorrect"); + result = EXIT_FAILURE; + } else { + diag(" ❌ Error checking MySQL monitor status"); + result = EXIT_FAILURE; + } + + // Check PgSQL monitor status + diag(" Checking PgSQL monitor status..."); + int pgsql_result = check_monitor_status(mysql, "pgsql-monitor_enabled", test_case.pgsql_monitor_expected); + if (pgsql_result == 0) { + diag(" ✅ PgSQL monitor status correct"); + } else if (pgsql_result == 1) { + diag(" ❌ PgSQL monitor status incorrect"); + result = EXIT_FAILURE; + } else { + diag(" ❌ Error checking PgSQL monitor status"); + result = EXIT_FAILURE; + } + + mysql_close(mysql); + } + + // Force cleanup + string kill_cmd = "pkill -f \"proxysql.*" + string { cl.workdir } + "reg_test_4960_monitor_" + test_case.name + "\" 2>/dev/null || true"; + int kill_result = system(kill_cmd.c_str()); + (void)kill_result; + + // Cleanup ports + string cleanup_admin = "fuser -k " + std::to_string(test_case.mysql_admin_port) + "/tcp 2>/dev/null || true"; + string cleanup_worker = "fuser -k " + std::to_string(test_case.mysql_worker_port) + "/tcp 2>/dev/null || true"; + int cleanup_admin_result = system(cleanup_admin.c_str()); + int cleanup_worker_result = system(cleanup_worker.c_str()); + (void)cleanup_admin_result; + (void)cleanup_worker_result; + + diag(" Monitor test completed"); + + return result; +} + +int main(int argc, char** argv) { + CommandLine cl; + + const char* WORKSPACE = getenv("WORKSPACE"); + + if (cl.getEnv() || WORKSPACE == nullptr) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + // Define monitor test cases - 4 combinations of monitor enable/disable + vector test_cases = { + // Test 1: Both monitors enabled (default) + { + "both_enabled", + {}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_monitor_both_enabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:14050\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:14051\"\n" + "}\n", + 14050, 14051, true, true + }, + + // Test 2: MySQL monitor disabled, PgSQL monitor enabled + { + "mysql_disabled", + {"--mysql-monitor", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_monitor_mysql_disabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:14052\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:14053\"\n" + "}\n", + 14052, 14053, false, true + }, + + // Test 3: MySQL monitor enabled, PgSQL monitor disabled + { + "pgsql_disabled", + {"--pgsql-monitor", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_monitor_pgsql_disabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:14054\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:14055\"\n" + "}\n", + 14054, 14055, true, false + }, + + // Test 4: Both monitors disabled + { + "both_disabled", + {"--mysql-monitor", "false", "--pgsql-monitor", "false"}, + string { "datadir=\"" } + cl.workdir + "reg_test_4960_monitor_both_disabled\"\n\n" + "admin_variables=\n" + "{\n" + " admin_credentials=\"admin:admin\"\n" + " mysql_ifaces=\"127.0.0.1:14056\"\n" + "}\n\n" + "mysql_variables=\n" + "{\n" + " interfaces=\"127.0.0.1:14057\"\n" + "}\n", + 14056, 14057, false, false + } + }; + + plan(test_cases.size()); + + // Run all monitor test cases + for (const auto& test_case : test_cases) { + diag("============================================================"); + int result = run_monitor_test_case(test_case, cl); + ok(result == EXIT_SUCCESS, "Monitor test case '%s' %s", test_case.name.c_str(), + result == EXIT_SUCCESS ? "passed" : "failed"); + } + diag("============================================================"); + + return exit_status(); +} \ No newline at end of file