Merge pull request #5647 from sysown/v3.0-slim-dbdeployer-images

v3.0: slim dbdeployer images + test-harness and monitor fixes
pull/5655/head
René Cannaò 1 month ago committed by GitHub
commit dbb40a30ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 }}

@ -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 }}

@ -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 }}

@ -189,7 +189,7 @@ endif
### main targets
.DEFAULT: default
.DEFAULT_GOAL := default
.PHONY: default
default: build_src

@ -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$<RRR>$<salt><hash>' where <RRR> 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=<N>$" and the MySQL hash prefix
+ "$A$<RRR>$" 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),

@ -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<gr_host_def_t>& 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<gr_host_def_t> 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;

@ -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);

@ -2,8 +2,15 @@
set -euo pipefail
# Generate compile_commands.json by wrapping make with Bear.
# Usage: ./generate-compile-commands.sh <build-cmd>
#
# 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

@ -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

@ -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'

@ -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

@ -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'

@ -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

@ -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

@ -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'

@ -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

@ -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

@ -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

@ -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

@ -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'

@ -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

@ -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

@ -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" ],

@ -1693,6 +1693,36 @@ pair<size_t,vector<line_match_t>> 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<size_t,std::vector<line_match_t>> get_matching_lines_from_filename(
const std::string& filename, const std::string& s_regex, bool get_matches, size_t max_lines
) {

@ -790,6 +790,38 @@ std::pair<size_t,std::vector<line_match_t>> 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.
*

@ -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

@ -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) {

@ -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

@ -565,7 +565,19 @@ int wait_for_node_sync(MYSQL* admin, const vector<string> 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<int32_t>((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'",

@ -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$<RRR>$<salt><hash>' (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<char>(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_input_t> 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]" },
};

@ -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)

Loading…
Cancel
Save