This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 6fd8ca7d27 [Feature] [CI] Add upgrade compatibility restore workflow 
(#11301)
6fd8ca7d27 is described below

commit 6fd8ca7d27506a7ed6c415c1e1798b0d3cb397bf
Author: Goutam Adwant <[email protected]>
AuthorDate: Tue Jul 21 16:33:48 2026 +0200

    [Feature] [CI] Add upgrade compatibility restore workflow (#11301)
    
    Signed-off-by: goutamadwant <[email protected]>
    Co-authored-by: David Zollo <[email protected]>
---
 .github/workflows/upgrade_compatibility.yml        |  85 ++++
 tools/upgrade_compatibility/README.md              |  85 ++++
 .../run_upgrade_compatibility.sh                   | 505 +++++++++++++++++++++
 .../scenarios/generic-fake-localfile/assert.conf   |  52 +++
 .../scenarios/generic-fake-localfile/endless       |  16 +
 .../scenarios/generic-fake-localfile/job.conf      |  50 ++
 .../scenarios/generic-fake-localfile/plugin_config |  20 +
 .../generic-fake-localfile/seatunnel.yaml          |  43 ++
 .../mysql-cdc-multitable-localfile/assert.conf     |  52 +++
 .../mysql-cdc-multitable-localfile/endless         |  16 +
 .../mysql-cdc-multitable-localfile/job.conf        |  60 +++
 .../mysql-cdc-multitable-localfile/mysql.cnf       |  24 +
 .../mysql-cdc-multitable-localfile/mysql.sql       |  50 ++
 .../mysql-cdc-multitable-localfile/plugin_config   |  20 +
 .../mysql-cdc-multitable-localfile/seatunnel.yaml  |  43 ++
 .../mysql-cdc-multitable-localfile/setup.sh        |  65 +++
 .../mysql-cdc-multitable-localfile/teardown.sh     |  23 +
 17 files changed, 1209 insertions(+)

diff --git a/.github/workflows/upgrade_compatibility.yml 
b/.github/workflows/upgrade_compatibility.yml
new file mode 100644
index 0000000000..e76a33e81d
--- /dev/null
+++ b/.github/workflows/upgrade_compatibility.yml
@@ -0,0 +1,85 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+name: Upgrade Compatibility
+
+on:
+  workflow_dispatch:
+    inputs:
+      old_version:
+        description: "SeaTunnel release used to create the savepoint"
+        required: false
+        default: "2.3.13"
+      scenario:
+        description: "Restore scenario under 
tools/upgrade_compatibility/scenarios"
+        required: false
+        default: "all"
+  schedule:
+    - cron: "0 18 * * *"
+
+concurrency:
+  group: upgrade-compatibility-${{ github.ref }}
+  cancel-in-progress: false
+
+permissions:
+  contents: read
+
+jobs:
+  cross-version-restore:
+    name: Cross-version restore
+    runs-on: ubuntu-latest
+    timeout-minutes: 90
+    env:
+      OLD_SEATUNNEL_VERSION: ${{ github.event.inputs.old_version || '2.3.13' }}
+      SCENARIO: ${{ github.event.inputs.scenario || 'all' }}
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-java@v4
+        with:
+          distribution: temurin
+          java-version: "8"
+
+      - name: Free disk space
+        run: bash tools/github/free_disk_space.sh
+
+      - name: Build current dev distribution
+        run: >
+          ./mvnw -B -T 1 package
+          -DskipTests
+          -DskipIT=true
+          -Dlicense.skipAddThirdParty=true
+          -Dskip.ui=true
+          -pl seatunnel-dist -am
+
+      - name: Run upgrade compatibility scenarios
+        run: |
+          if [ "${SCENARIO}" = "all" ]; then
+            for scenario in generic-fake-localfile 
mysql-cdc-multitable-localfile; do
+              SCENARIO="${scenario}" bash 
tools/upgrade_compatibility/run_upgrade_compatibility.sh
+            done
+          else
+            bash tools/upgrade_compatibility/run_upgrade_compatibility.sh
+          fi
+
+      - name: Upload compatibility logs
+        if: failure()
+        uses: actions/upload-artifact@v4
+        with:
+          name: upgrade-compatibility-logs
+          path: target/upgrade-compatibility/runs/**/logs/*.log
+          if-no-files-found: ignore
diff --git a/tools/upgrade_compatibility/README.md 
b/tools/upgrade_compatibility/README.md
new file mode 100644
index 0000000000..4c3f52a29a
--- /dev/null
+++ b/tools/upgrade_compatibility/README.md
@@ -0,0 +1,85 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+# Upgrade Compatibility Checks
+
+This directory contains the scripts and scenario files used by the
+`Upgrade Compatibility` GitHub Actions workflow.
+
+The workflow verifies that a savepoint written by the previous stable SeaTunnel
+release can be restored by the current `dev` distribution. A passing run means
+the current code can read the selected scenario's checkpoint, source state and
+sink state from the older release and finish the restored job.
+
+## Current Scenarios
+
+`generic-fake-localfile` starts a streaming `FakeSource` job on the old 
release,
+writes through the transactional `LocalFile` sink, creates a savepoint while 
the
+job is still running, starts the current `dev` distribution with the same
+checkpoint storage, restores the same job id, and verifies the restored output
+with the `Assert` sink.
+
+`mysql-cdc-multitable-localfile` starts a MySQL 8 container, seeds two source
+tables, starts a multi-table `MySQL-CDC` job on the old release, writes through
+the transactional `LocalFile` sink, creates a savepoint, restores the same job
+id on the current `dev` distribution, and verifies the restored output with the
+`Assert` sink.
+
+The generic scenario is intentionally service-free. The MySQL CDC scenario 
keeps
+its Docker setup local to the scenario so database-specific setup does not leak
+into the shared runner.
+
+## Running Locally
+
+Build the current distribution first:
+
+```shell
+./mvnw -B -T 1 package -DskipTests -DskipIT=true 
-Dlicense.skipAddThirdParty=true -Dskip.ui=true -pl seatunnel-dist -am
+```
+
+Then run the default scenario:
+
+```shell
+OLD_SEATUNNEL_VERSION=2.3.13 SCENARIO=generic-fake-localfile \
+  bash tools/upgrade_compatibility/run_upgrade_compatibility.sh
+```
+
+To run the MySQL CDC scenario:
+
+```shell
+OLD_SEATUNNEL_VERSION=2.3.13 SCENARIO=mysql-cdc-multitable-localfile \
+  bash tools/upgrade_compatibility/run_upgrade_compatibility.sh
+```
+
+## Adding Scenarios
+
+Add a new directory under `scenarios/` with these files:
+
+- `seatunnel.yaml`: engine configuration template. Use `__CHECKPOINT_DIR__`
+  for the shared checkpoint namespace.
+- `job.conf`: streaming job template. Use `__SINK_DIR__` for the output path.
+- `assert.conf`: batch assertion job template. Use `__SINK_DIR__` for the
+  output path.
+- `plugin_config`: connector artifact ids required by the old release.
+- `setup.sh` and `teardown.sh`: optional executable hooks for external services
+  or extra dependency setup.
+- `endless`: optional marker file for streaming sources that should be canceled
+  after savepoint instead of waiting for natural job completion.
+
+CDC scenarios should keep their database setup local to the scenario or a
+dedicated helper script, and should avoid broadening the generic runner unless
+the same hook is useful for more than one scenario.
diff --git a/tools/upgrade_compatibility/run_upgrade_compatibility.sh 
b/tools/upgrade_compatibility/run_upgrade_compatibility.sh
new file mode 100755
index 0000000000..7f7e459bc3
--- /dev/null
+++ b/tools/upgrade_compatibility/run_upgrade_compatibility.sh
@@ -0,0 +1,505 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "$0")/../.." >/dev/null; pwd)"
+OLD_SEATUNNEL_VERSION="${OLD_SEATUNNEL_VERSION:-2.3.13}"
+SCENARIO="${SCENARIO:-generic-fake-localfile}"
+WORK_DIR="${WORK_DIR:-${ROOT_DIR}/target/upgrade-compatibility}"
+CURRENT_DIST_ARCHIVE="${CURRENT_DIST_ARCHIVE:-}"
+JOB_ID="${JOB_ID:-11239001}"
+STARTUP_TIMEOUT_SECONDS="${STARTUP_TIMEOUT_SECONDS:-90}"
+JOB_TIMEOUT_SECONDS="${JOB_TIMEOUT_SECONDS:-180}"
+SAVEPOINT_DELAY_SECONDS="${SAVEPOINT_DELAY_SECONDS:-15}"
+MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
+MYSQL_PORT="${MYSQL_PORT:-3306}"
+
+SCENARIO_DIR="${ROOT_DIR}/tools/upgrade_compatibility/scenarios/${SCENARIO}"
+OLD_ARCHIVE="${WORK_DIR}/downloads/apache-seatunnel-${OLD_SEATUNNEL_VERSION}-bin.tar.gz"
+OLD_DIST_DIR="${WORK_DIR}/old"
+CURRENT_DIST_DIR="${WORK_DIR}/current"
+RUN_DIR="${WORK_DIR}/runs/${SCENARIO}"
+CHECKPOINT_DIR="${RUN_DIR}/checkpoint"
+SINK_DIR="${RUN_DIR}/sink"
+
+OLD_SERVER_PID=""
+CURRENT_SERVER_PID=""
+OLD_JOB_PID=""
+CURRENT_RESTORE_PID=""
+SCENARIO_SETUP_STARTED="false"
+
+stage() {
+    local message="$1"
+    if [ "${GITHUB_ACTIONS:-}" = "true" ]; then
+        echo "::group::${message}"
+    fi
+    echo "==> ${message}"
+}
+
+end_stage() {
+    if [ "${GITHUB_ACTIONS:-}" = "true" ]; then
+        echo "::endgroup::"
+    fi
+}
+
+fail() {
+    echo "ERROR: $*" >&2
+    exit 1
+}
+
+cleanup() {
+    stop_process "${CURRENT_RESTORE_PID}"
+    stop_process "${OLD_JOB_PID}"
+    stop_process "${OLD_SERVER_PID}"
+    stop_process "${CURRENT_SERVER_PID}"
+    if [ "${SCENARIO_SETUP_STARTED}" = "true" ]; then
+        run_scenario_hook "teardown.sh" || true
+    fi
+}
+
+stop_process() {
+    local pid="$1"
+    if [ -n "${pid}" ] && kill -0 "${pid}" >/dev/null 2>&1; then
+        kill "${pid}" >/dev/null 2>&1 || true
+        wait "${pid}" >/dev/null 2>&1 || true
+    fi
+}
+
+trap cleanup EXIT
+
+require_file() {
+    local path="$1"
+    [ -f "${path}" ] || fail "Missing required file: ${path}"
+}
+
+download_old_distribution() {
+    stage "Download SeaTunnel ${OLD_SEATUNNEL_VERSION} distribution"
+    mkdir -p "$(dirname "${OLD_ARCHIVE}")"
+    if [ -f "${OLD_ARCHIVE}" ]; then
+        echo "Using cached archive ${OLD_ARCHIVE}"
+        end_stage
+        return
+    fi
+
+    local file_name="apache-seatunnel-${OLD_SEATUNNEL_VERSION}-bin.tar.gz"
+    local urls=(
+        
"https://downloads.apache.org/seatunnel/${OLD_SEATUNNEL_VERSION}/${file_name}";
+        
"https://archive.apache.org/dist/seatunnel/${OLD_SEATUNNEL_VERSION}/${file_name}";
+    )
+
+    local url
+    for url in "${urls[@]}"; do
+        echo "Trying ${url}"
+        if curl --fail --location --retry 3 --retry-delay 5 --output 
"${OLD_ARCHIVE}.tmp" "${url}"; then
+            mv "${OLD_ARCHIVE}.tmp" "${OLD_ARCHIVE}"
+            end_stage
+            return
+        fi
+        rm -f "${OLD_ARCHIVE}.tmp"
+    done
+
+    fail "Could not download ${file_name}"
+}
+
+find_current_distribution() {
+    if [ -n "${CURRENT_DIST_ARCHIVE}" ]; then
+        require_file "${CURRENT_DIST_ARCHIVE}"
+        echo "${CURRENT_DIST_ARCHIVE}"
+        return
+    fi
+
+    local archive
+    archive="$(find "${ROOT_DIR}/seatunnel-dist/target" -maxdepth 1 \
+        -name "apache-seatunnel-*-bin.tar.gz" \
+        ! -name "*-src.tar.gz" \
+        | sort | tail -n 1)"
+    [ -n "${archive}" ] || fail "No current dev distribution found under 
seatunnel-dist/target"
+    echo "${archive}"
+}
+
+extract_distribution() {
+    local archive="$1"
+    local target_dir="$2"
+
+    rm -rf "${target_dir}"
+    mkdir -p "${target_dir}"
+    tar -xzf "${archive}" -C "${target_dir}" --strip-components=1
+    require_file "${target_dir}/bin/seatunnel.sh"
+    require_file "${target_dir}/bin/seatunnel-cluster.sh"
+    chmod +x "${target_dir}/bin/"*.sh
+}
+
+render_template() {
+    local source="$1"
+    local target="$2"
+
+    sed \
+        -e "s#__CHECKPOINT_DIR__#${CHECKPOINT_DIR}#g" \
+        -e "s#__SINK_DIR__#${SINK_DIR}#g" \
+        -e "s#__MYSQL_HOST__#${MYSQL_HOST}#g" \
+        -e "s#__MYSQL_PORT__#${MYSQL_PORT}#g" \
+        "${source}" > "${target}"
+}
+
+run_scenario_hook() {
+    local hook_name="$1"
+    local hook_path="${SCENARIO_DIR}/${hook_name}"
+
+    if [ ! -f "${hook_path}" ]; then
+        return 0
+    fi
+    [ -x "${hook_path}" ] || fail "Scenario hook is not executable: 
${hook_path}"
+
+    SCENARIO="${SCENARIO}" \
+        SCENARIO_DIR="${SCENARIO_DIR}" \
+        ROOT_DIR="${ROOT_DIR}" \
+        RUN_DIR="${RUN_DIR}" \
+        CHECKPOINT_DIR="${CHECKPOINT_DIR}" \
+        SINK_DIR="${SINK_DIR}" \
+        OLD_DIST_DIR="${OLD_DIST_DIR}" \
+        CURRENT_DIST_DIR="${CURRENT_DIST_DIR}" \
+        OLD_SEATUNNEL_VERSION="${OLD_SEATUNNEL_VERSION}" \
+        MYSQL_HOST="${MYSQL_HOST}" \
+        MYSQL_PORT="${MYSQL_PORT}" \
+        "${hook_path}"
+}
+
+is_endless_job() {
+    [ -f "${SCENARIO_DIR}/endless" ]
+}
+
+prepare_distribution() {
+    local dist_dir="$1"
+
+    render_template "${SCENARIO_DIR}/seatunnel.yaml" 
"${dist_dir}/config/seatunnel.yaml"
+    render_template "${SCENARIO_DIR}/job.conf" 
"${dist_dir}/config/upgrade-compatibility-job.conf"
+    render_template "${SCENARIO_DIR}/assert.conf" 
"${dist_dir}/config/upgrade-compatibility-assert.conf"
+}
+
+install_old_release_connectors() {
+    stage "Install old-release scenario connectors"
+    require_file "${SCENARIO_DIR}/plugin_config"
+    cp "${SCENARIO_DIR}/plugin_config" "${OLD_DIST_DIR}/config/plugin_config"
+    mkdir -p "${OLD_DIST_DIR}/connectors"
+
+    while IFS= read -r connector || [ -n "${connector}" ]; do
+        case "${connector}" in
+            ""|\#*) continue ;;
+        esac
+        if ls "${OLD_DIST_DIR}/connectors/${connector}-"*.jar >/dev/null 2>&1; 
then
+            echo "Already present: ${connector}"
+            continue
+        fi
+        echo "Installing ${connector}:${OLD_SEATUNNEL_VERSION}"
+        "${ROOT_DIR}/mvnw" --batch-mode dependency:get \
+            -Dtransitive=false \
+            -DgroupId=org.apache.seatunnel \
+            -DartifactId="${connector}" \
+            -Dversion="${OLD_SEATUNNEL_VERSION}" \
+            -Ddest="${OLD_DIST_DIR}/connectors"
+    done < "${SCENARIO_DIR}/plugin_config"
+    end_stage
+}
+
+wait_for_log() {
+    local log_file="$1"
+    local pattern="$2"
+    local timeout_seconds="$3"
+    local elapsed=0
+
+    until grep -q "${pattern}" "${log_file}" >/dev/null 2>&1; do
+        if [ "${elapsed}" -ge "${timeout_seconds}" ]; then
+            echo "Timed out waiting for pattern '${pattern}' in ${log_file}" 
>&2
+            tail -n 200 "${log_file}" >&2 || true
+            return 1
+        fi
+        sleep 1
+        elapsed=$((elapsed + 1))
+    done
+}
+
+run_with_timeout() {
+    local timeout_seconds="$1"
+    shift
+
+    "$@" &
+    local pid="$!"
+    local timeout_marker="${WORK_DIR}/run-${pid}.timeout"
+    local watcher_pid
+    local status=0
+
+    rm -f "${timeout_marker}"
+    (
+        sleep "${timeout_seconds}"
+        if kill -0 "${pid}" >/dev/null 2>&1; then
+            touch "${timeout_marker}"
+            kill "${pid}" >/dev/null 2>&1 || true
+        fi
+    ) &
+    watcher_pid="$!"
+
+    wait "${pid}" || status="$?"
+    kill "${watcher_pid}" >/dev/null 2>&1 || true
+    wait "${watcher_pid}" >/dev/null 2>&1 || true
+
+    if [ -f "${timeout_marker}" ]; then
+        rm -f "${timeout_marker}"
+        return 124
+    fi
+    return "${status}"
+}
+
+wait_for_process() {
+    local pid="$1"
+    local timeout_seconds="$2"
+    local log_file="$3"
+    local timeout_marker="${log_file}.timeout"
+    local watcher_pid
+    local status=0
+
+    rm -f "${timeout_marker}"
+    (
+        sleep "${timeout_seconds}"
+        if kill -0 "${pid}" >/dev/null 2>&1; then
+            touch "${timeout_marker}"
+            kill "${pid}" >/dev/null 2>&1 || true
+        fi
+    ) &
+    watcher_pid="$!"
+
+    wait "${pid}" || status="$?"
+    kill "${watcher_pid}" >/dev/null 2>&1 || true
+    wait "${watcher_pid}" >/dev/null 2>&1 || true
+
+    if [ -f "${timeout_marker}" ]; then
+        rm -f "${timeout_marker}"
+        echo "Timed out waiting for process ${pid}" >&2
+        tail -n 200 "${log_file}" >&2 || true
+        return 124
+    fi
+    if [ "${status}" -ne 0 ]; then
+        tail -n 200 "${log_file}" >&2 || true
+        return "${status}"
+    fi
+    return 0
+}
+
+wait_for_process_to_stop() {
+    local pid="$1"
+    local timeout_seconds="$2"
+    local log_file="$3"
+    local timeout_marker="${log_file}.timeout"
+    local watcher_pid
+
+    rm -f "${timeout_marker}"
+    (
+        sleep "${timeout_seconds}"
+        if kill -0 "${pid}" >/dev/null 2>&1; then
+            touch "${timeout_marker}"
+            kill "${pid}" >/dev/null 2>&1 || true
+        fi
+    ) &
+    watcher_pid="$!"
+
+    wait "${pid}" >/dev/null 2>&1 || true
+    kill "${watcher_pid}" >/dev/null 2>&1 || true
+    wait "${watcher_pid}" >/dev/null 2>&1 || true
+
+    if [ -f "${timeout_marker}" ]; then
+        rm -f "${timeout_marker}"
+        echo "Timed out waiting for process ${pid} to stop" >&2
+        tail -n 200 "${log_file}" >&2 || true
+        return 124
+    fi
+    return 0
+}
+
+start_cluster() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    mkdir -p "$(dirname "${log_file}")"
+    SEATUNNEL_CONFIG="${dist_dir}/config/seatunnel.yaml" \
+        "${dist_dir}/bin/seatunnel-cluster.sh" > "${log_file}" 2>&1 &
+    local pid="$!"
+    if ! wait_for_log "${log_file}" "received new worker register" 
"${STARTUP_TIMEOUT_SECONDS}"; then
+        stop_process "${pid}"
+        return 1
+    fi
+    echo "${pid}"
+}
+
+start_old_job() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    mkdir -p "$(dirname "${log_file}")"
+    "${dist_dir}/bin/seatunnel.sh" \
+        --config "${dist_dir}/config/upgrade-compatibility-job.conf" \
+        --name "upgrade-compatibility-${SCENARIO}" \
+        --set-job-id "${JOB_ID}" \
+        > "${log_file}" 2>&1 &
+    echo "$!"
+}
+
+savepoint_job() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    "${dist_dir}/bin/seatunnel.sh" -s "${JOB_ID}" > "${log_file}" 2>&1
+}
+
+cancel_job() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    "${dist_dir}/bin/seatunnel.sh" --cancel "${JOB_ID}" > "${log_file}" 2>&1
+}
+
+restore_current_job() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    run_with_timeout "${JOB_TIMEOUT_SECONDS}" \
+        "${dist_dir}/bin/seatunnel.sh" \
+        --config "${dist_dir}/config/upgrade-compatibility-job.conf" \
+        --name "upgrade-compatibility-${SCENARIO}-restore" \
+        -r "${JOB_ID}" \
+        > "${log_file}" 2>&1
+}
+
+start_current_restore_job() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    mkdir -p "$(dirname "${log_file}")"
+    "${dist_dir}/bin/seatunnel.sh" \
+        --config "${dist_dir}/config/upgrade-compatibility-job.conf" \
+        --name "upgrade-compatibility-${SCENARIO}-restore" \
+        -r "${JOB_ID}" \
+        > "${log_file}" 2>&1 &
+    echo "$!"
+}
+
+assert_output() {
+    local dist_dir="$1"
+    local log_file="$2"
+
+    run_with_timeout "${JOB_TIMEOUT_SECONDS}" \
+        "${dist_dir}/bin/seatunnel.sh" \
+        --config "${dist_dir}/config/upgrade-compatibility-assert.conf" \
+        --name "upgrade-compatibility-${SCENARIO}-assert" \
+        > "${log_file}" 2>&1
+}
+
+assert_output_with_retry() {
+    local dist_dir="$1"
+    local log_file="$2"
+    local timeout_seconds="$3"
+    local elapsed=0
+
+    until assert_output "${dist_dir}" "${log_file}"; do
+        if [ "${elapsed}" -ge "${timeout_seconds}" ]; then
+            tail -n 200 "${log_file}" >&2 || true
+            return 1
+        fi
+        sleep 5
+        elapsed=$((elapsed + 5))
+    done
+}
+
+run_scenario() {
+    stage "Prepare scenario ${SCENARIO}"
+    require_file "${SCENARIO_DIR}/seatunnel.yaml"
+    require_file "${SCENARIO_DIR}/job.conf"
+    require_file "${SCENARIO_DIR}/assert.conf"
+
+    rm -rf "${RUN_DIR}"
+    mkdir -p "${CHECKPOINT_DIR}" "${SINK_DIR}" "${RUN_DIR}/logs"
+    prepare_distribution "${OLD_DIST_DIR}"
+    prepare_distribution "${CURRENT_DIST_DIR}"
+    SCENARIO_SETUP_STARTED="true"
+    run_scenario_hook "setup.sh"
+    end_stage
+
+    stage "Create savepoint with SeaTunnel ${OLD_SEATUNNEL_VERSION}"
+    OLD_SERVER_PID="$(start_cluster "${OLD_DIST_DIR}" 
"${RUN_DIR}/logs/old-server.log")"
+    OLD_JOB_PID="$(start_old_job "${OLD_DIST_DIR}" 
"${RUN_DIR}/logs/old-job.log")"
+    sleep "${SAVEPOINT_DELAY_SECONDS}"
+    if ! savepoint_job "${OLD_DIST_DIR}" "${RUN_DIR}/logs/old-savepoint.log"; 
then
+        tail -n 200 "${RUN_DIR}/logs/old-savepoint.log" >&2 || true
+        fail "Savepoint failed on SeaTunnel ${OLD_SEATUNNEL_VERSION}"
+    fi
+    if is_endless_job; then
+        cancel_job "${OLD_DIST_DIR}" "${RUN_DIR}/logs/old-cancel.log" || true
+        wait_for_process_to_stop "${OLD_JOB_PID}" "${JOB_TIMEOUT_SECONDS}" 
"${RUN_DIR}/logs/old-job.log"
+    else
+        wait_for_process "${OLD_JOB_PID}" "${JOB_TIMEOUT_SECONDS}" 
"${RUN_DIR}/logs/old-job.log"
+    fi
+    OLD_JOB_PID=""
+    stop_process "${OLD_SERVER_PID}"
+    OLD_SERVER_PID=""
+    end_stage
+
+    stage "Restore savepoint with current dev"
+    CURRENT_SERVER_PID="$(start_cluster "${CURRENT_DIST_DIR}" 
"${RUN_DIR}/logs/current-server.log")"
+    if is_endless_job; then
+        CURRENT_RESTORE_PID="$(start_current_restore_job "${CURRENT_DIST_DIR}" 
"${RUN_DIR}/logs/current-restore.log")"
+        sleep "${SAVEPOINT_DELAY_SECONDS}"
+    else
+        if ! restore_current_job "${CURRENT_DIST_DIR}" 
"${RUN_DIR}/logs/current-restore.log"; then
+            tail -n 200 "${RUN_DIR}/logs/current-restore.log" >&2 || true
+            fail "Restore failed on current dev"
+        fi
+    fi
+    if ! assert_output "${CURRENT_DIST_DIR}" 
"${RUN_DIR}/logs/current-assert.log"; then
+        if ! assert_output_with_retry "${CURRENT_DIST_DIR}" 
"${RUN_DIR}/logs/current-assert.log" "${JOB_TIMEOUT_SECONDS}"; then
+            tail -n 200 "${RUN_DIR}/logs/current-assert.log" >&2 || true
+            fail "Restored output assertion failed"
+        fi
+    fi
+    if is_endless_job; then
+        cancel_job "${CURRENT_DIST_DIR}" "${RUN_DIR}/logs/current-cancel.log" 
|| true
+        wait_for_process_to_stop "${CURRENT_RESTORE_PID}" 
"${JOB_TIMEOUT_SECONDS}" "${RUN_DIR}/logs/current-restore.log"
+        CURRENT_RESTORE_PID=""
+    fi
+    stop_process "${CURRENT_SERVER_PID}"
+    CURRENT_SERVER_PID=""
+    run_scenario_hook "teardown.sh"
+    SCENARIO_SETUP_STARTED="false"
+    end_stage
+}
+
+main() {
+    download_old_distribution
+
+    stage "Extract distributions"
+    local current_archive
+    current_archive="$(find_current_distribution)"
+    extract_distribution "${OLD_ARCHIVE}" "${OLD_DIST_DIR}"
+    extract_distribution "${current_archive}" "${CURRENT_DIST_DIR}"
+    end_stage
+
+    install_old_release_connectors
+    run_scenario
+
+    echo "Upgrade compatibility scenario passed: ${OLD_SEATUNNEL_VERSION} -> 
current dev (${SCENARIO})"
+}
+
+main "$@"
diff --git 
a/tools/upgrade_compatibility/scenarios/generic-fake-localfile/assert.conf 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/assert.conf
new file mode 100644
index 0000000000..29a2963959
--- /dev/null
+++ b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/assert.conf
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  job.mode = "BATCH"
+  parallelism = 1
+}
+
+source {
+  LocalFile {
+    path = "__SINK_DIR__"
+    file_format_type = "text"
+    schema = {
+      fields {
+        c_string = string
+      }
+    }
+    plugin_output = "rows"
+  }
+}
+
+sink {
+  Assert {
+    plugin_input = "rows"
+    rules {
+      row_rules = [
+        {
+          rule_type = MAX_ROW
+          rule_value = 100
+        },
+        {
+          rule_type = MIN_ROW
+          rule_value = 100
+        }
+      ]
+    }
+  }
+}
diff --git 
a/tools/upgrade_compatibility/scenarios/generic-fake-localfile/endless 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/endless
new file mode 100644
index 0000000000..cce3acad34
--- /dev/null
+++ b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/endless
@@ -0,0 +1,16 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git 
a/tools/upgrade_compatibility/scenarios/generic-fake-localfile/job.conf 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/job.conf
new file mode 100644
index 0000000000..52fa5545e4
--- /dev/null
+++ b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/job.conf
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 3000
+}
+
+source {
+  FakeSource {
+    plugin_output = "fake"
+    row.num = 100
+    split.num = 5
+    split.read-interval = 3000
+    schema = {
+      fields {
+        id = "int"
+        name = "string"
+        age = "int"
+      }
+    }
+  }
+}
+
+sink {
+  LocalFile {
+    plugin_input = "fake"
+    path = "__SINK_DIR__"
+    row_delimiter = "\n"
+    file_name_expression = "${transactionId}_${now}"
+    file_format_type = "text"
+    filename_time_format = "yyyy.MM.dd"
+    is_enable_transaction = true
+  }
+}
diff --git 
a/tools/upgrade_compatibility/scenarios/generic-fake-localfile/plugin_config 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/plugin_config
new file mode 100644
index 0000000000..9cb3237ce4
--- /dev/null
+++ b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/plugin_config
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+connector-fake
+connector-file-local
+connector-assert
diff --git 
a/tools/upgrade_compatibility/scenarios/generic-fake-localfile/seatunnel.yaml 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/seatunnel.yaml
new file mode 100644
index 0000000000..6020960d4c
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/generic-fake-localfile/seatunnel.yaml
@@ -0,0 +1,43 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+seatunnel:
+  engine:
+    history-job-expire-minutes: 60
+    backup-count: 1
+    queue-type: blockingqueue
+    print-execution-info-interval: 10
+    print-job-metrics-info-interval: 10
+    classloader-cache-mode: false
+    slot-service:
+      dynamic-slot: true
+    checkpoint:
+      interval: 3000
+      timeout: 60000
+      storage:
+        type: localfile
+        max-retained: 3
+        plugin-config:
+          namespace: __CHECKPOINT_DIR__
+    http:
+      enable-http: true
+      port: 8080
+    telemetry:
+      metric:
+        enabled: false
+      logs:
+        scheduled-deletion-enable: false
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/assert.conf
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/assert.conf
new file mode 100644
index 0000000000..2c5ca7503c
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/assert.conf
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  job.mode = "BATCH"
+  parallelism = 1
+}
+
+source {
+  LocalFile {
+    path = "__SINK_DIR__"
+    file_format_type = "text"
+    schema = {
+      fields {
+        c_string = string
+      }
+    }
+    plugin_output = "rows"
+  }
+}
+
+sink {
+  Assert {
+    plugin_input = "rows"
+    rules {
+      row_rules = [
+        {
+          rule_type = MAX_ROW
+          rule_value = 6
+        },
+        {
+          rule_type = MIN_ROW
+          rule_value = 6
+        }
+      ]
+    }
+  }
+}
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/endless 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/endless
new file mode 100644
index 0000000000..cce3acad34
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/endless
@@ -0,0 +1,16 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/job.conf 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/job.conf
new file mode 100644
index 0000000000..e7ab6d6929
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/job.conf
@@ -0,0 +1,60 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 3000
+}
+
+source {
+  MySQL-CDC {
+    plugin_output = "mysql_cdc"
+    server-id = 5652-5660
+    username = "st_user_source"
+    password = "mysqlpw"
+    table-names = ["mysql_cdc.orders", "mysql_cdc.customers"]
+    url = "jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/mysql_cdc"
+    startup.mode = "initial"
+    snapshot.split.size = 1
+    snapshot.fetch.size = 1
+    table-name-config = [
+      {
+        table = "mysql_cdc.orders"
+        primaryKeys = ["id"]
+        snapshotSplitColumn = "id"
+      },
+      {
+        table = "mysql_cdc.customers"
+        primaryKeys = ["id"]
+        snapshotSplitColumn = "id"
+      }
+    ]
+  }
+}
+
+sink {
+  LocalFile {
+    plugin_input = "mysql_cdc"
+    path = "__SINK_DIR__"
+    row_delimiter = "\n"
+    file_name_expression = "${transactionId}_${now}"
+    file_format_type = "text"
+    filename_time_format = "yyyy.MM.dd"
+    is_enable_transaction = true
+  }
+}
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.cnf
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.cnf
new file mode 100644
index 0000000000..d358228b6d
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.cnf
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[mysqld]
+server-id = 223344
+log_bin = mysql-bin
+binlog_format = row
+binlog_row_image = full
+gtid_mode = on
+enforce_gtid_consistency = on
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.sql
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.sql
new file mode 100644
index 0000000000..6538f8fb5b
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/mysql.sql
@@ -0,0 +1,50 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE USER IF NOT EXISTS 'st_user_source'@'%' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT, 
LOCK TABLES
+    ON *.* TO 'st_user_source'@'%';
+
+CREATE DATABASE IF NOT EXISTS mysql_cdc;
+
+USE mysql_cdc;
+
+CREATE TABLE orders (
+    id BIGINT NOT NULL,
+    order_code VARCHAR(64) NOT NULL,
+    amount DECIMAL(10, 2) NOT NULL,
+    PRIMARY KEY (id)
+) ENGINE = InnoDB;
+
+CREATE TABLE customers (
+    id BIGINT NOT NULL,
+    customer_code VARCHAR(64) NOT NULL,
+    region VARCHAR(64) NOT NULL,
+    PRIMARY KEY (id)
+) ENGINE = InnoDB;
+
+INSERT INTO orders (id, order_code, amount)
+VALUES
+    (1, 'order-1', 10.50),
+    (2, 'order-2', 20.25),
+    (3, 'order-3', 30.75);
+
+INSERT INTO customers (id, customer_code, region)
+VALUES
+    (1, 'customer-1', 'east'),
+    (2, 'customer-2', 'west'),
+    (3, 'customer-3', 'central');
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/plugin_config
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/plugin_config
new file mode 100644
index 0000000000..669fad884b
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/plugin_config
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+connector-cdc-mysql
+connector-file-local
+connector-assert
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/seatunnel.yaml
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/seatunnel.yaml
new file mode 100644
index 0000000000..6020960d4c
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/seatunnel.yaml
@@ -0,0 +1,43 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+seatunnel:
+  engine:
+    history-job-expire-minutes: 60
+    backup-count: 1
+    queue-type: blockingqueue
+    print-execution-info-interval: 10
+    print-job-metrics-info-interval: 10
+    classloader-cache-mode: false
+    slot-service:
+      dynamic-slot: true
+    checkpoint:
+      interval: 3000
+      timeout: 60000
+      storage:
+        type: localfile
+        max-retained: 3
+        plugin-config:
+          namespace: __CHECKPOINT_DIR__
+    http:
+      enable-http: true
+      port: 8080
+    telemetry:
+      metric:
+        enabled: false
+      logs:
+        scheduled-deletion-enable: false
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/setup.sh 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/setup.sh
new file mode 100755
index 0000000000..531cbb62c6
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/setup.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -euo pipefail
+
+MYSQL_CONTAINER_NAME="${MYSQL_CONTAINER_NAME:-seatunnel-upgrade-compatibility-mysql}"
+MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.0}"
+MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-mysqlpw}"
+MYSQL_DRIVER_VERSION="${MYSQL_DRIVER_VERSION:-8.0.32}"
+MYSQL_DRIVER_JAR="${RUN_DIR}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar"
+
+docker rm -f "${MYSQL_CONTAINER_NAME}" >/dev/null 2>&1 || true
+docker run \
+    --detach \
+    --name "${MYSQL_CONTAINER_NAME}" \
+    --publish "${MYSQL_PORT}:3306" \
+    --env MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" \
+    --volume "${SCENARIO_DIR}/mysql.cnf:/etc/mysql/conf.d/seatunnel.cnf:ro" \
+    "${MYSQL_IMAGE}" >/dev/null
+
+for _ in $(seq 1 60); do
+    if docker exec "${MYSQL_CONTAINER_NAME}" \
+        mysqladmin ping -h127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent 
>/dev/null 2>&1; then
+        break
+    fi
+    sleep 2
+done
+
+docker exec "${MYSQL_CONTAINER_NAME}" \
+    mysqladmin ping -h127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent 
>/dev/null
+docker exec -i "${MYSQL_CONTAINER_NAME}" \
+    mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" < "${SCENARIO_DIR}/mysql.sql"
+
+"${ROOT_DIR}/mvnw" --batch-mode dependency:get \
+    -Dtransitive=false \
+    -DgroupId=com.mysql \
+    -DartifactId=mysql-connector-j \
+    -Dversion="${MYSQL_DRIVER_VERSION}" \
+    -Ddest="${MYSQL_DRIVER_JAR}"
+
+install_driver() {
+    local dist_dir="$1"
+
+    mkdir -p "${dist_dir}/plugins/MySQL-CDC/lib" "${dist_dir}/lib"
+    cp "${MYSQL_DRIVER_JAR}" "${dist_dir}/plugins/MySQL-CDC/lib/"
+    cp "${MYSQL_DRIVER_JAR}" "${dist_dir}/lib/"
+}
+
+install_driver "${OLD_DIST_DIR}"
+install_driver "${CURRENT_DIST_DIR}"
diff --git 
a/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/teardown.sh
 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/teardown.sh
new file mode 100755
index 0000000000..943794bbd6
--- /dev/null
+++ 
b/tools/upgrade_compatibility/scenarios/mysql-cdc-multitable-localfile/teardown.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -euo pipefail
+
+MYSQL_CONTAINER_NAME="${MYSQL_CONTAINER_NAME:-seatunnel-upgrade-compatibility-mysql}"
+
+docker rm -f "${MYSQL_CONTAINER_NAME}" >/dev/null 2>&1 || true


Reply via email to