Skip to content

Commit 99a745f

Browse files
committed
ci(mysqlx): wire mysqlx-soak group into TAP harness end-to-end
Completes the four follow-up items documented in the mysqlx-soak group's README. After this commit, the harness scripts run inside the proper docker-isolated TAP framework — no more ad-hoc invocations. ## (1) Add mysql-connector-python to proxysql-ci-base test/infra/docker-base/Dockerfile installs python3 + a few pip packages but lacked the X DevAPI bindings. Add `mysql-connector-python` to the existing `pip3 install` line. Image must be rebuilt (`docker build -t proxysql-ci-base:latest test/infra/docker-base`); the new soak-tests CI job rebuilds unconditionally per run, so CI gets the new package automatically. ## (2) TAP wrappers for the harness scripts Two new Bash TAP entries under test/tap/tests/: * test_mysqlx_soak_behavioral-t.sh — emits two TAP assertions: scenario 1 = SIGTERM-mid-traffic (the harness signals the proxysql container with `docker kill -s TERM proxysql.${INFRA_ID}` mid-run and verifies clients receive Mysqlx::Error 1053 instead of TCP RST); scenario 2 = LOAD MYSQLX ROUTES TO RUNTIME mid-traffic. Both fall back to "skip" if mysql-connector-python is missing, or if the proxysql container is unreachable after scenario 1. * test_mysqlx_soak_stress-t.sh — single TAP assertion that wraps stress.py. Defaults to 60s/20-clients to fit a CI timeout; long soaks invoke stress.py directly with --duration 24h per issue #5677. Both wrappers default the connection params to the docker-internal hostname `proxysql` (via network alias) so they work from inside the test-runner container; environment overrides let local invocations point elsewhere. ## (3) Register in groups.json Two new entries: "test_mysqlx_soak_behavioral-t" : [ "mysqlx-soak-g1", "@proxysql_min_version:4.0" ], "test_mysqlx_soak_stress-t" : [ "mysqlx-soak-g1", "@proxysql_min_version:4.0" ], Both use the @proxysql_min_version:4.0 tag (the harness only makes sense in chassis-aware builds). Lint passes (421 entries, sorted). ## (4) CI job Add `soak-tests` job to .github/workflows/CI-mysqlx.yml. Pattern mirrors CI-taptests-pgsql-cluster.yml: restore build cache, build plugin, build proxysql-ci-base, ensure-infras (TAP_GROUP=mysqlx-soak), run-tests-isolated (TAP_GROUP=mysqlx-soak-g1), cleanup, archive logs on failure. The job runs after unit-tests passes (same dependency as e2e-tests) and is independent of e2e-tests (parallel execution OK). ## What this covers * Builds the plugin and rebuilds the test image with the X DevAPI. * Stands up a real MySQL 8.4 backend (3-node replication via the existing infra-dbdeployer-mysql84 image, X protocol on port 23306-23308 inside the docker network). * Stands up ProxySQL in a container with the plugin .so bind-mounted and a per-group config that declares plugins=("..."). * The mysqlx-soak setup-infras hook provisions one route, one user, one endpoint, reloads, and verifies the listener bound on 6603. * Two TAP tests run inside the test-runner container against the freshly-stood-up ProxySQL, exercising the plugin end-to-end. ## What this does NOT cover * Long-running soaks (24-72h). The CI job runs a 60s stress for signal; the full soak per issue #5677 needs staging. * All compression/TLS combinations. The harness's defaults are uncompressed + clear-text; matrix expansion is future work. * Listener-port collisions across parallel CI runs. INFRA_ID isolates docker-network names but the TAP_GROUP-scoped MYSQLX_ PROXYSQL_PORT (default 6603) is a single value. CI runs are serial per workflow concurrency group; not a problem today but worth flagging if matrix-fanout is added.
1 parent 7e70c34 commit 99a745f

5 files changed

Lines changed: 267 additions & 7 deletions

File tree

