Xavier Simonart via dev <[email protected]> writes:

> When ovn is upgraded, ovn-controller is updated first on
> the compute nodes. Then ovn-northd and DB are upgraded.
> This patch tests whether the intermediate state (i.e. with
> ovn-controller being upgraded) works properly, running system
> tests from the base line (i.e. before the upgrade).
>
> Flow tables might change between releases.
> Hence this patch must take that into account by updating the (old)
> system tests with any updated table numbers.
> In some cases, (new) ovn-controller might change flows in existing
> tables, causing some 'upgrade' tests to fail.
> Such tests can be skipped using the TAG_TEST_NOT_UPGRADABLE tag.
>
> This patch upgrades the ci to run automatically some upgrade tests
> weekly. It also provides a shell script to run those tests locally.
>
> Upgrade-tests are run on push/pull only for LTS (24.03) and latest
> release (25.09) to avoid too long tests. Upgrades from other
> branches are run on schedule.
>
> This patch depends on patch [1] on branch-25.09.
>
> [1] "tests: Add new TAG_TEST_NOT_UPGRADABLE to some tests."
>
> Reported-at: https://issues.redhat.com/browse/FDP-1240
> Signed-off-by: Xavier Simonart <[email protected]>
> ---

Just an fyi - the CI system sometimes gives weird results when mixing
branch-XX.YY; what I mean is each time the script is going to apply a
patch, it will switch branches if that was specified.  In this case, it
means the first patch in this series will switch to branch-25.09, and
the second patch will be applied on top of that branch as well.

