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