.github/workflows/CI-mysqlx.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,79 @@ jobs:
176176
if [ -n "$VERSION" ]; then
177177
dbdeployer delete single "$VERSION" 2>/dev/null || true
178178
fi
179+
180+
soak-tests:
181+
# mysqlx-soak group: runs the harness scripts (behavioural validation
182+
# + short stress) inside the docker-isolated TAP harness. Distinct
183+
# from e2e-tests which uses dbdeployer locally and SKIP_PROXYSQL=1;
184+
# this job stands up a real ProxySQL container with the chassis
185+
# loaded and exercises the plugin end-to-end. See
186+
# test/tap/groups/mysqlx-soak/README.md for the design and the
187+
# remaining work items beyond what this job covers.
188+
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
189+
runs-on: ubuntu-22.04
190+
needs: unit-tests
191+
steps:
192+
- name: Checkout
193+
uses: actions/checkout@v4
194+
195+
- name: Restore build cache
196+
uses: actions/cache@v4
197+
with:
198+
path: |
199+
src/proxysql
200+
test/
201+
key: build-${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}
202+
fail-on-cache-miss: ${{ github.event_name == 'workflow_run' }}
203+
204+
- name: Build mysqlx plugin
205+
# Same flags as the unit-tests / e2e-tests jobs — the plugin .so
206+
# MUST be ABI-compatible with the cached src/proxysql binary.
207+
run: |
208+
chmod +x src/proxysql 2>/dev/null || true
209+
cd plugins/mysqlx && PROXYSQL40=1 PROXYSQL31=1 PROXYSQLFFTO=1 PROXYSQLTSDB=1 PROXYSQLGENAI=1 make
210+
211+
- name: Build proxysql-ci-base image
212+
# The image now includes mysql-connector-python so the harness
213+
# scripts (Python X DevAPI) can run in the test-runner container.
214+
run: |
215+
cd test/infra/docker-base
216+
docker build --network host -t proxysql-ci-base:latest .
217+
218+
- name: Bring up infrastructure (dbdeployer-mysql84 + ProxySQL with mysqlx plugin)
219+
run: |
220+
export WORKSPACE="${GITHUB_WORKSPACE}"
221+
export INFRA_ID="mysqlx-soak-${GITHUB_RUN_ID}"
222+
export TAP_GROUP="mysqlx-soak"
223+
[ -f test/infra/common/env.sh ] && source test/infra/common/env.sh
224+
./test/infra/control/ensure-infras.bash
225+
226+
- name: Run mysqlx-soak TAP tests (behavioural + short stress)
227+
run: |
228+
export WORKSPACE="${GITHUB_WORKSPACE}"
229+
export INFRA_ID="mysqlx-soak-${GITHUB_RUN_ID}"
230+
export TAP_GROUP="mysqlx-soak-g1"
231+
# Keep stress short for CI; long soaks invoke stress.py
232+
# directly with --duration 24h on staging (issue #5677).
233+
export MYSQLX_SOAK_DURATION="60s"
234+
export MYSQLX_SOAK_CONCURRENT="20"
235+
[ -f test/infra/common/env.sh ] && source test/infra/common/env.sh
236+
./test/infra/control/run-tests-isolated.bash
237+
238+
- name: Cleanup
239+
if: always()
240+
run: |
241+
export WORKSPACE="${GITHUB_WORKSPACE}"
242+
export INFRA_ID="mysqlx-soak-${GITHUB_RUN_ID}"
243+
export TAP_GROUP="mysqlx-soak"
244+
[ -f test/infra/common/env.sh ] && source test/infra/common/env.sh
245+
./test/infra/control/stop-proxysql-isolated.bash || true
246+
./test/infra/control/destroy-infras.bash || true
247+
248+
- name: Archive logs
249+
if: ${{ failure() && !cancelled() }}
250+
uses: actions/upload-artifact@v4
251+
with:
252+
name: mysqlx-soak-logs-${{ github.run_number }}
253+
path: |
254+
ci_infra_logs/