>  .ci/ci.sh                  |   1 +
>  .ci/linux-build.sh         |  95 +++++++++-
>  .ci/linux-util.sh          | 367 +++++++++++++++++++++++++++++++++++++
>  .ci/test-upgrade-local.sh  | 202 ++++++++++++++++++++
>  .github/workflows/test.yml |  41 ++++-
>  Makefile.am                |   1 +
>  tests/ovn-macros.at        |   6 +
>  7 files changed, 697 insertions(+), 16 deletions(-)
>  create mode 100755 .ci/test-upgrade-local.sh
>
> diff --git a/.ci/ci.sh b/.ci/ci.sh
> index 3640d3243..6798fbd78 100755
> --- a/.ci/ci.sh
> +++ b/.ci/ci.sh
> @@ -102,6 +102,7 @@ function run_tests() {
>          ARCH=$ARCH CC=$CC LIBS=$LIBS OPTS=$OPTS TESTSUITE=$TESTSUITE \
>          TEST_RANGE=$TEST_RANGE SANITIZERS=$SANITIZERS DPDK=$DPDK \
>          RECHECK=$RECHECK UNSTABLE=$UNSTABLE TIMEOUT=$TIMEOUT \
> +        BASE_VERSION=$BASE_VERSION \
>          ./.ci/linux-build.sh
>      "
>  }
> diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
> index 183833a16..890f4c59d 100755
> --- a/.ci/linux-build.sh
> +++ b/.ci/linux-build.sh
> @@ -1,7 +1,10 @@
>  #!/bin/bash
>  
>  set -o errexit
> -set -x
> +# Enable debug output for CI, optional for local
> +if [ "${NO_DEBUG:-0}" = "0" ]; then
> +    set -x
> +fi
>  
>  ARCH=${ARCH:-"x86_64"}
>  USE_SPARSE=${USE_SPARSE:-"yes"}
> @@ -181,7 +184,7 @@ function run_system_tests()
>  
>      if ! sudo timeout -k 5m -v $TIMEOUT make $JOBS $type \
>          TESTSUITEFLAGS="$TEST_RANGE" RECHECK=$RECHECK \
> -        SKIP_UNSTABLE=$SKIP_UNSTABLE; then
> +        SKIP_UNSTABLE=$SKIP_UNSTABLE UPGRADE_TEST=$UPGRADE_TEST; then
>          # $log_file is necessary for debugging.
>          cat tests/$log_file
>          return 1
> @@ -190,19 +193,28 @@ function run_system_tests()
>  
>  function execute_system_tests()
>  {
> -    configure_ovn $OPTS
> -    make $JOBS || { cat config.log; exit 1; }
> +    local test_type=$1
> +    local log_file=$2
> +    local skip_build=$3
> +
> +    # Only build if not already built (upgrade tests build separately)
> +    if [ "$skip_build" != "yes" ]; then
> +        configure_ovn $OPTS
> +        make $JOBS || { cat config.log; exit 1; }
> +    fi
>  
>      local stable_rc=0
>      local unstable_rc=0
>  
> -    if ! SKIP_UNSTABLE=yes run_system_tests $@; then
> +    if ! SKIP_UNSTABLE=yes UPGRADE_TEST=$UPGRADE_TEST \
> +            run_system_tests $test_type $log_file; then
>          stable_rc=1
>      fi
>  
>      if [ "$UNSTABLE" ]; then
>          if ! SKIP_UNSTABLE=no TEST_RANGE="-k unstable" RECHECK=yes \
> -                run_system_tests $@; then
> +                UPGRADE_TEST=$UPGRADE_TEST run_system_tests $test_type \
> +                                                            $log_file; then
>              unstable_rc=1
>          fi
>      fi
> @@ -212,6 +224,72 @@ function execute_system_tests()
>      fi
>  }
>  
> +function execute_upgrade_tests()
> +{
> +    . .ci/linux-util.sh
> +
> +    # Save current CI scripts (will be replaced by base version after 
> checkout)
> +    cp -rf .ci /tmp/ovn-upgrade-ci
> +
> +    # Build current version
> +    log "Building current version..."
> +    mkdir -p logs
> +    configure_ovn $OPTS >> logs/build-current.log 2>&1 || {
> +        log "configure ovn failed - see config.log and 
> logs/build-current.log"
> +        exit 1
> +    }
> +    make $JOBS >> logs/build-current.log 2>&1 || {
> +        log "building ovn failed - see logs/build-current.log"
> +        exit 1
> +    }
> +
> +    ovn_upgrade_save_current_binaries
> +
> +    # Checkout base version
> +    ovn_upgrade_checkout_base "$BASE_VERSION" logs/git.log
> +
> +    # Clean from current version
> +    log "Cleaning build artifacts..."
> +    make distclean >> logs/build-base.log 2>&1 || true
> +    (cd ovs && make distclean >> ../logs/build-base.log 2>&1) || true
> +
> +    # Apply test patches
> +    ovn_upgrade_apply_tests_patches
> +
> +    # Build base with patches
> +    ovn_upgrade_patch_for_ovn_debug
> +
> +    # Build (modified) base version
> +    log "Building base version (with patched lflow.h)..."
> +    configure_ovn $OPTS >> logs/build-base.log 2>&1 || {
> +        log "configure ovn failed - see config.log and logs/build-base.log"
> +        exit 1
> +    }
> +    make $JOBS >> logs/build-base.log 2>&1 || {
> +        log "building ovn failed - see logs/build-base.log"
> +        exit 1
> +    }
> +    ovn_upgrade_save_ovn_debug
> +
> +    # Build (clean) base version
> +    log "Rebuilding base version (clean lflow.h)..."
> +    git checkout controller/lflow.h >> logs/git.log 2>&1
> +    make $JOBS >> logs/build-base.log 2>&1 || {
> +        log "building ovn failed - see logs/build-base.log"
> +        exit 1
> +    }
> +
> +    # Restore binaries
> +    ovn_upgrade_restore_binaries
> +
> +    # Restore current CI scripts for test execution
> +    cp -f /tmp/ovn-upgrade-ci/linux-build.sh .ci/linux-build.sh
> +    cp -f /tmp/ovn-upgrade-ci/linux-util.sh .ci/linux-util.sh
> +
> +    UPGRADE_TEST=yes execute_system_tests "check-kernel" \
> +                                          "system-kmod-testsuite.log" "yes"
> +}
> +
>  configure_$CC
>  
>  if [ "$TESTSUITE" ]; then
> @@ -238,6 +316,11 @@ if [ "$TESTSUITE" ]; then
>          sudo bash -c "echo 2048 > 
> /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages"
>          execute_system_tests "check-system-dpdk" "system-dpdk-testsuite.log"
>          ;;
> +
> +        "upgrade-test")
> +        execute_upgrade_tests
> +        ;;
> +
>      esac
>  else
>      configure_ovn $OPTS
> diff --git a/.ci/linux-util.sh b/.ci/linux-util.sh
> index b5bd1f8c9..a62ff2ea0 100755
> --- a/.ci/linux-util.sh
> +++ b/.ci/linux-util.sh
> @@ -49,3 +49,370 @@ function disable_apparmor()
>      sudo aa-teardown || true
>      sudo systemctl disable --now apparmor.service
>  }
> +
> +function log() {
> +    echo "[$(date '+%H:%M:%S')] $*"
> +}
> +
> +# ovn_upgrade_save_current_binaries
> +# Saves current version's binaries and schemas to /tmp/ovn-upgrade-binaries/
> +function ovn_upgrade_save_current_binaries()
> +{
> +    mkdir -p /tmp/ovn-upgrade-binaries
> +
> +    # New ovn-controller may generate OpenFlow flows with new actions that 
> old
> +    # OVS doesn't understand, so we need also to use new OVS.
> +    # New ovs-vswitchd binary expects columns/tables defined in the current
> +    # schema, so we also need to use new schemas.
> +    files="controller/ovn-controller ovs/vswitchd/ovs-vswitchd
> +           ovs/ovsdb/ovsdb-server ovs/utilities/ovs-vsctl
> +           ovs/utilities/ovs-ofctl ovs/utilities/ovs-appctl
> +           ovs/utilities/ovs-dpctl ovs/vswitchd/vswitch.ovsschema"
> +    for file in $files; do
> +        if [ ! -f "$file" ]; then
> +            log "ERROR: $file not found"
> +            return 1
> +        fi
> +        cp "$file" /tmp/ovn-upgrade-binaries/
> +    done
> +
> +    # In the upgrade scenario we use old ovn-northd and new ovn-controller.
> +    # OFTABLES are defined through a combination of northd/northd.h and
> +    # controller/lflow.h. Tests uses either (old) table numbers, table names
> +    # (defined in ovn-macros) or ovn-debug.
> +    #
> +    # Extract OFCTL_* table defines from current lflow.h
> +    if ! grep '^#define OFTABLE_' controller/lflow.h > \
> +        /tmp/ovn-upgrade-ofctl-defines.h; then
> +        log "No #define OFTABLE_ found in lflow.h"
> +        return 1
> +    fi
> +
> +    # Extract OFTABLE m4 defines from current tests/ovn-macros.at
> +    # These are used by tests to reference table numbers
> +    # In old tests, there might be no OFTABLE_ in ovn-macros, so grep can 
> fail.
> +    grep '^m4_define(\[OFTABLE_' tests/ovn-macros.at > \
> +        /tmp/ovn-upgrade-oftable-m4-defines.txt || true
> +
> +    # Extract key table numbers for calculating shifts in hardcoded table
> +    # references. OFTABLE_SAVE_INPORT is where normal (unshifted) tables
> +    # resume.
> +    LINE=$(grep "define OFTABLE_LOG_EGRESS_PIPELINE" controller/lflow.h)
> +    NEW_LOG_EGRESS=$(echo "$LINE" | grep -oE '[0-9]+')
> +    LINE=$(grep "define OFTABLE_SAVE_INPORT" controller/lflow.h)
> +    NEW_SAVE_INPORT=$(echo "$LINE" | grep -oE '[0-9]+')
> +    if [ -z "$NEW_LOG_EGRESS" ]; then
> +        log "ERROR: Could not extract OFTABLE_LOG_EGRESS_PIPELINE value"
> +        return 1
> +    fi
> +    if [ -z "$NEW_SAVE_INPORT" ]; then
> +        log "ERROR: Could not extract OFTABLE_SAVE_INPORT value"
> +        return 1
> +    fi
> +
> +    echo "$NEW_LOG_EGRESS" > /tmp/ovn-upgrade-new-log-egress.txt
> +    echo "$NEW_SAVE_INPORT" > /tmp/ovn-upgrade-new-save-inport.txt
> +
> +    echo ""
> +    log "Saved current versions:"
> +    log " ovn-controller:$(/tmp/ovn-upgrade-binaries/ovn-controller 
> --version |
> +        grep ovn-controller)"
> +    log " SB DB schema:$(/tmp/ovn-upgrade-binaries/ovn-controller --version |
> +        grep "SB DB Schema")"
> +    log " ovs-vswitchd:$(/tmp/ovn-upgrade-binaries/ovs-vswitchd --version |
> +        grep vSwitch)"
> +}
> +
> +# ovn_upgrade_checkout_base BASE_VERSION LOG_FILE
> +# Checks out base version from git
> +function ovn_upgrade_checkout_base()
> +{
> +    local base_version=$1
> +    local log_file=$2
> +
> +    log "Checking out base version: $base_version"
> +
> +    # Try to checkout directly first (might already exist locally)
> +    if git checkout "$base_version" >> "$log_file" 2>&1; then
> +        log "Using locally available $base_version"
> +    else
> +        # Not available locally, try to fetch it
> +        log "Fetching $base_version from origin..."
> +
> +        # Try as a tag first
> +        if git fetch --depth=1 origin tag "$base_version" \
> +            >> "$log_file" 2>&1; then
> +            log "Fetched tag $base_version"
> +
> +        # Try as a branch
> +        elif git fetch --depth=1 origin "$base_version" \
> +            >> "$log_file" 2>&1; then
> +            log "Fetched branch $base_version"
> +
> +        else
> +            git fetch origin >> "$log_file" 2>&1 || true
> +            log "Fetched all refs from origin"
> +        fi
> +
> +        # Try checkout
> +        if git checkout "$base_version" >> "$log_file" 2>&1; then
> +            log "Using $base_version from origin"
> +        else
> +            # origin might be a private repo w/o all branches.
> +            # Try ovn-org as fallback.
> +            log "Not in origin, fetching from ovn-org..."
> +            git fetch https://github.com/ovn-org/ovn.git \
> +                "$base_version:$base_version" >> "$log_file" 2>&1 || return 1
> +            log "Fetched $base_version from ovn-org"
> +            git checkout "$base_version" >> "$log_file" 2>&1 || return 1
> +        fi
> +    fi
> +
> +    git submodule update --init >> "$log_file" 2>&1 || return 1
> +}
> +
> +# Patch base version's lflow.h with current OFTABLE table defines
> +# This ensures ovn-debug uses correct table numbers
> +function ovn_upgrade_patch_for_ovn_debug()
> +{
> +    if [ -f /tmp/ovn-upgrade-ofctl-defines.h ] && \
> +       [ -f controller/lflow.h ]; then
> +        # Replace old OFCTL defines with current ones in one pass
> +        awk '
> +            !inserted && /^#define OFTABLE_/ {
> +                system("cat /tmp/ovn-upgrade-ofctl-defines.h")
> +                inserted = 1
> +            }
> +            /^#define OFTABLE_/ { next }
> +            { print }
> +        ' controller/lflow.h > controller/lflow.h.tmp
> +
> +        mv controller/lflow.h.tmp controller/lflow.h
> +    fi
> +}
> +
> +# ovn_upgrade_save_ovn_debug
> +# Saves ovn-debug binary built with current OFTABLE defines
> +# This creates a hybrid ovn-debug: current table numbers + base logical flow
> +# stages
> +function ovn_upgrade_save_ovn_debug()
> +{
> +    log "Saving hybrid ovn-debug..."
> +    cp utilities/ovn-debug /tmp/ovn-upgrade-binaries/ovn-debug
> +}
> +
> +# update_test old_first_table old_last_table shift test_file
> +# Update test tables in test_file, for old_first <= tables < old_last_table
> +function update_test()
> +{
> +    test_file=$4
> +    awk -v old_start=$1 \
> +        -v old_end=$2 \
> +        -v shift=$3 '
> +    {
> +        result = ""
> +        rest = $0
> +        # Process all table=NUMBER matches in the line
> +        while (match(rest, /table *= *[0-9]+/)) {
> +            # Save match position before calling match() again
> +            pos = RSTART
> +            len = RLENGTH
> +
> +            # Add everything before the match
> +            result = result substr(rest, 1, pos-1)
> +
> +            # Extract the matched text and the number
> +            matched = substr(rest, pos, len)
> +            if (match(matched, /[0-9]+/)) {
> +                num = substr(matched, RSTART, RLENGTH)
> +            } else {
> +                num = 0
> +            }
> +
> +            # Check if this table number needs updating
> +            if (num >= old_start && num < old_end) {
> +                result = result "table=" (num + shift)
> +            } else {
> +                result = result matched
> +            }
> +
> +            # Continue with the rest of the line (use saved pos/len)
> +            rest = substr(rest, pos + len)
> +        }
> +        # Add any remaining text
> +        print result rest
> +    }' "$test_file" > "$test_file.tmp" && mv "$test_file.tmp" "$test_file"
> +}
> +
> +# ovn_upgrade_table_numbers_in_tests_patch: fix hardcoded table numbers in
> +# test files
> +function ovn_upgrade_table_numbers_in_tests_patch()
> +{
> +    # Old tests (e.g., branch-24.03) have hardcoded numbers like "table=45"
> +    # which refer to specific logical tables. When OFTABLE defines shift,
> +    # these numbers must be updated.
> +    # Example: v24.03.0 has OFTABLE_LOG_EGRESS_PIPELINE=42, so "table=45"
> +    # means egress+3.
> +    # In main, OFTABLE_LOG_EGRESS_PIPELINE=47, so it should become 
> "table=50".
> +    if [ ! -f /tmp/ovn-upgrade-new-log-egress.txt ] ||
> +       [ ! -f /tmp/ovn-upgrade-new-save-inport.txt ]; then
> +        log "WARNING: Table shift data not found, skipping hardcoded table \
> +             number updates"
> +        return
> +    fi
> +
> +    if [ ! -f controller/lflow.h ]; then
> +        log "WARNING: controller/lflow.h not found, skipping hardcoded table 
> \
> +             number updates"
> +        return
> +    fi
> +
> +    NEW_LOG_EGRESS=$(cat /tmp/ovn-upgrade-new-log-egress.txt)
> +    NEW_SAVE_INPORT=$(cat /tmp/ovn-upgrade-new-save-inport.txt)
> +
> +    # Get old values from base version's lflow.h (before we patched it)
> +    LINE=$(grep "#define OFTABLE_LOG_EGRESS_PIPELINE" controller/lflow.h)
> +    OLD_LOG_EGRESS=$(echo "$LINE" | grep -oE '[0-9]+')
> +    LINE=$(grep "#define OFTABLE_SAVE_INPORT" controller/lflow.h)
> +    OLD_SAVE_INPORT=$(echo "$LINE" | grep -oE '[0-9]+')
> +
> +    if [ -z "$OLD_LOG_EGRESS" ] || [ -z "$OLD_SAVE_INPORT" ] || \
> +       [ "$OLD_LOG_EGRESS" == "$NEW_LOG_EGRESS" ]; then
> +       log "No change in tests files as old_log_egress=$OLD_LOG_EGRESS,
> +            old_save_inport=$OLD_SAVE_INPORT and
> +            new_log_egress=$NEW_LOG_EGRESS"
> +       return
> +    fi
> +
> +    # Calculate the shift
> +    SHIFT=$((NEW_LOG_EGRESS - OLD_LOG_EGRESS))
> +
> +    log "Updating hardcoded table numbers in tests (shift: +$SHIFT for 
> tables \
> +         $OLD_LOG_EGRESS-$((OLD_SAVE_INPORT-1)))"
> +
> +    # Update hardcoded table numbers in test files
> +    for test_file in tests/system-ovn.at tests/system-ovn-kmod.at; do
> +        if [ -f "$test_file" ]; then
> +            log "Updating $test_file"
> +            update_test "$OLD_LOG_EGRESS" "$OLD_SAVE_INPORT" "$SHIFT" \
> +                        "$test_file"
> +        fi
> +    done
> +}
> +
> +# ovn_upgrade_cleanup_sbox_patch: filter out expected schema warnings.
> +function ovn_upgrade_cleanup_sbox_patch()
> +{
> +    cat << 'EOF' > /tmp/upgrade-schema-filter.patch
> +diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> +index a08252d50..a31dc87b4 100644
> +--- a/tests/ovn-macros.at
> ++++ b/tests/ovn-macros.at
> +@@ -98,6 +98,7 @@ m4_define([OVN_CLEANUP_SBOX],[
> +         $error
> +         /connection failed (No such file or directory)/d
> +         /has no network name*/d
> ++        /OVN_Southbound database lacks/d
> +         /receive tunnel port not found*/d
> +         /Failed to locate tunnel to reach main chassis/d
> +         /Transaction causes multiple rows.*MAC_Binding/d
> +diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
> +index 6f6670199..4bd1a2c90 100644
> +--- a/tests/system-kmod-macros.at
> ++++ b/tests/system-kmod-macros.at
> +@@ -45,7 +45,8 @@ m4_define([OVS_TRAFFIC_VSWITCHD_START],
> + # invoked. They can be used to perform additional cleanups such as name 
> space
> + # removal.
> + m4_define([OVS_TRAFFIC_VSWITCHD_STOP],
> +-  [OVS_VSWITCHD_STOP([$1])
> ++  [OVS_VSWITCHD_STOP([dnl
> ++$1";/OVN_Southbound database lacks/d"])
> +    AT_CHECK([:; $2])
> +   ])
> +
> +EOF
> +
> +    # Try to apply schema filter patch. May fail on old OVN versions where
> +    # OVN_CLEANUP_SBOX doesn't check errors - this is expected and okay.
> +    # If patch fails for more recent OVN, then the test will fail due to the
> +    # "OVN_Southbound database lacks".
> +    patch -p1 < /tmp/upgrade-schema-filter.patch > /dev/null 2>&1 || true
> +    rm -f /tmp/upgrade-schema-filter.patch
> +}
> +
> +# ovn_upgrade_oftable_ovn_macro_patch: update table numbers in ovn-macro
> +function ovn_upgrade_oftable_ovn_macro_patch()
> +{
> +    # Patch base version's tests/ovn-macros.at with current OFTABLE m4 
> defines
> +    # This ensures tests use correct table numbers when checking flows
> +    if [ -f /tmp/ovn-upgrade-oftable-m4-defines.txt ] &&
> +       [ -f tests/ovn-macros.at ]; then
> +        # Check if the base version has OFTABLE m4 defines
> +        if grep -q '^m4_define(\[OFTABLE_' tests/ovn-macros.at; then
> +            # Replace old m4_define OFTABLE statements with current ones
> +            awk '
> +                !inserted && /^m4_define\(\[OFTABLE_/ {
> +                    system("cat /tmp/ovn-upgrade-oftable-m4-defines.txt")
> +                    inserted = 1
> +                }
> +                /^m4_define\(\[OFTABLE_/ { next }
> +                { print }
> +            ' tests/ovn-macros.at > tests/ovn-macros.at.tmp
> +
> +            mv tests/ovn-macros.at.tmp tests/ovn-macros.at
> +        fi
> +    fi
> +}
> +
> +# Applies patches to base version after second build:
> +# 1. Schema error patch (filters "OVN_Southbound database lacks" warnings)
> +# 2. OFTABLE m4 defines patch in tests/ovn-macros.at (for test table numbers)
> +# 3. Hardcoded table numbers patch in test files
> +function ovn_upgrade_apply_tests_patches()
> +{
> +    log "Applying schema filter and table number patches..."
> +    ovn_upgrade_table_numbers_in_tests_patch
> +    ovn_upgrade_cleanup_sbox_patch
> +    ovn_upgrade_oftable_ovn_macro_patch
> +}
> +
> +# ovn_upgrade_restore_binaries
> +#
> +# Replaces base version binaries with saved current versions:
> +# - ovn-controller (from current)
> +# - OVS binaries and schema (from current)
> +# - ovn-debug (hybrid: current OFTABLE + base logical stages)
> +function ovn_upgrade_restore_binaries()
> +{
> +    log "Replacing binaries with current versions"
> +
> +    # Replace OVN controller
> +    cp /tmp/ovn-upgrade-binaries/ovn-controller controller/ovn-controller
> +
> +    # Replace ovn-debug with hybrid version (built with current OFTABLE + 
> base
> +    # northd.h)
> +    cp /tmp/ovn-upgrade-binaries/ovn-debug utilities/ovn-debug
> +
> +    # Replace OVS binaries
> +    cp /tmp/ovn-upgrade-binaries/ovs-vswitchd ovs/vswitchd/ovs-vswitchd
> +    cp /tmp/ovn-upgrade-binaries/ovsdb-server ovs/ovsdb/ovsdb-server
> +    cp /tmp/ovn-upgrade-binaries/ovs-vsctl ovs/utilities/ovs-vsctl
> +    cp /tmp/ovn-upgrade-binaries/ovs-ofctl ovs/utilities/ovs-ofctl
> +    cp /tmp/ovn-upgrade-binaries/ovs-appctl ovs/utilities/ovs-appctl
> +    cp /tmp/ovn-upgrade-binaries/ovs-dpctl ovs/utilities/ovs-dpctl
> +
> +    # Replace OVS schema (current binaries expect current schema)
> +    cp /tmp/ovn-upgrade-binaries/vswitch.ovsschema \
> +       ovs/vswitchd/vswitch.ovsschema
> +
> +    echo ""
> +    log "Verification - Current versions (from current patch):"
> +    log "  ovn-controller: $(controller/ovn-controller --version |
> +         grep ovn-controller)"
> +    log "  SB DB Schema: $(controller/ovn-controller --version |
> +         grep "SB DB Schema")"
> +    log "  ovs-vswitchd: $(ovs/vswitchd/ovs-vswitchd --version | grep 
> vSwitch)"
> +    log "Verification - Base versions (for compatibility testing):"
> +    log "  ovn-northd: $(northd/ovn-northd --version | grep ovn-northd)"
> +    log "  ovn-nbctl: $(utilities/ovn-nbctl --version | grep ovn-nbctl)"
> +}
> diff --git a/.ci/test-upgrade-local.sh b/.ci/test-upgrade-local.sh
> new file mode 100755
> index 000000000..3818ac465
> --- /dev/null
> +++ b/.ci/test-upgrade-local.sh
> @@ -0,0 +1,202 @@
> +#!/bin/bash
> +
> +set -e
> +
> +. "$(dirname $0)/linux-util.sh"
> +
> +BASE_VERSION="${BASE_VERSION:-branch-24.03}"
> +TEST_RANGE="${TEST_RANGE:-1-}"
> +KEEPALIVE_INT=50
> +
> +CLEANUP_DONE=0
> +TEST_STATUS=1
> +
> +usage() {
> +    cat << EOF
> +Usage: $0 [options]
> +
> +Test OVN upgrade compatibility without GitHub.
> +
> +Options:
> +    -b, --base-version VERSION   Base version to test (default: branch-24.03)
> +    -t, --test-range RANGE       Test range to run (default: 1-)
> +                                 Examples: -100, 101-, 55
> +    -h, --help                   Show this help message
> +
> +Environment Variables:
> +    BASE_VERSION                 Same as --base-version
> +    TEST_RANGE                   Same as --test-range
> +
> +Examples:
> +    # Test against branch-24.03 with all tests
> +    $0
> +
> +    # Test against specific version
> +    $0 --base-version v24.03.0
> +
> +    # Test specific test range
> +    $0 --test-range 101-200
> +EOF
> +}
> +
> +# Parse command line arguments
> +while [[ $# -gt 0 ]]; do
> +    case $1 in
> +        -b|--base-version)
> +            BASE_VERSION="$2"
> +            shift 2
> +            ;;
> +        -t|--test-range)
> +            TEST_RANGE="$2"
> +            shift 2
> +            ;;
> +        -h|--help)
> +            usage
> +            exit 0
> +            ;;
> +        *)
> +            echo "Unknown option: $1"
> +            usage
> +            exit 1
> +            ;;
> +    esac
> +done
> +
> +log "========================================"
> +log "OVN Upgrade Test"
> +log "Base version: $BASE_VERSION"
> +log "Test range: $TEST_RANGE"
> +
> +# Check if we're in the OVN repository root
> +if [ ! -f "configure.ac" ] || ! grep -q "ovn" configure.ac; then
> +    log "Error: This script must be run from the OVN repository root"
> +    exit 1
> +fi
> +
> +start_sudo_keepalive() {
> +    (while true; do sudo -n true; sleep "$KEEPALIVE_INT"; done) 2>/dev/null &
> +    SUDO_KEEPALIVE_PID=$!
> +    if ! kill -0 $SUDO_KEEPALIVE_PID 2>/dev/null; then
> +        log "ERROR: sudo keepalive failed to start"
> +        exit 1
> +    fi
> +}
> +
> +stop_sudo_keepalive() {
> +    if [ -n "$SUDO_KEEPALIVE_PID" ]; then
> +        kill $SUDO_KEEPALIVE_PID 2>/dev/null || true
> +    fi
> +}
> +
> +# Cleanup function - always runs on exit
> +cleanup() {
> +    if [ $CLEANUP_DONE -eq 1 ]; then
> +        return
> +    fi
> +    CLEANUP_DONE=1
> +
> +    stop_sudo_keepalive
> +
> +    echo
> +    log "Cleaning up..."
> +    log "Restoring modified files..."
> +    # Restore OVN test files
> +    git checkout tests/ovn-macros.at tests/system-kmod-macros.at \
> +                tests/system-ovn.at tests/system-ovn-kmod.at \
> +                >> logs/git.log 2>&1 || true
> +    # Restore OVS submodule files
> +    (cd ovs && git checkout vswitchd/vswitch.ovsschema \
> +        >> ../logs/git.log 2>&1 || true)
> +    # Restore CI scripts (may have been replaced during upgrade test)
> +    git checkout .ci/linux-build.sh .ci/linux-util.sh \
> +                >> logs/git.log 2>&1 || true
> +
> +    log "Restoring original branch/commit..."
> +    # If we were on a branch, restore to it; otherwise restore to commit
> +    if [ "$CURRENT_BRANCH" != "HEAD" ]; then
> +        if ! git checkout "$CURRENT_BRANCH" >> logs/git.log 2>&1; then
> +            log "WARNING: Failed to restore branch $CURRENT_BRANCH" >&2
> +        fi
> +    else
> +        # We were in detached HEAD state, restore to the commit
> +        if ! git checkout "$CURRENT_COMMIT" >> logs/git.log 2>&1; then
> +            log "WARNING: Failed to restore commit $CURRENT_COMMIT" >&2
> +        fi
> +    fi
> +
> +    log "Updating submodules..."
> +    git submodule update --init >> logs/git.log 2>&1 || true
> +    log "Restored to: $CURRENT_BRANCH ($CURRENT_COMMIT)"
> +
> +    # Cleanup temporary files
> +    rm -rf /tmp/ovn-upgrade-binaries /tmp/ovn-upgrade-ci
> +    rm -f /tmp/ovn-upgrade-ofctl-defines.h
> +    rm -f /tmp/ovn-upgrade-oftable-m4-defines.txt
> +    rm -f /tmp/ovn-upgrade-new-log-egress.txt
> +    rm -f /tmp/ovn-upgrade-new-save-inport.txt
> +}
> +
> +trap cleanup EXIT INT TERM QUIT HUP
> +
> +# Request sudo credentials early
> +if ! sudo -nv 2>/dev/null; then
> +    log "This script requires sudo for running system tests."
> +    log "Please enter your password now:"
> +    sudo -v || {
> +        log "Error: sudo authentication failed"
> +        exit 1
> +    }
> +fi
> +
> +start_sudo_keepalive
> +
> +# Save current branch/commit
> +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
> +CURRENT_COMMIT=$(git rev-parse HEAD)
> +log "Current branch: $CURRENT_BRANCH"
> +log "Current commit: $CURRENT_COMMIT"
> +echo
> +
> +# Check if working directory is clean
> +if ! git diff-index --quiet HEAD --; then
> +    log "Warning: Working directory has uncommitted changes"
> +    read -p "Continue anyway? (y/n) " -n 1 -r
> +    echo
> +    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
> +        exit 1
> +    fi
> +fi
> +
> +# Create logs directory
> +mkdir -p logs
> +
> +# Export environment variables for linux-build.sh
> +export TESTSUITE="upgrade-test"
> +export BASE_VERSION="$BASE_VERSION"
> +export TEST_RANGE="$TEST_RANGE"
> +export JOBS="${JOBS:--j$(nproc 2>/dev/null || echo 4)}"
> +export CC="${CC:-gcc}"
> +export NO_DEBUG="${NO_DEBUG:-1}"  # Disable verbose set -x output by default
> +export USE_SPARSE="${USE_SPARSE:-no}"  # Disable sparse for local tests
> +
> +log "Running upgrade tests via linux-build.sh..."
> +echo
> +
> +# Run linux-build.sh which will call execute_upgrade_tests()
> +if ./.ci/linux-build.sh; then
> +    echo
> +    log "Upgrade test completed successfully"
> +    TEST_STATUS=0
> +else
> +    echo
> +    log "Upgrade test failed - check logs"
> +    TEST_STATUS=1
> +fi
> +
> +# Print summary
> +echo
> +log "Logs saved to:"
> +log "  - logs/git.log"
> +log "  - tests/system-kmod-testsuite.log"
> +
> +exit $TEST_STATUS
> diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
> index b6e461129..06b677f55 100644
> --- a/.github/workflows/test.yml
> +++ b/.github/workflows/test.yml
> @@ -94,7 +94,6 @@ jobs:
>  
>      name: linux ${{ join(matrix.cfg.*, ' ') }}
>      runs-on: ubuntu-24.04
> -
>      strategy:
>        fail-fast: false
>        matrix:
> @@ -123,30 +122,42 @@ jobs:
>          - { compiler: clang, testsuite: system-test, sanitizers: sanitizers, 
> test_range: "-100" }
>          - { compiler: clang, testsuite: system-test, sanitizers: sanitizers, 
> test_range: "101-200" }
>          - { compiler: clang, testsuite: system-test, sanitizers: sanitizers, 
> test_range: "201-", unstable: unstable }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-24.03, test_range: "-100" }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-24.03, test_range: "101-", unstable: unstable }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-24.09, test_range: "-100", run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-24.09, test_range: "101-200", run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-24.09, test_range: "201-", unstable: unstable, run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.03, test_range: "-100", run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.03, test_range: "101-200", run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.03, test_range: "201-", unstable: unstable, run_on: schedule }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.09, test_range: "-100" }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.09, test_range: "101-200" }
> +        - { compiler: gcc, testsuite: upgrade-test, base_version: 
> branch-25.09, test_range: "201-", unstable: unstable }
>          - { arch: x86, compiler: gcc, opts: --disable-ssl }
>  
>      steps:
>      - name: system-level-dependencies
> -      if: ${{ startsWith(matrix.cfg.testsuite, 'system-test') }}
> +      if: ${{ (startsWith(matrix.cfg.testsuite, 'system-test') || 
> (matrix.cfg.testsuite == 'upgrade-test')) && (matrix.cfg.run_on != 'schedule' 
> || github.event_name == 'schedule') }}
>        run: |
>          sudo apt update
>          sudo apt -y install linux-modules-extra-$(uname -r)
>  
>      - name: checkout
> -      if: github.event_name == 'push' || github.event_name == 'pull_request'
> +      if: (github.event_name == 'push' || github.event_name == 
> 'pull_request' || matrix.cfg.testsuite == 'upgrade-test') && 
> (matrix.cfg.run_on != 'schedule' || github.event_name == 'schedule')
>        uses: actions/checkout@v4
>        with:
>          submodules: recursive
> +        fetch-depth: ${{ matrix.cfg.testsuite == 'upgrade-test' && 0 || 1 }}
>  
> -    # For weekly runs, don't update submodules
> +    # For weekly runs (no upgrade-tests), don't update submodules
>      - name: checkout without submodule
> -      if: github.event_name == 'schedule'
> +      if: github.event_name == 'schedule' && matrix.cfg.testsuite != 
> 'upgrade-test'
>        uses: actions/checkout@v4
>  
> -    # Weekly runs test using the tip of the most recent stable OVS branch
> +    # Weekly runs (no upgrade-tests) test using the tip of the most recent 
> stable OVS branch
>      # instead of the submodule.
>      - name: checkout OVS
> -      if: github.event_name == 'schedule'
> +      if: github.event_name == 'schedule' && matrix.cfg.testsuite != 
> 'upgrade-test'
>        uses: actions/checkout@v4
>        with:
>          repository: 'openvswitch/ovs'
> @@ -154,7 +165,7 @@ jobs:
>          path: 'ovs'
>  
>      - name: checkout OVS most recent stable branch.
> -      if: github.event_name == 'schedule'
> +      if: github.event_name == 'schedule' && matrix.cfg.testsuite != 
> 'upgrade-test'
>        run: |
>          git checkout \
>            $(git branch -a -l '*branch-*' | sed 's/remotes\/origin\///' | \
> @@ -162,16 +173,19 @@ jobs:
>        working-directory: ovs
>  
>      - name: Fix /etc/hosts file
> +      if: matrix.cfg.run_on != 'schedule' || github.event_name == 'schedule'
>        run: |
>          . .ci/linux-util.sh
>          fix_etc_hosts
>  
>      - name: Disable apparmor
> +      if: matrix.cfg.run_on != 'schedule' || github.event_name == 'schedule'
>        run: |
>          . .ci/linux-util.sh
>          disable_apparmor
>  
>      - name: image cache
> +      if: matrix.cfg.run_on != 'schedule' || github.event_name == 'schedule'
>        id: image_cache
>        uses: actions/cache@v4
>        with:
> @@ -179,17 +193,24 @@ jobs:
>          key: ${{ github.sha }}/${{ github.event_name }}
>  
>      - name: load image
> +      if: matrix.cfg.run_on != 'schedule' || github.event_name == 'schedule'
>        run: |
>          sudo podman load -i /tmp/image.tar
>          podman load -i /tmp/image.tar
>          rm -rf /tmp/image.tar
>  
> +    # Set BASE_VERSION env var for upgrade tests
> +    - name: Set upgrade test env
> +      if: matrix.cfg.testsuite == 'upgrade-test' && (matrix.cfg.run_on != 
> 'schedule' || github.event_name == 'schedule')
> +      run: echo "BASE_VERSION=${{ matrix.cfg.base_version }}" >> $GITHUB_ENV
> +
> +    # Regular build steps
>      - name: build
> -      if: ${{ startsWith(matrix.cfg.testsuite, 'system-test') }}
> +      if: ${{ (startsWith(matrix.cfg.testsuite, 'system-test') || 
> (matrix.cfg.testsuite == 'upgrade-test')) && (matrix.cfg.run_on != 'schedule' 
> || github.event_name == 'schedule') }}
>        run: sudo -E ./.ci/ci.sh --archive-logs --timeout=2h
>  
>      - name: build
> -      if: ${{ !startsWith(matrix.cfg.testsuite, 'system-test') }}
> +      if: ${{ !startsWith(matrix.cfg.testsuite, 'system-test') && 
> matrix.cfg.testsuite != 'upgrade-test' && (matrix.cfg.run_on != 'schedule' || 
> github.event_name == 'schedule') }}
>        run: ./.ci/ci.sh --archive-logs --timeout=2h
>  
>      - name: upload logs on failure
> diff --git a/Makefile.am b/Makefile.am
> index 3ad2077b3..e57bfb297 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -91,6 +91,7 @@ EXTRA_DIST = \
>       .ci/linux-util.sh \
>       .ci/osx-build.sh \
>       .ci/osx-prepare.sh \
> +     .ci/test-upgrade-local.sh \
>       .ci/ovn-kubernetes/Dockerfile \
>       .ci/ovn-kubernetes/prepare.sh \
>       .ci/ovn-kubernetes/custom.patch \
> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> index 1c981e5a8..834ed60be 100644
> --- a/tests/ovn-macros.at
> +++ b/tests/ovn-macros.at
> @@ -1520,6 +1520,12 @@ m4_define([TAG_UNSTABLE], [
>      AT_SKIP_IF([test X"$SKIP_UNSTABLE" = Xyes])
>  ])
>  
> +# TAG_TEST_NOT_UPGRADABLE tag indicates that the test would fail
> +# "upgrade" test (i.e. running old ovn-northd and new ovn-controller)
> +m4_define([TAG_TEST_NOT_UPGRADABLE], [
> +    AT_SKIP_IF([test X"$UPGRADE_TEST" = Xyes])
> +])
> +
>  m4_define([OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT],
>  [
>      AT_SKIP_IF([test $HAVE_SCAPY = no])

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to