From 8436ee55b18b3e819ec2ea37c450b550fdca480c Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Thu, 9 Apr 2026 05:56:21 +0000 Subject: [PATCH 1/4] Add MySQL version matrix and named channels functional tests (#80, #81) MySQL version matrix (Issue #80): - Parameterize docker-compose.yml with MYSQL_IMAGE env var (default mysql:8.4) - Use log-slave-updates in cnf files (compatible with all MySQL versions 5.7-9.x) - Make setup-replication.sh version-aware (CHANGE MASTER vs CHANGE REPLICATION SOURCE) - Add version helper functions to lib.sh (mysql_version, mysql_is_57, mysql_change_source_sql, mysql_start/stop/reset_replica_sql, etc.) - Update test-failover.sh restore section to use version-aware helpers - Split CI workflow into build + functional-mysql (7-version matrix) + functional-postgresql jobs with unique artifact names per matrix entry Named channels functional tests (Issue #81): - Create test-named-channels.sh testing multi-source replication discovery - Sets up mysql3 with a named channel 'extra' replicating from mysql2 - Tests channel API endpoints (/api/v2/channels, /api/instance-channels) - Verifies data replication through named channel - Gracefully skips on MySQL 5.7 (requires 8.0+) - Cleans up channel after tests --- .github/workflows/functional.yml | 110 ++++++++++++++++++-- tests/functional/docker-compose.yml | 6 +- tests/functional/lib.sh | 66 +++++++++++- tests/functional/mysql/master.cnf | 2 +- tests/functional/mysql/replica.cnf | 2 +- tests/functional/mysql/replica2.cnf | 2 +- tests/functional/setup-replication.sh | 27 +++-- tests/functional/test-failover.sh | 13 ++- tests/functional/test-named-channels.sh | 127 ++++++++++++++++++++++++ 9 files changed, 323 insertions(+), 32 deletions(-) create mode 100755 tests/functional/test-named-channels.sh diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml index ccaf2c8b..29f8343b 100644 --- a/.github/workflows/functional.yml +++ b/.github/workflows/functional.yml @@ -10,9 +10,9 @@ on: workflow_dispatch: jobs: - functional: + build: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -23,12 +23,44 @@ jobs: go-version: '1.25.7' cache: true - - name: Build orchestrator (for CLI tests on host) + - name: Build orchestrator run: go build -o bin/orchestrator ./go/cmd/orchestrator - - name: Start test infrastructure (MySQL + ProxySQL + Orchestrator) + - name: Upload orchestrator binary + uses: actions/upload-artifact@v4 + with: + name: orchestrator-binary + path: bin/orchestrator + retention-days: 1 + + functional-mysql: + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: build + + strategy: + fail-fast: false + matrix: + mysql_version: ['5.7', '8.0', '8.4', '9.0', '9.2', '9.5', '9.6'] + + steps: + - uses: actions/checkout@v4 + + - name: Download orchestrator binary + uses: actions/download-artifact@v4 + with: + name: orchestrator-binary + path: bin + + - name: Make binary executable + run: chmod +x bin/orchestrator + + - name: Start test infrastructure (MySQL + ProxySQL) working-directory: tests/functional + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} run: | + echo "Using MySQL image: $MYSQL_IMAGE" docker compose up -d mysql1 mysql2 mysql3 proxysql echo "Waiting for MySQL and ProxySQL to be healthy..." timeout 120 bash -c ' @@ -51,10 +83,14 @@ jobs: ' || { echo "Timeout"; docker compose ps; docker compose logs --tail=30; exit 1; } - name: Setup replication + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} run: bash tests/functional/setup-replication.sh - name: Start orchestrator in Docker network working-directory: tests/functional + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} run: | docker compose up -d orchestrator echo "Waiting for orchestrator to be ready..." @@ -77,7 +113,62 @@ jobs: - name: Run failover tests run: bash tests/functional/test-failover.sh - # ---- PostgreSQL functional tests ---- + - name: Run named channels tests + run: bash tests/functional/test-named-channels.sh + + - name: Collect orchestrator logs + if: always() + working-directory: tests/functional + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} + run: | + docker compose logs orchestrator > /tmp/orchestrator-test.log 2>&1 || true + + - name: Upload orchestrator logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: orchestrator-test-logs-mysql-${{ matrix.mysql_version }} + path: /tmp/orchestrator-test.log + + - name: Collect all docker logs on failure + if: failure() + working-directory: tests/functional + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} + run: docker compose logs > /tmp/docker-compose-logs.txt 2>&1 || true + + - name: Upload docker logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: docker-compose-logs-mysql-${{ matrix.mysql_version }} + path: /tmp/docker-compose-logs.txt + + - name: Cleanup + if: always() + working-directory: tests/functional + env: + MYSQL_IMAGE: mysql:${{ matrix.mysql_version }} + run: docker compose down -v --remove-orphans 2>/dev/null || true + + functional-postgresql: + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: build + + steps: + - uses: actions/checkout@v4 + + - name: Download orchestrator binary + uses: actions/download-artifact@v4 + with: + name: orchestrator-binary + path: bin + + - name: Make binary executable + run: chmod +x bin/orchestrator + - name: Start PostgreSQL containers working-directory: tests/functional run: | @@ -145,17 +236,14 @@ jobs: if: always() working-directory: tests/functional run: | - docker compose logs orchestrator > /tmp/orchestrator-test.log 2>&1 || true docker compose logs orchestrator-pg > /tmp/orchestrator-pg-test.log 2>&1 || true - name: Upload orchestrator logs if: always() uses: actions/upload-artifact@v4 with: - name: orchestrator-test-logs - path: | - /tmp/orchestrator-test.log - /tmp/orchestrator-pg-test.log + name: orchestrator-test-logs-postgresql + path: /tmp/orchestrator-pg-test.log - name: Collect all docker logs on failure if: failure() @@ -166,7 +254,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: docker-compose-logs + name: docker-compose-logs-postgresql path: /tmp/docker-compose-logs.txt - name: Cleanup diff --git a/tests/functional/docker-compose.yml b/tests/functional/docker-compose.yml index 78cdf0dc..78aaa119 100644 --- a/tests/functional/docker-compose.yml +++ b/tests/functional/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: mysql1: - image: mysql:8.4 + image: ${MYSQL_IMAGE:-mysql:8.4} hostname: mysql1 environment: MYSQL_ROOT_PASSWORD: testpass @@ -23,7 +23,7 @@ services: - mysql1 mysql2: - image: mysql:8.4 + image: ${MYSQL_IMAGE:-mysql:8.4} hostname: mysql2 environment: MYSQL_ROOT_PASSWORD: testpass @@ -47,7 +47,7 @@ services: - mysql2 mysql3: - image: mysql:8.4 + image: ${MYSQL_IMAGE:-mysql:8.4} hostname: mysql3 environment: MYSQL_ROOT_PASSWORD: testpass diff --git a/tests/functional/lib.sh b/tests/functional/lib.sh index bbad116d..e92011de 100755 --- a/tests/functional/lib.sh +++ b/tests/functional/lib.sh @@ -102,6 +102,63 @@ discover_topology() { return 1 } +# Detect MySQL major version (e.g., "5.7", "8.0", "8.4", "9.0") +# Caches result in MYSQL_MAJOR_VERSION +MYSQL_MAJOR_VERSION="" +mysql_version() { + if [ -n "$MYSQL_MAJOR_VERSION" ]; then + echo "$MYSQL_MAJOR_VERSION" + return + fi + local FULL + FULL=$(docker compose -f tests/functional/docker-compose.yml exec -T mysql1 \ + mysql -uroot -ptestpass -Nse "SELECT VERSION()" 2>/dev/null | tr -d '[:space:]') + MYSQL_MAJOR_VERSION=$(echo "$FULL" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/') + echo "$MYSQL_MAJOR_VERSION" +} + +# Check if MySQL version is 5.7 (returns 0 for 5.7, 1 otherwise) +mysql_is_57() { + [ "$(mysql_version)" = "5.7" ] +} + +# Return the correct CHANGE REPLICATION SOURCE / CHANGE MASTER command +# Arguments: HOST PORT USER PASSWORD +mysql_change_source_sql() { + local HOST="$1" PORT="$2" USER="$3" PASS="$4" + if mysql_is_57; then + echo "CHANGE MASTER TO MASTER_HOST='${HOST}', MASTER_PORT=${PORT}, MASTER_USER='${USER}', MASTER_PASSWORD='${PASS}', MASTER_AUTO_POSITION=1;" + else + echo "CHANGE REPLICATION SOURCE TO SOURCE_HOST='${HOST}', SOURCE_PORT=${PORT}, SOURCE_USER='${USER}', SOURCE_PASSWORD='${PASS}', SOURCE_AUTO_POSITION=1, GET_SOURCE_PUBLIC_KEY=1;" + fi +} + +# Return the correct CHANGE REPLICATION SOURCE / CHANGE MASTER command with FOR CHANNEL +# Arguments: HOST PORT USER PASSWORD CHANNEL +mysql_change_source_channel_sql() { + local HOST="$1" PORT="$2" USER="$3" PASS="$4" CHANNEL="$5" + if mysql_is_57; then + echo "CHANGE MASTER TO MASTER_HOST='${HOST}', MASTER_PORT=${PORT}, MASTER_USER='${USER}', MASTER_PASSWORD='${PASS}', MASTER_AUTO_POSITION=1 FOR CHANNEL '${CHANNEL}';" + else + echo "CHANGE REPLICATION SOURCE TO SOURCE_HOST='${HOST}', SOURCE_PORT=${PORT}, SOURCE_USER='${USER}', SOURCE_PASSWORD='${PASS}', SOURCE_AUTO_POSITION=1, GET_SOURCE_PUBLIC_KEY=1 FOR CHANNEL '${CHANNEL}';" + fi +} + +# Return the correct START REPLICA / START SLAVE command +mysql_start_replica_sql() { + if mysql_is_57; then echo "START SLAVE;"; else echo "START REPLICA;"; fi +} + +# Return the correct STOP REPLICA / STOP SLAVE command +mysql_stop_replica_sql() { + if mysql_is_57; then echo "STOP SLAVE;"; else echo "STOP REPLICA;"; fi +} + +# Return the correct RESET REPLICA ALL / RESET SLAVE ALL command +mysql_reset_replica_all_sql() { + if mysql_is_57; then echo "RESET SLAVE ALL;"; else echo "RESET REPLICA ALL;"; fi +} + # Get ProxySQL servers for a hostgroup proxysql_servers() { local HG="$1" @@ -120,6 +177,11 @@ mysql_read_only() { # Get MySQL replication source mysql_source_host() { local CONTAINER="$1" - docker compose -f tests/functional/docker-compose.yml exec -T "$CONTAINER" \ - mysql -uroot -ptestpass -Nse "SHOW REPLICA STATUS\G" 2>/dev/null | grep "Source_Host" | awk '{print $2}' + if mysql_is_57; then + docker compose -f tests/functional/docker-compose.yml exec -T "$CONTAINER" \ + mysql -uroot -ptestpass -Nse "SHOW SLAVE STATUS\G" 2>/dev/null | grep "Master_Host" | awk '{print $2}' + else + docker compose -f tests/functional/docker-compose.yml exec -T "$CONTAINER" \ + mysql -uroot -ptestpass -Nse "SHOW REPLICA STATUS\G" 2>/dev/null | grep "Source_Host" | awk '{print $2}' + fi } diff --git a/tests/functional/mysql/master.cnf b/tests/functional/mysql/master.cnf index 2fe72f7c..11427c23 100644 --- a/tests/functional/mysql/master.cnf +++ b/tests/functional/mysql/master.cnf @@ -4,6 +4,6 @@ log-bin=mysql-bin binlog-format=ROW gtid-mode=ON enforce-gtid-consistency=ON -log-replica-updates=ON +log-slave-updates=ON binlog-row-image=MINIMAL report-host=mysql1 diff --git a/tests/functional/mysql/replica.cnf b/tests/functional/mysql/replica.cnf index 193e1bd3..ed4503ad 100644 --- a/tests/functional/mysql/replica.cnf +++ b/tests/functional/mysql/replica.cnf @@ -4,6 +4,6 @@ log-bin=mysql-bin binlog-format=ROW gtid-mode=ON enforce-gtid-consistency=ON -log-replica-updates=ON +log-slave-updates=ON binlog-row-image=MINIMAL read-only=ON diff --git a/tests/functional/mysql/replica2.cnf b/tests/functional/mysql/replica2.cnf index 2636a9bc..1ca73b6f 100644 --- a/tests/functional/mysql/replica2.cnf +++ b/tests/functional/mysql/replica2.cnf @@ -4,6 +4,6 @@ log-bin=mysql-bin binlog-format=ROW gtid-mode=ON enforce-gtid-consistency=ON -log-replica-updates=ON +log-slave-updates=ON binlog-row-image=MINIMAL read-only=ON diff --git a/tests/functional/setup-replication.sh b/tests/functional/setup-replication.sh index 97693e38..dfab2442 100755 --- a/tests/functional/setup-replication.sh +++ b/tests/functional/setup-replication.sh @@ -6,18 +6,27 @@ COMPOSE="docker compose -f tests/functional/docker-compose.yml" echo "Setting up replication..." +# Detect MySQL version +MYSQL_FULL_VERSION=$($COMPOSE exec -T mysql1 mysql -uroot -ptestpass -Nse "SELECT VERSION()" 2>/dev/null | tr -d '[:space:]') +MYSQL_MAJOR=$(echo "$MYSQL_FULL_VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/') +echo "Detected MySQL version: $MYSQL_FULL_VERSION (major: $MYSQL_MAJOR)" + +if [ "$MYSQL_MAJOR" = "5.7" ]; then + CHANGE_SOURCE_CMD="CHANGE MASTER TO MASTER_HOST='mysql1', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='repl_pass', MASTER_AUTO_POSITION=1" + START_REPLICA_CMD="START SLAVE" + SHOW_REPLICA_CMD="SHOW SLAVE STATUS\G" +else + CHANGE_SOURCE_CMD="CHANGE REPLICATION SOURCE TO SOURCE_HOST='mysql1', SOURCE_PORT=3306, SOURCE_USER='repl', SOURCE_PASSWORD='repl_pass', SOURCE_AUTO_POSITION=1, GET_SOURCE_PUBLIC_KEY=1" + START_REPLICA_CMD="START REPLICA" + SHOW_REPLICA_CMD="SHOW REPLICA STATUS\G" +fi + for REPLICA in mysql2 mysql3; do echo "Configuring $REPLICA to replicate from mysql1..." for i in $(seq 1 30); do $COMPOSE exec -T "$REPLICA" mysql -uroot -ptestpass -e " - CHANGE REPLICATION SOURCE TO - SOURCE_HOST='mysql1', - SOURCE_PORT=3306, - SOURCE_USER='repl', - SOURCE_PASSWORD='repl_pass', - SOURCE_AUTO_POSITION=1, - GET_SOURCE_PUBLIC_KEY=1; - START REPLICA; + $CHANGE_SOURCE_CMD; + $START_REPLICA_CMD; " 2>/dev/null && break sleep 1 done @@ -34,7 +43,7 @@ for REPLICA in mysql2 mysql3; do fi if [ "$i" -eq 60 ]; then echo "$REPLICA: replication FAILED after 60s" - $COMPOSE exec -T "$REPLICA" mysql -uroot -ptestpass -e "SHOW REPLICA STATUS\G" 2>/dev/null || true + $COMPOSE exec -T "$REPLICA" mysql -uroot -ptestpass -e "$SHOW_REPLICA_CMD" 2>/dev/null || true exit 1 fi sleep 1 diff --git a/tests/functional/test-failover.sh b/tests/functional/test-failover.sh index ef707f7b..c7f6af84 100755 --- a/tests/functional/test-failover.sh +++ b/tests/functional/test-failover.sh @@ -75,13 +75,18 @@ fi echo "" echo "--- Restore topology for hard failover test ---" -# Restore mysql1 as master +# Restore mysql1 as master (version-aware commands) +STOP_SQL=$(mysql_stop_replica_sql) +RESET_SQL=$(mysql_reset_replica_all_sql) +CHANGE_SQL=$(mysql_change_source_sql mysql1 3306 repl repl_pass) +START_SQL=$(mysql_start_replica_sql) + docker compose -f tests/functional/docker-compose.yml exec -T mysql1 \ - mysql -uroot -ptestpass -e "STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=0;" 2>/dev/null + mysql -uroot -ptestpass -e "$STOP_SQL $RESET_SQL SET GLOBAL read_only=0;" 2>/dev/null docker compose -f tests/functional/docker-compose.yml exec -T mysql2 \ - mysql -uroot -ptestpass -e "STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST='mysql1', SOURCE_PORT=3306, SOURCE_USER='repl', SOURCE_PASSWORD='repl_pass', SOURCE_AUTO_POSITION=1; START REPLICA; SET GLOBAL read_only=1;" 2>/dev/null + mysql -uroot -ptestpass -e "$STOP_SQL $CHANGE_SQL $START_SQL SET GLOBAL read_only=1;" 2>/dev/null docker compose -f tests/functional/docker-compose.yml exec -T mysql3 \ - mysql -uroot -ptestpass -e "STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST='mysql1', SOURCE_PORT=3306, SOURCE_USER='repl', SOURCE_PASSWORD='repl_pass', SOURCE_AUTO_POSITION=1; START REPLICA; SET GLOBAL read_only=1;" 2>/dev/null + mysql -uroot -ptestpass -e "$STOP_SQL $CHANGE_SQL $START_SQL SET GLOBAL read_only=1;" 2>/dev/null # Reset ProxySQL docker compose -f tests/functional/docker-compose.yml exec -T proxysql \ diff --git a/tests/functional/test-named-channels.sh b/tests/functional/test-named-channels.sh new file mode 100755 index 00000000..69b8f194 --- /dev/null +++ b/tests/functional/test-named-channels.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# Named channels functional tests — verify multi-source replication channel discovery +set -uo pipefail # no -e: we handle failures ourselves +cd "$(dirname "$0")/../.." +source tests/functional/lib.sh + +echo "=== NAMED CHANNELS TESTS ===" + +# Named channels with GTID are only reliable on MySQL 8.0+ +VERSION=$(mysql_version) +echo "Detected MySQL version: $VERSION" +if [ "$VERSION" = "5.7" ]; then + skip "Named channels tests require MySQL 8.0+ (detected $VERSION)" + summary +fi + +wait_for_orchestrator || { echo "FATAL: Orchestrator not reachable"; exit 1; } + +COMPOSE="docker compose -f tests/functional/docker-compose.yml" + +# ---------------------------------------------------------------- +echo "" +echo "--- Setup: Configure multi-source replication on mysql3 ---" + +# Create test database and table on mysql2 for the extra channel +$COMPOSE exec -T mysql2 mysql -uroot -ptestpass -e " + CREATE DATABASE IF NOT EXISTS extra_db; + CREATE TABLE IF NOT EXISTS extra_db.test (id INT PRIMARY KEY AUTO_INCREMENT, val VARCHAR(100)); + INSERT INTO extra_db.test (val) VALUES ('channel-test'); +" 2>/dev/null + +# Add a named channel 'extra' on mysql3 replicating from mysql2 +CHANGE_SQL=$(mysql_change_source_channel_sql mysql2 3306 repl repl_pass extra) +START_SQL=$(mysql_start_replica_sql) + +$COMPOSE exec -T mysql3 mysql -uroot -ptestpass -e " + $CHANGE_SQL + $START_SQL +" 2>/dev/null + +# Verify the extra channel is running +sleep 3 +CHANNEL_STATUS=$($COMPOSE exec -T mysql3 mysql -uroot -ptestpass -Nse \ + "SELECT SERVICE_STATE FROM performance_schema.replication_connection_status WHERE CHANNEL_NAME='extra'" 2>/dev/null | tr -d '[:space:]') + +if [ "$CHANNEL_STATUS" = "ON" ]; then + pass "Named channel 'extra' is running on mysql3" +else + fail "Named channel 'extra' not running on mysql3 (status: $CHANNEL_STATUS)" +fi + +# Verify data replicated through the extra channel +sleep 2 +REPLICATED=$($COMPOSE exec -T mysql3 mysql -uroot -ptestpass -Nse \ + "SELECT val FROM extra_db.test LIMIT 1" 2>/dev/null | tr -d '[:space:]') + +if [ "$REPLICATED" = "channel-test" ]; then + pass "Data replicated through named channel 'extra'" +else + fail "Data not replicated through named channel (got: $REPLICATED)" +fi + +# ---------------------------------------------------------------- +echo "" +echo "--- Test 1: Discovery of named channels ---" + +# Re-seed discovery so orchestrator picks up the channel +curl -s "$ORC_URL/api/discover/mysql3/3306" > /dev/null +sleep 10 + +# Check channels API endpoint +test_endpoint "GET /api/v2/channels" "$ORC_URL/api/v2/channels" "200" + +CHANNELS_BODY=$(curl -s "$ORC_URL/api/v2/channels" 2>&1) +if echo "$CHANNELS_BODY" | grep -q "extra"; then + pass "Channel 'extra' listed in /api/v2/channels" +else + fail "Channel 'extra' not found in /api/v2/channels" "Body: $(echo "$CHANNELS_BODY" | head -c 300)" +fi + +# ---------------------------------------------------------------- +echo "" +echo "--- Test 2: Instance channels endpoint ---" + +test_endpoint "GET /api/instance-channels/mysql3/3306" "$ORC_URL/api/instance-channels/mysql3/3306" "200" + +INSTANCE_CHANNELS=$(curl -s "$ORC_URL/api/instance-channels/mysql3/3306" 2>&1) +if echo "$INSTANCE_CHANNELS" | grep -q "extra"; then + pass "Channel 'extra' listed in /api/instance-channels/mysql3/3306" +else + fail "Channel 'extra' not found in instance-channels" "Body: $(echo "$INSTANCE_CHANNELS" | head -c 300)" +fi + +# ---------------------------------------------------------------- +echo "" +echo "--- Test 3: Instance data includes channel info ---" + +INSTANCE_DATA=$(curl -s "$ORC_URL/api/instance/mysql3/3306" 2>&1) +if echo "$INSTANCE_DATA" | grep -qi "channel\|replicat"; then + pass "Instance data for mysql3 includes replication/channel info" +else + skip "Instance data for mysql3 may not expose channel info in this version" +fi + +# ---------------------------------------------------------------- +echo "" +echo "--- Cleanup: Remove extra channel from mysql3 ---" + +if mysql_is_57; then + RESET_CHANNEL_SQL="STOP SLAVE FOR CHANNEL 'extra'; RESET SLAVE ALL FOR CHANNEL 'extra';" +else + RESET_CHANNEL_SQL="STOP REPLICA FOR CHANNEL 'extra'; RESET REPLICA ALL FOR CHANNEL 'extra';" +fi + +$COMPOSE exec -T mysql3 mysql -uroot -ptestpass -e "$RESET_CHANNEL_SQL" 2>/dev/null + +# Verify channel removed +CHANNEL_AFTER=$($COMPOSE exec -T mysql3 mysql -uroot -ptestpass -Nse \ + "SELECT COUNT(*) FROM performance_schema.replication_connection_status WHERE CHANNEL_NAME='extra'" 2>/dev/null | tr -d '[:space:]') + +if [ "$CHANNEL_AFTER" = "0" ]; then + pass "Named channel 'extra' cleaned up successfully" +else + fail "Named channel 'extra' still present after cleanup" +fi + +summary From f2d2a0fe0b6fe01ee0b7e0f74005034f91fd6ac9 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Thu, 9 Apr 2026 06:10:45 +0000 Subject: [PATCH 2/4] Fix CI: link named-channels.md in docs TOC, clean up test - Add named-channels.md to docs/toc.md and README.md to fix doc validation check - Remove unreachable MySQL 5.7 branch in channel cleanup code (script already exits on 5.7) --- docs/README.md | 1 + docs/toc.md | 1 + tests/functional/test-named-channels.sh | 7 ++----- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 1bb39be9..53c77c56 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,6 +36,7 @@ - [Topology recovery](topology-recovery.md): recovery process, promotion and hooks. - [Key-Value stores](kv.md): master discovery for your apps - [ProxySQL hooks](proxysql-hooks.md): built-in ProxySQL failover integration +- [Named replication channels](named-channels.md): multi-source replication with named channels #### Operation - [Observability](observability.md): Prometheus metrics and Kubernetes health endpoints diff --git a/docs/toc.md b/docs/toc.md index 1bb39be9..53c77c56 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -36,6 +36,7 @@ - [Topology recovery](topology-recovery.md): recovery process, promotion and hooks. - [Key-Value stores](kv.md): master discovery for your apps - [ProxySQL hooks](proxysql-hooks.md): built-in ProxySQL failover integration +- [Named replication channels](named-channels.md): multi-source replication with named channels #### Operation - [Observability](observability.md): Prometheus metrics and Kubernetes health endpoints diff --git a/tests/functional/test-named-channels.sh b/tests/functional/test-named-channels.sh index 69b8f194..60233b9c 100755 --- a/tests/functional/test-named-channels.sh +++ b/tests/functional/test-named-channels.sh @@ -106,11 +106,8 @@ fi echo "" echo "--- Cleanup: Remove extra channel from mysql3 ---" -if mysql_is_57; then - RESET_CHANNEL_SQL="STOP SLAVE FOR CHANNEL 'extra'; RESET SLAVE ALL FOR CHANNEL 'extra';" -else - RESET_CHANNEL_SQL="STOP REPLICA FOR CHANNEL 'extra'; RESET REPLICA ALL FOR CHANNEL 'extra';" -fi +# Script already exits on MySQL 5.7, so only 8.0+ syntax needed here +RESET_CHANNEL_SQL="STOP REPLICA FOR CHANNEL 'extra'; RESET REPLICA ALL FOR CHANNEL 'extra';" $COMPOSE exec -T mysql3 mysql -uroot -ptestpass -e "$RESET_CHANNEL_SQL" 2>/dev/null From fa42679d9c7ddf07691f35b2150e18b95e8355fa Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Thu, 9 Apr 2026 06:18:52 +0000 Subject: [PATCH 3/4] Fix stuck functional tests: add curl timeouts to all HTTP calls All curl calls in test scripts and lib.sh lacked --max-time, causing tests to hang indefinitely if any API endpoint was slow to respond. Added --max-time 10 to every curl call across all test scripts. --- tests/functional/lib.sh | 20 ++++++------- tests/functional/test-failover.sh | 12 ++++---- tests/functional/test-named-channels.sh | 8 +++--- tests/functional/test-postgresql.sh | 38 ++++++++++++------------- tests/functional/test-smoke.sh | 6 ++-- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/functional/lib.sh b/tests/functional/lib.sh index e92011de..e9e9fb36 100755 --- a/tests/functional/lib.sh +++ b/tests/functional/lib.sh @@ -33,7 +33,7 @@ summary() { test_endpoint() { local NAME="$1" URL="$2" EXPECT="$3" local CODE - CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL" 2>&1) + CODE=$(curl -s --max-time 10 -o /dev/null -w "%{http_code}" "$URL" 2>&1) if [ "$CODE" = "$EXPECT" ]; then pass "$NAME (HTTP $CODE)" else @@ -45,7 +45,7 @@ test_endpoint() { test_body_contains() { local NAME="$1" URL="$2" EXPECT="$3" local BODY - BODY=$(curl -s "$URL" 2>&1) + BODY=$(curl -s --max-time 10 "$URL" 2>&1) if echo "$BODY" | grep -q "$EXPECT"; then pass "$NAME" else @@ -57,7 +57,7 @@ test_body_contains() { wait_for_orchestrator() { echo "Waiting for orchestrator to be ready..." for i in $(seq 1 30); do - if curl -s -o /dev/null "$ORC_URL/api/clusters" 2>/dev/null; then + if curl -s --max-time 5 -o /dev/null "$ORC_URL/api/clusters" 2>/dev/null; then echo "Orchestrator ready after ${i}s" return 0 fi @@ -73,19 +73,19 @@ CLUSTER_NAME="" discover_topology() { local MASTER_HOST="$1" echo "Seeding discovery with $MASTER_HOST..." - curl -s "$ORC_URL/api/discover/$MASTER_HOST/3306" > /dev/null + curl -s --max-time 10 "$ORC_URL/api/discover/$MASTER_HOST/3306" > /dev/null # Also seed replicas directly - curl -s "$ORC_URL/api/discover/mysql2/3306" > /dev/null 2>&1 - curl -s "$ORC_URL/api/discover/mysql3/3306" > /dev/null 2>&1 + curl -s --max-time 10 "$ORC_URL/api/discover/mysql2/3306" > /dev/null 2>&1 + curl -s --max-time 10 "$ORC_URL/api/discover/mysql3/3306" > /dev/null 2>&1 echo "Waiting for topology discovery..." for i in $(seq 1 60); do # Get the cluster name dynamically - CLUSTER_NAME=$(curl -s "$ORC_URL/api/clusters" 2>/dev/null | python3 -c "import json,sys; c=json.load(sys.stdin); print(c[0] if c else '')" 2>/dev/null || echo "") + CLUSTER_NAME=$(curl -s --max-time 5 "$ORC_URL/api/clusters" 2>/dev/null | python3 -c "import json,sys; c=json.load(sys.stdin); print(c[0] if c else '')" 2>/dev/null || echo "") if [ -n "$CLUSTER_NAME" ]; then local COUNT - COUNT=$(curl -s "$ORC_URL/api/cluster/$CLUSTER_NAME" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") + COUNT=$(curl -s --max-time 5 "$ORC_URL/api/cluster/$CLUSTER_NAME" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") if [ "$COUNT" -ge 3 ] 2>/dev/null; then echo "Full topology discovered (${COUNT} instances, cluster=$CLUSTER_NAME) after ${i}s" return 0 @@ -93,8 +93,8 @@ discover_topology() { fi # Re-seed replicas periodically if [ "$((i % 10))" = "0" ]; then - curl -s "$ORC_URL/api/discover/mysql2/3306" > /dev/null 2>&1 - curl -s "$ORC_URL/api/discover/mysql3/3306" > /dev/null 2>&1 + curl -s --max-time 10 "$ORC_URL/api/discover/mysql2/3306" > /dev/null 2>&1 + curl -s --max-time 10 "$ORC_URL/api/discover/mysql3/3306" > /dev/null 2>&1 fi sleep 1 done diff --git a/tests/functional/test-failover.sh b/tests/functional/test-failover.sh index c7f6af84..fa536768 100755 --- a/tests/functional/test-failover.sh +++ b/tests/functional/test-failover.sh @@ -32,7 +32,7 @@ fi echo "" echo "--- Test 1: Graceful master takeover ---" -RESULT=$(curl -s "$ORC_URL/api/graceful-master-takeover/$CLUSTER_NAME/mysql2/3306") +RESULT=$(curl -s --max-time 10 "$ORC_URL/api/graceful-master-takeover/$CLUSTER_NAME/mysql2/3306") CODE=$(echo "$RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('Code',''))" 2>/dev/null) if [ "$CODE" = "OK" ]; then pass "Graceful takeover API returned OK" @@ -95,9 +95,9 @@ docker compose -f tests/functional/docker-compose.yml exec -T proxysql \ # Re-discover after topology change sleep 5 -curl -s "$ORC_URL/api/discover/mysql1/3306" > /dev/null -curl -s "$ORC_URL/api/discover/mysql2/3306" > /dev/null -curl -s "$ORC_URL/api/discover/mysql3/3306" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/mysql1/3306" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/mysql2/3306" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/mysql3/3306" > /dev/null sleep 15 echo "Topology restored, waiting for orchestrator to stabilize..." @@ -113,7 +113,7 @@ docker compose -f tests/functional/docker-compose.yml stop mysql1 echo "Waiting for orchestrator to detect DeadMaster and recover (max 60s)..." RECOVERED=false for i in $(seq 1 60); do - RECOVERIES=$(curl -s "$ORC_URL/api/v2/recoveries" 2>/dev/null) + RECOVERIES=$(curl -s --max-time 10 "$ORC_URL/api/v2/recoveries" 2>/dev/null) # Check for a successful recovery with DeadMaster analysis HAS_RECOVERY=$(echo "$RECOVERIES" | python3 -c " import json, sys @@ -155,7 +155,7 @@ else fi # Check recovery via API -RECOVERY_API=$(curl -s "$ORC_URL/api/v2/recoveries" 2>/dev/null) +RECOVERY_API=$(curl -s --max-time 10 "$ORC_URL/api/v2/recoveries" 2>/dev/null) if echo "$RECOVERY_API" | grep -q '"IsSuccessful":true'; then pass "Recovery audit: /api/v2/recoveries shows successful recovery" else diff --git a/tests/functional/test-named-channels.sh b/tests/functional/test-named-channels.sh index 60233b9c..e7e96891 100755 --- a/tests/functional/test-named-channels.sh +++ b/tests/functional/test-named-channels.sh @@ -65,13 +65,13 @@ echo "" echo "--- Test 1: Discovery of named channels ---" # Re-seed discovery so orchestrator picks up the channel -curl -s "$ORC_URL/api/discover/mysql3/3306" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/mysql3/3306" > /dev/null sleep 10 # Check channels API endpoint test_endpoint "GET /api/v2/channels" "$ORC_URL/api/v2/channels" "200" -CHANNELS_BODY=$(curl -s "$ORC_URL/api/v2/channels" 2>&1) +CHANNELS_BODY=$(curl -s --max-time 10 "$ORC_URL/api/v2/channels" 2>&1) if echo "$CHANNELS_BODY" | grep -q "extra"; then pass "Channel 'extra' listed in /api/v2/channels" else @@ -84,7 +84,7 @@ echo "--- Test 2: Instance channels endpoint ---" test_endpoint "GET /api/instance-channels/mysql3/3306" "$ORC_URL/api/instance-channels/mysql3/3306" "200" -INSTANCE_CHANNELS=$(curl -s "$ORC_URL/api/instance-channels/mysql3/3306" 2>&1) +INSTANCE_CHANNELS=$(curl -s --max-time 10 "$ORC_URL/api/instance-channels/mysql3/3306" 2>&1) if echo "$INSTANCE_CHANNELS" | grep -q "extra"; then pass "Channel 'extra' listed in /api/instance-channels/mysql3/3306" else @@ -95,7 +95,7 @@ fi echo "" echo "--- Test 3: Instance data includes channel info ---" -INSTANCE_DATA=$(curl -s "$ORC_URL/api/instance/mysql3/3306" 2>&1) +INSTANCE_DATA=$(curl -s --max-time 10 "$ORC_URL/api/instance/mysql3/3306" 2>&1) if echo "$INSTANCE_DATA" | grep -qi "channel\|replicat"; then pass "Instance data for mysql3 includes replication/channel info" else diff --git a/tests/functional/test-postgresql.sh b/tests/functional/test-postgresql.sh index 6d0c2e1f..392ccb43 100755 --- a/tests/functional/test-postgresql.sh +++ b/tests/functional/test-postgresql.sh @@ -24,15 +24,15 @@ echo "--- Discovery tests ---" # Discover primary first, wait for it to be written to DB, then discover standby. # This ensures the standby inherits the primary's cluster_name during WriteInstance. echo "Seeding discovery with 172.30.0.20:5432 (pgprimary)..." -curl -s "$ORC_URL/api/discover/172.30.0.20/5432" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/172.30.0.20/5432" > /dev/null sleep 5 echo "Seeding discovery with 172.30.0.21:5432 (pgstandby1)..." -curl -s "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null +curl -s --max-time 10 "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null echo "Waiting for topology discovery..." PG_CLUSTER="" for i in $(seq 1 60); do - PG_CLUSTER=$(curl -s "$ORC_URL/api/clusters" 2>/dev/null | python3 -c " + PG_CLUSTER=$(curl -s --max-time 10 "$ORC_URL/api/clusters" 2>/dev/null | python3 -c " import json, sys c = json.load(sys.stdin) for name in c: @@ -43,7 +43,7 @@ if c: print(c[0]) " 2>/dev/null || echo "") if [ -n "$PG_CLUSTER" ]; then - COUNT=$(curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") + COUNT=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") if [ "$COUNT" -ge 2 ] 2>/dev/null; then echo "PostgreSQL topology discovered (${COUNT} instances, cluster=$PG_CLUSTER) after ${i}s" break @@ -51,7 +51,7 @@ if c: fi # Re-seed standby periodically if [ "$((i % 10))" = "0" ]; then - curl -s "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 + curl -s --max-time 10 "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 fi sleep 1 done @@ -62,7 +62,7 @@ else fail "No PostgreSQL cluster discovered" fi -INST_COUNT=$(curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") +INST_COUNT=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") if [ "$INST_COUNT" -ge 2 ]; then pass "PostgreSQL instances discovered: $INST_COUNT" else @@ -70,7 +70,7 @@ else fi # Verify primary is not read-only (not in recovery) -PRIMARY_RO=$(curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " +PRIMARY_RO=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " import json, sys instances = json.load(sys.stdin) for inst in instances: @@ -88,7 +88,7 @@ else fi # Verify standby is read-only (in recovery) -STANDBY_RO=$(curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " +STANDBY_RO=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " import json, sys instances = json.load(sys.stdin) for inst in instances: @@ -126,20 +126,20 @@ $COMPOSE stop pgprimary # Re-seed standby discovery by IP to ensure orchestrator can still reach it sleep 2 -curl -s "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 +curl -s --max-time 10 "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 sleep 3 -curl -s "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 +curl -s --max-time 10 "$ORC_URL/api/discover/172.30.0.21/5432" > /dev/null 2>&1 # Debug: dump cluster state before and during failover echo "DEBUG: ALL instances in PG orchestrator:" -curl -s "$ORC_URL/api/all-instances" 2>/dev/null | python3 -c " +curl -s --max-time 10 "$ORC_URL/api/all-instances" 2>/dev/null | python3 -c " import json, sys for i in json.load(sys.stdin): k=i['Key']; m=i['MasterKey'] print(f' {k[\"Hostname\"]}:{k[\"Port\"]} Cluster={i[\"ClusterName\"]} RO={i[\"ReadOnly\"]} Master={m[\"Hostname\"]}:{m[\"Port\"]}')" 2>/dev/null || echo " (failed)" echo "DEBUG: Cluster state before failover (cluster=$PG_CLUSTER):" -curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " +curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " import json, sys for i in json.load(sys.stdin): k=i['Key']; m=i['MasterKey'] @@ -148,13 +148,13 @@ for i in json.load(sys.stdin): # Wait a bit for re-discovery, then dump state sleep 15 echo "DEBUG: Cluster state after primary stop + re-discovery:" -curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " +curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " import json, sys for i in json.load(sys.stdin): k=i['Key']; m=i['MasterKey'] print(f' {k[\"Hostname\"]}:{k[\"Port\"]} RO={i[\"ReadOnly\"]} Master={m[\"Hostname\"]}:{m[\"Port\"]} Depth={i[\"ReplicationDepth\"]} Valid={i[\"IsLastCheckValid\"]}')" 2>/dev/null || echo " (failed)" echo "DEBUG: Replication analysis:" -curl -s "$ORC_URL/api/replication-analysis" 2>/dev/null | python3 -c " +curl -s --max-time 10 "$ORC_URL/api/replication-analysis" 2>/dev/null | python3 -c " import json, sys for a in json.load(sys.stdin): print(f' {a[\"AnalyzedInstanceKey\"][\"Hostname\"]}:{a[\"AnalyzedInstanceKey\"][\"Port\"]} Analysis={a[\"Analysis\"]} Replicas={a[\"CountReplicas\"]} ValidReplicas={a[\"CountValidReplicas\"]}')" 2>/dev/null || echo " (failed)" @@ -163,7 +163,7 @@ echo "Waiting for orchestrator to detect DeadPrimary and recover (max 120s)..." RECOVERED=false SUCCESSOR="" for i in $(seq 1 120); do - RECOVERIES=$(curl -s "$ORC_URL/api/v2/recoveries" 2>/dev/null) + RECOVERIES=$(curl -s --max-time 10 "$ORC_URL/api/v2/recoveries" 2>/dev/null) HAS_RECOVERY=$(echo "$RECOVERIES" | python3 -c " import json, sys d = json.load(sys.stdin) @@ -192,15 +192,15 @@ else fail "DeadPrimary: no recovery detected within 120s" # Dump debug info echo " DEBUG: Recent recoveries:" - curl -s "$ORC_URL/api/v2/recoveries" 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30 + curl -s --max-time 10 "$ORC_URL/api/v2/recoveries" 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30 echo " DEBUG: Cluster topology:" - curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30 + curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -m json.tool 2>/dev/null | head -30 fi # Verify successor is no longer in recovery (promoted to primary) if [ "$RECOVERED" = "true" ]; then sleep 3 - SUCCESSOR_RO=$(curl -s "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " + SUCCESSOR_RO=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$PG_CLUSTER" 2>/dev/null | python3 -c " import json, sys instances = json.load(sys.stdin) for inst in instances: @@ -220,7 +220,7 @@ print('unknown') fi # Verify recovery is recorded -RECOVERY_API=$(curl -s "$ORC_URL/api/v2/recoveries" 2>/dev/null) +RECOVERY_API=$(curl -s --max-time 10 "$ORC_URL/api/v2/recoveries" 2>/dev/null) if echo "$RECOVERY_API" | grep -q '"IsSuccessful":true'; then pass "Recovery audit: /api/v2/recoveries shows successful recovery" else diff --git a/tests/functional/test-smoke.sh b/tests/functional/test-smoke.sh index 3c1c7460..5d6f9e79 100755 --- a/tests/functional/test-smoke.sh +++ b/tests/functional/test-smoke.sh @@ -14,7 +14,7 @@ echo "" echo "--- Discovery ---" # Get the actual cluster name (may differ from simple "mysql1") -CLUSTERS=$(curl -s "$ORC_URL/api/clusters" 2>/dev/null) +CLUSTERS=$(curl -s --max-time 10 "$ORC_URL/api/clusters" 2>/dev/null) CLUSTER_NAME=$(echo "$CLUSTERS" | python3 -c "import json,sys; c=json.load(sys.stdin); print(c[0] if c else '')" 2>/dev/null || echo "") echo " Cluster name: $CLUSTER_NAME" @@ -24,7 +24,7 @@ else fail "No cluster discovered" "Response: $CLUSTERS" fi -INST_COUNT=$(curl -s "$ORC_URL/api/cluster/$CLUSTER_NAME" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") +INST_COUNT=$(curl -s --max-time 10 "$ORC_URL/api/cluster/$CLUSTER_NAME" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0") if [ "$INST_COUNT" -ge 2 ]; then pass "Instances discovered: $INST_COUNT" else @@ -52,7 +52,7 @@ test_endpoint "GET /api/v2/recoveries" "$ORC_URL/api/v2/recoveries" "200" test_endpoint "GET /api/v2/proxysql/servers" "$ORC_URL/api/v2/proxysql/servers" "200" test_body_contains "V2 response has status field" "$ORC_URL/api/v2/clusters" '"status"' -V2_404=$(curl -s -o /dev/null -w "%{http_code}" "$ORC_URL/api/v2/instances/nonexistent/9999") +V2_404=$(curl -s --max-time 10 -o /dev/null -w "%{http_code}" "$ORC_URL/api/v2/instances/nonexistent/9999") if [ "$V2_404" = "404" ]; then pass "V2 returns 404 for unknown instance" else From 75fe695c082a508cb60c229c8f2a5093c01e8dd8 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Thu, 9 Apr 2026 07:54:37 +0000 Subject: [PATCH 4/4] Fix named channels test: use correct API endpoint, fix unbound $2 - Use /api/v2/instances/{host}/{port}/channels (actual endpoint) instead of nonexistent /api/v2/channels and /api/instance-channels - Fix fail() function: use ${2:-} to avoid 'unbound variable' error with set -u when called with 1 argument --- tests/functional/lib.sh | 2 +- tests/functional/test-named-channels.sh | 23 +++++------------------ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/functional/lib.sh b/tests/functional/lib.sh index e9e9fb36..3968887e 100755 --- a/tests/functional/lib.sh +++ b/tests/functional/lib.sh @@ -13,7 +13,7 @@ pass() { fail() { echo " ❌ FAIL: $1" - [ -n "$2" ] && echo " $2" + [ -n "${2:-}" ] && echo " $2" ((FAIL_COUNT++)) } diff --git a/tests/functional/test-named-channels.sh b/tests/functional/test-named-channels.sh index e7e96891..f57adacb 100755 --- a/tests/functional/test-named-channels.sh +++ b/tests/functional/test-named-channels.sh @@ -68,27 +68,14 @@ echo "--- Test 1: Discovery of named channels ---" curl -s --max-time 10 "$ORC_URL/api/discover/mysql3/3306" > /dev/null sleep 10 -# Check channels API endpoint -test_endpoint "GET /api/v2/channels" "$ORC_URL/api/v2/channels" "200" +# Check instance channels API endpoint +test_endpoint "GET /api/v2/instances/mysql3/3306/channels" "$ORC_URL/api/v2/instances/mysql3/3306/channels" "200" -CHANNELS_BODY=$(curl -s --max-time 10 "$ORC_URL/api/v2/channels" 2>&1) +CHANNELS_BODY=$(curl -s --max-time 10 "$ORC_URL/api/v2/instances/mysql3/3306/channels" 2>&1) if echo "$CHANNELS_BODY" | grep -q "extra"; then - pass "Channel 'extra' listed in /api/v2/channels" + pass "Channel 'extra' listed in instance channels API" else - fail "Channel 'extra' not found in /api/v2/channels" "Body: $(echo "$CHANNELS_BODY" | head -c 300)" -fi - -# ---------------------------------------------------------------- -echo "" -echo "--- Test 2: Instance channels endpoint ---" - -test_endpoint "GET /api/instance-channels/mysql3/3306" "$ORC_URL/api/instance-channels/mysql3/3306" "200" - -INSTANCE_CHANNELS=$(curl -s --max-time 10 "$ORC_URL/api/instance-channels/mysql3/3306" 2>&1) -if echo "$INSTANCE_CHANNELS" | grep -q "extra"; then - pass "Channel 'extra' listed in /api/instance-channels/mysql3/3306" -else - fail "Channel 'extra' not found in instance-channels" "Body: $(echo "$INSTANCE_CHANNELS" | head -c 300)" + fail "Channel 'extra' not found in instance channels API" "Body: $(echo "$CHANNELS_BODY" | head -c 300)" fi # ----------------------------------------------------------------