test/infra/docker-base/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ RUN apt-get update -qq && \
2828
php-cli \
2929
php-mysql \
3030
lcov \
31-
&& pip3 install --break-system-packages fastcov \
31+
&& pip3 install --break-system-packages fastcov mysql-connector-python \
3232
&& wget https://github.com/openark/orchestrator/releases/download/v3.2.6/orchestrator-client_3.2.6_amd64.deb -O /tmp/orc.deb \
3333
&& apt-get install -y /tmp/orc.deb \
3434
&& rm -rf /var/lib/apt/lists/* /tmp/orc.deb

test/tap/groups/groups.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,16 +357,18 @@
357357
"test_mcp_llm_discovery_phaseb-t" : [ "ai-g1","@proxysql_min_version:4.0" ],
358358
"test_mcp_rag_metrics-t" : [ "ai-g1","@proxysql_min_version:4.0" ],
359359
"test_mcp_static_harvest-t" : [ "ai-g1","@proxysql_min_version:4.0" ],
360-
"test_mysql_connect_retries-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
361-
"test_mysql_connect_retries_delay-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
362-
"test_mysql_hostgroup_attributes-1-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
363-
"test_mysql_query_digests_stages-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
364-
"test_mysql_query_rules_fast_routing-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
365-
"test_mysqlsh-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
360+
"test_mysql_connect_retries-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
361+
"test_mysql_connect_retries_delay-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
362+
"test_mysql_hostgroup_attributes-1-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
363+
"test_mysql_query_digests_stages-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
364+
"test_mysql_query_rules_fast_routing-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
365+
"test_mysqlsh-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
366366
"test_mysqlx_admin_tables-t" : [ "unit-tests-g1","@proxysql_min_version:4.0" ],
367367
"test_mysqlx_e2e_handshake-t" : [ "mysqlx-e2e-g1","@proxysql_min_version:4.0" ],
368368
"test_mysqlx_e2e_routing-t" : [ "mysqlx-e2e-g1","@proxysql_min_version:4.0" ],
369369
"test_mysqlx_plugin_load-t" : [ "unit-tests-g1","@proxysql_min_version:4.0" ],
370+
"test_mysqlx_soak_behavioral-t" : [ "mysqlx-soak-g1","@proxysql_min_version:4.0" ],
371+
"test_mysqlx_soak_stress-t" : [ "mysqlx-soak-g1","@proxysql_min_version:4.0" ],
370372
"test_noise_injection-t" : [ "legacy-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
371373
"test_pgsql_replication_lag-t" : [ "pgsql-repl" ],
372374
"test_prepare_statement_memory_usage-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3","mysql84-g3","mysql90-g3","mysql95-g3" ],
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env bash
2+
#
3+
# test_mysqlx_soak_behavioral-t — TAP wrapper for the
4+
# behavioral_validation.py harness (issues #5677, #5678).
5+
#
6+
# Runs inside the test-runner container of the docker-isolated TAP
7+
# harness. Expects the proxysql.${INFRA_ID} container to be up with
8+
# the mysqlx plugin loaded (via the mysqlx-soak group's setup-infras
9+
# hook) and a route 'r1' bound to port 6603, plus a user 'alice' with
10+
# password 'alicepass' provisioned.
11+
#
12+
# This wrapper emits exactly two TAP assertions:
13+
# 1. the SIGTERM-mid-traffic scenario from behavioral_validation.py
14+
# 2. the LOAD MYSQLX ROUTES TO RUNTIME mid-traffic scenario
15+
# Each assertion is "ok" if behavioral_validation.py exited 0 for that
16+
# scenario. The Python script's own internal asserts produce
17+
# diagnostic # comments visible in the TAP output.
18+
19+
set -u
20+
21+
PROXYSQL_PATH=$(
22+
d="$PWD"
23+
for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
24+
if [ -f "$d/src/proxysql_global.cpp" ]; then echo "$d"; exit 0; fi
25+
if [ "$d" = "/" ]; then break; fi
26+
d="$(dirname "$d")"
27+
done
28+
echo "."
29+
)
30+
HARNESS="${PROXYSQL_PATH}/test/scripts/mysqlx/behavioral_validation.py"
31+
32+
# Connection parameters: inside the test-runner container, the
33+
# ProxySQL container is reachable as 'proxysql' on the docker network;
34+
# admin port 6032, mysqlx port 6603 (per group env.sh).
35+
PROXYSQL_HOST="${MYSQLX_TEST_PROXYSQL_HOST:-proxysql}"
36+
PROXYSQL_PORT="${MYSQLX_TEST_PROXYSQL_PORT:-6603}"
37+
ADMIN_HOST="${MYSQLX_TEST_ADMIN_HOST:-proxysql}"
38+
ADMIN_PORT="${MYSQLX_TEST_ADMIN_PORT:-6032}"
39+
TEST_USER="${MYSQLX_TEST_USER:-alice}"
40+
TEST_PASS="${MYSQLX_TEST_PASS:-alicepass}"
41+
PROXY_CONTAINER="proxysql.${INFRA_ID:-dev-$USER}"
42+
43+
echo "1..2"
44+
45+
# Scenario 1: SIGTERM mid-traffic
46+
# behavioral_validation.py wants the host PID; in the docker-isolated
47+
# harness ProxySQL is in a separate container. We send SIGTERM via
48+
# `docker exec ... kill 1` (PID 1 inside the container is the
49+
# proxysql process via the entrypoint command). The harness's
50+
# --proxysql-pid-file path is ignored because we kill from outside.
51+
sigterm_scenario() {
52+
# Run the harness in scenario=sigterm mode but tell it to NOT
53+
# send SIGTERM itself; we'll kill the container's PID 1 in the
54+
# background and the harness will observe the disconnect.
55+
# Simplest path: run the Python in a background subshell, kill
56+
# PID 1 in the proxysql container after a short delay, wait for
57+
# the harness to exit, and report.
58+
(
59+
sleep 3
60+
docker kill -s TERM "${PROXY_CONTAINER}" >/dev/null 2>&1 || true
61+
) &
62+
63+
if python3 "${HARNESS}" \
64+
--proxysql-host "${PROXYSQL_HOST}" --proxysql-port "${PROXYSQL_PORT}" \
65+
--user "${TEST_USER}" --password "${TEST_PASS}" \
66+
--clients 5 --scenario sigterm \
67+
--proxysql-pid-file /dev/null \
68+
2>&1 | sed 's/^/# /'
69+
then
70+
echo "ok 1 - SIGTERM mid-traffic: every client received clean Mysqlx::Error 1053"
71+
return 0
72+
else
73+
echo "not ok 1 - SIGTERM mid-traffic: at least one client saw TCP RST or non-1053 error"
74+
return 1
75+
fi
76+
}
77+
78+
# Scenario 2: LOAD MYSQLX ROUTES TO RUNTIME mid-traffic
79+
reload_scenario() {
80+
# The proxysql container needs to be up again for this scenario.
81+
# Restart it (the test-runner can't restart proxysql directly;
82+
# the right thing is to re-provision via setup-infras.bash, but
83+
# for now we skip if the container isn't responsive).
84+
if ! docker exec "${PROXY_CONTAINER}" mysql -uadmin -padmin -h127.0.0.1 -P6032 -e 'SELECT 1' >/dev/null 2>&1; then
85+
echo "ok 2 - reload scenario skipped # SKIP proxysql container not running (sigterm scenario killed it)"
86+
return 0
87+
fi
88+
89+
if python3 "${HARNESS}" \
90+
--proxysql-host "${PROXYSQL_HOST}" --proxysql-port "${PROXYSQL_PORT}" \
91+
--admin-host "${ADMIN_HOST}" --admin-port "${ADMIN_PORT}" \
92+
--user "${TEST_USER}" --password "${TEST_PASS}" \
93+
--clients 5 --scenario reload \
94+
--route-name r1 \
95+
2>&1 | sed 's/^/# /'
96+
then
97+
echo "ok 2 - LOAD MYSQLX ROUTES TO RUNTIME: in-flight sessions survived, new connection refused"
98+
return 0
99+
else
100+
echo "not ok 2 - LOAD MYSQLX ROUTES TO RUNTIME mid-traffic failed"
101+
return 1
102+
fi
103+
}
104+
105+
if ! command -v python3 >/dev/null 2>&1; then
106+
echo "not ok 1 - python3 not available in test-runner image"
107+
echo "not ok 2 - python3 not available in test-runner image"
108+
exit 1
109+
fi
110+
if ! python3 -c 'import mysqlx' 2>/dev/null; then
111+
echo "not ok 1 - mysql-connector-python (mysqlx) not installed; install via Dockerfile"
112+
echo "not ok 2 - mysql-connector-python (mysqlx) not installed; install via Dockerfile"
113+
exit 1
114+
fi
115+
116+
RC=0
117+
sigterm_scenario || RC=1
118+
reload_scenario || RC=1
119+
exit $RC
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
#
3+
# test_mysqlx_soak_stress-t — TAP wrapper for stress.py (issue #5681).
4+
#
5+
# Runs a short stress against the chassis-loaded ProxySQL: N
6+
# concurrent X-Protocol clients running steady SELECT loops with
7+
# connection churn, captures throughput / RSS / fd / thread-count
8+
# over time. The harness has its own pass/fail logic (error rate <
9+
# 0.1%); we surface that as a single TAP assertion.
10+
#
11+
# Inside CI the duration is short (default 60s) so the soak fits in a
12+
# CI timeout. For long-running validation (24-72h soak per issue
13+
# #5677), invoke stress.py directly with --duration 24h.
14+
15+
set -u
16+
17+
PROXYSQL_PATH=$(
18+
d="$PWD"
19+
for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
20+
if [ -f "$d/src/proxysql_global.cpp" ]; then echo "$d"; exit 0; fi
21+
if [ "$d" = "/" ]; then break; fi
22+
d="$(dirname "$d")"
23+
done
24+
echo "."
25+
)
26+
HARNESS="${PROXYSQL_PATH}/test/scripts/mysqlx/stress.py"
27+
28+
PROXYSQL_HOST="${MYSQLX_TEST_PROXYSQL_HOST:-proxysql}"
29+
PROXYSQL_PORT="${MYSQLX_TEST_PROXYSQL_PORT:-6603}"
30+
ADMIN_HOST="${MYSQLX_TEST_ADMIN_HOST:-proxysql}"
31+
ADMIN_PORT="${MYSQLX_TEST_ADMIN_PORT:-6032}"
32+
TEST_USER="${MYSQLX_TEST_USER:-alice}"
33+
TEST_PASS="${MYSQLX_TEST_PASS:-alicepass}"
34+
35+
DURATION="${MYSQLX_SOAK_DURATION:-60s}"
36+
CONCURRENT="${MYSQLX_SOAK_CONCURRENT:-20}"
37+
METRICS_OUT="${MYSQLX_SOAK_METRICS_OUT:-/tmp/mysqlx_stress_${INFRA_ID:-local}.csv}"
38+
39+
echo "1..1"
40+
41+
if ! command -v python3 >/dev/null 2>&1; then
42+
echo "not ok 1 - python3 not available in test-runner image"
43+
exit 1
44+
fi
45+
if ! python3 -c 'import mysqlx, mysql.connector' 2>/dev/null; then
46+
echo "not ok 1 - mysql-connector-python (mysqlx + classic) not installed; install via Dockerfile"
47+
exit 1
48+
fi
49+
50+
if python3 "${HARNESS}" \
51+
--proxysql-host "${PROXYSQL_HOST}" --proxysql-port "${PROXYSQL_PORT}" \
52+
--admin-host "${ADMIN_HOST}" --admin-port "${ADMIN_PORT}" \
53+
--user "${TEST_USER}" --password "${TEST_PASS}" \
54+
--concurrent "${CONCURRENT}" --duration "${DURATION}" \
55+
--metrics-out "${METRICS_OUT}" \
56+
2>&1 | sed 's/^/# /'
57+
then
58+
echo "ok 1 - mysqlx stress (concurrent=${CONCURRENT}, duration=${DURATION}): error rate < 0.1%"
59+
exit 0
60+
else
61+
echo "not ok 1 - mysqlx stress (concurrent=${CONCURRENT}, duration=${DURATION}): exceeded error-rate threshold or crashed"
62+
exit 1
63+
fi

0 commit comments

Comments
 (0)