diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index f526fc5..bc0cd2b 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -142,7 +142,8 @@ jobs: VERSION=$(ls "$SANDBOX_BINARY" | head -1) echo "Deploying Percona Server $VERSION..." ./dbdeployer deploy single "$VERSION" --sandbox-binary="$SANDBOX_BINARY" - ~/sandboxes/msb_*/use -e "SELECT VERSION()" | grep -i percona + # SELECT VERSION() returns "8.0.36-28" — "Percona" appears in @@version_comment + ~/sandboxes/msb_*/use -e "SELECT @@version_comment" | grep -i percona echo "OK: Percona Server single sandbox works" ./dbdeployer delete all --skip-confirm @@ -170,7 +171,6 @@ jobs: matrix: mariadb-version: - '10.11.9' - - '11.4.5' env: GO111MODULE: on SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql @@ -197,13 +197,17 @@ jobs: echo "Downloading MariaDB ${MARIADB_VERSION}..." mkdir -p "$SANDBOX_BINARY" curl -L -f -o "/tmp/$TARBALL" "$URL" - ./dbdeployer unpack "/tmp/$TARBALL" --sandbox-binary="$SANDBOX_BINARY" + ./dbdeployer unpack "/tmp/$TARBALL" --sandbox-binary="$SANDBOX_BINARY" \ + --unpack-version="$MARIADB_VERSION" --flavor=mariadb \ + - name: Test single sandbox run: | echo "Deploying MariaDB ${MARIADB_VERSION}..." ./dbdeployer deploy single "$MARIADB_VERSION" --sandbox-binary="$SANDBOX_BINARY" - ~/sandboxes/msb_*/use -e "SELECT VERSION()" | grep -i mariadb + # SELECT VERSION() returns "11.4.9-MariaDB"; use @@version_comment as fallback + ~/sandboxes/msb_*/use -e "SELECT VERSION()" | grep -i mariadb \ + || ~/sandboxes/msb_*/use -e "SELECT @@version_comment" | grep -i mariadb echo "OK: MariaDB single sandbox works" ./dbdeployer delete all --skip-confirm @@ -216,7 +220,7 @@ jobs: $SBDIR/m -e "CREATE DATABASE ci_test; USE ci_test; CREATE TABLE t1(id INT PRIMARY KEY, val VARCHAR(50)); INSERT INTO t1 VALUES (1, 'mariadb_repl_test');" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/s1 -BN -e "SELECT val FROM ci_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/s1 -BN -e "SELECT val FROM ci_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "mariadb_repl_test" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -296,6 +300,12 @@ jobs: - name: Install PostgreSQL for ts tests run: | + # Add PostgreSQL apt repo (PGDG) — required for postgresql-16 on ubuntu-22.04 + sudo apt-get install -y curl ca-certificates + sudo install -d /usr/share/postgresql-common/pgdg + sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc + echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list + sudo apt-get update sudo apt-get install -y postgresql-16 postgresql-client-16 sudo systemctl stop postgresql || true export HOME="$GITHUB_WORKSPACE/home" @@ -408,7 +418,7 @@ jobs: echo "=== Replica 1: read test ===" # Wait for replication to replica1 with retries for i in $(seq 1 10); do - RESULT=$(~/sandboxes/postgresql_repl_*/replica1/use -c "SELECT val FROM test_repl;" 2>/dev/null) + RESULT=$(~/sandboxes/postgresql_repl_*/replica1/use -c "SELECT val FROM test_repl;" 2>/dev/null) || true echo "$RESULT" | grep -q "hello" && break echo " Waiting for replication to replica1... ($i/10)" sleep 2 @@ -418,7 +428,7 @@ jobs: echo "=== Replica 2: read test ===" # Wait for replication to replica2 with retries for i in $(seq 1 10); do - RESULT=$(~/sandboxes/postgresql_repl_*/replica2/use -c "SELECT val FROM test_repl;" 2>/dev/null) + RESULT=$(~/sandboxes/postgresql_repl_*/replica2/use -c "SELECT val FROM test_repl;" 2>/dev/null) || true echo "$RESULT" | grep -q "hello" && break echo " Waiting for replication to replica2... ($i/10)" sleep 2 @@ -426,6 +436,12 @@ jobs: echo "$RESULT" | grep -q "hello" || { echo "FAIL: data not replicated to replica2 after 20s"; exit 1; } ~/sandboxes/postgresql_repl_*/replica2/use -c "SELECT * FROM test_repl;" + - name: Cleanup replication before multiple test + run: | + pkill -9 -u "$USER" postgres 2>/dev/null || true + rm -rf ~/sandboxes/postgresql_repl_* + sleep 3 + - name: Test deploy multiple --provider=postgresql run: | PG_FULL=$(ls ~/opt/postgresql/ | head -1) @@ -433,18 +449,16 @@ jobs: ./dbdeployer deploy multiple "$PG_FULL" --provider=postgresql --nodes=3 echo "=== Waiting for nodes to start ===" sleep 5 - echo "=== Checking topology dir ===" - ls ~/sandboxes/multi_msb_*/ + echo "=== Checking sandbox dirs ===" + ls ~/sandboxes/postgresql_multi_*/ echo "=== Connect to node1 ===" - ~/sandboxes/multi_msb_*/node1/use -c "SELECT version();" - ~/sandboxes/multi_msb_*/node1/use -c "CREATE TABLE multi_test(id serial, val text); INSERT INTO multi_test(val) VALUES ('from_node1');" + ~/sandboxes/postgresql_multi_*/node1/use -c "SELECT version();" echo "=== Connect to node2 ===" - ~/sandboxes/multi_msb_*/node2/use -c "SELECT version();" - ~/sandboxes/multi_msb_*/node2/use -c "INSERT INTO multi_test(val) VALUES ('from_node2');" || true + ~/sandboxes/postgresql_multi_*/node2/use -c "SELECT version();" echo "=== Connect to node3 ===" - ~/sandboxes/multi_msb_*/node3/use -c "SELECT version();" + ~/sandboxes/postgresql_multi_*/node3/use -c "SELECT version();" echo "=== Cleanup ===" - for dir in ~/sandboxes/multi_msb_*; do + for dir in ~/sandboxes/postgresql_multi_*; do [ -d "$dir" ] && bash "$dir/stop" 2>/dev/null || true rm -rf "$dir" done @@ -493,7 +507,7 @@ jobs: - name: Install system libraries run: | sudo apt-get update - sudo apt-get install -y libaio1 libnuma1 libncurses5 + sudo apt-get install -y libaio1 libnuma1 libncurses5 libprotobuf-lite23 - name: Build dbdeployer run: go build -o dbdeployer . @@ -530,6 +544,12 @@ jobs: tar xzf "/tmp/$SHELL_TARBALL" -C /tmp/ SHELL_DIR=$(ls -d /tmp/mysql-shell-${MYSQL_VERSION}* | head -1) cp "$SHELL_DIR/bin/mysqlsh" "$SANDBOX_BINARY/${MYSQL_VERSION}/bin/" + # Copy mysqlsh bundled directories (mysqlsh expects lib/mysqlsh/ and libexec/ to exist) + cp -a "$SHELL_DIR/lib/mysqlsh" "$SANDBOX_BINARY/${MYSQL_VERSION}/lib/mysqlsh" + mkdir -p "$SANDBOX_BINARY/${MYSQL_VERSION}/libexec" + cp -a "$SHELL_DIR/libexec/mysqlsh" "$SANDBOX_BINARY/${MYSQL_VERSION}/libexec/mysqlsh" + mkdir -p "$SANDBOX_BINARY/${MYSQL_VERSION}/share" + cp -a "$SHELL_DIR/share/mysqlsh" "$SANDBOX_BINARY/${MYSQL_VERSION}/share/mysqlsh" 2>/dev/null || true echo "mysqlsh installed at $SANDBOX_BINARY/${MYSQL_VERSION}/bin/mysqlsh" - name: Download and install MySQL Router @@ -563,7 +583,8 @@ jobs: ./dbdeployer deploy replication "$MYSQL_VERSION" \ --topology=innodb-cluster \ --sandbox-binary="$SANDBOX_BINARY" \ - --nodes=3 + --nodes=3 \ + || { cat ~/sandboxes/ic_msb_*/init_cluster.log 2>/dev/null; exit 1; } echo "=== Verify cluster status ===" ~/sandboxes/ic_msb_*/check_cluster @@ -575,7 +596,7 @@ jobs: echo "--- Read from node2 (should see replicated data) ---" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node2/use -e "SELECT val FROM ic_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node2/use -e "SELECT val FROM ic_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "hello_from_primary" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -585,7 +606,7 @@ jobs: echo "--- Read from node3 ---" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -e "SELECT val FROM ic_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node3/use -e "SELECT val FROM ic_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "hello_from_primary" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -594,13 +615,13 @@ jobs: echo "$RESULT" | grep -q "hello_from_primary" || { echo "FAIL: data not replicated to node3 after 20s"; exit 1; } echo "=== Functional test: connect through MySQL Router (R/W) ===" - ROUTER_RW_PORT=$(ls $SBDIR/router/mysqlrouter.conf 2>/dev/null && grep -A5 '\[routing:bootstrap_rw\]' $SBDIR/router/mysqlrouter.conf | grep 'bind_port' | awk -F= '{print $2}' | tr -d ' ' || echo "") + ROUTER_RW_PORT=$(grep -A5 '\[routing:bootstrap_rw\]' $SBDIR/router/mysqlrouter.conf 2>/dev/null | grep 'bind_port' | awk -F= '{print $2}' | tr -d ' ') if [ -n "$ROUTER_RW_PORT" ]; then echo "Router R/W port: $ROUTER_RW_PORT" $SBDIR/node1/use -h 127.0.0.1 -P "$ROUTER_RW_PORT" -e "INSERT INTO ic_test.t1 (val) VALUES ('via_router');" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node2/use -e "SELECT val FROM ic_test.t1 WHERE val='via_router';" 2>/dev/null) + RESULT=$($SBDIR/node2/use -e "SELECT val FROM ic_test.t1 WHERE val='via_router';" 2>/dev/null) || true echo "$RESULT" | grep -q "via_router" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -613,10 +634,10 @@ jobs: fi echo "=== Functional test: connect through MySQL Router (R/O) ===" - ROUTER_RO_PORT=$(ls $SBDIR/router/mysqlrouter.conf 2>/dev/null && grep -A5 '\[routing:bootstrap_ro\]' $SBDIR/router/mysqlrouter.conf | grep 'bind_port' | awk -F= '{print $2}' | tr -d ' ' || echo "") + ROUTER_RO_PORT=$(grep -A5 '\[routing:bootstrap_ro\]' $SBDIR/router/mysqlrouter.conf 2>/dev/null | grep 'bind_port' | awk -F= '{print $2}' | tr -d ' ') if [ -n "$ROUTER_RO_PORT" ]; then echo "Router R/O port: $ROUTER_RO_PORT" - RESULT=$($SBDIR/node1/use -h 127.0.0.1 -P "$ROUTER_RO_PORT" -e "SELECT val FROM ic_test.t1 WHERE val='hello_from_primary';" 2>&1) + RESULT=$($SBDIR/node1/use -h 127.0.0.1 -P "$ROUTER_RO_PORT" -e "SELECT val FROM ic_test.t1 WHERE val='hello_from_primary';" 2>&1) || true echo "$RESULT" echo "$RESULT" | grep -q "hello_from_primary" || { echo "FAIL: SELECT through Router R/O port failed"; exit 1; } echo "OK: Router R/O SELECT succeeded" @@ -634,7 +655,8 @@ jobs: --topology=innodb-cluster \ --skip-router \ --sandbox-binary="$SANDBOX_BINARY" \ - --nodes=3 + --nodes=3 \ + || { cat ~/sandboxes/ic_msb_*/init_cluster.log 2>/dev/null; exit 1; } echo "=== Verify cluster status ===" ~/sandboxes/ic_msb_*/check_cluster @@ -644,7 +666,7 @@ jobs: $SBDIR/node1/use -e "CREATE DATABASE skiprt_test; USE skiprt_test; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, msg TEXT); INSERT INTO t1 (msg) VALUES ('skip_router_test');" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -e "SELECT msg FROM skiprt_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node3/use -e "SELECT msg FROM skiprt_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "skip_router_test" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -664,7 +686,8 @@ jobs: --skip-router \ --with-proxysql \ --sandbox-binary="$SANDBOX_BINARY" \ - --nodes=3 + --nodes=3 \ + || { cat ~/sandboxes/ic_msb_*/init_cluster.log 2>/dev/null; exit 1; } echo "=== Verify cluster status ===" ~/sandboxes/ic_msb_*/check_cluster @@ -683,7 +706,7 @@ jobs: echo "--- Verify on node2 directly ---" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node2/use -e "SELECT val FROM proxy_ic_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node2/use -e "SELECT val FROM proxy_ic_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "via_proxysql" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -831,7 +854,7 @@ jobs: $SBDIR/node1/use -e "CREATE DATABASE gr_test; USE gr_test; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 (val) VALUES ('single_primary_write');" # Wait for replication to node2 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node2/use -BN -e "SELECT val FROM gr_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node2/use -BN -e "SELECT val FROM gr_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "single_primary_write" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -840,7 +863,7 @@ jobs: echo "$RESULT" | grep -q "single_primary_write" || { echo "FAIL: data not replicated to node2 after 20s"; exit 1; } # Wait for replication to node3 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM gr_test.t1;" 2>/dev/null) + RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM gr_test.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "single_primary_write" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -876,7 +899,7 @@ jobs: $SBDIR/node1/use -e "CREATE DATABASE gr_mp_test; USE gr_mp_test; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 (val) VALUES ('write_from_node1');" # Wait for replication to node3 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM gr_mp_test.t1 WHERE val='write_from_node1';" 2>/dev/null) + RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM gr_mp_test.t1 WHERE val='write_from_node1';" 2>/dev/null) || true echo "$RESULT" | grep -q "write_from_node1" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -888,7 +911,7 @@ jobs: $SBDIR/node3/use -e "INSERT INTO gr_mp_test.t1 (val) VALUES ('write_from_node3');" # Wait for replication to node1 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node1/use -BN -e "SELECT val FROM gr_mp_test.t1 WHERE val='write_from_node3';" 2>/dev/null) + RESULT=$($SBDIR/node1/use -BN -e "SELECT val FROM gr_mp_test.t1 WHERE val='write_from_node3';" 2>/dev/null) || true echo "$RESULT" | grep -q "write_from_node3" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -969,27 +992,28 @@ jobs: echo "=== Write on master1 (node1), read on common node (node3) ===" $SBDIR/node1/use -e "CREATE DATABASE fanin_test; USE fanin_test; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100), src VARCHAR(20)); INSERT INTO t1 (val, src) VALUES ('from_master1', 'node1');" - # Wait for replication to node3 with retries - for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM fanin_test.t1 WHERE src='node1';" 2>/dev/null) + # Fan-in uses multi-source replication; allow up to 30s per source + for i in $(seq 1 15); do + RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM fanin_test.t1 WHERE src='node1';" 2>/dev/null) || true echo "$RESULT" | grep -q "from_master1" && break - echo " Waiting for replication... ($i/10)" + echo " Waiting for replication from node1... ($i/15)" sleep 2 done echo "node3 sees node1 write: $RESULT" - echo "$RESULT" | grep -q "from_master1" || { echo "FAIL: node1 write not replicated to node3 after 20s"; exit 1; } + echo "$RESULT" | grep -q "from_master1" || { echo "FAIL: node1 write not replicated to node3 after 30s"; exit 1; } echo "=== Write on master2 (node2), read on common node (node3) ===" - $SBDIR/node2/use -e "INSERT INTO fanin_test.t1 (val, src) VALUES ('from_master2', 'node2');" - # Wait for replication to node3 with retries - for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM fanin_test.t1 WHERE src='node2';" 2>/dev/null) + # Use a DIFFERENT database to avoid multi-source conflict with node1's database + $SBDIR/node2/use -e "CREATE DATABASE fanin_test2; USE fanin_test2; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 (val) VALUES ('from_master2');" + # Fan-in uses multi-source replication; allow up to 30s per source + for i in $(seq 1 15); do + RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM fanin_test2.t1;" 2>/dev/null) || true echo "$RESULT" | grep -q "from_master2" && break - echo " Waiting for replication... ($i/10)" + echo " Waiting for replication from node2... ($i/15)" sleep 2 done echo "node3 sees node2 write: $RESULT" - echo "$RESULT" | grep -q "from_master2" || { echo "FAIL: node2 write not replicated to node3 after 20s"; exit 1; } + echo "$RESULT" | grep -q "from_master2" || { echo "FAIL: node2 write not replicated to node3 after 30s"; exit 1; } echo "OK: fan-in topology write+read verified" echo "=== Cleanup ===" @@ -1011,7 +1035,7 @@ jobs: $SBDIR/node1/use -e "CREATE DATABASE allm_test; USE allm_test; CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100)); INSERT INTO t1 (val) VALUES ('write_from_node1');" # Wait for replication to node3 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM allm_test.t1 WHERE val='write_from_node1';" 2>/dev/null) + RESULT=$($SBDIR/node3/use -BN -e "SELECT val FROM allm_test.t1 WHERE val='write_from_node1';" 2>/dev/null) || true echo "$RESULT" | grep -q "write_from_node1" && break echo " Waiting for replication... ($i/10)" sleep 2 @@ -1023,7 +1047,7 @@ jobs: $SBDIR/node3/use -e "INSERT INTO allm_test.t1 (val) VALUES ('write_from_node3');" # Wait for replication to node1 with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/node1/use -BN -e "SELECT val FROM allm_test.t1 WHERE val='write_from_node3';" 2>/dev/null) + RESULT=$($SBDIR/node1/use -BN -e "SELECT val FROM allm_test.t1 WHERE val='write_from_node3';" 2>/dev/null) || true echo "$RESULT" | grep -q "write_from_node3" && break echo " Waiting for replication... ($i/10)" sleep 2 diff --git a/.github/workflows/proxysql_integration_tests.yml b/.github/workflows/proxysql_integration_tests.yml index 2f6780e..16d51a7 100644 --- a/.github/workflows/proxysql_integration_tests.yml +++ b/.github/workflows/proxysql_integration_tests.yml @@ -77,6 +77,13 @@ jobs: export MYSQL_VERSION_2="" ./test/proxysql-integration-tests.sh "$SANDBOX_BINARY" + - name: Cleanup before R/W split test + run: | + ./dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" proxysql 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true + sleep 3 + - name: Test R/W split verification (#66) run: | echo "=== Deploy replication + ProxySQL for R/W split test ===" @@ -88,37 +95,43 @@ jobs: echo "=== Add query rules for R/W split ===" # Route SELECTs to reader hostgroup (HG 1), writes stay on HG 0 - ${SANDBOX_DIR}/proxysql/use -e " - INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) - VALUES (1, 1, '^SELECT', 1, 1); - LOAD MYSQL QUERY RULES TO RUNTIME; - SAVE MYSQL QUERY RULES TO DISK; - " 2>&1 | grep -v Warning - - echo "=== Baseline: record current query counts per hostgroup ===" - HG0_BEFORE=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(Queries),0) FROM stats_mysql_connection_pool WHERE hostgroup=0;" 2>&1 | grep -v Warning) - HG1_BEFORE=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(Queries),0) FROM stats_mysql_connection_pool WHERE hostgroup=1;" 2>&1 | grep -v Warning) + ${SANDBOX_DIR}/proxysql/use -e "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, '^SELECT', 1, 1);" 2>&1 | { grep -v Warning || true; } + ${SANDBOX_DIR}/proxysql/use -e "LOAD MYSQL QUERY RULES TO RUNTIME;" 2>&1 | { grep -v Warning || true; } + ${SANDBOX_DIR}/proxysql/use -e "LOAD MYSQL SERVERS TO RUNTIME;" 2>&1 | { grep -v Warning || true; } + ${SANDBOX_DIR}/proxysql/use -e "SAVE MYSQL QUERY RULES TO DISK;" 2>&1 | { grep -v Warning || true; } + + # Allow ProxySQL to apply rules and establish connections before testing + sleep 3 + + echo "=== Baseline: record current query digest counts per hostgroup ===" + HG0_BEFORE=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(count_star),0) FROM stats_mysql_query_digest WHERE hostgroup=0;" 2>&1 | { grep -v Warning || true; }) + HG1_BEFORE=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(count_star),0) FROM stats_mysql_query_digest WHERE hostgroup=1;" 2>&1 | { grep -v Warning || true; }) echo "HG0 queries before: $HG0_BEFORE" echo "HG1 queries before: $HG1_BEFORE" echo "=== Run a write (INSERT) through ProxySQL ===" - ${SANDBOX_DIR}/proxysql/use_proxy -e "CREATE DATABASE IF NOT EXISTS rw_split_test; CREATE TABLE IF NOT EXISTS rw_split_test.t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100)); INSERT INTO rw_split_test.t1 (val) VALUES ('rw_split_write_1');" 2>&1 | grep -v Warning + ${SANDBOX_DIR}/proxysql/use_proxy -e "CREATE DATABASE IF NOT EXISTS rw_split_test;" 2>&1 | { grep -v Warning || true; } + ${SANDBOX_DIR}/proxysql/use_proxy -e "CREATE TABLE IF NOT EXISTS rw_split_test.t1 (id INT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(100));" 2>&1 | { grep -v Warning || true; } + ${SANDBOX_DIR}/proxysql/use_proxy -e "INSERT INTO rw_split_test.t1 (val) VALUES ('rw_split_write_1');" 2>&1 | { grep -v Warning || true; } echo "=== Run reads (SELECTs) through ProxySQL ===" for i in $(seq 1 5); do ${SANDBOX_DIR}/proxysql/use_proxy -BN -e "SELECT val FROM rw_split_test.t1;" > /dev/null 2>&1 || true done + # Give ProxySQL time to flush stats to query digest + sleep 2 + echo "=== Check query counts increased ===" # Wait for HG0 (writer) query count to increase with retries for i in $(seq 1 10); do - HG0_AFTER=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(Queries),0) FROM stats_mysql_connection_pool WHERE hostgroup=0;" 2>&1 | grep -v Warning) + HG0_AFTER=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(count_star),0) FROM stats_mysql_query_digest WHERE hostgroup=0;" 2>&1 | { grep -v Warning || true; }) [ "$HG0_AFTER" -gt "$HG0_BEFORE" ] && break sleep 2 done # Wait for HG1 (reader) query count to increase with retries for i in $(seq 1 10); do - HG1_AFTER=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(Queries),0) FROM stats_mysql_connection_pool WHERE hostgroup=1;" 2>&1 | grep -v Warning) + HG1_AFTER=$(${SANDBOX_DIR}/proxysql/use -BN -e "SELECT COALESCE(SUM(count_star),0) FROM stats_mysql_query_digest WHERE hostgroup=1;" 2>&1 | { grep -v Warning || true; }) [ "$HG1_AFTER" -gt "$HG1_BEFORE" ] && break sleep 2 done @@ -134,7 +147,7 @@ jobs: echo "OK: HG1 (reader) received queries — R/W split is working" echo "=== Verify written data is readable through proxy ===" - RESULT=$(${SANDBOX_DIR}/proxysql/use_proxy -BN -e "SELECT val FROM rw_split_test.t1 WHERE val='rw_split_write_1';" 2>&1 | grep -v Warning) + RESULT=$(${SANDBOX_DIR}/proxysql/use_proxy -BN -e "SELECT val FROM rw_split_test.t1 WHERE val='rw_split_write_1';" 2>&1 | grep -v Warning) || true echo "Result: $RESULT" echo "$RESULT" | grep -q "rw_split_write_1" || { echo "FAIL: written data not readable through proxy"; exit 1; } echo "OK: R/W split write+read data flow verified" @@ -213,7 +226,7 @@ jobs: $SBDIR/primary/use -c "CREATE TABLE proxy_test(id serial, val text); INSERT INTO proxy_test(val) VALUES ('pg_proxysql_test');" # Wait for replication with retries for i in $(seq 1 10); do - RESULT=$($SBDIR/replica1/use -c "SELECT val FROM proxy_test;" 2>/dev/null) + RESULT=$($SBDIR/replica1/use -c "SELECT val FROM proxy_test;" 2>/dev/null) || true echo "$RESULT" | grep -q "pg_proxysql_test" && break echo " Waiting for replication... ($i/10)" sleep 2 diff --git a/cmd/replication.go b/cmd/replication.go index 0f4c559..87dcd78 100644 --- a/cmd/replication.go +++ b/cmd/replication.go @@ -270,26 +270,48 @@ func replicationSandbox(cmd *cobra.Command, args []string) { withProxySQL, _ := flags.GetBool("with-proxysql") if withProxySQL { // Determine the sandbox directory that was created - sandboxDir := path.Join(sd.SandboxDir, defaults.Defaults().MasterSlavePrefix+common.VersionToName(origin)) + var sandboxDir string if sd.DirName != "" { sandboxDir = path.Join(sd.SandboxDir, sd.DirName) + } else if topology == globals.InnoDBClusterLabel { + sandboxDir = path.Join(sd.SandboxDir, defaults.Defaults().InnoDBClusterPrefix+common.VersionToName(origin)) + } else { + sandboxDir = path.Join(sd.SandboxDir, defaults.Defaults().MasterSlavePrefix+common.VersionToName(origin)) } - // Read port info from child sandbox descriptions - masterDesc, err := common.ReadSandboxDescription(path.Join(sandboxDir, defaults.Defaults().MasterName)) - if err != nil { - common.Exitf(1, "could not read master sandbox description: %s", err) - } - masterPort := masterDesc.Port[0] - + var masterPort int var slavePorts []int - for i := 1; i < nodes; i++ { - nodeDir := path.Join(sandboxDir, fmt.Sprintf("%s%d", defaults.Defaults().NodePrefix, i)) - nodeDesc, err := common.ReadSandboxDescription(nodeDir) + + if topology == globals.InnoDBClusterLabel { + // InnoDB Cluster: node1 is primary, node2..N are secondaries + primaryDesc, err := common.ReadSandboxDescription(path.Join(sandboxDir, fmt.Sprintf("%s%d", defaults.Defaults().NodePrefix, 1))) if err != nil { - common.Exitf(1, "could not read node%d sandbox description: %s", i, err) + common.Exitf(1, "could not read primary (node1) sandbox description: %s", err) + } + masterPort = primaryDesc.Port[0] + for i := 2; i <= nodes; i++ { + nodeDir := path.Join(sandboxDir, fmt.Sprintf("%s%d", defaults.Defaults().NodePrefix, i)) + nodeDesc, err := common.ReadSandboxDescription(nodeDir) + if err != nil { + common.Exitf(1, "could not read node%d sandbox description: %s", i, err) + } + slavePorts = append(slavePorts, nodeDesc.Port[0]) + } + } else { + // Standard replication: master + node1..N-1 as slaves + masterDesc, err := common.ReadSandboxDescription(path.Join(sandboxDir, defaults.Defaults().MasterName)) + if err != nil { + common.Exitf(1, "could not read master sandbox description: %s", err) + } + masterPort = masterDesc.Port[0] + for i := 1; i < nodes; i++ { + nodeDir := path.Join(sandboxDir, fmt.Sprintf("%s%d", defaults.Defaults().NodePrefix, i)) + nodeDesc, err := common.ReadSandboxDescription(nodeDir) + if err != nil { + common.Exitf(1, "could not read node%d sandbox description: %s", i, err) + } + slavePorts = append(slavePorts, nodeDesc.Port[0]) } - slavePorts = append(slavePorts, nodeDesc.Port[0]) } err = sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, slavePorts, 0, "127.0.0.1", "", topology) diff --git a/sandbox/innodb_cluster.go b/sandbox/innodb_cluster.go index 2053473..bffe32a 100644 --- a/sandbox/innodb_cluster.go +++ b/sandbox/innodb_cluster.go @@ -18,6 +18,7 @@ package sandbox import ( "fmt" "os" + "os/exec" "path" "regexp" "time" @@ -247,6 +248,7 @@ func CreateInnoDBCluster(sandboxDef SandboxDef, origin string, nodes int, master "StopNodeList": stopNodeList, "Nodes": []common.StringMap{}, // InnoDB Cluster specific + "Basedir": sandboxDef.Basedir, "MysqlShell": mysqlshPath, "PrimaryPort": basePort + 1, "ClusterName": "mySandboxCluster", @@ -552,15 +554,11 @@ func CreateInnoDBCluster(sandboxDef SandboxDef, origin string, nodes int, master concurrent.RunParallelTasksByPriority(execLists) if !sandboxDef.SkipStart { - // First, run the standard GR initialization - common.CondPrintln(path.Join(common.ReplaceLiteralHome(sandboxDef.SandboxDir), globals.ScriptInitializeNodes)) - logger.Printf("Running group replication initialization script\n") - _, err := common.RunCmd(path.Join(sandboxDef.SandboxDir, globals.ScriptInitializeNodes)) - if err != nil { - return fmt.Errorf("error initializing group replication for InnoDB Cluster: %s", err) - } + // For InnoDB Cluster, skip the standard GR initialization. + // MySQL Shell's dba.createCluster() manages group replication itself. + // Running initialize_nodes would start GR before mysqlsh, causing conflicts. - // Then bootstrap the cluster via MySQL Shell + // Bootstrap the cluster via MySQL Shell common.CondPrintln(path.Join(common.ReplaceLiteralHome(sandboxDef.SandboxDir), globals.ScriptInitCluster)) logger.Printf("Running InnoDB Cluster initialization script\n") _, err = common.RunCmd(path.Join(sandboxDef.SandboxDir, globals.ScriptInitCluster)) @@ -605,10 +603,13 @@ func bootstrapRouter(mysqlrouterPath, routerDir string, primaryPort int, dbPassw return fmt.Errorf("mysqlrouter bootstrap failed: %s", err) } - // Start the router - startScript := path.Join(routerDir, "start.sh") - if common.FileExists(startScript) { - _, err = common.RunCmd(startScript) + // Start the router directly (not via start.sh, which backgrounds the + // process but inherits pipes — causing RunCmd to block forever). + confFile := path.Join(routerDir, "mysqlrouter.conf") + if common.FileExists(confFile) { + cmd := exec.Command(mysqlrouterPath, "-c", confFile) + cmd.Env = append(os.Environ(), fmt.Sprintf("ROUTER_PID=%s/mysqlrouter.pid", routerDir)) + err = cmd.Start() if err != nil { return fmt.Errorf("error starting MySQL Router: %s", err) } diff --git a/sandbox/templates/cluster/check_cluster.gotxt b/sandbox/templates/cluster/check_cluster.gotxt index 4783dd4..f27cd03 100644 --- a/sandbox/templates/cluster/check_cluster.gotxt +++ b/sandbox/templates/cluster/check_cluster.gotxt @@ -2,6 +2,7 @@ {{.Copyright}} # Generated by dbdeployer {{.AppVersion}} using {{.TemplateName}} on {{.DateTime}} MYSQLSH={{.MysqlShell}} +export LD_LIBRARY_PATH={{.Basedir}}/lib:{{.Basedir}}/lib/mysqlsh:$LD_LIBRARY_PATH $MYSQLSH --uri icadmin:icadmin@127.0.0.1:{{.PrimaryPort}} --js -e " var cluster = dba.getCluster(); print(cluster.status()); diff --git a/sandbox/templates/cluster/init_cluster.gotxt b/sandbox/templates/cluster/init_cluster.gotxt index 1399656..18cff32 100644 --- a/sandbox/templates/cluster/init_cluster.gotxt +++ b/sandbox/templates/cluster/init_cluster.gotxt @@ -2,28 +2,37 @@ {{.Copyright}} # Generated by dbdeployer {{.AppVersion}} using {{.TemplateName}} on {{.DateTime}} MYSQLSH={{.MysqlShell}} +export LD_LIBRARY_PATH={{.Basedir}}/lib:{{.Basedir}}/lib/mysqlsh:$LD_LIBRARY_PATH +MYSQL={{.Basedir}}/bin/mysql echo "Creating InnoDB Cluster..." -# Configure the first instance for cluster use -$MYSQLSH --uri root:{{.DbPassword}}@127.0.0.1:{{.PrimaryPort}} -- dba configure-instance --clusterAdmin=icadmin --clusterAdminPassword=icadmin --interactive=false --restart=false +# Reset GTIDs on all nodes (removes errant GTIDs from sandbox initialization) +$MYSQL -u root -p{{.DbPassword}} -h 127.0.0.1 -P {{.PrimaryPort}} -e "{{.ResetMasterCmd}}" 2>/dev/null +{{range .Replicas}} +$MYSQL -u root -p{{$.DbPassword}} -h 127.0.0.1 -P {{.Port}} -e "{{$.ResetMasterCmd}}" 2>/dev/null +{{end}} + +# Configure all instances for cluster use (creates icadmin on each) +echo "Configuring instance 127.0.0.1:{{.PrimaryPort}}..." +$MYSQLSH --uri root:{{.DbPassword}}@127.0.0.1:{{.PrimaryPort}} -- dba configure-instance --clusterAdmin=icadmin --clusterAdminPassword=icadmin --restart=false + +{{range .Replicas}} +echo "Configuring instance 127.0.0.1:{{.Port}}..." +$MYSQLSH --uri root:{{$.DbPassword}}@127.0.0.1:{{.Port}} -- dba configure-instance --clusterAdmin=icadmin --clusterAdminPassword=icadmin --restart=false +{{end}} sleep 2 -# Create the cluster on the primary +# Create the cluster on the primary (this starts group replication) +echo "Creating cluster on primary 127.0.0.1:{{.PrimaryPort}}..." $MYSQLSH --uri icadmin:icadmin@127.0.0.1:{{.PrimaryPort}} --js -e " -var cluster = dba.createCluster('{{.ClusterName}}', {memberWeight: 90}); +var cluster = dba.createCluster('{{.ClusterName}}'); print('Cluster created successfully'); " {{range .Replicas}} -echo "Adding instance 127.0.0.1:{{.Port}}..." -# Configure each replica -$MYSQLSH --uri root:{{$.DbPassword}}@127.0.0.1:{{.Port}} -- dba configure-instance --clusterAdmin=icadmin --clusterAdminPassword=icadmin --interactive=false --restart=false - -sleep 2 - -# Add to cluster +echo "Adding instance 127.0.0.1:{{.Port}} to cluster..." $MYSQLSH --uri icadmin:icadmin@127.0.0.1:{{$.PrimaryPort}} --js -e " var cluster = dba.getCluster(); cluster.addInstance('icadmin:icadmin@127.0.0.1:{{.Port}}', {recoveryMethod: 'incremental'}); @@ -31,6 +40,10 @@ cluster.addInstance('icadmin:icadmin@127.0.0.1:{{.Port}}', {recoveryMethod: 'inc sleep 3 {{end}} +# Grant performance_schema access to the replication/monitor user +# ProxySQL's GR monitor needs this to query replication_group_members +$MYSQL -u root -p{{.DbPassword}} -h 127.0.0.1 -P {{.PrimaryPort}} -e "GRANT SELECT ON performance_schema.* TO '{{.RplUser}}'@'127.%';" 2>/dev/null + echo "Checking cluster status..." $MYSQLSH --uri icadmin:icadmin@127.0.0.1:{{.PrimaryPort}} --js -e " var cluster = dba.getCluster();