diff --git a/.github/workflows/CI-repltests.yml b/.github/workflows/CI-repltests.yml index f2beac23a..3450d2f50 100644 --- a/.github/workflows/CI-repltests.yml +++ b/.github/workflows/CI-repltests.yml @@ -2,10 +2,13 @@ name: CI-repltests run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}' on: + # Auto-triggering disabled: jenkins-build-scripts repo access currently + # returns HTTP 403 from this workflow. Re-enable by restoring the + # workflow_run trigger once jenkins-build-scripts access is fixed. workflow_dispatch: - workflow_run: - workflows: [ CI-trigger ] - types: [ completed ] + # workflow_run: + # workflows: [ CI-trigger ] + # types: [ completed ] concurrency: group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} diff --git a/.github/workflows/CI-shuntest.yml b/.github/workflows/CI-shuntest.yml index 59b59b659..8347d2ab1 100644 --- a/.github/workflows/CI-shuntest.yml +++ b/.github/workflows/CI-shuntest.yml @@ -2,10 +2,13 @@ name: CI-shuntest run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}' on: + # Auto-triggering disabled: jenkins-build-scripts repo access currently + # returns HTTP 403 from this workflow. Re-enable by restoring the + # workflow_run trigger once jenkins-build-scripts access is fixed. workflow_dispatch: - workflow_run: - workflows: [ CI-trigger ] - types: [ completed ] + # workflow_run: + # workflows: [ CI-trigger ] + # types: [ completed ] concurrency: group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} diff --git a/.github/workflows/CI-taptests-groups.yml b/.github/workflows/CI-taptests-groups.yml index 01f8ea76f..d5ab4aba9 100644 --- a/.github/workflows/CI-taptests-groups.yml +++ b/.github/workflows/CI-taptests-groups.yml @@ -2,10 +2,14 @@ name: CI-taptests-groups run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}' on: + # Auto-triggering disabled: jenkins-build-scripts repo access currently + # returns HTTP 403 from this workflow (same root cause as + # CI-repltests / CI-shuntest). Re-enable by restoring the workflow_run + # trigger once jenkins-build-scripts access is fixed. workflow_dispatch: - workflow_run: - workflows: [ CI-trigger ] - types: [ completed ] + # workflow_run: + # workflows: [ CI-trigger ] + # types: [ completed ] concurrency: group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} diff --git a/Makefile b/Makefile index cbf4b281d..a8296645d 100644 --- a/Makefile +++ b/Makefile @@ -189,7 +189,7 @@ endif ### main targets -.DEFAULT: default +.DEFAULT_GOAL := default .PHONY: default default: build_src diff --git a/deps/sqlite3/sqlite3_pass_exts.patch b/deps/sqlite3/sqlite3_pass_exts.patch index 83d940212..89b613119 100644 --- a/deps/sqlite3/sqlite3_pass_exts.patch +++ b/deps/sqlite3/sqlite3_pass_exts.patch @@ -1,6 +1,6 @@ --- sqlite3.c 2024-03-22 19:22:47.046093173 +0100 +++ sqlite3-pass-exts.c 2024-03-22 19:24:09.557303716 +0100 -@@ -26313,6 +26313,183 @@ +@@ -26275,6 +26275,207 @@ sqlite3ResultStrAccum(context, &sRes); } @@ -27,30 +27,35 @@ +int check_args_types(int argc, sqlite3_value** argv) { + int inv_type = sqlite3_value_type(argv[0]) != SQLITE_TEXT; + -+ if (inv_type == 0 && argc == 2) { -+ return ++ if (inv_type == 0 && argc >= 2) { ++ inv_type = + sqlite3_value_type(argv[1]) != SQLITE_TEXT && + sqlite3_value_type(argv[1]) != SQLITE_BLOB; -+ } else { -+ return inv_type; + } ++ ++ if (inv_type == 0 && argc >= 3) { ++ inv_type = sqlite3_value_type(argv[2]) != SQLITE_INTEGER; ++ } ++ ++ return inv_type; +} + +int check_args_lengths(int argc, sqlite3_value** argv) { -+ int inv_size = 1; -+ + int pass_size = sqlite3_value_bytes(argv[0]); -+ if (pass_size > 0) { -+ inv_size = 0; ++ if (pass_size <= 0) { ++ return 1; + } + -+ if (inv_size == 0 && argc == 2) { ++ if (argc >= 2) { + int salt_size = sqlite3_value_bytes(argv[1]); -+ -+ return salt_size <= 0 || salt_size > DEF_SALT_SIZE; -+ } else { -+ return inv_size; ++ if (salt_size <= 0 || salt_size > DEF_SALT_SIZE) { ++ return 1; ++ } + } ++ ++ /* argc == 3: rounds is an integer; range is checked in caching_sha2_passwordFunc(). */ ++ ++ return 0; +} + +/** @@ -103,15 +108,19 @@ +/** + * @brief SQLite3 extension function for hash generation. + * @details Computes a hash equivalent to the one generated by MySQL for 'caching_sha2_password'. ++ * Output format matches MySQL's storage: '$A$$' where is the ++ * 3-char zero-padded uppercase hex of (rounds/1000) — see MySQL ++ * sql/auth/sha2_password.cc::Caching_sha2_password::digest_round_separator(). + * @param context SQLite3 context used for returning computation result. -+ * @param argc Number of arguments; either 1 or 2. One for random salt, two providing salt. -+ * @param argv Argument list; expected to hold either 1 or 2 arguments: -+ * 1. Password to be hashed; with len > 0 and of type 'SQLITE_TEXT'. -+ * 1. Optional salt; with (len > 0 && len <= 20) and of type ('SQLITE_TEXT' || 'SQLITE_BLOB'). If no salt is -+ * provided a randomly generated salt with length 20 will be used. ++ * @param argc Number of arguments; 1, 2, or 3. ++ * @param argv Argument list: ++ * 1. Password to be hashed; len > 0, type 'SQLITE_TEXT'. ++ * 2. Optional salt; len in (0,20], type 'SQLITE_TEXT' or 'SQLITE_BLOB'. Random if omitted. ++ * 3. Optional rounds; integer in [5000,4095000] in steps of 1000 (matches MySQL's ++ * caching_sha2_password_digest_rounds). Defaults to 5000 when omitted. + */ +static void caching_sha2_passwordFunc(sqlite3_context* context, int argc, sqlite3_value** argv) { -+ if (argc < 1 || argc > 2) { ++ if (argc < 1 || argc > 3) { + sqlite3_result_text(context, "Invalid number of arguments", -1, SQLITE_TRANSIENT); + return; + } else { @@ -125,11 +134,24 @@ + } + } + ++ /* Resolve rounds: default 5000 (MySQL 8.0 historical default), overridable via ++ argv[2]. Range matches MySQL's caching_sha2_password_digest_rounds bounds. */ ++ long rounds = 5000; ++ if (argc == 3) { ++ rounds = (long)sqlite3_value_int64(argv[2]); ++ if (rounds < 5000 || rounds > 4095000 || (rounds % 1000) != 0) { ++ sqlite3_result_text(context, ++ "Invalid rounds: expected multiple of 1000 in [5000,4095000]", ++ -1, SQLITE_TRANSIENT); ++ return; ++ } ++ } ++ + unsigned int salt_size = DEF_SALT_SIZE; + const char* cpass = (const char*)sqlite3_value_text(argv[0]); + unsigned char salt[DEF_SALT_SIZE + 1] = { 0 }; + -+ if (argc == 2) { ++ if (argc >= 2) { + salt_size = sqlite3_value_bytes(argv[1]); + const void* b_salt = sqlite3_value_blob(argv[1]); + @@ -163,17 +185,19 @@ + } + } + -+ #define BASE_SHA2_SALT "$5$rounds=5000$" -+ #define BASE_SHA2_HASH "$A$005$" ++ /* Build the sha256_crypt salt-prefix "$5$rounds=$" and the MySQL hash prefix ++ "$A$$" dynamically from the resolved rounds value. Buffers fit comfortably: ++ "$5$rounds=4095000$" is 18 chars + 20 salt + NUL = 39 (<100). */ ++ char sha2_salt[100] = { 0 }; ++ char sha2_hash[100] = { 0 }; ++ int salt_prefix_len = snprintf(sha2_salt, sizeof(sha2_salt), "$5$rounds=%ld$", rounds); ++ snprintf(sha2_hash, sizeof(sha2_hash), "$A$%03X$", (unsigned int)(rounds / 1000)); + + char sha2_buf[100] = { 0 }; -+ char sha2_salt[100] = { BASE_SHA2_SALT }; -+ + strcat(sha2_salt, (const char*)salt); + sha256_crypt_r(cpass, sha2_salt, sha2_buf, sizeof(sha2_buf)); + -+ char sha2_hash[100] = { BASE_SHA2_HASH }; -+ const char* sha256 = sha2_buf + salt_size + strlen(BASE_SHA2_SALT) + 1; ++ const char* sha256 = sha2_buf + salt_size + salt_prefix_len + 1; + + strcat(sha2_hash, (const char*)salt); + strcat(sha2_hash, sha256); @@ -184,13 +208,14 @@ /* ** current_time() ** -@@ -133269,6 +133269,9 @@ +@@ -133230,6 +133431,10 @@ FUNCTION(substr, 3, 0, 0, substrFunc ), FUNCTION(substring, 2, 0, 0, substrFunc ), FUNCTION(substring, 3, 0, 0, substrFunc ), + FUNCTION(mysql_native_password, 1, 0, 0, mysql_native_passwordFunc ), + FUNCTION(caching_sha2_password, 1, 0, 0, caching_sha2_passwordFunc ), + FUNCTION(caching_sha2_password, 2, 0, 0, caching_sha2_passwordFunc ), ++ FUNCTION(caching_sha2_password, 3, 0, 0, caching_sha2_passwordFunc ), WAGGREGATE(sum, 1,0,0, sumStep, sumFinalize, sumFinalize, sumInverse, 0), WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0), WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0), diff --git a/lib/MySQL_Monitor.cpp b/lib/MySQL_Monitor.cpp index 3a363f11c..416fa7770 100644 --- a/lib/MySQL_Monitor.cpp +++ b/lib/MySQL_Monitor.cpp @@ -4105,6 +4105,13 @@ void* monitor_GR_thread_HG(void *arg) { uint64_t next_check_time = 0; uint64_t MAX_CHECK_DELAY_US = 500000; + // On first iteration after thread (re)start, ignore the cached ping state + // in mysql_server_ping_log — it may reflect stale failures from before the + // monitor was reconfigured (e.g. a previous test left these hostnames + // marked unpingable). Probing all configured hosts forces a fresh ping_log + // entry, so subsequent iterations see real state instead of skipping + // healthcheck_interval seconds while the writer HG stays empty. + bool first_iteration = true; while (GloMyMon->shutdown == false && mysql_thread___monitor_enabled == true) { if (!GloMTH) { break; } // quick exit during shutdown/restart @@ -4145,8 +4152,16 @@ void* monitor_GR_thread_HG(void *arg) { continue; } - // Get the current 'pingable' status for the servers. - const vector& resp_srvs { find_resp_srvs(hosts_defs) }; + // Get the current 'pingable' status for the servers. See first_iteration + // note above: skip the cache filter on the very first cycle so we do not + // inherit stale ping failures from before this thread was started. + vector resp_srvs; + if (first_iteration) { + resp_srvs = hosts_defs; + first_iteration = false; + } else { + resp_srvs = find_resp_srvs(hosts_defs); + } if (resp_srvs.empty()) { proxy_error("No node is pingable for Group Replication cluster with writer HG %u\n", wr_hg); next_check_time = curtime + mysql_thread___monitor_groupreplication_healthcheck_interval * 1000; diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index b189c9ee1..8f3108400 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -2194,7 +2194,12 @@ bool MySQL_Protocol::PPHR_verify_sha2( } else if (passformat == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { assert(strlen(vars1.password) == 70); string sp = string(vars1.password); - long rounds = stol(sp.substr(3,3)); + // MySQL stores rounds as 3-char zero-padded uppercase hex of (rounds/1000). + // See sql/auth/sha2_password.cc::Caching_sha2_password::digest_round_separator(): + // sprintf(rounds_str, "%03X", m_stored_digest_rounds) + // Parsing as base-10 silently truncates at the first hex digit (A-F), + // breaking auth for any backend with caching_sha2_password_digest_rounds >= 10000. + long rounds = stol(sp.substr(3,3), nullptr, 16); string salt = sp.substr(7,20); string sha256hash = sp.substr(27,43); char buf[100]; @@ -2248,7 +2253,9 @@ void MySQL_Protocol::PPHR_sha2full( } else if (passformat == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { assert(strlen(vars1.password) == 70); string sp = string(vars1.password); - long rounds = stol(sp.substr(3,3)); + // MySQL stores rounds as 3-char zero-padded uppercase hex of (rounds/1000) — see + // PPHR_verify_sha2() above for the upstream format reference. Must parse base-16. + long rounds = stol(sp.substr(3,3), nullptr, 16); string salt = sp.substr(7,20); string sha256hash = sp.substr(27,43); //char * sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen); diff --git a/scripts/lint/generate-compile-commands.sh b/scripts/lint/generate-compile-commands.sh index 77b5b4c70..d20273790 100755 --- a/scripts/lint/generate-compile-commands.sh +++ b/scripts/lint/generate-compile-commands.sh @@ -2,8 +2,15 @@ set -euo pipefail # Generate compile_commands.json by wrapping make with Bear. # Usage: ./generate-compile-commands.sh +# +# Note: the default build command explicitly targets `build_src` rather than +# relying on GNU make's default goal. Invoking `bear -- make` with no target +# would re-enter whatever the default goal is — which, if it ever happens to +# be `lint-generate-cdb` (the target that calls this script), produces an +# exponential fork bomb under `-j$(nproc)` because every recursive invocation +# also wraps its own recursive invocation in Bear's exec intercept. -BUILD_CMD=${1:-"make -j$(nproc)"} +BUILD_CMD=${1:-"make build_src -j$(nproc)"} if ! command -v bear >/dev/null 2>&1; then echo "bear is required. Install it (e.g. apt install bear)" >&2 diff --git a/test/infra/infra-dbdeployer-mariadb10/docker/Dockerfile b/test/infra/infra-dbdeployer-mariadb10/docker/Dockerfile index 6ab0fa2d2..9a237e693 100644 --- a/test/infra/infra-dbdeployer-mariadb10/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mariadb10/docker/Dockerfile @@ -20,9 +20,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MariaDB 10.11.9 tarball via dbdeployer +# Download and unpack MariaDB 10.11.9 tarball via dbdeployer. +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug if present, +# and strip all ELF binaries/shared libs. binutils is installed temporarily and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mariadb-10.11.9-linux-systemd-x86_64.tar.gz + && dbdeployer downloads get-unpack mariadb-10.11.9-linux-systemd-x86_64.tar.gz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql57-binlog/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql57-binlog/docker/Dockerfile index af62f08af..7b34812a5 100644 --- a/test/infra/infra-dbdeployer-mysql57-binlog/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql57-binlog/docker/Dockerfile @@ -23,11 +23,21 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 5.7 tarball (pre-baked so no download at runtime) +# Download and unpack MySQL 5.7 tarball (pre-baked so no download at runtime). +# Cleanup in the same layer: drop mysqld-debug if present, and strip all ELF binaries/shared +# libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/downloads /root/opt/mysql \ && curl -fsSL -o /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz "${MYSQL_TARBALL_URL}" \ && dbdeployer unpack /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz \ - && rm -f /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz + && rm -f /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && rm -f /root/opt/mysql/*/lib/libmysqld.a /root/opt/mysql/*/lib/libmysqld-debug.a \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) RUN dbdeployer defaults update reserved-ports '0' diff --git a/test/infra/infra-dbdeployer-mysql57/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql57/docker/Dockerfile index 8bd92e8f0..3f6f4e06c 100644 --- a/test/infra/infra-dbdeployer-mysql57/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql57/docker/Dockerfile @@ -20,11 +20,21 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 5.7 tarball (pre-baked so no download at runtime) +# Download and unpack MySQL 5.7 tarball (pre-baked so no download at runtime). +# Cleanup in the same layer: drop mysqld-debug if present, and strip all ELF binaries/shared +# libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/downloads /root/opt/mysql \ && curl -fsSL -o /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz "${MYSQL_TARBALL_URL}" \ && dbdeployer unpack /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz \ - && rm -f /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz + && rm -f /root/downloads/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && rm -f /root/opt/mysql/*/lib/libmysqld.a /root/opt/mysql/*/lib/libmysqld-debug.a \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql84-binlog/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql84-binlog/docker/Dockerfile index 1d3e00c5d..1efeb41ac 100644 --- a/test/infra/infra-dbdeployer-mysql84-binlog/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql84-binlog/docker/Dockerfile @@ -26,9 +26,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) RUN dbdeployer defaults update reserved-ports '0' diff --git a/test/infra/infra-dbdeployer-mysql84-gr/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql84-gr/docker/Dockerfile index 564341808..24efc693e 100644 --- a/test/infra/infra-dbdeployer-mysql84-gr/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql84-gr/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql84/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql84/docker/Dockerfile index 564341808..24efc693e 100644 --- a/test/infra/infra-dbdeployer-mysql84/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql84/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 8.4 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-8.4.8-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql90-binlog/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql90-binlog/docker/Dockerfile index 65ec2daa8..622b6f357 100644 --- a/test/infra/infra-dbdeployer-mysql90-binlog/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql90-binlog/docker/Dockerfile @@ -26,9 +26,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) RUN dbdeployer defaults update reserved-ports '0' diff --git a/test/infra/infra-dbdeployer-mysql90-gr/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql90-gr/docker/Dockerfile index b5a1f6d24..791079935 100644 --- a/test/infra/infra-dbdeployer-mysql90-gr/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql90-gr/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql90/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql90/docker/Dockerfile index b5a1f6d24..791079935 100644 --- a/test/infra/infra-dbdeployer-mysql90/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql90/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.0 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.0.1-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql93-gr/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql93-gr/docker/Dockerfile index e9e24ddd8..415a0f136 100644 --- a/test/infra/infra-dbdeployer-mysql93-gr/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql93-gr/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.3 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.3 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.3.0-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.3.0-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql93/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql93/docker/Dockerfile index e9e24ddd8..415a0f136 100644 --- a/test/infra/infra-dbdeployer-mysql93/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql93/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.3 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.3 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.3.0-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.3.0-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql95-binlog/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql95-binlog/docker/Dockerfile index 764527453..0558c5aa3 100644 --- a/test/infra/infra-dbdeployer-mysql95-binlog/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql95-binlog/docker/Dockerfile @@ -26,9 +26,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) RUN dbdeployer defaults update reserved-ports '0' diff --git a/test/infra/infra-dbdeployer-mysql95-gr/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql95-gr/docker/Dockerfile index 282901cc4..e7aac0439 100644 --- a/test/infra/infra-dbdeployer-mysql95-gr/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql95-gr/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/infra/infra-dbdeployer-mysql95/docker/Dockerfile b/test/infra/infra-dbdeployer-mysql95/docker/Dockerfile index 282901cc4..e7aac0439 100644 --- a/test/infra/infra-dbdeployer-mysql95/docker/Dockerfile +++ b/test/infra/infra-dbdeployer-mysql95/docker/Dockerfile @@ -19,9 +19,19 @@ RUN curl -fsSL "https://github.com/ProxySQL/dbdeployer/releases/download/v${DBDE | tar -xz -C /usr/local/bin/ \ && chmod +x /usr/local/bin/dbdeployer -# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime) +# Download and unpack MySQL 9.5 tarball using dbdeployer (pre-baked so no download at runtime). +# Cleanup in the same layer: remove the downloaded tarball, drop mysqld-debug, and strip +# all ELF binaries/shared libs. binutils is installed temporarily for strip and purged after. RUN mkdir -p /root/opt/mysql \ - && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz + && dbdeployer downloads get-unpack mysql-9.5.0-linux-glibc2.17-x86_64.tar.xz \ + && rm -f /*.tar.xz /*.tar.gz \ + && rm -f /root/opt/mysql/*/bin/mysqld-debug \ + && apt-get update && apt-get install -y --no-install-recommends binutils \ + && find /root/opt/mysql -type f \( -path '*/bin/*' -o -name '*.so' -o -name '*.so.*' \) \ + -exec sh -c 'strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \; \ + && apt-get purge -y binutils \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* # Clear reserved ports so dbdeployer allows base-port 3305 (nodes get 3306,3307,3308) # dbdeployer reserves 1186,3306,5432,33060,33062 by default; replace with harmless value diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index 684a8db50..f9187524e 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -206,9 +206,9 @@ "reg_test_3765_ssl_pollout-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql84-g4","mysql90-g2","mysql90-g4","mysql95-g2","mysql95-g4" ], "reg_test_3838-restapi_eintr-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], "reg_test_3847_admin_lock-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], - "reg_test_3992_fast_forward_malformed_packet-mysqlsh-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], - "reg_test_3992_fast_forward_malformed_packet-pymysql-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], - "reg_test_3992_fast_forward_malformed_packet-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], + "reg_test_3992_fast_forward_malformed_packet-mysqlsh-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], + "reg_test_3992_fast_forward_malformed_packet-pymysql-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], + "reg_test_3992_fast_forward_malformed_packet-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "reg_test_4001-restapi_scripts_num_fds-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], "reg_test_4055_restapi-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], "reg_test_4072-show-warnings-t" : [ "legacy-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2","mysql84-g2","mysql90-g2","mysql95-g2" ], diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index 7068edb0d..1f73eba4c 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -1693,6 +1693,36 @@ pair> get_matching_lines( } +bool wait_for_log_match( + fstream& f_stream, const string& s_regex, uint32_t timeout_ms, uint32_t poll_interval_ms +) { + // Guard against a zero interval, which would spin. + if (poll_interval_ms == 0) { poll_interval_ms = 10; } + + const useconds_t poll_us = poll_interval_ms * 1000; + uint32_t elapsed_ms = 0; + + while (true) { + // Clear both eofbit and failbit so the subsequent getline() in + // get_matching_lines() can read any bytes appended since the last scan. + // Without this, once the stream hits EOF on the first iteration, every + // retry short-circuits and returns no matches. + f_stream.clear(f_stream.rdstate() & ~std::ios_base::eofbit & ~std::ios_base::failbit); + + const auto& [_, matches] = get_matching_lines(f_stream, s_regex); + if (!matches.empty()) { + return true; + } + if (elapsed_ms >= timeout_ms) { + return false; + } + + usleep(poll_us); + elapsed_ms += poll_interval_ms; + } +} + + std::pair> get_matching_lines_from_filename( const std::string& filename, const std::string& s_regex, bool get_matches, size_t max_lines ) { diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 5c8382f92..2bbc68ed2 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -790,6 +790,38 @@ std::pair> get_matching_lines( ); +/** + * @brief Poll a log stream for a regex match, retrying until a match appears or the + * timeout elapses. Handles the EOF sticky state that a single-shot + * `get_matching_lines()` leaves behind after exhausting the stream. + * + * Use this in place of single-shot `get_matching_lines()` calls that check whether + * a log line was emitted as a consequence of an earlier action: ProxySQL's writes + * to its log are asynchronous with respect to the SQL that triggers them, so a + * single scan immediately after the SQL completes is racy. Retrying gives the + * producer time to flush; when the match is already present the first iteration + * returns immediately. + * + * @param f_stream Log stream (opened with `open_file_and_seek_end` or similar). On + * match, the stream position is advanced past the last match (same + * semantics as `get_matching_lines`); on no-match, the stream is + * rewound to its position at entry. + * @param regex Pattern to search for. + * @param timeout_ms Maximum wall time to wait for the first match. Defaults to + * 2000 ms (20 attempts of 100 ms), which is the same budget used + * by admin_set_credentials_logging-t. + * @param poll_interval_ms Delay between scans when no match has appeared yet. + * Defaults to 100 ms. + * @return true if at least one matching line was observed within the budget. + */ +bool wait_for_log_match( + std::fstream& f_stream, + const std::string& regex, + uint32_t timeout_ms = 2000, + uint32_t poll_interval_ms = 100 +); + + /** * @brief Scan last N lines from a file and find lines matching a regex pattern. * diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index c07f23780..8624fa593 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -132,6 +132,7 @@ tests: tests-cpp \ prepare_statement_err3024_libmysql-t \ prepare_statement_err3024_async-t \ fast_forward_grace_close_libmysql-t \ + mysql-zstd_compression_level_libmysql-t \ fast_forward_switch_replication_deprecate_eof_libmysql-t \ reg_test_mariadb_stmt_store_result_libmysql-t \ reg_test_mariadb_stmt_store_result_async-t diff --git a/test/tap/tests/pgsql-copy_from_test-t.cpp b/test/tap/tests/pgsql-copy_from_test-t.cpp index 2b0cbdea3..8f3832be7 100644 --- a/test/tap/tests/pgsql-copy_from_test-t.cpp +++ b/test/tap/tests/pgsql-copy_from_test-t.cpp @@ -286,8 +286,15 @@ int is_string_in_result(PGresult* result, const char* target_str) { } bool check_logs_for_command(std::fstream& f_proxysql_log, const std::string& command_regex) { - const auto& [_, cmd_lines] { get_matching_lines(f_proxysql_log, command_regex) }; - return cmd_lines.empty() ? false : true; + // ProxySQL's log writes for 'Switching {to Fast Forward,back to Normal} mode' + // happen asynchronously relative to the SQL that triggers them, so a single + // scan right after the SQL completes is racy. wait_for_log_match() retries + // with a short timeout, clearing EOF between scans; on hit it returns + // immediately, so assertions against a line that is already present pay no + // extra latency. Negative assertions (check_logs_for_command(...) == false) + // also benefit: they now wait up to the timeout to confirm absence, avoiding + // a false pass when the line would have arrived a few hundred ms later. + return wait_for_log_match(f_proxysql_log, command_regex); } bool setupTestTable(PGconn* conn) { diff --git a/test/tap/tests/pgsql-servers_ssl_params-t.cpp b/test/tap/tests/pgsql-servers_ssl_params-t.cpp index 13f6627a6..b376f5852 100644 --- a/test/tap/tests/pgsql-servers_ssl_params-t.cpp +++ b/test/tap/tests/pgsql-servers_ssl_params-t.cpp @@ -499,8 +499,20 @@ static void test_monitor_uses_per_server_row(PGconn* admin) { exec_ok(admin, q.str().c_str()); exec_ok(admin, "LOAD PGSQL SERVERS TO RUNTIME"); + // Drain any monitor connect cycle scheduled with the pre-LOAD config. + // Why: the monitor thread may already be mid-cycle when LOAD runs; that + // in-flight connect was scheduled with the OLD SSL params (no TLSv1 pin) + // and will succeed and bump the counter AFTER we sample ok_before. Wait + // until two consecutive 1s samples are equal — meaning the new config + // is in effect and the counter has plateaued. long ok_before = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); - diag("With TLSv1 per-server pin, ssl OK before wait: %ld", ok_before); + for (int i = 0; i < 10; i++) { + usleep(1000000); // 1s — longer than the test's monitor connect interval + long sample = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); + if (sample == ok_before) break; + ok_before = sample; + } + diag("With TLSv1 per-server pin, ssl OK before wait (post-drain): %ld", ok_before); usleep(5000000); // 5 seconds — multiple monitor cycles diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp index 467d98d42..294f64385 100644 --- a/test/tap/tests/test_cluster_sync-t.cpp +++ b/test/tap/tests/test_cluster_sync-t.cpp @@ -565,7 +565,19 @@ int wait_for_node_sync(MYSQL* admin, const vector queries, uint32_t time mysql_free_result(myres); - if (row_value == 0) { + // <= 0 rather than == 0: "== 0" only flagged "row present but zero", + // which missed the "row absent" case (row_value stays at its -1 + // default when mysql_fetch_row returns NULL). For the common + // 'SELECT LENGTH(checksum) FROM stats_proxysql_servers_checksums + // WHERE hostname=... AND port=... AND name=...' predicate the row + // does not yet exist at the moment a freshly-spawned replica is + // polled — so the loop was exiting as "synced" before the replica + // had recorded a checksum for its peer, after which the caller's + // fetch_remote_checksum() correctly returned empty and aborted the + // test with "Failed to fetch current checksum". The race is + // load-dependent: mysql95-g5 passed because it started minutes + // later when concurrent groups had finished. + if (row_value <= 0) { not_synced = true; failed_query = query; @@ -670,7 +682,17 @@ int32_t get_checksum_sync_timeout(MYSQL* admin) { return -1; } - return ((ext_check_intv.val/1000) * ext_sts_freq.val) + 1; + // Compute the status-collection period in ms and round up to avoid the + // integer-division truncation of the earlier (interval_ms/1000 * freq) + 1 + // formula (1001ms would collapse to 1s of period). Then enforce a 30s + // floor: the +1s slack was too tight under concurrent CI load — a single + // missed cycle on legacy-g5 / mysql84-g5 / mysql90-g5 was enough to time + // out. The fast path still exits on the first synced poll, so healthy + // clusters pay no extra cost. + const int64_t period_ms = ext_check_intv.val * ext_sts_freq.val; + const int32_t period_s = static_cast((period_ms + 999) / 1000); + const int32_t timeout_s = period_s + 5; + return timeout_s > 30 ? timeout_s : 30; } int check_module_checksums_sync( @@ -920,27 +942,26 @@ int check_module_checksums_sync( // This is important to avoid race conditions. If this sync is not performed, the primary may detect the // new checksum in the replica confusing this with the previous check. { - const char prim_repl_sync_t[] { + // Both assertions originally formatted the same template with the same + // args (conn_opts = primary), so "own" and "in the replica" polled the + // identical query. Distinguish them: the replica-check filters on + // r_admin, the own-check filters on admin (== conn_opts in this call). + const char prim_chksm_sync_t[] { "SELECT count(*) FROM stats_proxysql_servers_checksums WHERE " "hostname='%s' AND port='%d' AND name='%s' AND checksum='%s'" }; cfmt_t wait_remote_chksm_syn { - cstr_format( prim_repl_sync_t, - conn_opts.host.c_str(), - conn_opts.port, + cstr_format( prim_chksm_sync_t, + r_admin->host, + r_admin->port, module.c_str(), ext_checksum.str.c_str() ) }; - const char prim_own_sync_t[] { - "SELECT count(*) FROM stats_proxysql_servers_checksums WHERE " - "hostname='%s' AND port='%d' AND name='%s' AND checksum='%s'" - }; cfmt_t wait_own_chksm_sync { - cstr_format( - prim_repl_sync_t, - conn_opts.host.c_str(), - conn_opts.port, + cstr_format( prim_chksm_sync_t, + admin->host, + admin->port, module.c_str(), ext_checksum.str.c_str() ) @@ -953,7 +974,7 @@ int check_module_checksums_sync( admin->host, admin->port, ext_checksum.str.c_str(), r_admin->host, r_admin->port ); - sync_res = wait_for_node_sync(admin, { wait_remote_chksm_syn.str }, CHECKSUM_SYNC_TIMEOUT); + sync_res = wait_for_node_sync(admin, { wait_own_chksm_sync.str }, CHECKSUM_SYNC_TIMEOUT); ok( sync_res == 0, "Primary(%s:%d) has detected its own new checksum '%s'", diff --git a/test/tap/tests/test_sqlite3_pass_exts-t.cpp b/test/tap/tests/test_sqlite3_pass_exts-t.cpp index 9cef59e10..a354cc443 100644 --- a/test/tap/tests/test_sqlite3_pass_exts-t.cpp +++ b/test/tap/tests/test_sqlite3_pass_exts-t.cpp @@ -262,8 +262,29 @@ int test_pass_match(MYSQL* admin, const user_def_t& def) { mysql_free_result(myres); } else if (def.auth == "caching_sha2_password") { + // MySQL stores caching_sha2_password as '$A$$' (70 ASCII bytes). + // def.hash is the hex-encoding of those 70 bytes (140 chars). The 3 ASCII chars + // at offset 3..5 (hex offset 6..11) carry rounds/1000 in 3-char zero-padded + // uppercase hex. Decode and pass the resolved rounds value to the SQLite + // extension so the regenerated digest uses the same iteration count as the + // backend (matters once MySQL ships with default rounds != 5000). + long rounds = 5000; + if (def.hash.size() >= 12) { + string rounds_field; + for (size_t i = 6; i < 12; i += 2) { + rounds_field += static_cast(stoi(def.hash.substr(i, 2), nullptr, 16)); + } + try { + rounds = stol(rounds_field, nullptr, 16) * 1000; + } catch (const std::exception&) { + diag("Failed to parse rounds field '%s' from MySQL hash '%s'", + rounds_field.c_str(), def.hash.c_str()); + } + } + const string GEN_SHA2_PASS { - "SELECT HEX(CACHING_SHA2_PASSWORD('" + def.pass + "', UNHEX('" + def.salt + "')))" + "SELECT HEX(CACHING_SHA2_PASSWORD('" + def.pass + "', UNHEX('" + def.salt + "'), " + + std::to_string(rounds) + "))" }; MYSQL_QUERY_T(admin, GEN_SHA2_PASS.c_str()); @@ -274,8 +295,8 @@ int test_pass_match(MYSQL* admin, const user_def_t& def) { ok( def.hash == admin_hash, - "MySQL hash should match ProxySQL generated mysql:'%s', admin:'%s'", - def.hash.c_str(), admin_hash.c_str() + "MySQL hash should match ProxySQL generated mysql:'%s', admin:'%s', rounds:%ld", + def.hash.c_str(), admin_hash.c_str(), rounds ); mysql_free_result(myres); @@ -404,13 +425,20 @@ const vector INV_INPUTS { "ProxySQL Admin Error: wrong number of arguments to function CACHING_SHA2_PASSWORD()" }, { - "SELECT CACHING_SHA2_PASSWORD('00', '00', '00')", 1, + "SELECT CACHING_SHA2_PASSWORD('00', '00', 5000, 'extra')", 1, "ProxySQL Admin Error: wrong number of arguments to function CACHING_SHA2_PASSWORD()" }, { "SELECT CACHING_SHA2_PASSWORD('', '')", 0, "Invalid argument size" }, { "SELECT CACHING_SHA2_PASSWORD('', '000000000000000000000')", 0, "Invalid argument size" }, { "SELECT CACHING_SHA2_PASSWORD(2, '00')", 0, "Invalid argument type" }, { "SELECT CACHING_SHA2_PASSWORD('00', 2)", 0, "Invalid argument type" }, + { "SELECT CACHING_SHA2_PASSWORD('00', '00', '00')", 0, "Invalid argument type" }, + { "SELECT CACHING_SHA2_PASSWORD('00', '00', 1000)", 0, + "Invalid rounds: expected multiple of 1000 in [5000,4095000]" }, + { "SELECT CACHING_SHA2_PASSWORD('00', '00', 5500)", 0, + "Invalid rounds: expected multiple of 1000 in [5000,4095000]" }, + { "SELECT CACHING_SHA2_PASSWORD('00', '00', 4096000)", 0, + "Invalid rounds: expected multiple of 1000 in [5000,4095000]" }, }; diff --git a/test/tap/tests/unit/Makefile b/test/tap/tests/unit/Makefile index c62fcfd7d..71bc459ff 100644 --- a/test/tap/tests/unit/Makefile +++ b/test/tap/tests/unit/Makefile @@ -297,6 +297,12 @@ UNIT_TESTS := smoke_test-t query_cache_unit-t query_processor_unit-t \ .PHONY: all all: $(UNIT_TESTS) +# Alias: top-level 'make unit_tests' (via test/tap/Makefile) forwards the goal +# name here via $(MAKECMDGOALS), so this target must exist even though `all` +# does the same thing. +.PHONY: unit_tests +unit_tests: all + .PHONY: debug debug: OPT += -DDEBUG debug: $(UNIT_TESTS)