diff --git a/test/infra/SKILL.md b/test/infra/SKILL.md index 15332a663c..ec7ddd02d7 100644 --- a/test/infra/SKILL.md +++ b/test/infra/SKILL.md @@ -138,18 +138,18 @@ docker network inspect ${INFRA_ID}_backend ### Issue: "GTID: failed to connect to ProxySQL binlog reader on port 6020" -**Cause:** Reader containers not running or gtid_port misconfigured. +**Cause:** Binlog reader on the mysqlN containers is not listening on 6020, or `gtid_port` is misconfigured. **Solution:** -1. Verify reader containers are running: - ```bash - docker ps | grep reader - ``` -2. Check mysql_servers has gtid_port set: +1. Check mysql_servers has gtid_port set: ```bash docker exec proxysql.${INFRA_ID} mysql -uradmin -pradmin -h127.0.0.1 -P6032 \ -e "SELECT hostname, gtid_port FROM mysql_servers;" ``` +2. Verify the mysqlN container is listening on 6020: + ```bash + docker exec ${INFRA}-${INFRA_ID}-mysql1-1 bash -lc 'timeout 3 bash -lc " +# Run a command against a host path through a temporary container. +# Use this for host-mounted files/directories whose ownership was set by container users and +# cannot be reliably modified from the host user context. +# Example: docker_fs_exec "rm -f proxysql/proxysql.db" "${INFRA_LOGS_PATH}/${INFRA_ID}" +docker_fs_exec() { + local fs_command="$1" + local target_path="$2" + + if [ -z "${fs_command}" ] || [ -z "${target_path}" ]; then + echo "docker_fs_exec requires a command and a path" >&2 + return 1 + fi + + # Work around host-mounted paths owned by container users different from the host user. + # Alpine/busybox would also work here, but proxysql-ci-base:latest is already pulled in this workflow. + docker run --rm \ + -v "${target_path}:/target" \ + proxysql-ci-base:latest \ + /bin/sh -ec "cd /target && ${fs_command}" +} diff --git a/test/infra/control/ensure-infras.bash b/test/infra/control/ensure-infras.bash index 6dd07896b4..b7f9383962 100755 --- a/test/infra/control/ensure-infras.bash +++ b/test/infra/control/ensure-infras.bash @@ -148,4 +148,4 @@ if [ -f "${SETUP_HOOK}" ]; then "${SETUP_HOOK}" fi -# ensure-infras.bash completed successfully \ No newline at end of file +# ensure-infras.bash completed successfully diff --git a/test/infra/control/run-tests-isolated.bash b/test/infra/control/run-tests-isolated.bash index fa876b9533..1bebcb7fb6 100755 --- a/test/infra/control/run-tests-isolated.bash +++ b/test/infra/control/run-tests-isolated.bash @@ -269,7 +269,6 @@ chmod 777 "${TESTS_LOGS_PATH_HOST}" # Find binaries MYSQL_BINLOG_BIN=$(find "${WORKSPACE}" -path "${WORKSPACE}/ci_infra_logs" -prune -o -path "${WORKSPACE}/.git" -prune -o -name "mysqlbinlog" -type f -executable -print | head -n 1) -BINLOG_READER_BIN=$(find "${WORKSPACE}" -path "${WORKSPACE}/ci_infra_logs" -prune -o -path "${WORKSPACE}/.git" -prune -o -name "test_binlog_reader-t" -type f -executable -print | head -n 1) # Execution: run the container docker run \ @@ -295,7 +294,6 @@ docker run \ -e COVERAGE_REPORT_DIR="${COVERAGE_REPORT_DIR}" \ -e SCRIPT_DIR="${SCRIPT_DIR}" \ -e MYSQL_BINLOG_BIN="${MYSQL_BINLOG_BIN}" \ - -e BINLOG_READER_BIN="${BINLOG_READER_BIN}" \ -e TAP_USE_NOISE="${TAP_USE_NOISE:-0}" \ -e TAP_PGSQL_SYNC_REPLICA_PORT="${TAP_PGSQL_SYNC_REPLICA_PORT:-}" \ -e MULTI_GROUP="${MULTI_GROUP:-0}" \ @@ -394,7 +392,6 @@ docker run \ mkdir -p \"${WORKSPACE}/test-scripts/deps\" [ -n \"${MYSQL_BINLOG_BIN}\" ] && ln -sf \"${MYSQL_BINLOG_BIN}\" \"${WORKSPACE}/test-scripts/deps/mysqlbinlog\" - [ -n \"${BINLOG_READER_BIN}\" ] && ln -sf \"${BINLOG_READER_BIN}\" \"${WORKSPACE}/test-scripts/deps/test_binlog_reader-t\" # Source group environment first (sets TEST_PY_* flags etc.) if [ -n \"${TAP_GROUP}\" ]; then diff --git a/test/infra/control/start-proxysql-isolated.bash b/test/infra/control/start-proxysql-isolated.bash index 7132e29074..6d67fcf91b 100755 --- a/test/infra/control/start-proxysql-isolated.bash +++ b/test/infra/control/start-proxysql-isolated.bash @@ -2,15 +2,13 @@ set -e set -o pipefail -# SUDO helper: empty if root -SUDO="" -if [ "$(id -u)" != "0" ]; then SUDO="sudo"; fi - # Derive Workspace relative to script SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" export WORKSPACE="${REPO_ROOT}" +source "${SCRIPT_DIR}/docker-fs-helper.bash" + if [ -z "${INFRA_ID}" ]; then echo "Error: INFRA_ID is not set."; exit 1; fi export ROOT_PASSWORD=$(echo -n "${INFRA_ID}" | sha256sum | head -c 10) @@ -35,9 +33,9 @@ echo ">>> Setting up isolated network: ${NETWORK_NAME}" docker network inspect ${NETWORK_NAME} >/dev/null 2>&1 || docker network create ${NETWORK_NAME} echo ">>> Preparing ProxySQL data directory: ${PROXY_DATA_DIR}" -$SUDO mkdir -p "${PROXY_DATA_DIR}" -$SUDO chmod -R 777 "${INFRA_LOGS_PATH}/${INFRA_ID}" -$SUDO rm -f "${PROXY_DATA_DIR}/proxysql.db" "${PROXY_DATA_DIR}"/*.pem +mkdir -p "${PROXY_DATA_DIR}" +docker_fs_exec "chmod -R 777 ." "${INFRA_LOGS_PATH}/${INFRA_ID}" +docker_fs_exec "rm -f proxysql/proxysql.db proxysql/*.pem" "${INFRA_LOGS_PATH}/${INFRA_ID}" docker rm -f "${PROXY_CONTAINER}" >/dev/null 2>&1 || true diff --git a/test/infra/control/stop-proxysql-isolated.bash b/test/infra/control/stop-proxysql-isolated.bash index 5d36433a03..bc404e3ca3 100755 --- a/test/infra/control/stop-proxysql-isolated.bash +++ b/test/infra/control/stop-proxysql-isolated.bash @@ -7,6 +7,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" export WORKSPACE="${REPO_ROOT}" +source "${SCRIPT_DIR}/docker-fs-helper.bash" + # Default INFRA_ID if not provided export INFRA_ID="${INFRA_ID:-dev-$USER}" @@ -34,5 +36,6 @@ done echo ">>> Log permissions cleanup" INFRA_LOGS_PATH="${WORKSPACE}/ci_infra_logs" if [ -d "${INFRA_LOGS_PATH}/${INFRA_ID}" ]; then - sudo chmod -R 777 "${INFRA_LOGS_PATH}/${INFRA_ID}" 2>/dev/null || true + chmod -R 777 "${INFRA_LOGS_PATH}/${INFRA_ID}" 2>/dev/null || \ + docker_fs_exec "chmod -R 777 ." "${INFRA_LOGS_PATH}/${INFRA_ID}" >/dev/null 2>&1 || true fi diff --git a/test/infra/infra-mysql57-binlog/bin/docker-mysql-post.bash b/test/infra/infra-mysql57-binlog/bin/docker-mysql-post.bash index afb451f165..d103dd443f 100755 --- a/test/infra/infra-mysql57-binlog/bin/docker-mysql-post.bash +++ b/test/infra/infra-mysql57-binlog/bin/docker-mysql-post.bash @@ -80,6 +80,8 @@ SQL else echo "Configuring slave (mysql${i})..." docker exec -i "${CONTAINER}" mysql -h127.0.0.1 -uroot ${PASS_OPT} </dev/null -trap 'popd &>/dev/null' EXIT - -# Load .env but ensure INFRA_ID is preserved -if [ ! -f .env ]; then echo "Error: .env not found"; exit 1; fi -SAVED_INFRA_ID="${INFRA_ID}" -set -a; . .env; set +a -export INFRA_ID="${SAVED_INFRA_ID}" - -# Docker Compose version helper - prefer plugin (v2) -COMPOSE_CMD="docker compose" -if ! $COMPOSE_CMD version &>/dev/null; then - COMPOSE_CMD="docker-compose" - if ! $COMPOSE_CMD version &>/dev/null; then - echo "ERROR: Neither 'docker compose' nor 'docker-compose' found!" - exit 1 - fi -fi - -if [ -z "${INFRA_ID}" ]; then echo "Error: INFRA_ID must be set"; exit 1; fi - -export ROOT_PASSWORD=$(echo -n "${INFRA_ID}" | sha256sum | head -c 10) -export INFRA=${PWD##*/} -export COMPOSE_PROJECT="${INFRA}-${INFRA_ID}" -export INFRA_LOGS_PATH=${INFRA_LOGS_PATH:-${WORKSPACE}/ci_infra_logs} - -echo "=================================================================================" -echo "Destroying CI Infra '${INFRA}' (Project: ${COMPOSE_PROJECT}) ..." -echo "=================================================================================" - -# Create temp env file -ENV_FILE=".env.isolated.${INFRA_ID}" -cat < "${ENV_FILE}" -INFRA_ID=${INFRA_ID} -ROOT_PASSWORD=${ROOT_PASSWORD} -INFRA=${INFRA} -COMPOSE_PROJECT=${COMPOSE_PROJECT} -INFRA_LOGS_PATH=${INFRA_LOGS_PATH} -ENVEOF - -$COMPOSE_CMD --env-file .env --env-file "${ENV_FILE}" -p "${COMPOSE_PROJECT}" down -v -rm -f "${ENV_FILE}" - -echo "=================================================================================" -echo "Done." -echo "=================================================================================" diff --git a/test/infra/infra-mysql57-binlog/docker-compose-init.bash b/test/infra/infra-mysql57-binlog/docker-compose-init.bash index 53d60397ca..2c6b964e26 100755 --- a/test/infra/infra-mysql57-binlog/docker-compose-init.bash +++ b/test/infra/infra-mysql57-binlog/docker-compose-init.bash @@ -13,13 +13,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" export WORKSPACE="${REPO_ROOT}" +source "${REPO_ROOT}/test/infra/control/docker-fs-helper.bash" + set -e set -o pipefail -# SUDO helper: empty if root -SUDO="" -if [ "$(id -u)" != "0" ]; then SUDO="sudo"; fi - # relaunch self with timeout [[ $(ps -o command= $(ps -o ppid= $$)) =~ timeout ]] || exec timeout -v -s 9 ${TIMEOUT:-600} "${BASH_SOURCE}" "$@" @@ -80,8 +78,8 @@ for RAW_PATH in ${MOUNTED_PATHS}; do fi echo "Preparing directory: ${ACTUAL_PATH}" - $SUDO mkdir -p "${ACTUAL_PATH}" - $SUDO chmod -R 777 "${ACTUAL_PATH}" + mkdir -p "${ACTUAL_PATH}" + docker_fs_exec "chmod -R 777 ." "${ACTUAL_PATH}" done # 3. Inject dynamic variables into Orchestrator configs @@ -128,6 +126,34 @@ fi # 7. Run post-scripts if they exist sleep 2 # wait a bit for engines to start [ -f ./bin/docker-mysql-post.bash ] && ./bin/docker-mysql-post.bash + +if [[ "${INFRA}" == *-binlog ]]; then + echo ">>> Waiting for binlog readers on port 6020..." + for i in 1 2 3; do + MYSQL_CONTAINER="${COMPOSE_PROJECT}-mysql${i}-1" + READER_CONTAINER="${COMPOSE_PROJECT}-reader${i}-1" + echo -n "Waiting for mysql${i} binlog reader ..." + MAX_WAIT=60 + COUNT=0 + while ! docker exec "${MYSQL_CONTAINER}" bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6020; exec 3>&-; exec 3<&-' >/dev/null 2>&1; do + echo -n "." + sleep 2 + COUNT=$((COUNT+2)) + if [ ${COUNT} -ge ${MAX_WAIT} ]; then + echo " FAILED" + echo ">>> ${MYSQL_CONTAINER} logs:" + docker logs "${MYSQL_CONTAINER}" | tail -n 50 || true + if docker inspect "${READER_CONTAINER}" >/dev/null 2>&1; then + echo ">>> ${READER_CONTAINER} logs:" + docker logs "${READER_CONTAINER}" | tail -n 50 || true + fi + exit 1 + fi + done + echo " OK." + done +fi + [ -f ./bin/docker-orchestrator-post.bash ] && ./bin/docker-orchestrator-post.bash [ -f ./bin/docker-proxy-post.bash ] && ./bin/docker-proxy-post.bash "$1" diff --git a/test/infra/infra-mysql57-binlog/docker-compose.yml b/test/infra/infra-mysql57-binlog/docker-compose.yml index ffd82d8365..47770c7cec 100644 --- a/test/infra/infra-mysql57-binlog/docker-compose.yml +++ b/test/infra/infra-mysql57-binlog/docker-compose.yml @@ -11,6 +11,16 @@ services: - mysql1.infra-mysql57-binlog environment: - MYSQL_ROOT_PASSWORD=${ROOT_PASSWORD} + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "(docker-entrypoint.sh mysqld &); + while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysql/binlogreader-error.log; + done" volumes: - ./conf/mysql/mysql1:/etc/mysql/conf.d - ./conf/mysql/ssl:/docker-entrypoint-initdb.d @@ -34,6 +44,16 @@ services: - mysql2.infra-mysql57-binlog environment: - MYSQL_ROOT_PASSWORD=${ROOT_PASSWORD} + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "(docker-entrypoint.sh mysqld &); + while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysql/binlogreader-error.log; + done" volumes: - ./conf/mysql/mysql2:/etc/mysql/conf.d - ./conf/mysql/ssl:/docker-entrypoint-initdb.d @@ -59,6 +79,16 @@ services: - mysql3.infra-mysql57-binlog environment: - MYSQL_ROOT_PASSWORD=${ROOT_PASSWORD} + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "(docker-entrypoint.sh mysqld &); + while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysql/binlogreader-error.log; + done" volumes: - ./conf/mysql/mysql3:/etc/mysql/conf.d - ./conf/mysql/ssl:/docker-entrypoint-initdb.d @@ -73,63 +103,6 @@ services: soft: 1048576 hard: 1048576 - reader1: - image: proxysql/ci-infra:proxysql-mysqlbinlog - hostname: reader1.${INFRA} - container_name: ${COMPOSE_PROJECT}-reader1-1 - environment: - - MYSQL_HOST=mysql1.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 - volumes: - - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader1:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader1.${INFRA} - - mysql1.${INFRA} - depends_on: - - mysql1 - - reader2: - image: proxysql/ci-infra:proxysql-mysqlbinlog - hostname: reader2.${INFRA} - container_name: ${COMPOSE_PROJECT}-reader2-1 - environment: - - MYSQL_HOST=mysql2.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 - volumes: - - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader2:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader2.${INFRA} - - mysql2.${INFRA} - depends_on: - - mysql2 - - reader3: - image: proxysql/ci-infra:proxysql-mysqlbinlog - hostname: reader3.${INFRA} - container_name: ${COMPOSE_PROJECT}-reader3-1 - environment: - - MYSQL_HOST=mysql3.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 - volumes: - - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader3:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader3.${INFRA} - - mysql3.${INFRA} - depends_on: - - mysql3 - orc1: hostname: orc1.${INFRA} image: proxysql/ci-infra:openark-orchestrator diff --git a/test/infra/infra-mysql84-binlog/bin/docker-mysql-post.bash b/test/infra/infra-mysql84-binlog/bin/docker-mysql-post.bash index 579e486e54..d9b4125d9e 100755 --- a/test/infra/infra-mysql84-binlog/bin/docker-mysql-post.bash +++ b/test/infra/infra-mysql84-binlog/bin/docker-mysql-post.bash @@ -80,14 +80,14 @@ SQL else echo "Configuring replica (mysql${i})..." docker exec -i "${CONTAINER}" mysql -h127.0.0.1 -uroot ${PASS_OPT} <>> Waiting for binlog readers on port 6020..." + for i in 1 2 3; do + MYSQL_CONTAINER="${COMPOSE_PROJECT}-mysql${i}-1" + READER_CONTAINER="${COMPOSE_PROJECT}-reader${i}-1" + echo -n "Waiting for mysql${i} binlog reader ..." + MAX_WAIT=60 + COUNT=0 + while ! docker exec "${MYSQL_CONTAINER}" bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6020; exec 3>&-; exec 3<&-' >/dev/null 2>&1; do + echo -n "." + sleep 2 + COUNT=$((COUNT+2)) + if [ ${COUNT} -ge ${MAX_WAIT} ]; then + echo " FAILED" + echo ">>> ${MYSQL_CONTAINER} logs:" + docker logs "${MYSQL_CONTAINER}" | tail -n 50 || true + if docker inspect "${READER_CONTAINER}" >/dev/null 2>&1; then + echo ">>> ${READER_CONTAINER} logs:" + docker logs "${READER_CONTAINER}" | tail -n 50 || true + fi + exit 1 + fi + done + echo " OK." + done +fi + [ -f ./bin/docker-orchestrator-post.bash ] && ./bin/docker-orchestrator-post.bash [ -f ./bin/docker-proxy-post.bash ] && ./bin/docker-proxy-post.bash "$1" diff --git a/test/infra/infra-mysql84-binlog/docker-compose.yml b/test/infra/infra-mysql84-binlog/docker-compose.yml index 1fe03c2365..b03fb14066 100644 --- a/test/infra/infra-mysql84-binlog/docker-compose.yml +++ b/test/infra/infra-mysql84-binlog/docker-compose.yml @@ -68,59 +68,59 @@ services: hard: 1048576 reader1: - image: proxysql/ci-infra:proxysql-mysqlbinlog-v2.3 - hostname: reader1.${INFRA} + image: proxysql/proxysql-mysqlbinlog container_name: ${COMPOSE_PROJECT}-reader1-1 environment: - - MYSQL_HOST=mysql1.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysqlbinlog/error.log; + done" volumes: - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader1:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader1.${INFRA} - - mysql1.${INFRA} + network_mode: service:mysql1 depends_on: - mysql1 reader2: - image: proxysql/ci-infra:proxysql-mysqlbinlog-v2.3 - hostname: reader2.${INFRA} + image: proxysql/proxysql-mysqlbinlog container_name: ${COMPOSE_PROJECT}-reader2-1 environment: - - MYSQL_HOST=mysql2.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysqlbinlog/error.log; + done" volumes: - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader2:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader2.${INFRA} - - mysql2.${INFRA} + network_mode: service:mysql2 depends_on: - mysql2 reader3: - image: proxysql/ci-infra:proxysql-mysqlbinlog-v2.3 - hostname: reader3.${INFRA} + image: proxysql/proxysql-mysqlbinlog container_name: ${COMPOSE_PROJECT}-reader3-1 environment: - - MYSQL_HOST=mysql3.${INFRA} - - MYSQL_USER=binlog - - MYSQL_PASSWORD=binlog - - GTID_PORT=6020 + - MYSQL_HOST=127.0.0.1 + - BINLOG_READER_USER=root + - BINLOG_READER_PASSWORD=${ROOT_PASSWORD} + - LISTEN_PORT=6020 + command: > + bash -c "while true; do + sleep 5; + proxysql_binlog_reader -h \"$${MYSQL_HOST:-127.0.0.1}\" -u \"$${BINLOG_READER_USER:=root}\" -p \"$${BINLOG_READER_PASSWORD:-root}\" -P \"$${MYSQL_PORT:-3306}\" -l \"$${LISTEN_PORT:-6020}\" -f 2>&1 | tee -a /var/log/mysqlbinlog/error.log; + done" volumes: - ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/reader3:/var/log/mysqlbinlog - networks: - backend: - aliases: - - reader3.${INFRA} - - mysql3.${INFRA} + network_mode: service:mysql3 depends_on: - mysql3 diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index 4562336f1f..3da0e5bad6 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -259,10 +259,10 @@ "test_auth_methods-t" : [ "mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "test_auto_increment_delay_multiplex-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "test_backend_conn_ping-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], - "test_binlog_dump_multi_backend_crash-t" : [ "legacy-binlog-g1" ], - "test_binlog_fast_forward-t" : [ "legacy-binlog-g1","mysql84-binlog-g2" ], + "test_binlog_dump_multi_backend_crash-t" : [ "legacy-binlog-g1", "mysql84-binlog-g1" ], + "test_binlog_fast_forward-t" : [ "legacy-binlog-g1","mysql84-binlog-g1" ], "test_binlog_reader-t" : [ "legacy-binlog-g1","mysql84-binlog-g1" ], - "test_binlog_reader_uses_previous_hostgroup-t" : [ "legacy-binlog-g1","mysql84-g5" ], + "test_binlog_reader_uses_previous_hostgroup-t" : [ "legacy-binlog-g1", "mysql84-binlog-g1", "mysql84-g5" ], "test_cacert_load_and_verify_duration-t" : [ "legacy-g3","mysql84-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], "test_change_user-t" : [ "legacy-g3","mysql84-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], "test_clickhouse_server-t" : [ "legacy-clickhouse-g1","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index 7b34324a01..a92ffaf927 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -181,6 +181,9 @@ sh-%: anomaly_detection-t: anomaly_detection-t.cpp $(TAP_LDIR)/libtap.so $(CXX) -DEXCLUDE_TRACKING_VARIABLES $< ../tap/SQLite3_Server.cpp -I$(CLICKHOUSE_CPP_IDIR) $(IDIRS) $(LDIRS) -L$(CLICKHOUSE_CPP_LDIR) -L$(LZ4_LDIR) $(OPT) $(OBJ) $(MYLIBSJEMALLOC) $(MYLIBS) $(STATIC_LIBS) $(CLICKHOUSE_CPP_LDIR)/libclickhouse-cpp-lib.a $(CLICKHOUSE_CPP_PATH)/contrib/zstd/zstd/libzstdstatic.a $(LZ4_LDIR)/liblz4.a -lscram -lusual -Wl,--allow-multiple-definition -o $@ +test_binlog_reader_uses_previous_hostgroup-t: binlog_rpl.h +test_com_register_slave_enables_fast_forward-t: binlog_rpl.h + %-t: %-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ diff --git a/test/tap/tests/binlog_rpl.h b/test/tap/tests/binlog_rpl.h new file mode 100644 index 0000000000..d1ecd0b9ce --- /dev/null +++ b/test/tap/tests/binlog_rpl.h @@ -0,0 +1,192 @@ +/** + * @file binlog_rpl.h + * @brief Runs binlog replication sessions via MARIADB_RPL with COM_REGISTER_SLAVE. + * @details Uses the MariaDB client replication API to exercise binlog streaming + * functionality. Setting rpl->host before mariadb_rpl_open() triggers + * COM_REGISTER_SLAVE registration. + * + * The helper runs two replication sessions. Each session: + * 1. Opens a fresh connection to ProxySQL + * 2. Issues a lightweight pre-replication query + * 3. Configures replication session variables (checksum, heartbeat) + * 4. Initializes MARIADB_RPL with rpl->host set (triggers COM_REGISTER_SLAVE) + * 5. Opens replication and fetches events to verify the stream is active + * 6. Closes the replication session cleanly + * + * Returns EXIT_SUCCESS only if both sessions succeed. + */ + +#ifndef BINLOG_RPL_H +#define BINLOG_RPL_H + +#include +#include +#include +#include + +#include "mysql.h" +#include "mariadb_rpl.h" +#include "command_line.h" +#include "tap.h" +#include "utils.h" + +// Number of heartbeats to wait per replication session to confirm stream is active +#define BINLOG_RPL_NHB 3 + +/** + * @brief Run a replication session against ProxySQL. + * @details Opens a connection, issues a pre-query, configures replication + * variables, then uses MARIADB_RPL with rpl->host set to send + * COM_REGISTER_SLAVE. Fetches events until BINLOG_RPL_NHB heartbeats + * are received, verifying the binlog stream is active. + * + * @param cl CommandLine with connection parameters. + * @param session_id A label for diagnostic output (e.g. 1 or 2). + * @param server_id Unique server_id for the replication registration. + * @return EXIT_SUCCESS if the session completes successfully, EXIT_FAILURE otherwise. + */ +static int run_replication_session(const CommandLine& cl, int session_id, int server_id) { + int rc = EXIT_FAILURE; + MYSQL* mysql = NULL; + MYSQL_RES* res = NULL; + MARIADB_RPL* rpl = NULL; + MARIADB_RPL_EVENT* event = NULL; + int num_heartbeats = 0; + int num_events = 0; + + diag("Session %d: Connecting to ProxySQL at %s:%d as %s", + session_id, cl.host, cl.port, cl.username); + + mysql = mysql_init(NULL); + if (!mysql) { + diag("Session %d: mysql_init failed", session_id); + goto cleanup; + } + + // Force CLIENT_DEPRECATE_EOF to match backend capabilities for fast-forward + mysql->options.client_flag |= CLIENT_DEPRECATE_EOF; + + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + diag("Session %d: Connection failed: %s", session_id, mysql_error(mysql)); + goto cleanup; + } + + // Issue a pre-replication query that disables multiplexing. This ensures the + // backend connection stays pinned to the session so that when COM_REGISTER_SLAVE + // triggers fast_forward, the existing backend is reused instead of requiring a + // new connection. + if (run_q(mysql, "SELECT @@hostname")) { + goto cleanup; + } + res = mysql_store_result(mysql); + mysql_free_result(res); + + // Configure replication session variables + if (run_q(mysql, "SET @master_binlog_checksum = 'NONE'")) { + goto cleanup; + } + if (run_q(mysql, "SET @master_heartbeat_period = 2000000000")) { + goto cleanup; + } + + // Initialize replication handle + rpl = mariadb_rpl_init(mysql); + if (!rpl) { + diag("Session %d: mariadb_rpl_init failed", session_id); + goto cleanup; + } + + rpl->server_id = server_id; + rpl->start_position = 4; + rpl->flags = MARIADB_RPL_BINLOG_SEND_ANNOTATE_ROWS; + + // KEY: Setting rpl->host causes mariadb_rpl_open() to send COM_REGISTER_SLAVE + // before proceeding with the binlog dump. This is the core mechanism under test. + rpl->host = strdup("127.0.0.1"); + rpl->port = cl.port; + + diag("Session %d: Opening replication with server_id=%d (COM_REGISTER_SLAVE will be sent)", + session_id, server_id); + + if (mariadb_rpl_open(rpl)) { + diag("Session %d: mariadb_rpl_open failed: [%d] %s", + session_id, mysql_errno(rpl->mysql), mysql_error(rpl->mysql)); + goto cleanup; + } + + // Fetch events until we receive enough heartbeats to confirm the stream is active + diag("Session %d: Fetching binlog events, waiting for %d heartbeats...", + session_id, BINLOG_RPL_NHB); + + while (num_heartbeats < BINLOG_RPL_NHB && + (event = mariadb_rpl_fetch(rpl, event))) { + num_events++; + if (event->event_type == HEARTBEAT_LOG_EVENT_V2 || + event->event_type == HEARTBEAT_LOG_EVENT) { + num_heartbeats++; + diag("Session %d: Heartbeat %d/%d received (total events: %d)", + session_id, num_heartbeats, BINLOG_RPL_NHB, num_events); + } + } + + if (num_heartbeats < BINLOG_RPL_NHB && event == NULL) { + diag("Session %d: Replication stream ended prematurely: %s", + session_id, mysql_error(mysql)); + } + + if (num_heartbeats < BINLOG_RPL_NHB) { + diag("Session %d: FAILED - received only %d/%d heartbeats", + session_id, num_heartbeats, BINLOG_RPL_NHB); + goto cleanup; + } + + diag("Session %d: SUCCESS - received %d heartbeats across %d events", + session_id, num_heartbeats, num_events); + rc = EXIT_SUCCESS; + +cleanup: + mariadb_free_rpl_event(event); + if (rpl) { + mariadb_rpl_close(rpl); + } + if (mysql) { + mysql_close(mysql); + } + return rc; +} + +/** + * @brief Run two binlog replication sessions via MARIADB_RPL. + * @details Executes two independent replication sessions, each sending + * COM_REGISTER_SLAVE. Used by TAP tests: + * - test_com_register_slave_enables_fast_forward-t: verifies COM_REGISTER_SLAVE + * enables fast-forward mode and binlog streaming works + * - test_binlog_reader_uses_previous_hostgroup-t: verifies fast-forward + * connections use the hostgroup from prior COM_QUERY + * + * @param cl CommandLine with connection parameters. + * @return EXIT_SUCCESS if both sessions succeed, EXIT_FAILURE otherwise. + */ +static int run_binlog_rpl(const CommandLine& cl) { + diag("=== Binlog RPL test: Starting ==="); + diag("Running two replication sessions to exercise COM_REGISTER_SLAVE"); + + // Session 1 + int rc = run_replication_session(cl, 1, 100001); + if (rc != EXIT_SUCCESS) { + diag("=== Binlog RPL test: FAILED (session 1) ==="); + return EXIT_FAILURE; + } + + // Session 2 + rc = run_replication_session(cl, 2, 100002); + if (rc != EXIT_SUCCESS) { + diag("=== Binlog RPL test: FAILED (session 2) ==="); + return EXIT_FAILURE; + } + + diag("=== Binlog RPL test: SUCCESS (both sessions completed) ==="); + return EXIT_SUCCESS; +} + +#endif // BINLOG_RPL_H \ No newline at end of file diff --git a/test/tap/tests/test_binlog_reader_uses_previous_hostgroup-t.cpp b/test/tap/tests/test_binlog_reader_uses_previous_hostgroup-t.cpp index b37de627be..850e329498 100644 --- a/test/tap/tests/test_binlog_reader_uses_previous_hostgroup-t.cpp +++ b/test/tap/tests/test_binlog_reader_uses_previous_hostgroup-t.cpp @@ -2,11 +2,11 @@ * @file test_binlog_reader_uses_previous_hostgroup-t.cpp * @brief Test binlog reader uses the hostgroup of the previous COM_QUERY. * @details When a COM_REGISTER_SLAVE command is received, test that ProxySQL - * will automatically switch from not fast_forward mode to fast_forward mode. - * It also test that the destination hostgroup assigned from previous COM_QUERY - * commands is the one used to establish the fast_forward connection. To test - * this we look at how many connections are closed in the hostgroup that should - * have been used for the fast_forward connections. + * will automatically switch from not fast_forward mode to fast_forward mode. + * It also tests that the destination hostgroup assigned from previous + * COM_QUERY commands is the one used to establish the fast_forward connection. + * To test this we look at how many connections are closed in the hostgroup + * that should have been used for the fast_forward connections. */ #include @@ -18,6 +18,7 @@ #include "command_line.h" #include "utils.h" #include "tap.h" +#include "binlog_rpl.h" using std::vector; using std::string; @@ -105,10 +106,9 @@ int main(int argc, char** argv) { } const long conn_closed_before = std::stol(hg_stats_row[0]); - const char * tdp = getenv("TEST_DEPS"); - const std::string test_binlog_reader = ( tdp == nullptr || *tdp == '\0' ) ? "./test_binlog_reader-t" : std::string(tdp) + "/test_binlog_reader-t"; - const int test_binlog_reader_res = system(test_binlog_reader.c_str()); - if (test_binlog_reader_res) { + const int res = run_binlog_rpl(cl); + if (res) { + diag("Binlog RPL test failed with exit code: %d", res); mysql_close(proxy_admin); return EXIT_FAILURE; } diff --git a/test/tap/tests/test_com_register_slave_enables_fast_forward-t.cpp b/test/tap/tests/test_com_register_slave_enables_fast_forward-t.cpp index 16acaa95b8..7426ff770e 100644 --- a/test/tap/tests/test_com_register_slave_enables_fast_forward-t.cpp +++ b/test/tap/tests/test_com_register_slave_enables_fast_forward-t.cpp @@ -1,40 +1,39 @@ /** - * @file test_com_register_slave_enables_fast_forward-t.cpp @brief Test - * COM_REGISTER_SLAVE enables fast forward. @details Test checks if - * test_binlog_reader is executed successfully using a user with fast forward - * flag set to false. test_binlog_reader sends command COM_REGISTER_SLAVE, then - * ProxySQL enables fast forward. test_binlog_reader then uses libslave to - * listen binlog events. It listen two times, one after sending a query that do - * not disable multiplexing and the other after sending a query that disables - * multiplexing. - * - * The repository for test_binlog_reader-t is: - * https://github.com/ProxySQL/proxysql_binlog_test + * @file test_com_register_slave_enables_fast_forward-t.cpp + * @brief Test COM_REGISTER_SLAVE enables fast forward. + * @details Verifies that ProxySQL correctly enables fast forward for a user + * when it receives COM_REGISTER_SLAVE, even if fast_forward was initially + * disabled. Uses MARIADB_RPL to send COM_REGISTER_SLAVE commands. */ -#include #include #include "tap.h" +#include "command_line.h" +#include "binlog_rpl.h" int main(int argc, char** argv) { + CommandLine cl; + plan(1); diag("Testing COM_REGISTER_SLAVE enables fast forward"); - diag("This test verifies that ProxySQL correctly enables fast forward for a user when it receives COM_REGISTER_SLAVE, even if it was initially disabled."); + diag("This test verifies that ProxySQL correctly enables fast forward for a" + " user when it receives COM_REGISTER_SLAVE, even if it was initially disabled."); + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } - const char * tdp = getenv("TEST_DEPS"); - const std::string test_binlog_reader = ( tdp == nullptr || *tdp == '\0' ) ? "./test_binlog_reader-t" : std::string(tdp) + "/test_binlog_reader-t"; + const int res = run_binlog_rpl(cl); - diag("Executing test_binlog_reader-t from: %s", test_binlog_reader.c_str()); - const int test_binlog_reader_res = system(test_binlog_reader.c_str()); - - if (test_binlog_reader_res != 0) { - diag("test_binlog_reader-t failed with exit code: %d", test_binlog_reader_res); + if (res != 0) { + diag("Binlog RPL test failed with exit code: %d", res); } ok( - test_binlog_reader_res == 0, - "'test_binlog_reader-t' should be correctly executed. Err code was: %d", - test_binlog_reader_res + res == 0, + "Binlog RPL test should complete successfully. Err code was: %d", + res ); diag("Test completed");