From 40ca4ad6d04c02e479bd768093d593c711e09f0b Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Fri, 5 Jul 2024 15:19:23 -0400 Subject: [PATCH] [NET-5622] build: consolidate Envoy version management (#21245) * build: consolidate Envoy version management Simplify Envoy version management by consolidating all runtime, build, and CI sources of Envoy versions into a single plaintext file. The goal of this change is to avoid common mistakes missing an update of some Envoy versions (both in general and due to release branch inconsistency), and enable automated Envoy version updates in the future. * ci: add missing ref argument for get-go-version Supports nightly tests. --- .../nightly-test-integ-peering_commontopo.yml | 10 +- .../nightly-test-integrations-1.15.x.yml | 50 +++++--- .../nightly-test-integrations-1.17.x.yml | 50 +++++--- .../nightly-test-integrations-1.18.x.yml | 51 +++++--- .../nightly-test-integrations-1.19.x.yml | 50 +++++--- .../workflows/nightly-test-integrations.yml | 52 +++++--- .../workflows/reusable-get-envoy-versions.yml | 71 +++++++++++ .github/workflows/reusable-get-go-version.yml | 9 ++ .github/workflows/test-integrations.yml | 35 ++--- Makefile | 3 +- command/connect/envoy/envoy.go | 4 +- command/connect/envoy/envoy_test.go | 2 +- envoyextensions/xdscommon/ENVOY_VERSIONS | 14 ++ envoyextensions/xdscommon/envoy_versioning.go | 2 +- .../xdscommon/envoy_versioning_test.go | 120 ++++++------------ envoyextensions/xdscommon/proxysupport.go | 92 +++++++++++--- .../xdscommon/proxysupport_test.go | 51 ++++++++ test/integration/connect/envoy/helpers.bash | 10 -- test/integration/connect/envoy/run-tests.sh | 5 +- 19 files changed, 460 insertions(+), 221 deletions(-) create mode 100644 .github/workflows/reusable-get-envoy-versions.yml create mode 100644 envoyextensions/xdscommon/ENVOY_VERSIONS diff --git a/.github/workflows/nightly-test-integ-peering_commontopo.yml b/.github/workflows/nightly-test-integ-peering_commontopo.yml index ce55159233..5c7f5fa23b 100644 --- a/.github/workflows/nightly-test-integ-peering_commontopo.yml +++ b/.github/workflows/nightly-test-integ-peering_commontopo.yml @@ -39,12 +39,20 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: ${{ inputs.branch }} + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: ${{ inputs.branch }} tests: runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl ) }} needs: - setup - get-go-version + - get-envoy-versions permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. contents: read @@ -62,7 +70,7 @@ jobs: name: '${{matrix.test-case}}' env: - ENVOY_VERSION: "1.29.5" + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. diff --git a/.github/workflows/nightly-test-integrations-1.15.x.yml b/.github/workflows/nightly-test-integrations-1.15.x.yml index 5bef4c85fa..8ac92f282f 100644 --- a/.github/workflows/nightly-test-integrations-1.15.x.yml +++ b/.github/workflows/nightly-test-integrations-1.15.x.yml @@ -50,6 +50,14 @@ jobs: get-go-version: needs: [check-ent] uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.15.x + + get-envoy-versions: + needs: [check-ent] + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.15.x dev-build: needs: @@ -79,36 +87,34 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # 14 based on these values: - # envoy-version: ["1.22.11", "1.23.12", "1.24.12", "1.25.11", "1.26.8", "1.27.5", "1.28.3"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 7 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -117,7 +123,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.22.11", "1.23.12", "1.24.12", "1.25.11", "1.26.8", "1.27.6", "1.28.4"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -196,7 +202,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: @@ -212,7 +218,17 @@ jobs: consul-version: ["1.14", "1.15"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: "1.24.12" steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/.github/workflows/nightly-test-integrations-1.17.x.yml b/.github/workflows/nightly-test-integrations-1.17.x.yml index 4bc89cab7c..df952d8234 100644 --- a/.github/workflows/nightly-test-integrations-1.17.x.yml +++ b/.github/workflows/nightly-test-integrations-1.17.x.yml @@ -50,6 +50,14 @@ jobs: get-go-version: needs: [check-ent] uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.17.x + + get-envoy-versions: + needs: [check-ent] + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.17.x dev-build: needs: @@ -79,36 +87,34 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.5"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 4 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -117,7 +123,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.6"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -199,7 +205,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: @@ -215,7 +221,17 @@ jobs: consul-version: ["1.15", "1.16", "1.17"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/.github/workflows/nightly-test-integrations-1.18.x.yml b/.github/workflows/nightly-test-integrations-1.18.x.yml index 9986b54e81..c432dc1c09 100644 --- a/.github/workflows/nightly-test-integrations-1.18.x.yml +++ b/.github/workflows/nightly-test-integrations-1.18.x.yml @@ -50,6 +50,13 @@ jobs: get-go-version: needs: [check-ent] uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.18.x + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.18.x dev-build: needs: @@ -79,36 +86,34 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.6"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 4 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -117,7 +122,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.6"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -199,7 +204,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: @@ -212,10 +217,20 @@ jobs: strategy: fail-fast: false matrix: - consul-version: ["1.15", "1.16", "1.17"] + consul-version: ["1.15", "1.16", "1.17", "1.18"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/.github/workflows/nightly-test-integrations-1.19.x.yml b/.github/workflows/nightly-test-integrations-1.19.x.yml index eb1f759e8f..452d174e1e 100644 --- a/.github/workflows/nightly-test-integrations-1.19.x.yml +++ b/.github/workflows/nightly-test-integrations-1.19.x.yml @@ -42,6 +42,13 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.19.x + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.19.x dev-build: needs: @@ -71,36 +78,34 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.6"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 4 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -109,7 +114,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.26.8", "1.27.6", "1.28.4", "1.29.5"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -197,6 +202,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. @@ -204,10 +210,20 @@ jobs: strategy: fail-fast: false matrix: - consul-version: ["1.15", "1.17", "1.18"] + consul-version: ["1.15", "1.17", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/.github/workflows/nightly-test-integrations.yml b/.github/workflows/nightly-test-integrations.yml index fdb63d0738..cfaa253030 100644 --- a/.github/workflows/nightly-test-integrations.yml +++ b/.github/workflows/nightly-test-integrations.yml @@ -41,6 +41,9 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + dev-build: needs: - setup @@ -55,7 +58,9 @@ jobs: elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} generate-envoy-job-matrices: - needs: [setup] + needs: + - setup + - get-envoy-versions runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} name: Generate Envoy Job Matrices outputs: @@ -68,28 +73,25 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.26.8", "1.27.5", "1.28.3", "1.29.4"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 8 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" @@ -98,6 +100,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -106,7 +109,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.26.8", "1.27.6", "1.28.4", "1.29.5"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -188,7 +191,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large ) }} needs: @@ -201,13 +204,20 @@ jobs: strategy: fail-fast: false matrix: - consul-version: [ "1.17", "1.18"] + consul-version: ["1.17", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - # ENVOY_VERSION should be the latest version upported by all - # consul versions in the matrix.consul-version, since we are testing upgrade from - # an older consul version, e.g., 1.27.x is supported by both 1.17 and 1.18. - ENVOY_VERSION: "1.27.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/.github/workflows/reusable-get-envoy-versions.yml b/.github/workflows/reusable-get-envoy-versions.yml new file mode 100644 index 0000000000..1d40587cd7 --- /dev/null +++ b/.github/workflows/reusable-get-envoy-versions.yml @@ -0,0 +1,71 @@ +name: get-envoy-versions + +# Reads the canonical ENVOY_VERSIONS file for either the current branch or a specified version of Consul, +# and returns both the max and all supported Envoy versions. + +on: + workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine supported Envoy versions. + If not provided, the default actions/checkout value (current ref) is used. + type: string + outputs: + max-envoy-version: + description: The max supported Envoy version for the specified Consul version + value: ${{ jobs.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: + description: | + All supported Envoy versions for the specified Consul version (formatted as multiline string with one version + per line, in descending order) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: + description: | + All supported Envoy versions for the specified Consul version (formatted as JSON array) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions-json }} + +jobs: + get-envoy-versions: + name: "Determine supported Envoy versions" + runs-on: ubuntu-latest + outputs: + max-envoy-version: ${{ steps.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: ${{ steps.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: ${{ steps.get-envoy-versions.outputs.envoy-versions-json }} + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} + - name: Determine Envoy versions + id: get-envoy-versions + # Note that this script assumes that the ENVOY_VERSIONS file is in the envoyextensions/xdscommon directory. + # If in the future this file moves between branches, we could introduce a workflow input for the path that + # defaults to the new value, and manually configure the old value as needed. + run: | + MAX_ENVOY_VERSION=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) + ENVOY_VERSIONS=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr) + ENVOY_VERSIONS_JSON=$(echo -n '[' && echo "${ENVOY_VERSIONS}" | awk '{printf "\"%s\",", $0}' | sed 's/,$//' && echo -n ']') + + # Loop through each line of ENVOY_VERSIONS and compare it to the regex + while IFS= read -r version; do + if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid version in ENVOY_VERSIONS: '$version' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + done <<< "$ENVOY_VERSIONS" + if ! [[ $MAX_ENVOY_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid MAX_ENVOY_VERSION: '$MAX_ENVOY_VERSION' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + + echo "Supported Envoy versions:" + echo "${ENVOY_VERSIONS}" + echo "envoy-versions<> $GITHUB_OUTPUT + echo "${ENVOY_VERSIONS}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "Supported Envoy versions JSON: ${ENVOY_VERSIONS_JSON}" + echo "envoy-versions-json=${ENVOY_VERSIONS_JSON}" >> $GITHUB_OUTPUT + echo "Max supported Envoy version: ${MAX_ENVOY_VERSION}" + echo "max-envoy-version=${MAX_ENVOY_VERSION}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/reusable-get-go-version.yml b/.github/workflows/reusable-get-go-version.yml index 2fac43b9da..91e870c73c 100644 --- a/.github/workflows/reusable-get-go-version.yml +++ b/.github/workflows/reusable-get-go-version.yml @@ -2,6 +2,12 @@ name: get-go-version on: workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine the Go version. + If not provided, the default actions/checkout value (current ref) is used. + type: string outputs: go-version: description: "The Go version detected by this workflow" @@ -19,6 +25,9 @@ jobs: go-version-previous: ${{ steps.get-go-version.outputs.go-version-previous }} steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index b106c30a53..3d57c90994 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -63,6 +63,9 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + dev-build: needs: - setup @@ -269,28 +272,25 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 2 based on these values: - # envoy-version: ["1.29.5"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 2 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" @@ -299,6 +299,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -307,11 +308,10 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.29.5"] xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: - ENVOY_VERSION: ${{ matrix.envoy-version }} + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} XDS_TARGET: ${{ matrix.xds-target }} AWS_LAMBDA_REGION: us-west-2 steps: @@ -392,13 +392,14 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. contents: read env: - ENVOY_VERSION: "1.29.5" - CONSUL_DATAPLANE_IMAGE: "docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi" + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} + CONSUL_DATAPLANE_IMAGE: "docker.io/hashicorppreview/consul-dataplane:1.5-dev-ubi" steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. diff --git a/Makefile b/Makefile index b4bf3807b9..71dcbef074 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,8 @@ CONSUL_IMAGE_VERSION?=latest # When changing the method of Go version detection, also update # version detection in CI workflows (reusable-get-go-version.yml). GOLANG_VERSION?=$(shell head -n 1 .go-version) -ENVOY_VERSION?='1.29.5' +# Takes the highest version from the ENVOY_VERSIONS file. +ENVOY_VERSION?=$(shell cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) CONSUL_DATAPLANE_IMAGE := $(or $(CONSUL_DATAPLANE_IMAGE),"docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi") DEPLOYER_CONSUL_DATAPLANE_IMAGE := $(or $(DEPLOYER_CONSUL_DATAPLANE_IMAGE), "docker.io/hashicorppreview/consul-dataplane:1.3-dev") diff --git a/command/connect/envoy/envoy.go b/command/connect/envoy/envoy.go index 3489f1017a..b974a525e9 100644 --- a/command/connect/envoy/envoy.go +++ b/command/connect/envoy/envoy.go @@ -1052,7 +1052,7 @@ func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []strin // Next build the constraint string using the bounds, make sure that we are less than but not equal to // maxSupported since we will add 1. Need to add one to the max minor version so that we accept all patches - splitS := strings.Split(xdscommon.GetMaxEnvoyMinorVersion(), ".") + splitS := strings.Split(xdscommon.GetMaxEnvoyMajorVersion(), ".") minor, err := strconv.Atoi(splitS[1]) if err != nil { return envoyCompat{}, err @@ -1061,7 +1061,7 @@ func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []strin maxSupported := fmt.Sprintf("%s.%d", splitS[0], minor) cs.Reset() - cs.WriteString(fmt.Sprintf(">= %s, < %s", xdscommon.GetMinEnvoyMinorVersion(), maxSupported)) + cs.WriteString(fmt.Sprintf(">= %s, < %s", xdscommon.GetMinEnvoyMajorVersion(), maxSupported)) constraints, err := version.NewConstraint(cs.String()) if err != nil { return envoyCompat{}, err diff --git a/command/connect/envoy/envoy_test.go b/command/connect/envoy/envoy_test.go index 6f4237af52..64fd46c158 100644 --- a/command/connect/envoy/envoy_test.go +++ b/command/connect/envoy/envoy_test.go @@ -1850,7 +1850,7 @@ func TestCheckEnvoyVersionCompatibility(t *testing.T) { }, { name: "supported-at-max", - envoyVersion: xdscommon.GetMaxEnvoyMinorVersion(), + envoyVersion: xdscommon.GetMaxEnvoyMajorVersion(), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: true, diff --git a/envoyextensions/xdscommon/ENVOY_VERSIONS b/envoyextensions/xdscommon/ENVOY_VERSIONS new file mode 100644 index 0000000000..c604e79dc1 --- /dev/null +++ b/envoyextensions/xdscommon/ENVOY_VERSIONS @@ -0,0 +1,14 @@ +# This file represents the canonical list of supported Envoy versions for this version of Consul. +# +# Every line must contain a valid version number in the format "x.y.z" where x, y, and z are integers. +# All other lines must be comments beginning with a "#", or a blank line. +# +# Every prior "minor" version for a given "major" (x.y) version is implicitly supported unless excluded by +# `xdscommon.UnsupportedEnvoyVersions`. For example, 1.28.3 implies support for 1.28.0, 1.28.1, and 1.28.2. +# +# See https://www.consul.io/docs/connect/proxies/envoy#supported-versions for more information on Consul's Envoy +# version support. +1.29.5 +1.28.4 +1.27.6 +1.26.8 \ No newline at end of file diff --git a/envoyextensions/xdscommon/envoy_versioning.go b/envoyextensions/xdscommon/envoy_versioning.go index c5f9d4798c..4b95e1bf2c 100644 --- a/envoyextensions/xdscommon/envoy_versioning.go +++ b/envoyextensions/xdscommon/envoy_versioning.go @@ -13,7 +13,7 @@ import ( var ( // minSupportedVersion is the oldest mainline version we support. This should always be // the zero'th point release of the last element of xdscommon.EnvoyVersions. - minSupportedVersion = version.Must(version.NewVersion(GetMinEnvoyMinorVersion())) + minSupportedVersion = version.Must(version.NewVersion(GetMinEnvoyMajorVersion())) specificUnsupportedVersions = []unsupportedVersion{} ) diff --git a/envoyextensions/xdscommon/envoy_versioning_test.go b/envoyextensions/xdscommon/envoy_versioning_test.go index cca8f88f78..8b0b42fc8c 100644 --- a/envoyextensions/xdscommon/envoy_versioning_test.go +++ b/envoyextensions/xdscommon/envoy_versioning_test.go @@ -4,6 +4,8 @@ package xdscommon import ( + "fmt" + "slices" "testing" envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" @@ -70,99 +72,53 @@ func TestDetermineEnvoyVersionFromNode(t *testing.T) { } func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { - const ( - errTooOld = "is too old and is not supported by Consul" - ) + const errTooOld = "is too old and is not supported by Consul" type testcase struct { + name string expect SupportedProxyFeatures expectErr string } + var cases []testcase - // Just the bad versions - cases := map[string]testcase{ - "1.9.0": {expectErr: "Envoy 1.9.0 " + errTooOld}, - "1.10.0": {expectErr: "Envoy 1.10.0 " + errTooOld}, - "1.11.0": {expectErr: "Envoy 1.11.0 " + errTooOld}, - "1.12.0": {expectErr: "Envoy 1.12.0 " + errTooOld}, - "1.12.1": {expectErr: "Envoy 1.12.1 " + errTooOld}, - "1.12.2": {expectErr: "Envoy 1.12.2 " + errTooOld}, - "1.12.3": {expectErr: "Envoy 1.12.3 " + errTooOld}, - "1.12.4": {expectErr: "Envoy 1.12.4 " + errTooOld}, - "1.12.5": {expectErr: "Envoy 1.12.5 " + errTooOld}, - "1.12.6": {expectErr: "Envoy 1.12.6 " + errTooOld}, - "1.12.7": {expectErr: "Envoy 1.12.7 " + errTooOld}, - "1.13.0": {expectErr: "Envoy 1.13.0 " + errTooOld}, - "1.13.1": {expectErr: "Envoy 1.13.1 " + errTooOld}, - "1.13.2": {expectErr: "Envoy 1.13.2 " + errTooOld}, - "1.13.3": {expectErr: "Envoy 1.13.3 " + errTooOld}, - "1.13.4": {expectErr: "Envoy 1.13.4 " + errTooOld}, - "1.13.5": {expectErr: "Envoy 1.13.5 " + errTooOld}, - "1.13.6": {expectErr: "Envoy 1.13.6 " + errTooOld}, - "1.13.7": {expectErr: "Envoy 1.13.7 " + errTooOld}, - "1.14.0": {expectErr: "Envoy 1.14.0 " + errTooOld}, - "1.14.1": {expectErr: "Envoy 1.14.1 " + errTooOld}, - "1.14.2": {expectErr: "Envoy 1.14.2 " + errTooOld}, - "1.14.3": {expectErr: "Envoy 1.14.3 " + errTooOld}, - "1.14.4": {expectErr: "Envoy 1.14.4 " + errTooOld}, - "1.14.5": {expectErr: "Envoy 1.14.5 " + errTooOld}, - "1.14.6": {expectErr: "Envoy 1.14.6 " + errTooOld}, - "1.14.7": {expectErr: "Envoy 1.14.7 " + errTooOld}, - "1.15.0": {expectErr: "Envoy 1.15.0 " + errTooOld}, - "1.15.1": {expectErr: "Envoy 1.15.1 " + errTooOld}, - "1.15.2": {expectErr: "Envoy 1.15.2 " + errTooOld}, - "1.15.3": {expectErr: "Envoy 1.15.3 " + errTooOld}, - "1.15.4": {expectErr: "Envoy 1.15.4 " + errTooOld}, - "1.15.5": {expectErr: "Envoy 1.15.5 " + errTooOld}, - "1.16.1": {expectErr: "Envoy 1.16.1 " + errTooOld}, - "1.16.2": {expectErr: "Envoy 1.16.2 " + errTooOld}, - "1.16.3": {expectErr: "Envoy 1.16.3 " + errTooOld}, - "1.16.4": {expectErr: "Envoy 1.16.4 " + errTooOld}, - "1.16.5": {expectErr: "Envoy 1.16.5 " + errTooOld}, - "1.16.6": {expectErr: "Envoy 1.16.6 " + errTooOld}, - "1.17.4": {expectErr: "Envoy 1.17.4 " + errTooOld}, - "1.18.6": {expectErr: "Envoy 1.18.6 " + errTooOld}, - "1.19.5": {expectErr: "Envoy 1.19.5 " + errTooOld}, - "1.20.7": {expectErr: "Envoy 1.20.7 " + errTooOld}, - "1.21.5": {expectErr: "Envoy 1.21.5 " + errTooOld}, - "1.22.0": {expectErr: "Envoy 1.22.0 " + errTooOld}, - "1.22.1": {expectErr: "Envoy 1.22.1 " + errTooOld}, - "1.22.2": {expectErr: "Envoy 1.22.2 " + errTooOld}, - "1.22.3": {expectErr: "Envoy 1.22.3 " + errTooOld}, - "1.22.4": {expectErr: "Envoy 1.22.4 " + errTooOld}, - "1.22.5": {expectErr: "Envoy 1.22.5 " + errTooOld}, - "1.22.6": {expectErr: "Envoy 1.22.6 " + errTooOld}, - "1.22.7": {expectErr: "Envoy 1.22.7 " + errTooOld}, - "1.22.8": {expectErr: "Envoy 1.22.8 " + errTooOld}, - "1.22.9": {expectErr: "Envoy 1.22.9 " + errTooOld}, - "1.22.10": {expectErr: "Envoy 1.22.10 " + errTooOld}, - "1.22.11": {expectErr: "Envoy 1.22.11 " + errTooOld}, + // Bad versions. + minMajorVersion := version.Must(version.NewVersion(getMinEnvoyVersion())) + minMajorVersionMajorPart := minMajorVersion.Segments()[len(minMajorVersion.Segments())-2] + for major := 9; major < minMajorVersionMajorPart; major++ { + for minor := 0; minor < 10; minor++ { + cases = append(cases, testcase{ + name: version.Must(version.NewVersion(fmt.Sprintf("1.%d.%d", major, minor))).String(), + expectErr: errTooOld, + }) + } } - // Insert a bunch of valid versions. - // Populate feature flags here when appropriate. See consul 1.10.x for reference. - /* Example from 1.18 - for _, v := range []string{ - "1.18.0", "1.18.1", "1.18.2", "1.18.3", "1.18.4", "1.18.5", "1.18.6", - } { - cases[v] = testcase{expect: SupportedProxyFeatures{ - ForceLDSandCDSToAlwaysUseWildcardsOnReconnect: true, - }} - } - */ - for _, v := range []string{ - "1.26.0", "1.26.1", "1.26.2", "1.26.3", "1.26.4", "1.26.5", "1.26.6", "1.26.7", "1.26.8", - "1.27.0", "1.27.1", "1.27.2", "1.27.3", "1.27.4", "1.27.5", "1.27.6", - "1.28.0", "1.28.1", "1.28.2", "1.28.3", "1.28.4", - "1.29.0", "1.29.1", "1.29.2", "1.29.3", "1.29.4", "1.29.5", - } { - cases[v] = testcase{expect: SupportedProxyFeatures{}} + // Good versions. + // Sort ascending so test output is ordered like bad cases above. + var supportedVersionsAscending []string + supportedVersionsAscending = append(supportedVersionsAscending, EnvoyVersions...) + slices.Reverse(supportedVersionsAscending) + for _, v := range supportedVersionsAscending { + envoyVersion := version.Must(version.NewVersion(v)) + // e.g. this is 27 in 1.27.4 + versionMajorPart := envoyVersion.Segments()[len(envoyVersion.Segments())-2] + // e.g. this is 4 in 1.27.4 + versionMinorPart := envoyVersion.Segments()[len(envoyVersion.Segments())-1] + + // Create synthetic minor versions from .0 through the actual configured version. + for minor := 0; minor <= versionMinorPart; minor++ { + minorVersion := version.Must(version.NewVersion(fmt.Sprintf("1.%d.%d", versionMajorPart, minor))) + cases = append(cases, testcase{ + name: minorVersion.String(), + expect: SupportedProxyFeatures{}, + }) + } } - for name, tc := range cases { + for _, tc := range cases { tc := tc - t.Run(name, func(t *testing.T) { - sf, err := DetermineSupportedProxyFeaturesFromString(name) + t.Run(tc.name, func(t *testing.T) { + sf, err := DetermineSupportedProxyFeaturesFromString(tc.name) if tc.expectErr == "" { require.NoError(t, err) require.Equal(t, tc.expect, sf) diff --git a/envoyextensions/xdscommon/proxysupport.go b/envoyextensions/xdscommon/proxysupport.go index 77c1da0ad5..d0afcc7390 100644 --- a/envoyextensions/xdscommon/proxysupport.go +++ b/envoyextensions/xdscommon/proxysupport.go @@ -3,7 +3,64 @@ package xdscommon -import "strings" +import ( + _ "embed" + "fmt" + "slices" + "strconv" + "strings" +) + +// File containing the canonical range of supported Envoy versions for this version of Consul. +// This file should contain exactly one point release for each major release of Envoy, per line. +// All other contents must be blank lines or comments. Comments must be on their own line starting with '#'. +// +//go:embed ENVOY_VERSIONS +var envoyVersionsRaw string + +// initEnvoyVersions calls parseEnvoyVersions and panics if it returns an error. Used to set EnvoyVersions. +func initEnvoyVersions() []string { + versions, err := parseEnvoyVersions(envoyVersionsRaw) + if err != nil { + panic(err) + } + return versions +} + +// parseEnvoyVersions parses the ENVOY_VERSIONS file and returns a list of supported Envoy versions. +func parseEnvoyVersions(raw string) ([]string, error) { + lines := strings.Split(raw, "\n") + var versionLines []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue // skip empty lines and comments + } + + // Assume all remaining lines are valid Envoy versions in the format "X.Y.Z". + versionParts := strings.Split(trimmed, ".") + if len(versionParts) != 3 { + return nil, fmt.Errorf("invalid version in ENVOY_VERSIONS: %s", line) + } + for _, v := range versionParts { + if _, err := strconv.Atoi(v); err != nil { + return nil, fmt.Errorf("invalid version in ENVOY_VERSIONS: %s", line) + } + } + versionLines = append(versionLines, trimmed) + } + + // Ensure sorted in descending order. + // We do this here as well as tests because other code (e.g. Makefile) may depend on the order + // of these values, so we want early detection in case tests are not run before compilation. + if !slices.IsSortedFunc(versionLines, func(v1, v2 string) int { + return strings.Compare(v2, v1) + }) { + return nil, fmt.Errorf("ENVOY_VERSIONS must be sorted in descending order") + } + + return versionLines, nil +} // EnvoyVersions lists the latest officially supported versions of envoy. // @@ -11,12 +68,7 @@ import "strings" // each major release should be present. // // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions -var EnvoyVersions = []string{ - "1.29.5", - "1.28.4", - "1.27.6", - "1.26.8", -} +var EnvoyVersions = initEnvoyVersions() // UnsupportedEnvoyVersions lists any unsupported Envoy versions (mainly minor versions) that fall // within the range of EnvoyVersions above. @@ -27,18 +79,28 @@ var EnvoyVersions = []string{ // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions var UnsupportedEnvoyVersions = []string{} -// GetMaxEnvoyMinorVersion grabs the first value in EnvoyVersions and strips the patch number off in order -// to return the maximum supported Envoy minor version +// GetMaxEnvoyMajorVersion grabs the first value in EnvoyVersions and strips the last number off in order +// to return the maximum supported Envoy "major" version. // For example, if the input string is "1.14.1", the function would return "1.14". -func GetMaxEnvoyMinorVersion() string { - s := strings.Split(EnvoyVersions[0], ".") +func GetMaxEnvoyMajorVersion() string { + s := strings.Split(getMaxEnvoyVersion(), ".") return s[0] + "." + s[1] } -// GetMinEnvoyMinorVersion grabs the last value in EnvoyVersions and strips the patch number off in order -// to return the minimum supported Envoy minor version +// GetMinEnvoyMajorVersion grabs the last value in EnvoyVersions and strips the patch number off in order +// to return the minimum supported Envoy "major" version. // For example, if the input string is "1.12.1", the function would return "1.12". -func GetMinEnvoyMinorVersion() string { - s := strings.Split(EnvoyVersions[len(EnvoyVersions)-1], ".") +func GetMinEnvoyMajorVersion() string { + s := strings.Split(getMinEnvoyVersion(), ".") return s[0] + "." + s[1] } + +// getMaxEnvoyVersion returns the first (highest) value in EnvoyVersions. +func getMaxEnvoyVersion() string { + return EnvoyVersions[0] +} + +// getMinEnvoyVersion returns the last (lowest) value in EnvoyVersions. +func getMinEnvoyVersion() string { + return EnvoyVersions[len(EnvoyVersions)-1] +} diff --git a/envoyextensions/xdscommon/proxysupport_test.go b/envoyextensions/xdscommon/proxysupport_test.go index cc90b726c9..d618478345 100644 --- a/envoyextensions/xdscommon/proxysupport_test.go +++ b/envoyextensions/xdscommon/proxysupport_test.go @@ -4,6 +4,7 @@ package xdscommon import ( + "slices" "sort" "testing" @@ -11,11 +12,17 @@ import ( "github.com/stretchr/testify/assert" ) +// TestProxySupportOrder tests that the values in EnvoyVersions are valid (X.Y.Z), contiguous by "major" (X.Y) version, +// and sorted in descending order. func TestProxySupportOrder(t *testing.T) { versions := make([]*version.Version, len(EnvoyVersions)) beforeSort := make([]*version.Version, len(EnvoyVersions)) for i, raw := range EnvoyVersions { v, _ := version.NewVersion(raw) + if v.Segments()[0] != 1 { + // If this fails, we need to add support for a new semver-major (x in x.y.z) version of Envoy + t.Fatalf("Expected major version to be 1, got: %v", v.Segments()[0]) + } versions[i] = v beforeSort[i] = v } @@ -30,4 +37,48 @@ func TestProxySupportOrder(t *testing.T) { for i := range EnvoyVersions { assert.True(t, versions[i].Equal(beforeSort[i])) } + + // Check that we have a continues set of versions + for i := 1; i < len(versions); i++ { + previousMajorVersion := getMajorVersion(versions[i-1]) + majorVersion := getMajorVersion(versions[i]) + assert.True(t, majorVersion == previousMajorVersion-1, + "Expected Envoy major version following %d.%d to be %d.%d, got %d.%d", + versions[i-1].Segments()[0], + previousMajorVersion, + versions[i-1].Segments()[0], + previousMajorVersion-1, + versions[i].Segments()[0], + majorVersion) + } +} + +func TestParseEnvoyVersions(t *testing.T) { + // Test with valid versions, comments, and blank lines + raw := "# Comment\n1.29.4\n\n# More\n# comments\n1.28.3\n\n1.27.5\n1.26.8\n\n" + expected := []string{"1.29.4", "1.28.3", "1.27.5", "1.26.8"} + + versions, err := parseEnvoyVersions(raw) + assert.NoError(t, err) + + if !slices.Equal(versions, expected) { + t.Fatalf("Expected %v, got: %v", expected, versions) + } + + // Test with invalid version + raw = "1.29.4\n1.26.8\nfoo" + + _, err = parseEnvoyVersions(raw) + assert.EqualError(t, err, "invalid version in ENVOY_VERSIONS: foo") + + // Test with out-of-order values + raw = "1.29.4\n1.26.8\n1.27.5" + + _, err = parseEnvoyVersions(raw) + assert.EqualError(t, err, "ENVOY_VERSIONS must be sorted in descending order") +} + +// getMajorVersion returns the "major" (Y in X.Y.Z) version of the given Envoy version. +func getMajorVersion(version *version.Version) int { + return version.Segments()[1] } diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index dad6508930..3efcd38e82 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -182,16 +182,6 @@ function assert_envoy_version { echo "Got version=$VERSION" echo "Want version=$ENVOY_VERSION" - # 1.20.2, 1.19.3 and 1.18.6 are special snowflakes in that the version for - # the release is reported with a '-dev' suffix (eg 1.20.2-dev). - if [ "$ENVOY_VERSION" = "1.20.2" ]; then - ENVOY_VERSION="1.20.2-dev" - elif [ "$ENVOY_VERSION" = "1.19.3" ]; then - ENVOY_VERSION="1.19.3-dev" - elif [ "$ENVOY_VERSION" = "1.18.6" ]; then - ENVOY_VERSION="1.18.6-dev" - fi - echo $VERSION | grep "/$ENVOY_VERSION/" } diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 1f825268b0..46dcb9965f 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -15,7 +15,10 @@ DEBUG=${DEBUG:-} XDS_TARGET=${XDS_TARGET:-server} # ENVOY_VERSION to run each test against -ENVOY_VERSION=${ENVOY_VERSION:-"1.29.5"} +if [[ -z "${ENVOY_VERSION:-}" ]]; then + echo "please set Envoy version via ENVOY_VERSION" + exit 1 +fi export ENVOY_VERSION export DOCKER_BUILDKIT=1