diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 75a99abd7842..0e1a770af1ed 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Go", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm", + "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm", // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 1c04a756b9ab..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 90 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 21 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - "stage/tracked" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions. -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -# pulls: -# daysUntilStale: 30 -# markComment: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/antithesis-test.yml b/.github/workflows/antithesis-test.yml new file mode 100644 index 000000000000..914829c22a9d --- /dev/null +++ b/.github/workflows/antithesis-test.yml @@ -0,0 +1,115 @@ +--- +name: Build and trigger Antithesis exploration + +on: + # Disabled as discussed in https://github.com/etcd-io/etcd/pull/19750#issuecomment-2809840402 + # pull_request: + # branches: [main] + # schedule: + # - cron: "0 0 * * *" # run every day at midnight + workflow_dispatch: + inputs: + test: + description: 'Test name' + required: false + default: 'etcd nightly antithesis run' + type: string + config_image: + description: 'Config image' + required: true + default: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-config:latest + type: string + images: + description: 'System images (separate with ;)' + required: true + default: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-client:latest;us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-server:latest;docker.io/library/ubuntu:latest + type: string + duration: + description: 'Duration (exploration hours)' + required: true + type: int + default: 12 + description: + description: 'Description (avoid quotes, please!)' + required: true + type: string + default: "etcd nightly antithesis run" + etcd_ref: + description: 'etcd version to build etcd-server from' + required: false + type: string + default: 'release-3.5' + email: + description: 'Additional email notification recipient (separate with ;)' + required: true + type: string + default: "" + +# Declare default permissions as read only. +permissions: read-all + +env: + REGISTRY: us-central1-docker.pkg.dev + REPOSITORY: molten-verve-216720/linuxfoundation-repository + +jobs: + build-and-push-and-test: + runs-on: ubuntu-latest + environment: Antithesis + steps: + - name: Checkout the code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Login to Antithesis Docker Registry + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ${{ env.REGISTRY }} + username: _json_key + password: ${{ secrets.ANTITHESIS_CONTAINER_REGISTRY_TOKEN }} + + - name: Build and push config image + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + with: + context: ./tests/antithesis + file: ./tests/antithesis/Dockerfile.config + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-config:latest, + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-config:${{ github.sha }} + + - name: Build and push client image + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + with: + context: . + file: ./tests/antithesis/test-template/Dockerfile + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-client:latest, + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-client:${{ github.sha }} + + - name: Build and push etcd image + working-directory: ./tests/antithesis + run: | + make antithesis-build-etcd-image-${{ inputs.etcd_ref }} + export TAG="${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-server:latest" + docker tag etcd-server:latest $TAG + docker push $TAG + export TAG="${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-server:${{ github.sha }}" + docker tag etcd-server:latest $TAG + docker push $TAG + + - name: Run Antithesis Tests + uses: antithesishq/antithesis-trigger-action@b7d0c9d1d9316bd4de73a44144c56636ea3a64ba # main commit on Mar 13, 2025 + with: + notebook_name: etcd + tenant: linuxfoundation + username: ${{ secrets.ANTITHESIS_WEBHOOK_USERNAME }} + password: ${{ secrets.ANTITHESIS_WEBHOOK_PASSWORD }} + github_token: ${{ secrets.GH_PAT }} + config_image: ${{ inputs.config_image }} + images: ${{ inputs.images }} + description: ${{ inputs.description }} + email_recipients: ${{ inputs.email }} + test_name: ${{ inputs.test }} + additional_parameters: |- + custom.duration = ${{ inputs.duration }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dddd2746729a..4dc86ea59f65 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. @@ -50,6 +50,6 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 diff --git a/.github/workflows/contrib.yaml b/.github/workflows/contrib.yaml deleted file mode 100644 index 835d7b256fdb..000000000000 --- a/.github/workflows/contrib.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Test contrib/mixin -on: [push, pull_request] -permissions: read-all -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: goversion - run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 - with: - go-version: ${{ steps.goversion.outputs.goversion }} - - run: | - set -euo pipefail - - make -C contrib/mixin tools test diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml deleted file mode 100644 index 9f30c0c816a5..000000000000 --- a/.github/workflows/coverage.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Coverage -on: [push, pull_request] -permissions: read-all -jobs: - coverage: - # this is to prevent the job to run at forked projects - if: github.repository == 'etcd-io/etcd' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - target: - - linux-amd64-coverage - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: goversion - run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 - with: - go-version: ${{ steps.goversion.outputs.goversion }} - - env: - TARGET: ${{ matrix.target }} - run: | - mkdir "${TARGET}" - case "${TARGET}" in - linux-amd64-coverage) - GOARCH=amd64 ./scripts/codecov_upload.sh - ;; - *) - echo "Failed to find target" - exit 1 - ;; - esac diff --git a/.github/workflows/fuzzing.yaml b/.github/workflows/fuzzing.yaml deleted file mode 100644 index 52ae502e51db..000000000000 --- a/.github/workflows/fuzzing.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: Fuzzing v3rpc -on: [push, pull_request] -permissions: read-all -jobs: - fuzzing: - runs-on: ubuntu-latest - strategy: - fail-fast: false - env: - TARGET_PATH: ./server/etcdserver/api/v3rpc - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: goversion - run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 - with: - go-version: ${{ steps.goversion.outputs.goversion }} - - run: | - set -euo pipefail - - GOARCH=amd64 CPU=4 make fuzz - - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - if: failure() - with: - path: "${{env.TARGET_PATH}}/testdata/fuzz/**/*" diff --git a/.github/workflows/grpcproxy.yaml b/.github/workflows/grpcproxy.yaml deleted file mode 100644 index 93e6a20bdf2e..000000000000 --- a/.github/workflows/grpcproxy.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: grpcProxy-tests -on: [push, pull_request] -permissions: read-all -jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - target: - - linux-amd64-grpcproxy-integration - - linux-amd64-grpcproxy-e2e - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: goversion - run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 - with: - go-version: ${{ steps.goversion.outputs.goversion }} - - env: - TARGET: ${{ matrix.target }} - run: | - set -euo pipefail - - echo "${TARGET}" - case "${TARGET}" in - linux-amd64-grpcproxy-integration) - GOOS=linux GOARCH=amd64 CPU=4 make test-grpcproxy-integration - ;; - linux-amd64-grpcproxy-e2e) - GOOS=linux GOARCH=amd64 CPU=4 make test-grpcproxy-e2e - ;; - *) - echo "Failed to find target" - exit 1 - ;; - esac diff --git a/.github/workflows/measure-testgrid-flakiness.yaml b/.github/workflows/measure-testgrid-flakiness.yaml index 67bdd68d39f0..8c5a8fd73a0d 100644 --- a/.github/workflows/measure-testgrid-flakiness.yaml +++ b/.github/workflows/measure-testgrid-flakiness.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - env: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 401ec8f3661f..000000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,77 +0,0 @@ ---- -name: Release -on: [push, pull_request] -permissions: read-all -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: goversion - run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 - with: - go-version: ${{ steps.goversion.outputs.goversion }} - - name: release - run: | - set -euo pipefail - - git config --global user.email "github-action@etcd.io" - git config --global user.name "Github Action" - gpg --batch --gen-key < +--- ## [v2.3.8](https://github.com/etcd-io/etcd/releases/tag/v2.3.8) (2017-02-17) @@ -12,5 +12,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.7...v2.3.8). - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.0.md b/CHANGELOG/CHANGELOG-3.0.md index bc11c80a5f0d..19a032fc83ac 100644 --- a/CHANGELOG/CHANGELOG-3.0.md +++ b/CHANGELOG/CHANGELOG-3.0.md @@ -1,6 +1,6 @@ -
+--- ## [v3.0.16](https://github.com/etcd-io/etcd/releases/tag/v3.0.16) (2016-11-13) @@ -14,7 +14,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.15...v3.0.16) an - Compile with [*Go 1.6.4*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.15](https://github.com/etcd-io/etcd/releases/tag/v3.0.15) (2016-11-11) @@ -32,7 +32,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.14...v3.0.15) an - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.14](https://github.com/etcd-io/etcd/releases/tag/v3.0.14) (2016-11-04) @@ -50,7 +50,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.13...v3.0.14) an - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.13](https://github.com/etcd-io/etcd/releases/tag/v3.0.13) (2016-10-24) @@ -64,7 +64,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.12...v3.0.13) an - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.12](https://github.com/etcd-io/etcd/releases/tag/v3.0.12) (2016-10-07) @@ -78,7 +78,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.11...v3.0.12) an - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.11](https://github.com/etcd-io/etcd/releases/tag/v3.0.11) (2016-10-07) @@ -98,7 +98,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.10...v3.0.11) an - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.10](https://github.com/etcd-io/etcd/releases/tag/v3.0.10) (2016-09-23) @@ -112,7 +112,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.9...v3.0.10) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.9](https://github.com/etcd-io/etcd/releases/tag/v3.0.9) (2016-09-15) @@ -130,7 +130,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.8...v3.0.9) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.8](https://github.com/etcd-io/etcd/releases/tag/v3.0.8) (2016-09-09) @@ -148,7 +148,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.7...v3.0.8) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.7](https://github.com/etcd-io/etcd/releases/tag/v3.0.7) (2016-08-31) @@ -166,7 +166,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.6...v3.0.7) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.6](https://github.com/etcd-io/etcd/releases/tag/v3.0.6) (2016-08-19) @@ -180,7 +180,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.5...v3.0.6) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.5](https://github.com/etcd-io/etcd/releases/tag/v3.0.5) (2016-08-19) @@ -198,7 +198,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.4...v3.0.5) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.4](https://github.com/etcd-io/etcd/releases/tag/v3.0.4) (2016-07-27) @@ -221,7 +221,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.3...v3.0.4) and - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.3](https://github.com/etcd-io/etcd/releases/tag/v3.0.3) (2016-07-15) @@ -241,7 +241,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.2...v3.0.3) and - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.2](https://github.com/etcd-io/etcd/releases/tag/v3.0.2) (2016-07-08) @@ -259,7 +259,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.1...v3.0.2) and - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.1](https://github.com/etcd-io/etcd/releases/tag/v3.0.1) (2016-07-01) @@ -273,7 +273,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.0...v3.0.1) and - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6). -
+--- ## [v3.0.0](https://github.com/etcd-io/etcd/releases/tag/v3.0.0) (2016-06-30) @@ -287,5 +287,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.0...v3.0.0) and - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.1.md b/CHANGELOG/CHANGELOG-3.1.md index 0c97517a7e24..e7d9a14606dc 100644 --- a/CHANGELOG/CHANGELOG-3.1.md +++ b/CHANGELOG/CHANGELOG-3.1.md @@ -2,7 +2,7 @@ Previous change logs can be found at [CHANGELOG-3.0](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.0.md). -
+--- ## [v3.1.21](https://github.com/etcd-io/etcd/releases/tag/v3.1.21) (2019-TBD) @@ -23,7 +23,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Fix bug where [db_compaction_total_duration_milliseconds metric incorrectly measured duration as 0](https://github.com/etcd-io/etcd/pull/10646). -
+--- ## [v3.1.20](https://github.com/etcd-io/etcd/releases/tag/v3.1.20) (2018-10-10) @@ -69,7 +69,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.19](https://github.com/etcd-io/etcd/releases/tag/v3.1.19) (2018-07-24) @@ -115,7 +115,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.18](https://github.com/etcd-io/etcd/releases/tag/v3.1.18) (2018-06-15) @@ -138,7 +138,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.17](https://github.com/etcd-io/etcd/releases/tag/v3.1.17) (2018-06-06) @@ -159,7 +159,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.16...v3.1.17) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.16](https://github.com/etcd-io/etcd/releases/tag/v3.1.16) (2018-05-31) @@ -179,7 +179,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.15...v3.1.16) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.15](https://github.com/etcd-io/etcd/releases/tag/v3.1.15) (2018-05-09) @@ -199,7 +199,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.14...v3.1.15) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.14](https://github.com/etcd-io/etcd/releases/tag/v3.1.14) (2018-04-24) @@ -233,7 +233,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.13](https://github.com/etcd-io/etcd/releases/tag/v3.1.13) (2018-03-29) @@ -261,7 +261,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.12](https://github.com/etcd-io/etcd/releases/tag/v3.1.12) (2018-03-08) @@ -284,7 +284,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.11...v3.1.12) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.11](https://github.com/etcd-io/etcd/releases/tag/v3.1.11) (2017-11-28) @@ -303,7 +303,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.10...v3.1.11) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.1.10](https://github.com/etcd-io/etcd/releases/tag/v3.1.10) (2017-07-14) @@ -323,7 +323,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.9...v3.1.10) and - Fix panic on `net/http.CloseNotify` -
+--- ## [v3.1.9](https://github.com/etcd-io/etcd/releases/tag/v3.1.9) (2017-06-09) @@ -341,7 +341,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.8...v3.1.9) and - Compile with [*Go 1.7.6*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.8](https://github.com/etcd-io/etcd/releases/tag/v3.1.8) (2017-05-19) @@ -355,7 +355,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.7...v3.1.8) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.7](https://github.com/etcd-io/etcd/releases/tag/v3.1.7) (2017-04-28) @@ -369,7 +369,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.6...v3.1.7) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.6](https://github.com/etcd-io/etcd/releases/tag/v3.1.6) (2017-04-19) @@ -388,7 +388,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.5...v3.1.6) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.5](https://github.com/etcd-io/etcd/releases/tag/v3.1.5) (2017-03-27) @@ -411,7 +411,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.4...v3.1.5) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.4](https://github.com/etcd-io/etcd/releases/tag/v3.1.4) (2017-03-22) @@ -425,7 +425,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.3...v3.1.4) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.3](https://github.com/etcd-io/etcd/releases/tag/v3.1.3) (2017-03-10) @@ -452,7 +452,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.2...v3.1.3) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.2](https://github.com/etcd-io/etcd/releases/tag/v3.1.2) (2017-02-24) @@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.1...v3.1.2) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.1](https://github.com/etcd-io/etcd/releases/tag/v3.1.1) (2017-02-17) @@ -488,7 +488,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.0...v3.1.1) and - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7). -
+--- ## [v3.1.0](https://github.com/etcd-io/etcd/releases/tag/v3.1.0) (2017-01-20) @@ -570,5 +570,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.7.4*](https://golang.org/doc/devel/release.html#go1.7). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.2.md b/CHANGELOG/CHANGELOG-3.2.md index 095ff6e9f2af..42bdc53a0a1e 100644 --- a/CHANGELOG/CHANGELOG-3.2.md +++ b/CHANGELOG/CHANGELOG-3.2.md @@ -5,7 +5,7 @@ Previous change logs can be found at [CHANGELOG-3.1](https://github.com/etcd-io/ ## v3.2.33 (TBD) -
+--- ## [v3.2.32](https://github.com/etcd-io/etcd/releases/tag/v3.2.32) (2021-03-28) See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) and [v3.2 upgrade guide](https://etcd.io/docs/latest/upgrades/upgrade_3_2/) for any breaking changes. @@ -23,7 +23,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.2.31](https://github.com/etcd-io/etcd/releases/tag/v3.2.31) (2020-08-18) @@ -49,7 +49,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.30...v3.2.31) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.2.30](https://github.com/etcd-io/etcd/releases/tag/v3.2.30) (2020-04-01) @@ -69,7 +69,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.29...v3.2.30) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.2.29](https://github.com/etcd-io/etcd/releases/tag/v3.2.29) (2020-03-18) @@ -97,7 +97,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.28](https://github.com/etcd-io/etcd/releases/tag/v3.2.28) (2019-11-10) @@ -125,7 +125,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.27](https://github.com/etcd-io/etcd/releases/tag/v3.2.27) (2019-09-17) @@ -161,7 +161,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.26](https://github.com/etcd-io/etcd/releases/tag/v3.2.26) (2019-01-11) @@ -183,7 +183,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.25...v3.2.26) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.25](https://github.com/etcd-io/etcd/releases/tag/v3.2.25) (2018-10-10) @@ -230,7 +230,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.24](https://github.com/etcd-io/etcd/releases/tag/v3.2.24) (2018-07-24) @@ -286,7 +286,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.23](https://github.com/etcd-io/etcd/releases/tag/v3.2.23) (2018-06-15) @@ -317,7 +317,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.22](https://github.com/etcd-io/etcd/releases/tag/v3.2.22) (2018-06-06) @@ -339,7 +339,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.21...v3.2.22) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.21](https://github.com/etcd-io/etcd/releases/tag/v3.2.21) (2018-05-31) @@ -360,7 +360,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.20...v3.2.21) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.20](https://github.com/etcd-io/etcd/releases/tag/v3.2.20) (2018-05-09) @@ -380,7 +380,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.19...v3.2.20) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.19](https://github.com/etcd-io/etcd/releases/tag/v3.2.19) (2018-04-24) @@ -423,7 +423,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.18](https://github.com/etcd-io/etcd/releases/tag/v3.2.18) (2018-03-29) @@ -451,7 +451,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.17](https://github.com/etcd-io/etcd/releases/tag/v3.2.17) (2018-03-08) @@ -481,7 +481,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.16...v3.2.17) an - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.16](https://github.com/etcd-io/etcd/releases/tag/v3.2.16) (2018-02-12) @@ -504,7 +504,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.15...v3.2.16) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.15](https://github.com/etcd-io/etcd/releases/tag/v3.2.15) (2018-01-22) @@ -523,7 +523,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.14...v3.2.15) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.14](https://github.com/etcd-io/etcd/releases/tag/v3.2.14) (2018-01-11) @@ -545,7 +545,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.13...v3.2.14) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.13](https://github.com/etcd-io/etcd/releases/tag/v3.2.13) (2018-01-02) @@ -564,7 +564,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.12...v3.2.13) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.12](https://github.com/etcd-io/etcd/releases/tag/v3.2.12) (2017-12-20) @@ -596,7 +596,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.11...v3.2.12) an - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.11](https://github.com/etcd-io/etcd/releases/tag/v3.2.11) (2017-12-05) @@ -629,7 +629,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.10](https://github.com/etcd-io/etcd/releases/tag/v3.2.10) (2017-11-16) @@ -663,7 +663,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.9](https://github.com/etcd-io/etcd/releases/tag/v3.2.9) (2017-10-06) @@ -685,7 +685,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.8.4*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.8](https://github.com/etcd-io/etcd/releases/tag/v3.2.8) (2017-09-29) @@ -707,7 +707,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.7...v3.2.8) and - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.7](https://github.com/etcd-io/etcd/releases/tag/v3.2.7) (2017-09-01) @@ -730,7 +730,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.6...v3.2.7) and - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.6](https://github.com/etcd-io/etcd/releases/tag/v3.2.6) (2017-08-21) @@ -758,7 +758,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.5](https://github.com/etcd-io/etcd/releases/tag/v3.2.5) (2017-08-04) @@ -798,7 +798,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.4](https://github.com/etcd-io/etcd/releases/tag/v3.2.4) (2017-07-19) @@ -820,7 +820,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.3...v3.2.4) and - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.3](https://github.com/etcd-io/etcd/releases/tag/v3.2.3) (2017-07-14) @@ -843,7 +843,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.2...v3.2.3) and - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.2](https://github.com/etcd-io/etcd/releases/tag/v3.2.2) (2017-07-07) @@ -879,7 +879,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.1](https://github.com/etcd-io/etcd/releases/tag/v3.2.1) (2017-06-23) @@ -909,7 +909,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- ## [v3.2.0](https://github.com/etcd-io/etcd/releases/tag/v3.2.0) (2017-06-09) @@ -1017,5 +1017,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.3.md b/CHANGELOG/CHANGELOG-3.3.md index 8addba112f6f..825883ab498d 100644 --- a/CHANGELOG/CHANGELOG-3.3.md +++ b/CHANGELOG/CHANGELOG-3.3.md @@ -2,7 +2,7 @@ Previous change logs can be found at [CHANGELOG-3.2](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.2.md). -
+--- ## v3.3.27 (2021-10-15) @@ -16,7 +16,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.26...v3.3.27) an - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads. -
+--- ## v3.3.26 (2021-10-03) @@ -34,7 +34,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.25...v3.3.26) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## v3.3.25 (2020-08-24) @@ -68,7 +68,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.23...v3.3.24) an -
+--- @@ -96,7 +96,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.22...v3.3.23) an -
+--- ## [v3.3.22](https://github.com/etcd-io/etcd/releases/tag/v3.3.22) (2020-05-20) @@ -113,7 +113,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.21...v3.3.22) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.3.21](https://github.com/etcd-io/etcd/releases/tag/v3.3.21) (2020-05-18) @@ -150,7 +150,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.20...v3.3.21) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.3.20](https://github.com/etcd-io/etcd/releases/tag/v3.3.20) (2020-04-01) @@ -170,7 +170,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.19...v3.3.20) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.3.19](https://github.com/etcd-io/etcd/releases/tag/v3.3.19) (2020-03-18) @@ -206,7 +206,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.3.18](https://github.com/etcd-io/etcd/releases/tag/v3.3.18) (2019-11-26) @@ -227,7 +227,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Now, etcd makes sure the purge file loop exits before server signals stop of the raft node. -
+--- ## [v3.3.17](https://github.com/etcd-io/etcd/releases/tag/v3.3.17) (2019-10-11) @@ -241,7 +241,7 @@ This release replaces 3.3.16. Due to the etcd 3.3.16 release being incorrectly released (see details below), please use this release instead. -
+--- ## [v3.3.16](https://github.com/etcd-io/etcd/releases/tag/v3.3.16) (2019-10-10) @@ -290,7 +290,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550). -
+--- ## [v3.3.15](https://github.com/etcd-io/etcd/releases/tag/v3.3.15) (2019-08-19) @@ -313,7 +313,7 @@ NOTE: This patch release had to include some new features from 3.4, while trying - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes. -
+--- ## [v3.3.14](https://github.com/etcd-io/etcd/releases/tag/v3.3.14) (2019-08-16) @@ -416,7 +416,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes. -
+--- ## [v3.3.13](https://github.com/etcd-io/etcd/releases/tag/v3.3.13) (2019-05-02) @@ -456,7 +456,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10). -
+--- ## [v3.3.12](https://github.com/etcd-io/etcd/releases/tag/v3.3.12) (2019-02-07) @@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.11...v3.3.12) an - Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10). -
+--- ## [v3.3.11](https://github.com/etcd-io/etcd/releases/tag/v3.3.11) (2019-01-11) @@ -496,7 +496,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.10...v3.3.11) an - Compile with [*Go 1.10.7*](https://golang.org/doc/devel/release.html#go1.10). -
+--- ## [v3.3.10](https://github.com/etcd-io/etcd/releases/tag/v3.3.10) (2018-10-10) @@ -542,7 +542,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.10.4*](https://golang.org/doc/devel/release.html#go1.10). -
+--- ## [v3.3.9](https://github.com/etcd-io/etcd/releases/tag/v3.3.9) (2018-07-24) @@ -597,7 +597,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.10.3*](https://golang.org/doc/devel/release.html#go1.10). -
+--- ## [v3.3.8](https://github.com/etcd-io/etcd/releases/tag/v3.3.8) (2018-06-15) @@ -619,7 +619,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.7...v3.3.8) and - Compile with [*Go 1.9.7*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.7](https://github.com/etcd-io/etcd/releases/tag/v3.3.7) (2018-06-06) @@ -645,7 +645,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.6...v3.3.7) and - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.6](https://github.com/etcd-io/etcd/releases/tag/v3.3.6) (2018-05-31) @@ -668,7 +668,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.5...v3.3.6) and - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.5](https://github.com/etcd-io/etcd/releases/tag/v3.3.5) (2018-05-09) @@ -687,7 +687,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.4...v3.3.5) and - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.4](https://github.com/etcd-io/etcd/releases/tag/v3.3.4) (2018-04-24) @@ -735,7 +735,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.3](https://github.com/etcd-io/etcd/releases/tag/v3.3.3) (2018-03-29) @@ -772,7 +772,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.2](https://github.com/etcd-io/etcd/releases/tag/v3.3.2) (2018-03-08) @@ -805,7 +805,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.1...v3.3.2) and - Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.1](https://github.com/etcd-io/etcd/releases/tag/v3.3.1) (2018-02-12) @@ -833,7 +833,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.0...v3.3.1) and - Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9). -
+--- ## [v3.3.0](https://github.com/etcd-io/etcd/releases/tag/v3.3.0) (2018-02-01) @@ -1117,5 +1117,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta - Deprecate [`golang.org/x/net/context`](https://github.com/etcd-io/etcd/pull/8511). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.4.md b/CHANGELOG/CHANGELOG-3.4.md index c4e5b05b8c0b..aab809de861d 100644 --- a/CHANGELOG/CHANGELOG-3.4.md +++ b/CHANGELOG/CHANGELOG-3.4.md @@ -2,9 +2,25 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.3.md). -
+--- -## v3.4.36 (TBC) +## v3.4.38 (TBC) + +### Dependencies + +- Compile binaries using [go 1.23.9](https://github.com/etcd-io/etcd/pull/19872). + +--- + +## v3.4.37 (2025-04-15) + +### Dependencies +- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19529). +- Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19726) + +--- + +## v3.4.36 (2025-02-25) ### etcd server - [Avoid deadlock in etcd.Close when stopping during bootstrapping](https://github.com/etcd-io/etcd/pull/19166) @@ -14,9 +30,11 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Fix [runtime panic that occurs when KeepAlive is called with a Context implemented by an uncomparable type](https://github.com/etcd-io/etcd/pull/18936) ### Dependencies -- Compile binaries using [go 1.22.12](https://github.com/etcd-io/etcd/pull/19337) +- Compile binaries using [go 1.23.6](https://github.com/etcd-io/etcd/pull/19429) +- Bump golang.org/x/crypto to v0.35.0 to address [CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19197) and [CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19477). +- Bump golang.org/x/net to v0.34.0 to address [CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19197). -
+--- ## v3.4.35 (2024-11-12) @@ -28,7 +46,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18850). -
+--- ## v3.4.34 (2024-09-11) @@ -43,7 +61,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18549). - Upgrade [bbolt to 1.3.11](https://github.com/etcd-io/etcd/pull/18488). -
+--- ## v3.4.33 (2024-06-13) @@ -54,7 +72,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Compile binaries using go [1.21.11](https://github.com/etcd-io/etcd/pull/18130). - Upgrade [bbolt to 1.3.10](https://github.com/etcd-io/etcd/pull/17945). -
+--- ## v3.4.32 (2024-04-25) @@ -73,7 +91,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.21.9](https://github.com/etcd-io/etcd/pull/17709). -
+--- ## v3.4.31 (2024-03-21) @@ -96,7 +114,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Others - [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17422). -
+--- ## v3.4.30 (2024-01-31) @@ -107,7 +125,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Compile binaries using go [1.20.13](https://github.com/etcd-io/etcd/pull/17276). - Upgrade [golang.org/x/crypto to v0.17+ to address CVE-2023-48795](https://github.com/etcd-io/etcd/pull/17347). -
+--- ## v3.4.29 (2024-01-09) @@ -121,7 +139,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Dependencies - Compile binaries using go [1.20.12](https://github.com/etcd-io/etcd/pull/17076). -
+--- ## v3.4.28 (2023-11-23) @@ -143,7 +161,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Upgrade [bbolt to 1.3.8](https://github.com/etcd-io/etcd/pull/16834). - Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16997 and https://github.com/etcd-io/etcd/pull/16999. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803). -
+--- ## v3.4.27 (2023-07-11) @@ -158,7 +176,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.19.10](https://github.com/etcd-io/etcd/pull/16038). -
+--- ## v3.4.26 (2023-05-12) @@ -169,7 +187,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15823) -
+--- ## v3.4.25 (2023-04-14) @@ -196,7 +214,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Docker image - Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15681) -
+--- ## v3.4.24 (2023-02-16) @@ -218,7 +236,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Docker image - Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15038). -
+--- ## v3.4.23 (2022-12-21) @@ -237,7 +255,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### Docker image - Use [distroless base image](https://github.com/etcd-io/etcd/pull/15017) to address critical Vulnerabilities. -
+--- ## v3.4.22 (2022-11-02) @@ -256,7 +274,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ ### etcd grpc-proxy - Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14601) flag to support adding configurable cipher list. -
+--- ## v3.4.21 (2022-09-15) @@ -269,7 +287,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Fix [etcdctl move-leader may fail for multiple endpoints](https://github.com/etcd-io/etcd/pull/14441) -
+--- ## v3.4.20 (2022-08-06) @@ -289,7 +307,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/ - Fix [Grant lease with negative ID can possibly cause db out of sync](https://github.com/etcd-io/etcd/pull/14239) - Fix [Allow non mutating requests pass through quotaKVServer when NOSPACE](https://github.com/etcd-io/etcd/pull/14254) -
+--- ## v3.4.19 (2022-07-12) @@ -317,7 +335,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.18...v3.4.19) an - Compile with [Go 1.16+](https://go.dev/doc/devel/release#go1.16). - etcd uses [go modules](https://github.com/etcd-io/etcd/pull/14136) (instead of vendor dir) to track dependencies. -
+--- ## v3.4.18 (2021-10-15) @@ -337,7 +355,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads. -
+--- ## v3.4.17 (2021-10-03) @@ -359,7 +377,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.16...v3.4.17) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## v3.4.16 (2021-05-11) @@ -381,7 +399,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.15...v3.4.16) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.15](https://github.com/etcd-io/etcd/releases/tag/v3.4.15) (2021-02-26) @@ -406,7 +424,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.14...v3.4.15) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.14](https://github.com/etcd-io/etcd/releases/tag/v3.4.14) (2020-11-25) @@ -436,7 +454,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.13...v3.4.14) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.13](https://github.com/etcd-io/etcd/releases/tag/v3.4.13) (2020-8-24) @@ -452,7 +470,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.12...v3.4.13) an - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.12](https://github.com/etcd-io/etcd/releases/tag/v3.4.12) (2020-08-19) @@ -470,7 +488,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.11...v3.4.12) an -
+--- @@ -504,7 +522,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.10...v3.4.11) an -
+--- @@ -533,7 +551,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.9...v3.4.10) and - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.9](https://github.com/etcd-io/etcd/releases/tag/v3.4.9) (2020-05-20) @@ -550,7 +568,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.8...v3.4.9) and - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.8](https://github.com/etcd-io/etcd/releases/tag/v3.4.8) (2020-05-18) @@ -587,7 +605,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.7...v3.4.8) and - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.7](https://github.com/etcd-io/etcd/releases/tag/v3.4.7) (2020-04-01) @@ -611,7 +629,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.6...v3.4.7) and - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.6](https://github.com/etcd-io/etcd/releases/tag/v3.4.6) (2020-03-29) @@ -629,7 +647,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.5...v3.4.6) and - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.5](https://github.com/etcd-io/etcd/releases/tag/v3.4.5) (2020-03-18) @@ -666,7 +684,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.4](https://github.com/etcd-io/etcd/releases/tag/v3.4.4) (2020-02-24) @@ -699,7 +717,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Fix bug where [some auth related messages are logged at wrong level](https://github.com/etcd-io/etcd/pull/11586) -
+--- ## [v3.4.3](https://github.com/etcd-io/etcd/releases/tag/v3.4.3) (2019-10-24) @@ -721,7 +739,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.12.12*](https://golang.org/doc/devel/release.html#go1.12). -
+--- ## [v3.4.2](https://github.com/etcd-io/etcd/releases/tag/v3.4.2) (2019-10-11) @@ -750,7 +768,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.1...v3.4.2) and - Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550). -
+--- ## [v3.4.1](https://github.com/etcd-io/etcd/releases/tag/v3.4.1) (2019-09-17) @@ -786,7 +804,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes. -
+--- ## v3.4.0 (2019-08-30) @@ -1391,5 +1409,5 @@ Note: **v3.5 will deprecate `etcd --log-package-levels` flag for `capnslog`**; ` - [Rebase etcd image from Alpine to Debian](https://github.com/etcd-io/etcd/pull/10805) to improve security and maintenance effort for etcd release. -
+--- diff --git a/CHANGELOG/CHANGELOG-3.5.md b/CHANGELOG/CHANGELOG-3.5.md index cacc0a947b23..68d94b64d354 100644 --- a/CHANGELOG/CHANGELOG-3.5.md +++ b/CHANGELOG/CHANGELOG-3.5.md @@ -1,20 +1,67 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.4.md). -
-## v3.5.19 (TBC) +--- + +## v3.5.22 (TBC) + +### etcd server + +- Fix [the compaction pause duration metric is not emitted for every compaction batch](https://github.com/etcd-io/etcd/pull/19771) + +### Package `clientv3` + +- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19783). +- [Deprecated the Metadata field in the Endpoint struct from the client/v3/naming/endpoints package](https://github.com/etcd-io/etcd/pull/19846). + +### Dependencies + +- Compile binaries using [go 1.23.9](https://github.com/etcd-io/etcd/pull/19870) + +--- + +## v3.5.21 (2025-03-27) + +### Dependencies + +- Bump [github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19646). +- Bump [bump golang.org/x/net from v0.36.0 to v0.38.0 to address CVE-2025-22870 and CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19686). + +--- + +## v3.5.20 (2025-03-21) + +### etcd server + +- Fix [the learner promotion changes not being persisted into v3store (bbolt)](https://github.com/etcd-io/etcd/pull/19563) +- Update [the RLock in Demoted method for read-only access to expiry](https://github.com/etcd-io/etcd/pull/19445) + +### etcdctl + +- Fix [command `etcdctl member promote` doesn't support json output](https://github.com/etcd-io/etcd/pull/19602) + +### etcd grpc-proxy + +- Fix [grpcproxy can get stuck in and endless loop causing high CPU usage](https://github.com/etcd-io/etcd/pull/19562) + +--- + +## v3.5.19 (2025-03-05) ### etcd server - Backport [add learner status check to readyz endpoint](https://github.com/etcd-io/etcd/pull/19280). +- Fix [performance regression due to uncertain compaction sleep interval](https://github.com/etcd-io/etcd/pull/19405). ### `tools/benchmark` - Backport [add mixed read-write performance evaluation scripts](https://github.com/etcd-io/etcd/pull/19275). ### Dependencies -- Compile binaries using [go 1.22.12](https://github.com/etcd-io/etcd/pull/19336). +- Compile binaries using [go 1.23.7](https://github.com/etcd-io/etcd/pull/19528). +- Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19478). +- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19530). -
+--- ## v3.5.18 (2025-01-24) @@ -38,7 +85,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Bump [golang.org/x/crypto to 0.32.0 to address CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19154). - Bump [golang.org/x/net to 0.34.0 to address CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19158). -
+--- ## v3.5.17 (2024-11-12) @@ -51,7 +98,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18849). -
+--- ## v3.5.16 (2024-09-10) @@ -64,7 +111,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18550). - Upgrade [bbolt to v1.3.11](https://github.com/etcd-io/etcd/pull/18489). -
+--- ## v3.5.15 (2024-07-19) @@ -103,7 +150,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Compile binaries using [go 1.21.10](https://github.com/etcd-io/etcd/pull/17980). - Upgrade [bbolt to v1.3.10](https://github.com/etcd-io/etcd/pull/17943). -
+--- ## v3.5.13 (2024-03-29) @@ -132,7 +179,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ ### Others - [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17421). -
+--- ## v3.5.12 (2024-01-31) @@ -159,7 +206,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Compile binaries using [go 1.20.12](https://github.com/etcd-io/etcd/pull/17077) - Fix [CVE-2023-47108](https://github.com/advisories/GHSA-8pgv-569h-w5rw) by [bumping go.opentelemetry.io/otel to 1.20.0 and go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to 0.46.0](https://github.com/etcd-io/etcd/pull/16946). -
+--- ## v3.5.10 (2023-10-27) @@ -190,7 +237,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16625, https://github.com/etcd-io/etcd/pull/16781 and https://github.com/etcd-io/etcd/pull/16790. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803). - Upgrade [bbolt to v1.3.8](https://github.com/etcd-io/etcd/pull/16833) -
+--- ## v3.5.9 (2023-05-11) @@ -200,7 +247,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ ### Dependencies - Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15822). -
+--- ## v3.5.8 (2023-04-13) @@ -233,7 +280,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - [Remove nsswitch.conf from docker image](https://github.com/etcd-io/etcd/pull/15161) - Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15612) -
+--- ## v3.5.7 (2023-01-20) @@ -256,7 +303,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - Use [distroless base image](https://github.com/etcd-io/etcd/pull/15016) to address critical Vulnerabilities. - Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15037). -
+--- ## v3.5.6 (2022-11-21) @@ -276,7 +323,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ ### etcd grpc-proxy - Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14500) flag to support adding configurable cipher list. -
+--- ## v3.5.5 (2022-09-15) @@ -311,7 +358,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - [Bump golang.org/x/crypto to latest version](https://github.com/etcd-io/etcd/pull/13996) to address [CVE-2022-27191](https://github.com/advisories/GHSA-8c26-wmh5-6g9v). - [Bump OpenTelemetry to 1.0.1 and gRPC to 1.41.0](https://github.com/etcd-io/etcd/pull/14312). -
+--- ## v3.5.4 (2022-04-24) @@ -323,7 +370,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - [Revert the change of trimming the trailing dot from SRV.Target](https://github.com/etcd-io/etcd/pull/13950) returned by DNS lookup -
+--- ## v3.5.3 (2022-04-13) @@ -344,7 +391,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/ - [Always print the raft_term in decimal](https://github.com/etcd-io/etcd/pull/13727) when displaying member list in json. -
+--- ## [v3.5.2](https://github.com/etcd-io/etcd/releases/tag/v3.5.2) (2022-02-01) @@ -357,7 +404,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.1...v3.5.2) and - Fix [assertion failed due to tx closed when recovering v3 backend from a snapshot db](https://github.com/etcd-io/etcd/pull/13501) - Fix [segmentation violation(SIGSEGV) error due to premature unlocking of watchableStore](https://github.com/etcd-io/etcd/pull/13541) -
+--- ## [v3.5.1](https://github.com/etcd-io/etcd/releases/tag/v3.5.1) (2021-10-15) @@ -384,7 +431,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.5.1) and - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads. -
+--- ## v3.5.0 (2021-06) @@ -681,5 +728,5 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - The etcd team has added, a well defined and openly discussed, project [governance](https://github.com/etcd-io/etcd/pull/11175). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.6.md b/CHANGELOG/CHANGELOG-3.6.md index 7e3267adeded..32a75dc918d8 100644 --- a/CHANGELOG/CHANGELOG-3.6.md +++ b/CHANGELOG/CHANGELOG-3.6.md @@ -2,9 +2,101 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.5.md). -
+--- -## v3.6.0 (TBD) +## v3.6.1 (TBD) + +### etcd server + +- [Replaced the deprecated/removed `UnaryServerInterceptor` and `StreamServerInterceptor` in otelgrpc with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20043) + +--- + +## v3.6.0 (2025-05-15) + +There isn't any production code change since v3.6.0-rc.5. + +--- + +## v3.6.0-rc.5 (2025-05-08) + +### etcd server + +- Fix [the compaction pause duration metric is not emitted for every compaction batch](https://github.com/etcd-io/etcd/pull/19770) + +### Package `clientv3` + +- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19782). +- [Deprecated the Metadata field in the Endpoint struct from the client/v3/naming/endpoints package](https://github.com/etcd-io/etcd/pull/19842). + +### Dependencies + +- Compile binaries using [go 1.23.9](https://github.com/etcd-io/etcd/pull/19867). + +--- + +## v3.6.0-rc.4 (2025-04-15) + +### etcd server + +- [Switch to validating v3 when v2 and v3 are synchronized](https://github.com/etcd-io/etcd/pull/19703). + +### Dependencies + +- Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19724) + +--- + +## v3.6.0-rc.3 (2025-03-27) + +### etcd server + +- [Auto sync members in v3store for the issues which have already been affected by #19557](https://github.com/etcd-io/etcd/pull/19636). +- [Move `client/internal/v2` into `server/internel/clientv2`](https://github.com/etcd-io/etcd/pull/19673). +- [Replace ExperimentalMaxLearners with a Feature Gate](https://github.com/etcd-io/etcd/pull/19560). + +### etcd grpc-proxy + +- Fix [grpcproxy can get stuck in and endless loop causing high CPU usage](https://github.com/etcd-io/etcd/pull/19562) + +### Dependencies + +- Bump [github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19647). +- Bump [bump golang.org/x/net from v0.37.0 to v0.38.0 to address CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19687). + +--- + +## v3.6.0-rc.2 (2025-03-05) + +### etcd server + +- Add [Prometheus metric to query server feature gates](https://github.com/etcd-io/etcd/pull/19495). + +### Dependencies + +- Compile binaries using [go 1.23.7](https://github.com/etcd-io/etcd/pull/19527). +- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19531). +- Bump [github.com/grpc-ecosystem/grpc-gateway/v2 to v2.26.3 to fix the issue of etcdserver crashing on receiving REST watch stream requests](https://github.com/etcd-io/etcd/pull/19522). + +--- + +## v3.6.0-rc.1 (2025-02-25) + +### etcdctl v3 + +- Add [`DowngradeInfo` in result of endpoint status](https://github.com/etcd-io/etcd/pull/19471) + +### etcd server + +- Add [`DowngradeInfo` to endpoint status response](https://github.com/etcd-io/etcd/pull/19471) + +### Dependencies + +- Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19480). + +--- + +## v3.6.0-rc.0 (2025-02-13) See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0). @@ -15,6 +107,19 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0). - `etcdctl` will sleep(2s) in case of range delete without `--range` flag. See [pull/13747](https://github.com/etcd-io/etcd/pull/13747) - Applications which depend on etcd v3.6 packages must be built with go version >= v1.18. +#### Flags Removed + +- The following flags have been removed: + + - `--enable-v2` + - `--experimental-enable-v2v3` + - `--proxy` + - `--proxy-failure-wait` + - `--proxy-refresh-interval` + - `--proxy-dial-timeout` + - `--proxy-write-timeout` + - `--proxy-read-timeout` + ### Deprecations - Deprecated [V2 discovery](https://etcd.io/docs/v3.5/dev-internal/discovery_protocol/). @@ -22,8 +127,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0). - Removed [etcdctl defrag --data-dir](https://github.com/etcd-io/etcd/pull/13793). - Removed [etcdctl snapshot status](https://github.com/etcd-io/etcd/pull/13809). - Removed [etcdctl snapshot restore](https://github.com/etcd-io/etcd/pull/13809). -- Removed [etcdutl snapshot save](https://github.com/etcd-io/etcd/pull/13809). - +- Removed [NewZapCoreLoggerBuilder in server/embed](https://github.com/etcd-io/etcd/pull/19404) ### etcdctl v3 @@ -78,7 +182,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0). - Decreased [`--snapshot-count` default value from 100,000 to 10,000](https://github.com/etcd-io/etcd/pull/15408) - Add [`etcd --tls-min-version --tls-max-version`](https://github.com/etcd-io/etcd/pull/15156) to enable support for TLS 1.3. - Add [quota to endpoint status response](https://github.com/etcd-io/etcd/pull/17877) -- Add ['etcd --experimental-set-member-localaddr'](https://github.com/etcd-io/etcd/pull/17661) to enable using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer. +- Add [feature gate `SetMemberLocalAddr`](https://github.com/etcd-io/etcd/pull/19413) to [enable using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer]((https://github.com/etcd-io/etcd/pull/17661)) - Add [Support multiple values for allowed client and peer TLS identities](https://github.com/etcd-io/etcd/pull/18015) - Add [`embed.Config.GRPCAdditionalServerOptions`](https://github.com/etcd-io/etcd/pull/14066) to support updating the default internal gRPC configuration for embedded use cases. @@ -110,4 +214,4 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per - [Upgrade grpc-gateway from v1 to v2](https://github.com/etcd-io/etcd/pull/16595). - [Switch from grpc-ecosystem/go-grpc-prometheus to grpc-ecosystem/go-grpc-middleware/providers/prometheus](https://github.com/etcd-io/etcd/pull/19195). -
+--- diff --git a/CHANGELOG/CHANGELOG-3.7.md b/CHANGELOG/CHANGELOG-3.7.md new file mode 100644 index 000000000000..3eb19ce12177 --- /dev/null +++ b/CHANGELOG/CHANGELOG-3.7.md @@ -0,0 +1,27 @@ + + +Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md). + +--- + +## v3.7.0 (TBD) + +### Breaking Changes + +- [Removed all deprecated experimental flags](https://github.com/etcd-io/etcd/pull/19959) + +### etcd server + +- [Update go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to v0.61.0 and replaced the deprecated `UnaryServerInterceptor` and `StreamServerInterceptor` with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20017) + +### Package `pkg` + +- [Optimize find performance by splitting intervals with the same left endpoint by their right endpoints](https://github.com/etcd-io/etcd/pull/19768) + +### Dependencies + +- Compile binaries using [go 1.24.3](https://github.com/etcd-io/etcd/pull/19908) + +### Deprecations + +- Deprecated [UsageFunc in pkg/cobrautl](https://github.com/etcd-io/etcd/pull/18356). diff --git a/CHANGELOG/CHANGELOG-4.0.md b/CHANGELOG/CHANGELOG-4.0.md index 860e5efd072b..f07bb34fd6e6 100644 --- a/CHANGELOG/CHANGELOG-4.0.md +++ b/CHANGELOG/CHANGELOG-4.0.md @@ -2,7 +2,7 @@ Previous change logs can be found at [CHANGELOG-3.x](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.x.md). -
+--- ## v4.0.0 (TBD) @@ -40,5 +40,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v4.0.0) and - Require [*Go 2*](https://blog.golang.org/go2draft). -
+--- diff --git a/Dockerfile b/Dockerfile index 16724fa401d4..66818aa5988a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG ARCH=amd64 -FROM --platform=linux/${ARCH} gcr.io/distroless/static-debian12@sha256:3f2b64ef97bd285e36132c684e6b2ae8f2723293d09aae046196cca64251acac +FROM --platform=linux/${ARCH} gcr.io/distroless/static-debian12@sha256:d9f9472a8f4541368192d714a995eb1a99bab1f7071fc8bde261d7eda3b667d8 ADD etcd /usr/local/bin/ ADD etcdctl /usr/local/bin/ diff --git a/Documentation/contributor-guide/bump_etcd_version_k8s.md b/Documentation/contributor-guide/bump_etcd_version_k8s.md new file mode 100644 index 000000000000..7b157ef6ed4a --- /dev/null +++ b/Documentation/contributor-guide/bump_etcd_version_k8s.md @@ -0,0 +1,191 @@ +# Bump etcd Version in Kubernetes + +This guide will walk through the update of etcd in Kubernetes to a new version (`kubernetes/kubernetes` repository). + +> Currently we bump etcd v3.5.x for K8s release-1.33 and lower versions, and we bump etcd v3.6.x for K8s release-1.34 and higher versions. + +You can use this [issue](https://github.com/kubernetes/kubernetes/issues/131101) as a reference when updating the etcd version in Kubernetes. + +Bumping the etcd version in Kubernetes consists of two steps. + +* Bump etcd client SDK +* Bump etcd image + +> The commented lines in this document signifies the line to be changed + +## Bump etcd client SDK + +> Reference: [link](https://github.com/kubernetes/kubernetes/pull/131103) + +You can refer to the guide [here](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/vendor.md) under the **Adding or updating a dependency** section. + +1. Get all the etcd modules used in Kubernetes. + + ```bash + $ grep 'go.etcd.io/etcd/' go.mod | awk '{print $1}' + go.etcd.io/etcd/api/v3 + go.etcd.io/etcd/client/pkg/v3 + go.etcd.io/etcd/client/v3 + go.etcd.io/etcd/client/v2 + go.etcd.io/etcd/pkg/v3 + go.etcd.io/etcd/raft/v3 + go.etcd.io/etcd/server/v3 + ``` + +2. For each module, in the root directory of the `kubernetes/kubernetes` repository, fetch the new version in `go.mod` using the following command (using `client/v3` as an example): + + ```bash + hack/pin-dependency.sh go.etcd.io/etcd/client/v3 NEW_VERSION + ``` + +3. Rebuild the `vendor` directory and update the `go.mod` files for all staging repositories using the command below. This automatically updates the licenses. + + ```bash + hack/update-vendor.sh + ``` + +4. Check if the new dependency requires newer versions of existing dependencies we have pinned. You can check this by: + + * Running `hack/lint-dependencies.sh` against your branch and against `master` and comparing the results. + * Checking if any new `replace` directives were added to `go.mod` files of components inside the staging directory. + +## Bump etcd image + +### Build etcd image + +> Reference: [link 1](https://github.com/kubernetes/kubernetes/pull/131105) [link 2](https://github.com/kubernetes/kubernetes/pull/131126) + +1. In `build/dependencies.yaml`, update the `version` of `etcd-image` to the new version. Update `golang: etcd release version` if necessary. + + ```yaml + - name: "etcd-image" + # version: 3.5.17 + version: 3.5.21 + refPaths: + - path: cluster/images/etcd/Makefile + match: BUNDLED_ETCD_VERSIONS\?| + --- + - name: "golang: etcd release version" + # version: 1.22.9 + version: 1.23.7 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md + ``` + +2. In `cluster/images/etcd/Makefile`, include the new version in `BUNDLED_ETCD_VERSIONS` and update the `LATEST_ETCD_VERSION` as well (the image tag will be generated from the `LATEST_ETCD_VERSION`). Update `GOLANG_VERSION` according to the version used to compile that release version (`"golang: etcd release version"` in step 1). + + ```Makefile + # BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.17 + BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.21 + + # LATEST_ETCD_VERSION?=3.5.17 + LATEST_ETCD_VERSION?=3.5.21 + + # GOLANG_VERSION := 1.22.9 + GOLANG_VERSION := 1.23.7 + ``` + +3. In `cluster/images/etcd/migrate/options.go`, include the new version in the `supportedEtcdVersions` slice. + + ```go + var ( + // supportedEtcdVersions = []string{"3.4.18", "3.5.17"} + supportedEtcdVersions = []string{"3.4.18", "3.5.21"} + ) + ``` + +### Publish etcd image + +> Reference: [link](https://github.com/kubernetes/k8s.io/pull/7957) + +1. When the previous step is merged, a post-commit job will run to build the image. You can find the newly built image in the [registry](https://gcr.io/k8s-staging-etcd/etcd). + +2. Locate the newly built image and copy its SHA256 digest. + +3. Inside the `kubernetes/k8s.io` repository, in `registry.k8s.io/images/k8s-staging-etcd/images.yaml`, create a new entry for the desired version and copy the SHA256 digest. + + ```yaml + "sha256:b4a9e4a7e1cf08844c7c4db6a19cab380fbf0aad702b8c01e578e9543671b9f9": ["3.5.17-0"] + # ADD: + "sha256:d58c035df557080a27387d687092e3fc2b64c6d0e3162dc51453a115f847d121": ["3.5.21-0"] + ``` + +### Update to use the new etcd image + +> Reference: [link](https://github.com/kubernetes/kubernetes/pull/131144) + +1. In `build/dependencies.yaml`, change the `version` of `etcd` to the new version. + + ```yaml + # etcd + - name: "etcd" + # version: 3.5.17 + version: 3.5.21 + refPaths: + - path: cluster/gce/manifests/etcd.manifest + match: etcd_docker_tag|etcd_version + ``` + +2. In `cluster/gce/manifests/etcd.manifest`, change the image tag to the new image tag and `TARGET_VERSION` to the new version. + + ```manifest + // "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.17-0') }}", + + "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.21-0') }}", + + --- + + { "name": "TARGET_VERSION", + // "value": "{{ pillar.get('etcd_version', '3.5.17') }}" + "value": "{{ pillar.get('etcd_version', '3.5.21') }}" + }, + ``` + +3. In `cluster/gce/upgrade-aliases.sh`, update the exports for `ETCD_IMAGE` to the new image tag and `ETCD_VERSION` to the new version. + + ```sh + # export ETCD_IMAGE=3.5.17-0 + export ETCD_IMAGE=3.5.21-0 + # export ETCD_VERSION=3.5.17 + export ETCD_VERSION=3.5.21 + ``` + +4. In `cmd/kubeadm/app/constants/constants.go`, change the `DefaultEtcdVersion` to the new version. In the same file, update `SupportedEtcdVersion` accordingly. + + ```go + // DefaultEtcdVersion = "3.5.17-0" + DefaultEtcdVersion = "3.5.21-0" + + --- + + SupportedEtcdVersion = map[uint8]string{ + // 30: "3.5.17-0", + // 31: "3.5.17-0", + // 32: "3.5.17-0", + // 33: "3.5.17-0", + 30: "3.5.21-0", + 31: "3.5.21-0", + 32: "3.5.21-0", + 33: "3.5.21-0", + } + ``` + +5. In `hack/lib/etcd.sh`, update the `ETCD_VERSION`. + + ```sh + # ETCD_VERSION=${ETCD_VERSION:-3.5.17} + ETCD_VERSION=${ETCD_VERSION:-3.5.21} + ``` + +6. In `staging/src/k8s.io/sample-apiserver/artifacts/example/deployment.yaml`, update the etcd image used. + + ```yaml + - name: etcd + # image: gcr.io/etcd-development/etcd:v3.5.17 + image: gcr.io/etcd-development/etcd:v3.5.21 + ``` + +7. In `test/utils/image/manifest.go`, update the etcd image tag. + + ```go + // configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.17-0"} + configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.21-0"} + ``` diff --git a/Documentation/contributor-guide/dependency_management.md b/Documentation/contributor-guide/dependency_management.md index 8b022b424f8f..5299677bad7c 100644 --- a/Documentation/contributor-guide/dependency_management.md +++ b/Documentation/contributor-guide/dependency_management.md @@ -147,11 +147,11 @@ References: [bbolt](https://github.com/etcd-io/bbolt) and [raft](https://github.com/etcd-io/raft) are two core dependencies of etcd. -Both etcd 3.4.x and 3.5.x depend on bbolt 1.3.x, and etcd 3.6.x (`main` branch) depends on bbolt 1.4.x. +Both etcd 3.4.x and 3.5.x depend on bbolt 1.3.x, and etcd 3.6.x depends on bbolt 1.4.x. raft is included in the etcd repository for release-3.4 and release-3.5 branches, so etcd 3.4.x and 3.5.x do not depend on any -external raft module. We moved raft into [a separate repository](https://github.com/etcd-io/raft) starting from 3.6 (`main` branch), and the first raft -release will be v3.6.0, so etcd 3.6.x will depend on raft 3.6.x. +external raft module. We moved raft into [a separate repository](https://github.com/etcd-io/raft) starting from 3.6, and the first raft +release is v3.6.0, so etcd 3.6.0 depends on raft v3.6.0. Please see the table below: diff --git a/Documentation/contributor-guide/features.md b/Documentation/contributor-guide/features.md index 5f98391077b7..ed2d55accd36 100644 --- a/Documentation/contributor-guide/features.md +++ b/Documentation/contributor-guide/features.md @@ -4,80 +4,84 @@ This document provides an overview of etcd features and general development guid ## Overview -The etcd features fall into three stages, experimental, stable, and unsafe. +The etcd features fall into three stages: Alpha, Beta, and GA. -### Experimental +### Alpha -Any new feature is usually added as an experimental feature. An experimental feature is characterized as below: +Any new feature is usually added as an Alpha feature. An Alpha feature is characterized as below: - Might be buggy due to a lack of user testing. Enabling the feature may not work as expected. -- Disabled by default when added initially. +- Disabled by default. - Support for such a feature may be dropped at any time without notice - Feature-related issues may be given lower priorities. - - It can be removed in the next minor or major release without following the feature deprecation policy unless it graduates to a stable future. + - It can be removed in the next minor or major release without following the feature deprecation policy unless it graduates to a more stable stage. -### Stable +### Beta -A stable feature is characterized as below: +A Beta feature is characterized as below: - Supported as part of the supported releases of etcd. -- May be enabled by default. +- Enabled by default. - Discontinuation of support must follow the feature deprecation policy. -### Unsafe +### GA -Unsafe features are rare and listed under the `Unsafe feature:` section in the etcd usage documentation. By default, they are disabled. They should be used with caution following documentation. An unsafe feature can be removed in the next minor or major release without following the feature deprecation policy. +A GA feature is characterized as below: +- Supported as part of the supported releases of etcd. +- Always enabled; you cannot disable it. The corresponding feature gate is no longer needed. +- Discontinuation of support must follow the feature deprecation policy. ## Development Guidelines ### Adding a new feature -Any new enhancements to the etcd are typically added as an experimental feature. The general development requirements are listed below. They can be somewhat flexible depending on the scope of the feature and review discussions and will evolve over time. -- Open an issue +Any new enhancements to the etcd are typically added as an Alpha feature. + +etcd follows the Kubernetes [KEP process](https://github.com/kubernetes/enhancements/blob/master/keps/sig-architecture/0000-kep-process/README.md) for new enhancements. The general development requirements are listed below. They can be somewhat flexible depending on the scope of the feature and review discussions and will evolve over time. +- Open a [KEP](https://github.com/kubernetes/enhancements/issues) issue - It must provide a clear need for the proposed feature. - - It should list development work items as checkboxes. There must be one work item towards future graduation to a stable future. - - Label the issue with `type/feature` and `experimental`. + - It should list development work items as checkboxes. There must be one work item towards future graduation to Beta. + - Label the issue with `/sig etcd`. - Keep the issue open for tracking purposes until a decision is made on graduation. -- Open a Pull Request (PR) +- Open a [KEP](https://github.com/kubernetes/enhancements) Pull Request (PR). + - The KEP template can be simplified for etcd. + - It must provide clear graduation criteria for each stage. + - The KEP doc should reside in [keps/sig-etcd](https://github.com/kubernetes/enhancements/tree/master/keps/sig-etcd/) +- Open Pull Requests (PRs) in [etcd](https://github.com/etcd-io/etcd) - Provide unit tests. Integration tests are also recommended as possible. - Provide robust e2e test coverage. If the feature being added is complicated or quickly needed, maintainers can decide to go with e2e tests for basic coverage initially and have robust coverage added at a later time before the feature graduation to the stable feature. - Provide logs for proper debugging. - Provide metrics and benchmarks as needed. - - The Feature should be disabled by default. - - Any configuration flags related to the implementation of the feature must be prefixed with `experimental` e.g. `--experimental-feature-name`. + - Add an Alpha [feature gate](https://etcd.io/docs/v3.6/feature-gates/). + - Any code changes or configuration flags related to the implementation of the feature must be gated with the feature gate e.g. `if cfg.ServerFeatureGate.Enabled(features.FeatureName)`. - Add a CHANGELOG entry. -- At least two maintainers must approve feature requirements and related code changes. +- At least two maintainers must approve the KEP and related code changes. -### Graduating an Experimental feature to Stable +### Graduating a feature to the next stage -It is important that experimental features don't get stuck in that stage. They should be revisited and moved to the stable stage following the graduation steps as described here. - -#### Locate graduation candidate -Decide if an experimental feature is ready for graduation to the stable stage. -- Find the issue that was used to enable the experimental feature initially. One way to find such issues is to search for issues with `type/feature` and `experimental` labels. -- Fix any known open issues against the feature. -- Make sure the feature was enabled for at least one previous release. Check the PR(s) reference from the issue to see when the feature-related code changes were merged. +It is important that features don't get stuck in one stage. They should be revisited and moved to the next stage once they meet the graduation criteria listed in the KEP. A feature should stay at one stage for at least one release before being promoted. #### Provide implementation -If an experimental feature is found ready for graduation to the stable stage, open a Pull Request (PR) with the following changes. -- Add robust e2e tests if not already provided. -- Add a new stable feature flag identical to the experimental feature flag but without the `--experimental` prefix. -- Deprecate the experimental feature following the [feature deprecation policy](#Deprecating-a-feature). -- Implementation must ensure that both the graduated and deprecated experimental feature flags work as expected. Note that both these flags will co-exist for the timeframe described in the feature deprecation policy. -- Enable the graduated feature by default if needed. + +If a feature is found ready for graduation to the next stage, open a Pull Request (PR) with the following changes. +- Update the feature `PreRelease` stage in `server/features/etcd_features.go`. +- Update the status in the original KEP issue. At least two maintainers must approve the work. Patch releases should not be considered for graduation. ### Deprecating a feature -#### Experimental -An experimental feature deprecates when it graduates to the stable stage. -- Add a deprecation message in the documentation of the experimental feature with a recommendation to use a related stable feature. e.g. `DEPRECATED. Use instead.` -- Add a `deprecated` label in the issue that was initially used to enable the experimental feature. +#### Alpha +Alpha features can be removed without going through the deprecation process. +- Remove the feature gate in `server/features/etcd_features.go`, and clean up all relevant code. +- Close the original KEP issue with reasons to drop the feature. + +#### Beta and GA +As the project evolves, a Beta/GA feature may sometimes need to be deprecated and removed. Such a situation should be handled using the steps below: -#### Stable -As the project evolves, a stable feature may sometimes need to be deprecated and removed. Such a situation should be handled using the steps below: -- Create an issue for tracking purposes. -- Add a deprecation message in the feature usage documentation before a planned release for feature deprecation. e.g. `To be deprecated in .`. If a new feature replaces the `To be deprecated` feature, then also provide a message saying so. e.g. `Use instead.`. -- Deprecate the feature in the planned release with a message as part of the feature usage documentation. e.g. `DEPRECATED`. If a new feature replaces the deprecated feature, then also provide a message saying so. e.g. `DEPRECATED. Use instead.`. -- Add a `deprecated` label in the related issue. +- A Beta/GA feature can only be deprecated after at least 2 minor or major releases. +- Update original KEP issue if it has not been closed or create a new etcd issue with reasons and steps to deprecate the feature. +- Add the feature deprecation documentation in the release notes and feature gates documentation of the next minor/major release. +- In the next minor/major release, set the feature gate to `{Default: false, PreRelease: featuregate.Deprecated, LockedToDefault: false}` in `server/features/etcd_features.go`. Deprecated feature gates must respond with a warning when used. + - If the feature has GAed, and the original gated codes has been cleaned up, add the disablement codes back with the feature gate. +- In the minor/major release after the next, set the feature gate to `{Default: false, PreRelease: featuregate.Deprecated, LockedToDefault: true}` in `server/features/etcd_features.go`, and start cleaning the code. -Remove the deprecated feature in the following release. Close any related issue(s). At least two maintainers must approve the work. Patch releases should not be considered for deprecation. +At least two maintainers must approve the work. Patch releases should not be considered for deprecation. diff --git a/Documentation/contributor-guide/release.md b/Documentation/contributor-guide/release.md index 266b500d1c4b..081c0ac96b2d 100644 --- a/Documentation/contributor-guide/release.md +++ b/Documentation/contributor-guide/release.md @@ -6,14 +6,17 @@ The procedure includes some manual steps for sanity checking, but it can probabl ## Release management -The following pool of release candidates manages the release of each etcd major/minor version as well as manages patches +Under the leadership of **James Blair** [@jmhbnz](https://github.com/jmhbnz) and **Ivan Valdes Castillo** [@ivanvc](https://github.com/ivanvc), the following pool of release candidates manages the release of each etcd major/minor version as well as manages patches to each stable release branch. They are responsible for communicating the timelines and status of each release and for ensuring the stability of the release branch. - Benjamin Wang [@ahrtr](https://github.com/ahrtr) +- Fu Wei [@fuweid](https://github.com/fuweid) - James Blair [@jmhbnz](https://github.com/jmhbnz) +- Ivan Valdes Castillo [@ivanvc](https://github.com/ivanvc) - Marek Siarkowicz [@serathius](https://github.com/serathius) - Sahdev Zala [@spzala](https://github.com/spzala) +- Siyuan Zhang [@siyuanfoundation](https://github.com/siyuanfoundation) - Wenjia Zhang [@wenjiaswe](https://github.com/wenjiaswe) All release version numbers follow the format of [semantic versioning 2.0.0](http://semver.org/). @@ -68,10 +71,14 @@ which don't need to be executed before releasing each version. ### Release steps +At least one day before the release: + 1. Raise an issue to publish the release plan, e.g. [issues/17350](https://github.com/etcd-io/etcd/issues/17350). -2. Raise a `kubernetes/org` pull request to temporarily elevate permissions for the GitHub release team. -3. Once permissions are elevated, temporarily relax [branch protections](https://github.com/etcd-io/etcd/settings/branches) to allow pushing changes directly to `release-*` branches in GitHub. -4. Verify you can pass the authentication to the image registries, +2. Raise a `kubernetes/org` pull request ([example PR](https://github.com/kubernetes/org/pull/5582)) to ensure members of the release team are added to the [release github team](https://github.com/orgs/etcd-io/teams/release-etcd). + +On the day of the release: + +1. Verify you can pass the authentication to the image registries, - `docker login gcr.io` - `docker login quay.io` - If the release person doesn't have access to 1password, one of the owners (@ahrtr, @ivanvc, @jmhbnz, @serathius) needs to share the password with them per [this guide](https://support.1password.com/share-items/). See rough steps below, @@ -80,45 +87,46 @@ which don't need to be executed before releasing each version. - Select `Password of quay.io`. - Click `Share` on the top right, and set expiration as `1 hour` and only available to the release person using his/her email. - Click `Copy Link` then send the link to the release person via slack or email. -5. Clone the etcd repository and checkout the target branch, +2. Clone the etcd repository and checkout the target branch, - `git clone --branch release-3.X git@github.com:etcd-io/etcd.git` -6. Run the release script under the repository's root directory, replacing `${VERSION}` with a value without the `v` prefix, i.e. `3.5.13`. +3. Run the release script under the repository's root directory, replacing `${VERSION}` with a value without the `v` prefix, i.e. `3.5.13`. - `DRY_RUN=false ./scripts/release.sh ${VERSION}` - **NOTE:** When doing a pre-release (i.e., a version from the main branch, 3.6.0-alpha.2), you will need to explicitly set the branch to main: - ``` + + ```bash DRY_RUN=false BRANCH=main ./scripts/release.sh ${VERSION} ``` It generates all release binaries under the directory `/tmp/etcd-release-${VERSION}/etcd/release/` and images. Binaries are pushed to the Google Cloud bucket under project `etcd-development`, and images are pushed to `quay.io` and `gcr.io`. -7. Publish the release page on GitHub +4. Publish the release page on GitHub - Open the **draft** release URL shown by the release script - Click the pen button at the top right to edit the release - Review that it looks correct, reviewing that the bottom checkboxes are checked depending on the release version (v3.4 no checkboxes, v3.5 has the set as latest release checkbox checked, v3.6 has the set as pre-release checkbox checked) - Then, publish the release -8. Announce to the etcd-dev googlegroup +5. Announce to the etcd-dev googlegroup Follow the format of previous release emails sent to etcd-dev@googlegroups.com, see an example below. After sending out the email, ask one of the mailing list maintainers to approve the email from the pending list. Additionally, label the release email as `Release`. -```text -Hello, + ```text + Hello, -etcd v3.4.30 is now public! + etcd v3.4.30 is now public! -https://github.com/etcd-io/etcd/releases/tag/v3.4.30 + https://github.com/etcd-io/etcd/releases/tag/v3.4.30 -Thanks to everyone who contributed to the release! + Thanks to everyone who contributed to the release! -etcd team -``` + etcd team + ``` -9. Update the changelog to reflect the correct release date. -10. Paste the release link to the issue raised in Step 1 and close the issue. -11. Restore standard branch protection settings and raise a follow-up `kubernetes/org` pull request to return to least privilege permissions. -12. Crease a new stable branch through `git push origin release-${VERSION_MAJOR}.${VERSION_MINOR}` if this is a new major or minor stable release. -13. Re-generate a new password for quay.io if needed (e.g. shared to a contributor who isn't in the release team, and we should rotate the password at least once every 3 months). +6. Update the changelog to reflect the correct release date. +7. Paste the release link to the issue raised in Step 1 and close the issue. +8. Raise a follow-up `kubernetes/org` pull request to return the GitHub release team to empty, least privilege state. +9. Crease a new stable branch through `git push origin release-${VERSION_MAJOR}.${VERSION_MINOR}` if this is a new major or minor stable release. +10. Re-generate a new password for quay.io if needed (e.g. shared to a contributor who isn't in the release team, and we should rotate the password at least once every 3 months). #### Release known issues diff --git a/Documentation/contributor-guide/roadmap.md b/Documentation/contributor-guide/roadmap.md index c036e2eb45c4..f683490e7193 100644 --- a/Documentation/contributor-guide/roadmap.md +++ b/Documentation/contributor-guide/roadmap.md @@ -12,18 +12,18 @@ Each item has an assigned priority. Refer to [priority definitions](https://gith For a full list of tasks in `v3.6.0`, please see [milestone etcd-v3.6](https://github.com/etcd-io/etcd/milestone/38). -| Title | Priority | Status | Note | -|--------------------------------------------------------------------------------------------------------------------|----------|-------------|--------------------------------------------------------------------------------------------------------------| -| [Support downgrade](https://github.com/etcd-io/etcd/issues/11716) | priority/important-soon | In progress | etcd will support downgrade starting from 3.6.0. But it will also support offline downgrade from 3.5 to 3.4. | -| [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | priority/important-soon | In progress | This task will be covered in both 3.6 and 3.7. | -| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89) | priority/important-soon | Not started | etcd 3.6.0 will depends on raft 3.6.0 | -| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553) | priority/important-soon | Not started | etcd 3.6.0 will depends on bbolt 1.4.0 | -| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007) | priority/important-longterm | In progress | It provides clearer APIs, and can also work around the stalled writes issue | +| Title | Priority | Status | Note | +|--------------------------------------------------------------------------------------------------------------------|-----------------------------|-------------|--------------------------------------------------------------------------------------------------------------| +| [Support downgrade](https://github.com/etcd-io/etcd/issues/11716) | priority/important-soon | In progress | etcd will support downgrade starting from 3.6.0. But it will also support offline downgrade from 3.5 to 3.4. | +| [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | priority/important-soon | In progress | This task will be covered in both 3.6 and 3.7. | +| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89) | priority/important-soon | Completed | etcd 3.6.0 will depends on raft 3.6.0 | +| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553) | priority/important-soon | Completed | etcd 3.6.0 will depends on bbolt 1.4.0 | +| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007) | priority/important-longterm | Completed | It provides clearer APIs, and can also work around the stalled writes issue | | [Bump gRPC](https://github.com/etcd-io/etcd/issues/16290) | priority/important-longterm | Completed | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk. | | [Deprecate grpc-gateway or bump it](https://github.com/etcd-io/etcd/issues/14499) | priority/important-longterm | Completed | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk. | | [bbolt: Add logger into bbolt](https://github.com/etcd-io/bbolt/issues/509) | priority/important-longterm | Completed | It's important to diagnose bbolt issues | | [bbolt: Add surgery commands](https://github.com/etcd-io/bbolt/issues/370) | priority/important-longterm | Completed | Surgery commands are important for fixing corrupted db files | -| [Evaluate and (Gradulate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | priority/backlog | Not started | This task will be covered in both 3.6 and 3.7. | +| [Evaluate and (Gradulate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | priority/backlog | Not started | This task will be covered in both 3.6 and 3.7. | ## v3.7.0 @@ -32,8 +32,9 @@ For a full list of tasks in `v3.7.0`, please see [milestone etcd-v3.7](https://g | Title | Priority | Note | |-------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------------------------------------------| | [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | P0 | Finish the remaining tasks 3.7. | +| [Support range stream](https://github.com/etcd-io/etcd/issues/12342) | P0 | to be investigated & discussed. | | [Refactor lease: Lease might be revoked by mistake by old leader](https://github.com/etcd-io/etcd/issues/15247) | P1 | to be investigated & discussed | -| [Integrate raft's new feature (async write) into etcd](https://github.com/etcd-io/etcd/issues/16291) | P1 | It should improve the performance | +| [Integrate raft's new feature (async write) into etcd](https://github.com/etcd-io/etcd/issues/16291) | P1 | It should improve the performance | | [bbolt: Support customizing the bbolt rebalance threshold](https://github.com/etcd-io/bbolt/issues/422) | P2 | It may get rid of etcd's defragmentation. Both bbolt and etcd need to be changed. | | [Evaluate and (graduate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | P2 | Finish the remaining tasks 3.7. | diff --git a/Documentation/dev-guide/apispec/swagger/rpc.swagger.json b/Documentation/dev-guide/apispec/swagger/rpc.swagger.json index b2e0a00b270a..34fc55dff23f 100644 --- a/Documentation/dev-guide/apispec/swagger/rpc.swagger.json +++ b/Documentation/dev-guide/apispec/swagger/rpc.swagger.json @@ -2135,6 +2135,19 @@ } } }, + "etcdserverpbDowngradeInfo": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "enabled indicates whether the cluster is enabled to downgrade." + }, + "targetVersion": { + "type": "string", + "description": "targetVersion is the target downgrade version." + } + } + }, "etcdserverpbDowngradeRequest": { "type": "object", "properties": { @@ -2689,7 +2702,7 @@ "count": { "type": "string", "format": "int64", - "description": "count is set to the number of keys within the range when requested." + "description": "count is set to the actual number of keys within the range when requested.\nUnlike Kvs, it is unaffected by limits and filters (e.g., Min/Max, Create/Modify, Revisions)\nand reflects the full count within the specified range." } } }, @@ -2834,12 +2847,16 @@ }, "storageVersion": { "type": "string", - "description": "storageVersion is the version of the db file. It might be get updated with delay in relationship to the target cluster version." + "description": "storageVersion is the version of the db file. It might be updated with delay in relationship to the target cluster version." }, "dbSizeQuota": { "type": "string", "format": "int64", "title": "dbSizeQuota is the configured etcd storage quota in bytes (the value passed to etcd instance by flag --quota-backend-bytes)" + }, + "downgradeInfo": { + "$ref": "#/definitions/etcdserverpbDowngradeInfo", + "description": "downgradeInfo indicates if there is downgrade process." } } }, diff --git a/Documentation/dev-guide/apispec/swagger/v3election.swagger.json b/Documentation/dev-guide/apispec/swagger/v3election.swagger.json index 75194fe04795..d118221bea33 100644 --- a/Documentation/dev-guide/apispec/swagger/v3election.swagger.json +++ b/Documentation/dev-guide/apispec/swagger/v3election.swagger.json @@ -318,7 +318,7 @@ "name": { "type": "string", "format": "byte", - "description": "name is the election identifier that correponds to the leadership key." + "description": "name is the election identifier that corresponds to the leadership key." }, "key": { "type": "string", diff --git a/Makefile b/Makefile index 5bc74672768a..802297166ba3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ +REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) + .PHONY: all all: build -include tests/robustness/makefile.mk +include $(REPOSITORY_ROOT)/tests/robustness/Makefile .PHONY: build build: @@ -54,10 +56,26 @@ test-grpcproxy-e2e: build test-e2e-release: build PASSES="release e2e" ./scripts/test.sh $(GO_TEST_FLAGS) +# When we release the first 3.7.0-alpha.0, we can remove `VERSION="3.7.99"` below. +.PHONY: test-release +test-release: + PASSES="release_tests" VERSION="3.7.99" ./scripts/test.sh $(GO_TEST_FLAGS) + .PHONY: test-robustness test-robustness: PASSES="robustness" ./scripts/test.sh $(GO_TEST_FLAGS) +.PHONY: test-coverage +test-coverage: + COVERDIR=covdir PASSES="build cov" ./scripts/test.sh $(GO_TEST_FLAGS) + +.PHONY: upload-coverage-report +upload-coverage-report: + return_code=0; \ + $(MAKE) test-coverage || return_code=$$?; \ + COVERDIR=covdir ./scripts/codecov_upload.sh; \ + exit $$return_code + .PHONY: fuzz fuzz: ./scripts/fuzzing.sh @@ -210,3 +228,7 @@ verify-go-versions: .PHONY: sync-toolchain-directive sync-toolchain-directive: ./scripts/sync_go_toolchain_directive.sh + +.PHONY: markdown-diff-lint +markdown-diff-lint: + ./scripts/markdown_diff_lint.sh diff --git a/OWNERS b/OWNERS index 393aea9670a9..30037eb01e49 100644 --- a/OWNERS +++ b/OWNERS @@ -2,11 +2,11 @@ approvers: - ahrtr # Benjamin Wang + - fuweid # Wei Fu - jmhbnz # James Blair - serathius # Marek Siarkowicz - spzala # Sahdev Zala - wenjiaswe # Wenjia Zhang reviewers: - - fuweid # Wei Fu - ivanvc # Ivan Valdes - siyuanfoundation # Siyuan Zhang diff --git a/api/etcdserverpb/gw/rpc.pb.gw.go b/api/etcdserverpb/gw/rpc.pb.gw.go index 5e8132a2fe73..6ad1e9d9c653 100644 --- a/api/etcdserverpb/gw/rpc.pb.gw.go +++ b/api/etcdserverpb/gw/rpc.pb.gw.go @@ -158,14 +158,12 @@ func local_request_KV_Compact_0(ctx context.Context, marshaler runtime.Marshaler return protov1.MessageV2(msg), metadata, err } -func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.WatchClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Watch_WatchClient, runtime.ServerMetadata, chan error, error) { +func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.WatchClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Watch_WatchClient, runtime.ServerMetadata, error) { var metadata runtime.ServerMetadata - errChan := make(chan error, 1) stream, err := client.Watch(ctx) if err != nil { grpclog.Errorf("Failed to start streaming: %v", err) - close(errChan) - return nil, metadata, errChan, err + return nil, metadata, err } dec := marshaler.NewDecoder(req.Body) handleSend := func() error { @@ -185,10 +183,8 @@ func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, cli return nil } go func() { - defer close(errChan) for { if err := handleSend(); err != nil { - errChan <- err break } } @@ -199,10 +195,10 @@ func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, cli header, err := stream.Header() if err != nil { grpclog.Errorf("Failed to get header from client: %v", err) - return nil, metadata, errChan, err + return nil, metadata, err } metadata.HeaderMD = header - return stream, metadata, errChan, nil + return stream, metadata, nil } func request_Lease_LeaseGrant_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { @@ -277,14 +273,12 @@ func local_request_Lease_LeaseRevoke_1(ctx context.Context, marshaler runtime.Ma return protov1.MessageV2(msg), metadata, err } -func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Lease_LeaseKeepAliveClient, runtime.ServerMetadata, chan error, error) { +func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Lease_LeaseKeepAliveClient, runtime.ServerMetadata, error) { var metadata runtime.ServerMetadata - errChan := make(chan error, 1) stream, err := client.LeaseKeepAlive(ctx) if err != nil { grpclog.Errorf("Failed to start streaming: %v", err) - close(errChan) - return nil, metadata, errChan, err + return nil, metadata, err } dec := marshaler.NewDecoder(req.Body) handleSend := func() error { @@ -304,10 +298,8 @@ func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marsh return nil } go func() { - defer close(errChan) for { if err := handleSend(); err != nil { - errChan <- err break } } @@ -318,10 +310,10 @@ func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marsh header, err := stream.Header() if err != nil { grpclog.Errorf("Failed to get header from client: %v", err) - return nil, metadata, errChan, err + return nil, metadata, err } metadata.HeaderMD = header - return stream, metadata, errChan, nil + return stream, metadata, nil } func request_Lease_LeaseTimeToLive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { @@ -2221,20 +2213,12 @@ func RegisterWatchHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - - resp, md, reqErrChan, err := request_Watch_Watch_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Watch_Watch_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - go func() { - for err := range reqErrChan { - if err != nil && !errors.Is(err, io.EOF) { - runtime.HTTPStreamError(annotatedContext, mux, outboundMarshaler, w, req, err) - } - } - }() forward_Watch_Watch_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { m1, err := resp.Recv() return protov1.MessageV2(m1), err @@ -2347,20 +2331,12 @@ func RegisterLeaseHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - - resp, md, reqErrChan, err := request_Lease_LeaseKeepAlive_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Lease_LeaseKeepAlive_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - go func() { - for err := range reqErrChan { - if err != nil && !errors.Is(err, io.EOF) { - runtime.HTTPStreamError(annotatedContext, mux, outboundMarshaler, w, req, err) - } - } - }() forward_Lease_LeaseKeepAlive_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { m1, err := resp.Recv() return protov1.MessageV2(m1), err diff --git a/api/etcdserverpb/rpc.pb.go b/api/etcdserverpb/rpc.pb.go index b313cc59a27f..42bf641db6b5 100644 --- a/api/etcdserverpb/rpc.pb.go +++ b/api/etcdserverpb/rpc.pb.go @@ -529,7 +529,9 @@ type RangeResponse struct { Kvs []*mvccpb.KeyValue `protobuf:"bytes,2,rep,name=kvs,proto3" json:"kvs,omitempty"` // more indicates if there are more keys to return in the requested range. More bool `protobuf:"varint,3,opt,name=more,proto3" json:"more,omitempty"` - // count is set to the number of keys within the range when requested. + // count is set to the actual number of keys within the range when requested. + // Unlike Kvs, it is unaffected by limits and filters (e.g., Min/Max, Create/Modify, Revisions) + // and reflects the full count within the specified range. Count int64 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -4299,13 +4301,15 @@ type StatusResponse struct { DbSizeInUse int64 `protobuf:"varint,9,opt,name=dbSizeInUse,proto3" json:"dbSizeInUse,omitempty"` // isLearner indicates if the member is raft learner. IsLearner bool `protobuf:"varint,10,opt,name=isLearner,proto3" json:"isLearner,omitempty"` - // storageVersion is the version of the db file. It might be get updated with delay in relationship to the target cluster version. + // storageVersion is the version of the db file. It might be updated with delay in relationship to the target cluster version. StorageVersion string `protobuf:"bytes,11,opt,name=storageVersion,proto3" json:"storageVersion,omitempty"` // dbSizeQuota is the configured etcd storage quota in bytes (the value passed to etcd instance by flag --quota-backend-bytes) - DbSizeQuota int64 `protobuf:"varint,12,opt,name=dbSizeQuota,proto3" json:"dbSizeQuota,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DbSizeQuota int64 `protobuf:"varint,12,opt,name=dbSizeQuota,proto3" json:"dbSizeQuota,omitempty"` + // downgradeInfo indicates if there is downgrade process. + DowngradeInfo *DowngradeInfo `protobuf:"bytes,13,opt,name=downgradeInfo,proto3" json:"downgradeInfo,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *StatusResponse) Reset() { *m = StatusResponse{} } @@ -4425,6 +4429,70 @@ func (m *StatusResponse) GetDbSizeQuota() int64 { return 0 } +func (m *StatusResponse) GetDowngradeInfo() *DowngradeInfo { + if m != nil { + return m.DowngradeInfo + } + return nil +} + +type DowngradeInfo struct { + // enabled indicates whether the cluster is enabled to downgrade. + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + // targetVersion is the target downgrade version. + TargetVersion string `protobuf:"bytes,2,opt,name=targetVersion,proto3" json:"targetVersion,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DowngradeInfo) Reset() { *m = DowngradeInfo{} } +func (m *DowngradeInfo) String() string { return proto.CompactTextString(m) } +func (*DowngradeInfo) ProtoMessage() {} +func (*DowngradeInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{62} +} +func (m *DowngradeInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DowngradeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DowngradeInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DowngradeInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_DowngradeInfo.Merge(m, src) +} +func (m *DowngradeInfo) XXX_Size() int { + return m.Size() +} +func (m *DowngradeInfo) XXX_DiscardUnknown() { + xxx_messageInfo_DowngradeInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_DowngradeInfo proto.InternalMessageInfo + +func (m *DowngradeInfo) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func (m *DowngradeInfo) GetTargetVersion() string { + if m != nil { + return m.TargetVersion + } + return "" +} + type AuthEnableRequest struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -4435,7 +4503,7 @@ func (m *AuthEnableRequest) Reset() { *m = AuthEnableRequest{} } func (m *AuthEnableRequest) String() string { return proto.CompactTextString(m) } func (*AuthEnableRequest) ProtoMessage() {} func (*AuthEnableRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } func (m *AuthEnableRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4474,7 +4542,7 @@ func (m *AuthDisableRequest) Reset() { *m = AuthDisableRequest{} } func (m *AuthDisableRequest) String() string { return proto.CompactTextString(m) } func (*AuthDisableRequest) ProtoMessage() {} func (*AuthDisableRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } func (m *AuthDisableRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4513,7 +4581,7 @@ func (m *AuthStatusRequest) Reset() { *m = AuthStatusRequest{} } func (m *AuthStatusRequest) String() string { return proto.CompactTextString(m) } func (*AuthStatusRequest) ProtoMessage() {} func (*AuthStatusRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } func (m *AuthStatusRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4554,7 +4622,7 @@ func (m *AuthenticateRequest) Reset() { *m = AuthenticateRequest{} } func (m *AuthenticateRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateRequest) ProtoMessage() {} func (*AuthenticateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } func (m *AuthenticateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4611,7 +4679,7 @@ func (m *AuthUserAddRequest) Reset() { *m = AuthUserAddRequest{} } func (m *AuthUserAddRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserAddRequest) ProtoMessage() {} func (*AuthUserAddRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } func (m *AuthUserAddRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4679,7 +4747,7 @@ func (m *AuthUserGetRequest) Reset() { *m = AuthUserGetRequest{} } func (m *AuthUserGetRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserGetRequest) ProtoMessage() {} func (*AuthUserGetRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } func (m *AuthUserGetRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4727,7 +4795,7 @@ func (m *AuthUserDeleteRequest) Reset() { *m = AuthUserDeleteRequest{} } func (m *AuthUserDeleteRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserDeleteRequest) ProtoMessage() {} func (*AuthUserDeleteRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } func (m *AuthUserDeleteRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4779,7 +4847,7 @@ func (m *AuthUserChangePasswordRequest) Reset() { *m = AuthUserChangePas func (m *AuthUserChangePasswordRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserChangePasswordRequest) ProtoMessage() {} func (*AuthUserChangePasswordRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } func (m *AuthUserChangePasswordRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4843,7 +4911,7 @@ func (m *AuthUserGrantRoleRequest) Reset() { *m = AuthUserGrantRoleReque func (m *AuthUserGrantRoleRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserGrantRoleRequest) ProtoMessage() {} func (*AuthUserGrantRoleRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } func (m *AuthUserGrantRoleRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4898,7 +4966,7 @@ func (m *AuthUserRevokeRoleRequest) Reset() { *m = AuthUserRevokeRoleReq func (m *AuthUserRevokeRoleRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserRevokeRoleRequest) ProtoMessage() {} func (*AuthUserRevokeRoleRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{71} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } func (m *AuthUserRevokeRoleRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4953,7 +5021,7 @@ func (m *AuthRoleAddRequest) Reset() { *m = AuthRoleAddRequest{} } func (m *AuthRoleAddRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleAddRequest) ProtoMessage() {} func (*AuthRoleAddRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{72} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } func (m *AuthRoleAddRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5000,7 +5068,7 @@ func (m *AuthRoleGetRequest) Reset() { *m = AuthRoleGetRequest{} } func (m *AuthRoleGetRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleGetRequest) ProtoMessage() {} func (*AuthRoleGetRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{73} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } func (m *AuthRoleGetRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5046,7 +5114,7 @@ func (m *AuthUserListRequest) Reset() { *m = AuthUserListRequest{} } func (m *AuthUserListRequest) String() string { return proto.CompactTextString(m) } func (*AuthUserListRequest) ProtoMessage() {} func (*AuthUserListRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{74} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } func (m *AuthUserListRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5085,7 +5153,7 @@ func (m *AuthRoleListRequest) Reset() { *m = AuthRoleListRequest{} } func (m *AuthRoleListRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleListRequest) ProtoMessage() {} func (*AuthRoleListRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{75} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } func (m *AuthRoleListRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5125,7 +5193,7 @@ func (m *AuthRoleDeleteRequest) Reset() { *m = AuthRoleDeleteRequest{} } func (m *AuthRoleDeleteRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleDeleteRequest) ProtoMessage() {} func (*AuthRoleDeleteRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{76} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } func (m *AuthRoleDeleteRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5175,7 +5243,7 @@ func (m *AuthRoleGrantPermissionRequest) Reset() { *m = AuthRoleGrantPer func (m *AuthRoleGrantPermissionRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleGrantPermissionRequest) ProtoMessage() {} func (*AuthRoleGrantPermissionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{77} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } func (m *AuthRoleGrantPermissionRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5231,7 +5299,7 @@ func (m *AuthRoleRevokePermissionRequest) Reset() { *m = AuthRoleRevokeP func (m *AuthRoleRevokePermissionRequest) String() string { return proto.CompactTextString(m) } func (*AuthRoleRevokePermissionRequest) ProtoMessage() {} func (*AuthRoleRevokePermissionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{78} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } func (m *AuthRoleRevokePermissionRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5292,7 +5360,7 @@ func (m *AuthEnableResponse) Reset() { *m = AuthEnableResponse{} } func (m *AuthEnableResponse) String() string { return proto.CompactTextString(m) } func (*AuthEnableResponse) ProtoMessage() {} func (*AuthEnableResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{79} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } func (m *AuthEnableResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5339,7 +5407,7 @@ func (m *AuthDisableResponse) Reset() { *m = AuthDisableResponse{} } func (m *AuthDisableResponse) String() string { return proto.CompactTextString(m) } func (*AuthDisableResponse) ProtoMessage() {} func (*AuthDisableResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{80} + return fileDescriptor_77a6da22d6a3feb1, []int{81} } func (m *AuthDisableResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5389,7 +5457,7 @@ func (m *AuthStatusResponse) Reset() { *m = AuthStatusResponse{} } func (m *AuthStatusResponse) String() string { return proto.CompactTextString(m) } func (*AuthStatusResponse) ProtoMessage() {} func (*AuthStatusResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{81} + return fileDescriptor_77a6da22d6a3feb1, []int{82} } func (m *AuthStatusResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5452,7 +5520,7 @@ func (m *AuthenticateResponse) Reset() { *m = AuthenticateResponse{} } func (m *AuthenticateResponse) String() string { return proto.CompactTextString(m) } func (*AuthenticateResponse) ProtoMessage() {} func (*AuthenticateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{82} + return fileDescriptor_77a6da22d6a3feb1, []int{83} } func (m *AuthenticateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5506,7 +5574,7 @@ func (m *AuthUserAddResponse) Reset() { *m = AuthUserAddResponse{} } func (m *AuthUserAddResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserAddResponse) ProtoMessage() {} func (*AuthUserAddResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{83} + return fileDescriptor_77a6da22d6a3feb1, []int{84} } func (m *AuthUserAddResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5554,7 +5622,7 @@ func (m *AuthUserGetResponse) Reset() { *m = AuthUserGetResponse{} } func (m *AuthUserGetResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserGetResponse) ProtoMessage() {} func (*AuthUserGetResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{84} + return fileDescriptor_77a6da22d6a3feb1, []int{85} } func (m *AuthUserGetResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5608,7 +5676,7 @@ func (m *AuthUserDeleteResponse) Reset() { *m = AuthUserDeleteResponse{} func (m *AuthUserDeleteResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserDeleteResponse) ProtoMessage() {} func (*AuthUserDeleteResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{85} + return fileDescriptor_77a6da22d6a3feb1, []int{86} } func (m *AuthUserDeleteResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5655,7 +5723,7 @@ func (m *AuthUserChangePasswordResponse) Reset() { *m = AuthUserChangePa func (m *AuthUserChangePasswordResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserChangePasswordResponse) ProtoMessage() {} func (*AuthUserChangePasswordResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{86} + return fileDescriptor_77a6da22d6a3feb1, []int{87} } func (m *AuthUserChangePasswordResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5702,7 +5770,7 @@ func (m *AuthUserGrantRoleResponse) Reset() { *m = AuthUserGrantRoleResp func (m *AuthUserGrantRoleResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserGrantRoleResponse) ProtoMessage() {} func (*AuthUserGrantRoleResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{87} + return fileDescriptor_77a6da22d6a3feb1, []int{88} } func (m *AuthUserGrantRoleResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5749,7 +5817,7 @@ func (m *AuthUserRevokeRoleResponse) Reset() { *m = AuthUserRevokeRoleRe func (m *AuthUserRevokeRoleResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserRevokeRoleResponse) ProtoMessage() {} func (*AuthUserRevokeRoleResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{88} + return fileDescriptor_77a6da22d6a3feb1, []int{89} } func (m *AuthUserRevokeRoleResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5796,7 +5864,7 @@ func (m *AuthRoleAddResponse) Reset() { *m = AuthRoleAddResponse{} } func (m *AuthRoleAddResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleAddResponse) ProtoMessage() {} func (*AuthRoleAddResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{89} + return fileDescriptor_77a6da22d6a3feb1, []int{90} } func (m *AuthRoleAddResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5844,7 +5912,7 @@ func (m *AuthRoleGetResponse) Reset() { *m = AuthRoleGetResponse{} } func (m *AuthRoleGetResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleGetResponse) ProtoMessage() {} func (*AuthRoleGetResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{90} + return fileDescriptor_77a6da22d6a3feb1, []int{91} } func (m *AuthRoleGetResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5899,7 +5967,7 @@ func (m *AuthRoleListResponse) Reset() { *m = AuthRoleListResponse{} } func (m *AuthRoleListResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleListResponse) ProtoMessage() {} func (*AuthRoleListResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{91} + return fileDescriptor_77a6da22d6a3feb1, []int{92} } func (m *AuthRoleListResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -5954,7 +6022,7 @@ func (m *AuthUserListResponse) Reset() { *m = AuthUserListResponse{} } func (m *AuthUserListResponse) String() string { return proto.CompactTextString(m) } func (*AuthUserListResponse) ProtoMessage() {} func (*AuthUserListResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{92} + return fileDescriptor_77a6da22d6a3feb1, []int{93} } func (m *AuthUserListResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6008,7 +6076,7 @@ func (m *AuthRoleDeleteResponse) Reset() { *m = AuthRoleDeleteResponse{} func (m *AuthRoleDeleteResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleDeleteResponse) ProtoMessage() {} func (*AuthRoleDeleteResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{93} + return fileDescriptor_77a6da22d6a3feb1, []int{94} } func (m *AuthRoleDeleteResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6055,7 +6123,7 @@ func (m *AuthRoleGrantPermissionResponse) Reset() { *m = AuthRoleGrantPe func (m *AuthRoleGrantPermissionResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleGrantPermissionResponse) ProtoMessage() {} func (*AuthRoleGrantPermissionResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{94} + return fileDescriptor_77a6da22d6a3feb1, []int{95} } func (m *AuthRoleGrantPermissionResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6102,7 +6170,7 @@ func (m *AuthRoleRevokePermissionResponse) Reset() { *m = AuthRoleRevoke func (m *AuthRoleRevokePermissionResponse) String() string { return proto.CompactTextString(m) } func (*AuthRoleRevokePermissionResponse) ProtoMessage() {} func (*AuthRoleRevokePermissionResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_77a6da22d6a3feb1, []int{95} + return fileDescriptor_77a6da22d6a3feb1, []int{96} } func (m *AuthRoleRevokePermissionResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -6209,6 +6277,7 @@ func init() { proto.RegisterType((*DowngradeVersionTestRequest)(nil), "etcdserverpb.DowngradeVersionTestRequest") proto.RegisterType((*StatusRequest)(nil), "etcdserverpb.StatusRequest") proto.RegisterType((*StatusResponse)(nil), "etcdserverpb.StatusResponse") + proto.RegisterType((*DowngradeInfo)(nil), "etcdserverpb.DowngradeInfo") proto.RegisterType((*AuthEnableRequest)(nil), "etcdserverpb.AuthEnableRequest") proto.RegisterType((*AuthDisableRequest)(nil), "etcdserverpb.AuthDisableRequest") proto.RegisterType((*AuthStatusRequest)(nil), "etcdserverpb.AuthStatusRequest") @@ -6248,291 +6317,293 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 4532 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1b, 0x49, - 0x76, 0x6a, 0x52, 0x12, 0xc5, 0x47, 0x8a, 0xa2, 0xca, 0xb2, 0x4d, 0xd3, 0xb6, 0xac, 0x69, 0x8f, - 0x67, 0x3d, 0x9e, 0xb1, 0x68, 0x4b, 0xf6, 0xcc, 0xc6, 0xc1, 0x4c, 0x96, 0x96, 0x38, 0xb6, 0x62, - 0x59, 0xd2, 0xb4, 0x68, 0xcf, 0x8e, 0x03, 0xac, 0xd2, 0x22, 0xcb, 0x54, 0xaf, 0xc8, 0x6e, 0x6e, - 0x77, 0x8b, 0x96, 0x26, 0x87, 0x9d, 0x6c, 0xb2, 0x59, 0x6c, 0x02, 0x2c, 0x90, 0x09, 0x10, 0x2c, - 0x82, 0xe4, 0x12, 0x04, 0x48, 0x0e, 0x49, 0x90, 0x1c, 0x72, 0x08, 0x12, 0x20, 0x87, 0xe4, 0x90, - 0x1c, 0x02, 0x04, 0xc8, 0x21, 0xd7, 0x64, 0xb2, 0xa7, 0xfc, 0x8a, 0xa0, 0xbe, 0xba, 0xaa, 0xbb, - 0xab, 0x25, 0xcf, 0x4a, 0x83, 0xbd, 0x58, 0x5d, 0xf5, 0x5e, 0xbd, 0xf7, 0xea, 0xbd, 0xaa, 0xf7, - 0xaa, 0xde, 0x2b, 0x13, 0x8a, 0xfe, 0xb0, 0xb3, 0x38, 0xf4, 0xbd, 0xd0, 0x43, 0x65, 0x1c, 0x76, - 0xba, 0x01, 0xf6, 0x47, 0xd8, 0x1f, 0xee, 0xd6, 0xe7, 0x7a, 0x5e, 0xcf, 0xa3, 0x80, 0x06, 0xf9, - 0x62, 0x38, 0xf5, 0x1a, 0xc1, 0x69, 0xd8, 0x43, 0xa7, 0x31, 0x18, 0x75, 0x3a, 0xc3, 0xdd, 0xc6, - 0xfe, 0x88, 0x43, 0xea, 0x11, 0xc4, 0x3e, 0x08, 0xf7, 0x86, 0xbb, 0xf4, 0x0f, 0x87, 0x2d, 0x44, - 0xb0, 0x11, 0xf6, 0x03, 0xc7, 0x73, 0x87, 0xbb, 0xe2, 0x8b, 0x63, 0x5c, 0xe9, 0x79, 0x5e, 0xaf, - 0x8f, 0xd9, 0x78, 0xd7, 0xf5, 0x42, 0x3b, 0x74, 0x3c, 0x37, 0xe0, 0x50, 0xf6, 0xa7, 0x73, 0xbb, - 0x87, 0xdd, 0xdb, 0xde, 0x10, 0xbb, 0xf6, 0xd0, 0x19, 0x2d, 0x35, 0xbc, 0x21, 0xc5, 0x49, 0xe3, - 0x9b, 0x3f, 0x31, 0xa0, 0x62, 0xe1, 0x60, 0xe8, 0xb9, 0x01, 0x7e, 0x8c, 0xed, 0x2e, 0xf6, 0xd1, - 0x55, 0x80, 0x4e, 0xff, 0x20, 0x08, 0xb1, 0xbf, 0xe3, 0x74, 0x6b, 0xc6, 0x82, 0x71, 0x73, 0xdc, - 0x2a, 0xf2, 0x9e, 0xb5, 0x2e, 0xba, 0x0c, 0xc5, 0x01, 0x1e, 0xec, 0x32, 0x68, 0x8e, 0x42, 0xa7, - 0x58, 0xc7, 0x5a, 0x17, 0xd5, 0x61, 0xca, 0xc7, 0x23, 0x87, 0x88, 0x5b, 0xcb, 0x2f, 0x18, 0x37, - 0xf3, 0x56, 0xd4, 0x26, 0x03, 0x7d, 0xfb, 0x65, 0xb8, 0x13, 0x62, 0x7f, 0x50, 0x1b, 0x67, 0x03, - 0x49, 0x47, 0x1b, 0xfb, 0x83, 0x07, 0x85, 0x1f, 0xfc, 0x5d, 0x2d, 0xbf, 0xbc, 0x78, 0xc7, 0xfc, - 0xe7, 0x09, 0x28, 0x5b, 0xb6, 0xdb, 0xc3, 0x16, 0xfe, 0xde, 0x01, 0x0e, 0x42, 0x54, 0x85, 0xfc, - 0x3e, 0x3e, 0xa2, 0x72, 0x94, 0x2d, 0xf2, 0xc9, 0x08, 0xb9, 0x3d, 0xbc, 0x83, 0x5d, 0x26, 0x41, - 0x99, 0x10, 0x72, 0x7b, 0xb8, 0xe5, 0x76, 0xd1, 0x1c, 0x4c, 0xf4, 0x9d, 0x81, 0x13, 0x72, 0xf6, - 0xac, 0x11, 0x93, 0x6b, 0x3c, 0x21, 0xd7, 0x0a, 0x40, 0xe0, 0xf9, 0xe1, 0x8e, 0xe7, 0x77, 0xb1, - 0x5f, 0x9b, 0x58, 0x30, 0x6e, 0x56, 0x96, 0xde, 0x5c, 0x54, 0x2d, 0xbc, 0xa8, 0x0a, 0xb4, 0xb8, - 0xed, 0xf9, 0xe1, 0x26, 0xc1, 0xb5, 0x8a, 0x81, 0xf8, 0x44, 0x1f, 0x41, 0x89, 0x12, 0x09, 0x6d, - 0xbf, 0x87, 0xc3, 0xda, 0x24, 0xa5, 0x72, 0xe3, 0x04, 0x2a, 0x6d, 0x8a, 0x6c, 0x51, 0xf6, 0xec, - 0x1b, 0x99, 0x50, 0x0e, 0xb0, 0xef, 0xd8, 0x7d, 0xe7, 0x33, 0x7b, 0xb7, 0x8f, 0x6b, 0x85, 0x05, - 0xe3, 0xe6, 0x94, 0x15, 0xeb, 0x23, 0xf3, 0xdf, 0xc7, 0x47, 0xc1, 0x8e, 0xe7, 0xf6, 0x8f, 0x6a, - 0x53, 0x14, 0x61, 0x8a, 0x74, 0x6c, 0xba, 0xfd, 0x23, 0x6a, 0x3d, 0xef, 0xc0, 0x0d, 0x19, 0xb4, - 0x48, 0xa1, 0x45, 0xda, 0x43, 0xc1, 0x77, 0xa1, 0x3a, 0x70, 0xdc, 0x9d, 0x81, 0xd7, 0xdd, 0x89, - 0x14, 0x02, 0x44, 0x21, 0x0f, 0x0b, 0xbf, 0x4b, 0x2d, 0x70, 0xd7, 0xaa, 0x0c, 0x1c, 0xf7, 0xa9, - 0xd7, 0xb5, 0x84, 0x7e, 0xc8, 0x10, 0xfb, 0x30, 0x3e, 0xa4, 0x94, 0x1c, 0x62, 0x1f, 0xaa, 0x43, - 0xde, 0x87, 0x73, 0x84, 0x4b, 0xc7, 0xc7, 0x76, 0x88, 0xe5, 0xa8, 0x72, 0x7c, 0xd4, 0xec, 0xc0, - 0x71, 0x57, 0x28, 0x4a, 0x6c, 0xa0, 0x7d, 0x98, 0x1a, 0x38, 0x9d, 0x1c, 0x68, 0x1f, 0xc6, 0x07, - 0x9a, 0xef, 0x43, 0x31, 0xb2, 0x0b, 0x9a, 0x82, 0xf1, 0x8d, 0xcd, 0x8d, 0x56, 0x75, 0x0c, 0x01, - 0x4c, 0x36, 0xb7, 0x57, 0x5a, 0x1b, 0xab, 0x55, 0x03, 0x95, 0xa0, 0xb0, 0xda, 0x62, 0x8d, 0x5c, - 0xbd, 0xf0, 0x05, 0x5f, 0x6f, 0x4f, 0x00, 0xa4, 0x29, 0x50, 0x01, 0xf2, 0x4f, 0x5a, 0x9f, 0x56, - 0xc7, 0x08, 0xf2, 0xf3, 0x96, 0xb5, 0xbd, 0xb6, 0xb9, 0x51, 0x35, 0x08, 0x95, 0x15, 0xab, 0xd5, - 0x6c, 0xb7, 0xaa, 0x39, 0x82, 0xf1, 0x74, 0x73, 0xb5, 0x9a, 0x47, 0x45, 0x98, 0x78, 0xde, 0x5c, - 0x7f, 0xd6, 0xaa, 0x8e, 0x47, 0xc4, 0xe4, 0x2a, 0xfe, 0x63, 0x03, 0xa6, 0xb9, 0xb9, 0xd9, 0xde, - 0x42, 0xf7, 0x60, 0x72, 0x8f, 0xee, 0x2f, 0xba, 0x92, 0x4b, 0x4b, 0x57, 0x12, 0x6b, 0x23, 0xb6, - 0x07, 0x2d, 0x8e, 0x8b, 0x4c, 0xc8, 0xef, 0x8f, 0x82, 0x5a, 0x6e, 0x21, 0x7f, 0xb3, 0xb4, 0x54, - 0x5d, 0x64, 0x9e, 0x64, 0xf1, 0x09, 0x3e, 0x7a, 0x6e, 0xf7, 0x0f, 0xb0, 0x45, 0x80, 0x08, 0xc1, - 0xf8, 0xc0, 0xf3, 0x31, 0x5d, 0xf0, 0x53, 0x16, 0xfd, 0x26, 0xbb, 0x80, 0xda, 0x9c, 0x2f, 0x76, - 0xd6, 0x90, 0xe2, 0xfd, 0xbb, 0x01, 0xb0, 0x75, 0x10, 0x66, 0x6f, 0xb1, 0x39, 0x98, 0x18, 0x11, - 0x0e, 0x7c, 0x7b, 0xb1, 0x06, 0xdd, 0x5b, 0xd8, 0x0e, 0x70, 0xb4, 0xb7, 0x48, 0x03, 0x2d, 0x40, - 0x61, 0xe8, 0xe3, 0xd1, 0xce, 0xfe, 0x88, 0x72, 0x9b, 0x92, 0x76, 0x9a, 0x24, 0xfd, 0x4f, 0x46, - 0xe8, 0x16, 0x94, 0x9d, 0x9e, 0xeb, 0xf9, 0x78, 0x87, 0x11, 0x9d, 0x50, 0xd1, 0x96, 0xac, 0x12, - 0x03, 0xd2, 0x29, 0x29, 0xb8, 0x8c, 0xd5, 0xa4, 0x16, 0x77, 0x9d, 0xc0, 0xe4, 0x7c, 0x3e, 0x37, - 0xa0, 0x44, 0xe7, 0x73, 0x2a, 0x65, 0x2f, 0xc9, 0x89, 0xe4, 0xe8, 0xb0, 0x94, 0xc2, 0x53, 0x53, - 0x93, 0x22, 0xb8, 0x80, 0x56, 0x71, 0x1f, 0x87, 0xf8, 0x34, 0xce, 0x4b, 0x51, 0x65, 0x5e, 0xab, - 0x4a, 0xc9, 0xef, 0xcf, 0x0c, 0x38, 0x17, 0x63, 0x78, 0xaa, 0xa9, 0xd7, 0xa0, 0xd0, 0xa5, 0xc4, - 0x98, 0x4c, 0x79, 0x4b, 0x34, 0xd1, 0x3d, 0x98, 0xe2, 0x22, 0x05, 0xb5, 0xbc, 0x7e, 0x19, 0x4a, - 0x29, 0x0b, 0x4c, 0xca, 0x40, 0x8a, 0xf9, 0x0f, 0x39, 0x28, 0x72, 0x65, 0x6c, 0x0e, 0x51, 0x13, - 0xa6, 0x7d, 0xd6, 0xd8, 0xa1, 0x73, 0xe6, 0x32, 0xd6, 0xb3, 0xfd, 0xe4, 0xe3, 0x31, 0xab, 0xcc, - 0x87, 0xd0, 0x6e, 0xf4, 0xcb, 0x50, 0x12, 0x24, 0x86, 0x07, 0x21, 0x37, 0x54, 0x2d, 0x4e, 0x40, - 0x2e, 0xed, 0xc7, 0x63, 0x16, 0x70, 0xf4, 0xad, 0x83, 0x10, 0xb5, 0x61, 0x4e, 0x0c, 0x66, 0xf3, - 0xe3, 0x62, 0xe4, 0x29, 0x95, 0x85, 0x38, 0x95, 0xb4, 0x39, 0x1f, 0x8f, 0x59, 0x88, 0x8f, 0x57, - 0x80, 0x68, 0x55, 0x8a, 0x14, 0x1e, 0xb2, 0xf8, 0x92, 0x12, 0xa9, 0x7d, 0xe8, 0x72, 0x22, 0x42, - 0x5b, 0xcb, 0x8a, 0x6c, 0xed, 0x43, 0x37, 0x52, 0xd9, 0xc3, 0x22, 0x14, 0x78, 0xb7, 0xf9, 0x6f, - 0x39, 0x00, 0x61, 0xb1, 0xcd, 0x21, 0x5a, 0x85, 0x8a, 0xcf, 0x5b, 0x31, 0xfd, 0x5d, 0xd6, 0xea, - 0x8f, 0x1b, 0x7a, 0xcc, 0x9a, 0x16, 0x83, 0x98, 0xb8, 0x1f, 0x42, 0x39, 0xa2, 0x22, 0x55, 0x78, - 0x49, 0xa3, 0xc2, 0x88, 0x42, 0x49, 0x0c, 0x20, 0x4a, 0xfc, 0x04, 0xce, 0x47, 0xe3, 0x35, 0x5a, - 0x7c, 0xe3, 0x18, 0x2d, 0x46, 0x04, 0xcf, 0x09, 0x0a, 0xaa, 0x1e, 0x1f, 0x29, 0x82, 0x49, 0x45, - 0x5e, 0xd2, 0x28, 0x92, 0x21, 0xa9, 0x9a, 0x8c, 0x24, 0x8c, 0xa9, 0x12, 0x48, 0xd8, 0x67, 0xfd, - 0xe6, 0x5f, 0x8c, 0x43, 0x61, 0xc5, 0x1b, 0x0c, 0x6d, 0x9f, 0x2c, 0xa2, 0x49, 0x1f, 0x07, 0x07, - 0xfd, 0x90, 0x2a, 0xb0, 0xb2, 0x74, 0x3d, 0xce, 0x83, 0xa3, 0x89, 0xbf, 0x16, 0x45, 0xb5, 0xf8, - 0x10, 0x32, 0x98, 0x47, 0xf9, 0xdc, 0x6b, 0x0c, 0xe6, 0x31, 0x9e, 0x0f, 0x11, 0x0e, 0x21, 0x2f, - 0x1d, 0x42, 0x1d, 0x0a, 0xfc, 0x80, 0xc7, 0x9c, 0xf5, 0xe3, 0x31, 0x4b, 0x74, 0xa0, 0xb7, 0x61, - 0x26, 0x19, 0x0a, 0x27, 0x38, 0x4e, 0xa5, 0x13, 0x8f, 0x9c, 0xd7, 0xa1, 0x1c, 0x8b, 0xd0, 0x93, - 0x1c, 0xaf, 0x34, 0x50, 0xe2, 0xf2, 0x05, 0xe1, 0xd6, 0xc9, 0xb1, 0xa2, 0xfc, 0x78, 0x4c, 0x38, - 0xf6, 0x6b, 0xc2, 0xb1, 0x4f, 0xa9, 0x81, 0x96, 0xe8, 0x95, 0xfb, 0xf8, 0x37, 0x55, 0xaf, 0xf5, - 0x2d, 0x32, 0x38, 0x42, 0x92, 0xee, 0xcb, 0xb4, 0x60, 0x3a, 0xa6, 0x32, 0x12, 0x23, 0x5b, 0x1f, - 0x3f, 0x6b, 0xae, 0xb3, 0x80, 0xfa, 0x88, 0xc6, 0x50, 0xab, 0x6a, 0x90, 0x00, 0xbd, 0xde, 0xda, - 0xde, 0xae, 0xe6, 0xd0, 0x05, 0x28, 0x6e, 0x6c, 0xb6, 0x77, 0x18, 0x56, 0xbe, 0x5e, 0xf8, 0x23, - 0xe6, 0x49, 0x64, 0x7c, 0xfe, 0x34, 0xa2, 0xc9, 0x43, 0xb4, 0x12, 0x99, 0xc7, 0x94, 0xc8, 0x6c, - 0x88, 0xc8, 0x9c, 0x93, 0x91, 0x39, 0x8f, 0x10, 0x4c, 0xac, 0xb7, 0x9a, 0xdb, 0x34, 0x48, 0x33, - 0xd2, 0xcb, 0xe9, 0x68, 0xfd, 0xb0, 0x02, 0x65, 0x66, 0x9e, 0x9d, 0x03, 0x97, 0x1c, 0x26, 0xfe, - 0xd2, 0x00, 0x90, 0x1b, 0x16, 0x35, 0xa0, 0xd0, 0x61, 0x22, 0xd4, 0x0c, 0xea, 0x01, 0xcf, 0x6b, - 0x2d, 0x6e, 0x09, 0x2c, 0x74, 0x17, 0x0a, 0xc1, 0x41, 0xa7, 0x83, 0x03, 0x11, 0xb9, 0x2f, 0x26, - 0x9d, 0x30, 0x77, 0x88, 0x96, 0xc0, 0x23, 0x43, 0x5e, 0xda, 0x4e, 0xff, 0x80, 0xc6, 0xf1, 0xe3, - 0x87, 0x70, 0x3c, 0xe9, 0x63, 0xff, 0xd4, 0x80, 0x92, 0xb2, 0x2d, 0x7e, 0xce, 0x10, 0x70, 0x05, - 0x8a, 0x54, 0x18, 0xdc, 0xe5, 0x41, 0x60, 0xca, 0x92, 0x1d, 0xe8, 0x3d, 0x28, 0x8a, 0x9d, 0x24, - 0xe2, 0x40, 0x4d, 0x4f, 0x76, 0x73, 0x68, 0x49, 0x54, 0x29, 0x64, 0x1b, 0x66, 0xa9, 0x9e, 0x3a, - 0xe4, 0xf6, 0x21, 0x34, 0xab, 0x1e, 0xcb, 0x8d, 0xc4, 0xb1, 0xbc, 0x0e, 0x53, 0xc3, 0xbd, 0xa3, - 0xc0, 0xe9, 0xd8, 0x7d, 0x2e, 0x4e, 0xd4, 0x96, 0x54, 0xb7, 0x01, 0xa9, 0x54, 0x4f, 0xa3, 0x00, - 0x49, 0xf4, 0x02, 0x94, 0x1e, 0xdb, 0xc1, 0x1e, 0x17, 0x52, 0xf6, 0xdf, 0x83, 0x69, 0xd2, 0xff, - 0xe4, 0xf9, 0x6b, 0x88, 0x2f, 0x46, 0x2d, 0x9b, 0xff, 0x68, 0x40, 0x45, 0x0c, 0x3b, 0x95, 0x81, - 0x10, 0x8c, 0xef, 0xd9, 0xc1, 0x1e, 0x55, 0xc6, 0xb4, 0x45, 0xbf, 0xd1, 0xdb, 0x50, 0xed, 0xb0, - 0xf9, 0xef, 0x24, 0xee, 0x5d, 0x33, 0xbc, 0x3f, 0xda, 0xfb, 0xef, 0xc2, 0x34, 0x19, 0xb2, 0x13, - 0xbf, 0x07, 0x89, 0x6d, 0xfc, 0x9e, 0x55, 0xde, 0xa3, 0x73, 0x4e, 0x8a, 0x6f, 0x43, 0x99, 0x29, - 0xe3, 0xac, 0x65, 0x97, 0x7a, 0xad, 0xc3, 0xcc, 0xb6, 0x6b, 0x0f, 0x83, 0x3d, 0x2f, 0x4c, 0xe8, - 0x7c, 0xd9, 0xfc, 0x5b, 0x03, 0xaa, 0x12, 0x78, 0x2a, 0x19, 0xbe, 0x01, 0x33, 0x3e, 0x1e, 0xd8, - 0x8e, 0xeb, 0xb8, 0xbd, 0x9d, 0xdd, 0xa3, 0x10, 0x07, 0xfc, 0xfa, 0x5a, 0x89, 0xba, 0x1f, 0x92, - 0x5e, 0x22, 0xec, 0x6e, 0xdf, 0xdb, 0xe5, 0x4e, 0x9a, 0x7e, 0xa3, 0x37, 0xe2, 0x5e, 0xba, 0x28, - 0xf5, 0x26, 0xfa, 0xa5, 0xcc, 0x3f, 0xcd, 0x41, 0xf9, 0x13, 0x3b, 0xec, 0x88, 0x15, 0x84, 0xd6, - 0xa0, 0x12, 0xb9, 0x71, 0xda, 0xc3, 0xe5, 0x4e, 0x1c, 0x38, 0xe8, 0x18, 0x71, 0xaf, 0x11, 0x07, - 0x8e, 0xe9, 0x8e, 0xda, 0x41, 0x49, 0xd9, 0x6e, 0x07, 0xf7, 0x23, 0x52, 0xb9, 0x6c, 0x52, 0x14, - 0x51, 0x25, 0xa5, 0x76, 0xa0, 0x6f, 0x43, 0x75, 0xe8, 0x7b, 0x3d, 0x1f, 0x07, 0x41, 0x44, 0x8c, - 0x85, 0x70, 0x53, 0x43, 0x6c, 0x8b, 0xa3, 0x26, 0x4e, 0x31, 0xf7, 0x1e, 0x8f, 0x59, 0x33, 0xc3, - 0x38, 0x4c, 0x3a, 0xd6, 0x19, 0x79, 0xde, 0x63, 0x9e, 0xf5, 0x47, 0x79, 0x40, 0xe9, 0x69, 0x7e, - 0xd5, 0x63, 0xf2, 0x0d, 0xa8, 0x04, 0xa1, 0xed, 0xa7, 0xd6, 0xfc, 0x34, 0xed, 0x8d, 0x56, 0xfc, - 0x37, 0x20, 0x92, 0x6c, 0xc7, 0xf5, 0x42, 0xe7, 0xe5, 0x11, 0xbb, 0xa0, 0x58, 0x15, 0xd1, 0xbd, - 0x41, 0x7b, 0xd1, 0x06, 0x14, 0x5e, 0x3a, 0xfd, 0x10, 0xfb, 0x41, 0x6d, 0x62, 0x21, 0x7f, 0xb3, - 0xb2, 0xf4, 0xce, 0x49, 0x86, 0x59, 0xfc, 0x88, 0xe2, 0xb7, 0x8f, 0x86, 0xea, 0xe9, 0x97, 0x13, - 0x51, 0x8f, 0xf1, 0x93, 0xfa, 0x1b, 0x91, 0x09, 0x53, 0xaf, 0x08, 0xd1, 0x1d, 0xa7, 0x4b, 0x63, - 0x71, 0xb4, 0x0f, 0xef, 0x59, 0x05, 0x0a, 0x58, 0xeb, 0xa2, 0xeb, 0x30, 0xf5, 0xd2, 0xb7, 0x7b, - 0x03, 0xec, 0x86, 0xec, 0x96, 0x2f, 0x71, 0x22, 0x80, 0xb9, 0x08, 0x20, 0x45, 0x21, 0x91, 0x6f, - 0x63, 0x73, 0xeb, 0x59, 0xbb, 0x3a, 0x86, 0xca, 0x30, 0xb5, 0xb1, 0xb9, 0xda, 0x5a, 0x6f, 0x91, - 0xd8, 0x28, 0x62, 0xde, 0x5d, 0xb9, 0xe9, 0x9a, 0xc2, 0x10, 0xb1, 0x35, 0xa1, 0xca, 0x65, 0xc4, - 0x2f, 0xdd, 0x42, 0x2e, 0x41, 0xe2, 0xae, 0x79, 0x0d, 0xe6, 0x74, 0x4b, 0x43, 0x20, 0xdc, 0x33, - 0xff, 0x25, 0x07, 0xd3, 0x7c, 0x23, 0x9c, 0x6a, 0xe7, 0x5e, 0x52, 0xa4, 0xe2, 0xd7, 0x13, 0xa1, - 0xa4, 0x1a, 0x14, 0xd8, 0x06, 0xe9, 0xf2, 0xfb, 0xaf, 0x68, 0x12, 0xe7, 0xcc, 0xd6, 0x3b, 0xee, - 0x72, 0xb3, 0x47, 0x6d, 0xad, 0xdb, 0x9c, 0xc8, 0x74, 0x9b, 0xd1, 0x86, 0xb3, 0x03, 0x7e, 0xb0, - 0x2a, 0x4a, 0x53, 0x94, 0xc5, 0xa6, 0x22, 0xc0, 0x98, 0xcd, 0x0a, 0x19, 0x36, 0x43, 0x37, 0x60, - 0x12, 0x8f, 0xb0, 0x1b, 0x06, 0xb5, 0x12, 0x0d, 0xa4, 0xd3, 0xe2, 0x42, 0xd5, 0x22, 0xbd, 0x16, - 0x07, 0x4a, 0x53, 0x7d, 0x08, 0xb3, 0xf4, 0xbe, 0xfb, 0xc8, 0xb7, 0x5d, 0xf5, 0xce, 0xde, 0x6e, - 0xaf, 0xf3, 0xb0, 0x43, 0x3e, 0x51, 0x05, 0x72, 0x6b, 0xab, 0x5c, 0x3f, 0xb9, 0xb5, 0x55, 0x39, - 0xfe, 0xf7, 0x0c, 0x40, 0x2a, 0x81, 0x53, 0xd9, 0x22, 0xc1, 0x45, 0xc8, 0x91, 0x97, 0x72, 0xcc, - 0xc1, 0x04, 0xf6, 0x7d, 0xcf, 0x67, 0x8e, 0xd2, 0x62, 0x0d, 0x29, 0xcd, 0x6d, 0x2e, 0x8c, 0x85, - 0x47, 0xde, 0x7e, 0xe4, 0x01, 0x18, 0x59, 0x23, 0x2d, 0x7c, 0x1b, 0xce, 0xc5, 0xd0, 0xcf, 0x26, - 0xc4, 0x6f, 0xc2, 0x0c, 0xa5, 0xba, 0xb2, 0x87, 0x3b, 0xfb, 0x43, 0xcf, 0x71, 0x53, 0x12, 0xa0, - 0xeb, 0xc4, 0x77, 0x89, 0x70, 0x41, 0xa6, 0xc8, 0xe6, 0x5c, 0x8e, 0x3a, 0xdb, 0xed, 0x75, 0xb9, - 0xd4, 0x77, 0xe1, 0x42, 0x82, 0xa0, 0x98, 0xd9, 0xaf, 0x40, 0xa9, 0x13, 0x75, 0x06, 0xfc, 0x04, - 0x79, 0x35, 0x2e, 0x6e, 0x72, 0xa8, 0x3a, 0x42, 0xf2, 0xf8, 0x36, 0x5c, 0x4c, 0xf1, 0x38, 0x0b, - 0x75, 0xdc, 0x33, 0xef, 0xc0, 0x79, 0x4a, 0xf9, 0x09, 0xc6, 0xc3, 0x66, 0xdf, 0x19, 0x9d, 0x6c, - 0x96, 0x23, 0x3e, 0x5f, 0x65, 0xc4, 0xd7, 0xbb, 0xac, 0x24, 0xeb, 0x16, 0x67, 0xdd, 0x76, 0x06, - 0xb8, 0xed, 0xad, 0x67, 0x4b, 0x4b, 0x02, 0xf9, 0x3e, 0x3e, 0x0a, 0xf8, 0xf1, 0x91, 0x7e, 0x4b, - 0xef, 0xf5, 0xd7, 0x06, 0x57, 0xa7, 0x4a, 0xe7, 0x6b, 0xde, 0x1a, 0xf3, 0x00, 0x3d, 0xb2, 0x07, - 0x71, 0x97, 0x00, 0x58, 0x6e, 0x4e, 0xe9, 0x89, 0x04, 0x26, 0x51, 0xa8, 0x9c, 0x14, 0xf8, 0x2a, - 0xdf, 0x38, 0xf4, 0x9f, 0x20, 0x75, 0x52, 0x7a, 0x0b, 0x4a, 0x14, 0xb2, 0x1d, 0xda, 0xe1, 0x41, - 0x90, 0x65, 0xb9, 0x65, 0xf3, 0x47, 0x06, 0xdf, 0x51, 0x82, 0xce, 0xa9, 0xe6, 0x7c, 0x17, 0x26, - 0xe9, 0x0d, 0x51, 0xdc, 0x74, 0x2e, 0x69, 0x16, 0x36, 0x93, 0xc8, 0xe2, 0x88, 0xca, 0x39, 0xc9, - 0x80, 0xc9, 0xa7, 0xb4, 0x72, 0xa0, 0x48, 0x3b, 0x2e, 0x2c, 0xe7, 0xda, 0x03, 0x96, 0x7e, 0x2c, - 0x5a, 0xf4, 0x9b, 0x5e, 0x08, 0x30, 0xf6, 0x9f, 0x59, 0xeb, 0xec, 0x06, 0x52, 0xb4, 0xa2, 0x36, - 0x51, 0x6c, 0xa7, 0xef, 0x60, 0x37, 0xa4, 0xd0, 0x71, 0x0a, 0x55, 0x7a, 0xd0, 0x0d, 0x28, 0x3a, - 0xc1, 0x3a, 0xb6, 0x7d, 0x97, 0xa7, 0xf8, 0x15, 0xc7, 0x2c, 0x21, 0x72, 0x8d, 0x7d, 0x07, 0xaa, - 0x4c, 0xb2, 0x66, 0xb7, 0xab, 0x9c, 0xf6, 0x23, 0xfe, 0x46, 0x82, 0x7f, 0x8c, 0x7e, 0xee, 0x64, - 0xfa, 0x7f, 0x63, 0xc0, 0xac, 0xc2, 0xe0, 0x54, 0x26, 0x78, 0x17, 0x26, 0x59, 0xfd, 0x85, 0x1f, - 0x05, 0xe7, 0xe2, 0xa3, 0x18, 0x1b, 0x8b, 0xe3, 0xa0, 0x45, 0x28, 0xb0, 0x2f, 0x71, 0x8d, 0xd3, - 0xa3, 0x0b, 0x24, 0x29, 0xf2, 0x22, 0x9c, 0xe3, 0x30, 0x3c, 0xf0, 0x74, 0x7b, 0x6e, 0x3c, 0xee, - 0x21, 0x7e, 0x68, 0xc0, 0x5c, 0x7c, 0xc0, 0xa9, 0x66, 0xa9, 0xc8, 0x9d, 0xfb, 0x4a, 0x72, 0xff, - 0xaa, 0x90, 0xfb, 0xd9, 0xb0, 0xab, 0x1c, 0x39, 0x93, 0x2b, 0x4e, 0xb5, 0x6e, 0x2e, 0x6e, 0x5d, - 0x49, 0xeb, 0x27, 0xd1, 0x9c, 0x04, 0xb1, 0x53, 0xcd, 0xe9, 0xfd, 0xd7, 0x9a, 0x93, 0x72, 0x04, - 0x4b, 0x4d, 0x6e, 0x4d, 0x2c, 0xa3, 0x75, 0x27, 0x88, 0x22, 0xce, 0x3b, 0x50, 0xee, 0x3b, 0x2e, - 0xb6, 0x7d, 0x5e, 0x43, 0x32, 0xd4, 0xf5, 0x78, 0xdf, 0x8a, 0x01, 0x25, 0xa9, 0xdf, 0x32, 0x00, - 0xa9, 0xb4, 0x7e, 0x31, 0xd6, 0x6a, 0x08, 0x05, 0x6f, 0xf9, 0xde, 0xc0, 0x0b, 0x4f, 0x5a, 0x66, - 0xf7, 0xcc, 0xdf, 0x31, 0xe0, 0x7c, 0x62, 0xc4, 0x2f, 0x42, 0xf2, 0x7b, 0xe6, 0x15, 0x98, 0x5d, - 0xc5, 0xe2, 0x8c, 0x97, 0xca, 0x1d, 0x6c, 0x03, 0x52, 0xa1, 0x67, 0x73, 0x8a, 0xf9, 0x26, 0xcc, - 0x3e, 0xf5, 0x46, 0xc4, 0x91, 0x13, 0xb0, 0x74, 0x53, 0x2c, 0x99, 0x15, 0xe9, 0x2b, 0x6a, 0x4b, - 0xd7, 0xbb, 0x0d, 0x48, 0x1d, 0x79, 0x16, 0xe2, 0x2c, 0x9b, 0xff, 0x63, 0x40, 0xb9, 0xd9, 0xb7, - 0xfd, 0x81, 0x10, 0xe5, 0x43, 0x98, 0x64, 0x99, 0x19, 0x9e, 0x66, 0x7d, 0x2b, 0x4e, 0x4f, 0xc5, - 0x65, 0x8d, 0x26, 0xcb, 0xe3, 0xf0, 0x51, 0x64, 0x2a, 0xbc, 0xb2, 0xbc, 0x9a, 0xa8, 0x34, 0xaf, - 0xa2, 0xdb, 0x30, 0x61, 0x93, 0x21, 0x34, 0xbc, 0x56, 0x92, 0xe9, 0x32, 0x4a, 0x8d, 0x5c, 0x89, - 0x2c, 0x86, 0x65, 0x7e, 0x00, 0x25, 0x85, 0x03, 0x2a, 0x40, 0xfe, 0x51, 0x8b, 0x5f, 0x93, 0x9a, - 0x2b, 0xed, 0xb5, 0xe7, 0x2c, 0x85, 0x58, 0x01, 0x58, 0x6d, 0x45, 0xed, 0x9c, 0xa6, 0xb0, 0x67, - 0x73, 0x3a, 0x3c, 0x6e, 0xa9, 0x12, 0x1a, 0x59, 0x12, 0xe6, 0x5e, 0x47, 0x42, 0xc9, 0xe2, 0x37, - 0x0d, 0x98, 0xe6, 0xaa, 0x39, 0x6d, 0x68, 0xa6, 0x94, 0x33, 0x42, 0xb3, 0x32, 0x0d, 0x8b, 0x23, - 0x4a, 0x19, 0xfe, 0xc9, 0x80, 0xea, 0xaa, 0xf7, 0xca, 0xed, 0xf9, 0x76, 0x37, 0xda, 0x83, 0x1f, - 0x25, 0xcc, 0xb9, 0x98, 0xc8, 0xf4, 0x27, 0xf0, 0x65, 0x47, 0xc2, 0xac, 0x35, 0x99, 0x4b, 0x61, - 0xf1, 0x5d, 0x34, 0xcd, 0x6f, 0xc1, 0x4c, 0x62, 0x10, 0x31, 0xd0, 0xf3, 0xe6, 0xfa, 0xda, 0x2a, - 0x31, 0x08, 0xcd, 0xf7, 0xb6, 0x36, 0x9a, 0x0f, 0xd7, 0x5b, 0xbc, 0x2a, 0xdb, 0xdc, 0x58, 0x69, - 0xad, 0x4b, 0x43, 0xdd, 0x17, 0x33, 0xb8, 0x6f, 0xf6, 0x61, 0x56, 0x11, 0xe8, 0xb4, 0xc5, 0x31, - 0xbd, 0xbc, 0x92, 0xdb, 0x37, 0xe1, 0x72, 0xc4, 0xed, 0x39, 0x03, 0xb6, 0x71, 0xa0, 0x5e, 0xd6, - 0x46, 0x9c, 0x69, 0xd1, 0x22, 0x9f, 0x62, 0xe4, 0x7b, 0x66, 0x0d, 0xa6, 0xf9, 0xf9, 0x28, 0xe9, - 0x32, 0xfe, 0x2b, 0x0f, 0x15, 0x01, 0xfa, 0x7a, 0xe4, 0x47, 0x17, 0x60, 0xb2, 0xbb, 0xbb, 0xed, - 0x7c, 0x26, 0x2a, 0xba, 0xbc, 0x45, 0xfa, 0xfb, 0x8c, 0x0f, 0x7b, 0xa7, 0xc1, 0x5b, 0xe8, 0x0a, - 0x7b, 0xc2, 0xb1, 0xe6, 0x76, 0xf1, 0x21, 0x3d, 0x46, 0x8d, 0x5b, 0xb2, 0x83, 0xa6, 0x43, 0xf9, - 0x7b, 0x0e, 0x7a, 0x4b, 0x56, 0xde, 0x77, 0xa0, 0x65, 0xa8, 0x92, 0xef, 0xe6, 0x70, 0xd8, 0x77, - 0x70, 0x97, 0x11, 0x20, 0x17, 0xe4, 0x71, 0x79, 0x4e, 0x4a, 0x21, 0xa0, 0x6b, 0x30, 0x49, 0x2f, - 0x8f, 0x41, 0x6d, 0x8a, 0x44, 0x64, 0x89, 0xca, 0xbb, 0xd1, 0xdb, 0x50, 0x62, 0x12, 0xaf, 0xb9, - 0xcf, 0x02, 0x4c, 0x5f, 0x3b, 0x28, 0x99, 0x14, 0x15, 0x16, 0x3f, 0xa1, 0x41, 0xd6, 0x09, 0x0d, - 0x35, 0xa0, 0x12, 0x84, 0x9e, 0x6f, 0xf7, 0x84, 0x19, 0xe9, 0x53, 0x07, 0x25, 0xdd, 0x97, 0x00, - 0x4b, 0x11, 0x3e, 0x3e, 0xf0, 0x42, 0x3b, 0xfe, 0xc4, 0xe1, 0x3d, 0x4b, 0x85, 0x49, 0xcb, 0x5e, - 0x81, 0xd9, 0xe6, 0x41, 0xb8, 0xd7, 0x72, 0x49, 0x04, 0x4e, 0xd9, 0xfd, 0x2a, 0x20, 0x02, 0x5d, - 0x75, 0x02, 0x2d, 0x98, 0x0f, 0xd6, 0x2e, 0x9a, 0xfb, 0xe6, 0x06, 0x9c, 0x23, 0x50, 0xec, 0x86, - 0x4e, 0x47, 0x39, 0xed, 0x88, 0xf3, 0xb4, 0x91, 0x38, 0x4f, 0xdb, 0x41, 0xf0, 0xca, 0xf3, 0xbb, - 0x7c, 0x5d, 0x44, 0x6d, 0xc9, 0xed, 0xef, 0x0d, 0x26, 0xcd, 0xb3, 0x20, 0x76, 0x16, 0xfe, 0x8a, - 0xf4, 0xd0, 0x2f, 0x41, 0x81, 0xbf, 0x41, 0xe2, 0x29, 0xc6, 0x0b, 0x8b, 0xec, 0xed, 0xd3, 0x22, - 0x27, 0xbc, 0xc9, 0xa0, 0x4a, 0x1a, 0x8c, 0xe3, 0x13, 0x8b, 0xec, 0xd9, 0xc1, 0x1e, 0xee, 0x6e, - 0x09, 0xe2, 0xb1, 0x04, 0xec, 0x7d, 0x2b, 0x01, 0x96, 0xb2, 0xdf, 0x95, 0xa2, 0x3f, 0xc2, 0xe1, - 0x31, 0xa2, 0xab, 0x29, 0xfe, 0xf3, 0x62, 0x08, 0xaf, 0x4c, 0xbe, 0xce, 0xa8, 0x1f, 0x1b, 0x70, - 0x55, 0x0c, 0x5b, 0xd9, 0xb3, 0xdd, 0x1e, 0x16, 0xc2, 0xfc, 0xbc, 0xfa, 0x4a, 0x4f, 0x3a, 0xff, - 0x9a, 0x93, 0x7e, 0x02, 0xb5, 0x68, 0xd2, 0x34, 0xdd, 0xe3, 0xf5, 0xd5, 0x49, 0x1c, 0x04, 0x91, - 0x1f, 0xa2, 0xdf, 0xa4, 0xcf, 0xf7, 0xfa, 0xd1, 0x4d, 0x8b, 0x7c, 0x4b, 0x62, 0xeb, 0x70, 0x49, - 0x10, 0xe3, 0xf9, 0x97, 0x38, 0xb5, 0xd4, 0x9c, 0x8e, 0xa5, 0xc6, 0xed, 0x41, 0x68, 0x1c, 0xbf, - 0x94, 0xb4, 0x43, 0xe2, 0x26, 0xa4, 0x5c, 0x0c, 0x1d, 0x97, 0x79, 0xb6, 0x03, 0x88, 0xcc, 0xca, - 0xa1, 0x38, 0x05, 0x27, 0x24, 0xb5, 0x70, 0xbe, 0x04, 0x08, 0x3c, 0xb5, 0x04, 0xb2, 0xb9, 0x62, - 0x98, 0x8f, 0x04, 0x25, 0x6a, 0xdf, 0xc2, 0xfe, 0xc0, 0x09, 0x02, 0xa5, 0xd6, 0xa5, 0x53, 0xd7, - 0x5b, 0x30, 0x3e, 0xc4, 0xfc, 0x84, 0x50, 0x5a, 0x42, 0x62, 0x4f, 0x28, 0x83, 0x29, 0x5c, 0xb2, - 0x19, 0xc0, 0x35, 0xc1, 0x86, 0x19, 0x44, 0xcb, 0x27, 0x29, 0xa6, 0xc8, 0xaf, 0xe7, 0x32, 0xf2, - 0xeb, 0xf9, 0x78, 0x7e, 0x3d, 0x76, 0x6a, 0x55, 0x1d, 0xd5, 0xd9, 0x9c, 0x5a, 0xdb, 0xcc, 0x00, - 0x91, 0x7f, 0x3b, 0x1b, 0xaa, 0xbf, 0xcf, 0x1d, 0xd5, 0x59, 0x45, 0x4c, 0x4c, 0xe7, 0x2c, 0x2a, - 0xa1, 0xa2, 0x89, 0x4c, 0x28, 0x13, 0x23, 0x59, 0x6a, 0xe1, 0x61, 0xdc, 0x8a, 0xf5, 0x49, 0x67, - 0xbc, 0x0f, 0x73, 0x71, 0x67, 0x7c, 0x2a, 0xa1, 0xe6, 0x60, 0x22, 0xf4, 0xf6, 0xb1, 0x08, 0xe2, - 0xac, 0x91, 0x52, 0x6b, 0xe4, 0xa8, 0xcf, 0x46, 0xad, 0xdf, 0x95, 0x54, 0xe9, 0x06, 0x3c, 0xed, - 0x0c, 0xc8, 0x72, 0x14, 0x17, 0x6c, 0xd6, 0x90, 0xbc, 0x3e, 0x81, 0x0b, 0x49, 0xe7, 0x7b, 0x36, - 0x93, 0xd8, 0x61, 0x9b, 0x53, 0xe7, 0x9e, 0xcf, 0x86, 0xc1, 0x0b, 0xe9, 0x27, 0x15, 0xa7, 0x7b, - 0x36, 0xb4, 0x7f, 0x0d, 0xea, 0x3a, 0x1f, 0x7c, 0xa6, 0x7b, 0x31, 0x72, 0xc9, 0x67, 0x43, 0xf5, - 0x87, 0x86, 0x24, 0xab, 0xae, 0x9a, 0x0f, 0xbe, 0x0a, 0x59, 0x11, 0xeb, 0xee, 0x44, 0xcb, 0xa7, - 0x11, 0x79, 0xcb, 0xbc, 0xde, 0x5b, 0xca, 0x21, 0x14, 0x51, 0xec, 0x3f, 0xe9, 0xea, 0xbf, 0xce, - 0xd5, 0xcb, 0x99, 0xc9, 0xb8, 0x73, 0x5a, 0x66, 0x24, 0x3c, 0x47, 0xcc, 0x68, 0x23, 0xb5, 0x55, - 0xd4, 0x20, 0x75, 0x36, 0xa6, 0xfb, 0x75, 0x19, 0x60, 0x52, 0x71, 0xec, 0x6c, 0x38, 0xd8, 0xb0, - 0x90, 0x1d, 0xc2, 0xce, 0x84, 0xc5, 0xad, 0x26, 0x14, 0xa3, 0xeb, 0xb5, 0xf2, 0x18, 0xb8, 0x04, - 0x85, 0x8d, 0xcd, 0xed, 0xad, 0xe6, 0x0a, 0xb9, 0x3d, 0xce, 0x41, 0x61, 0x65, 0xd3, 0xb2, 0x9e, - 0x6d, 0xb5, 0xc9, 0xf5, 0x31, 0xf9, 0x36, 0x68, 0xe9, 0x67, 0x79, 0xc8, 0x3d, 0x79, 0x8e, 0x3e, - 0x85, 0x09, 0xf6, 0x36, 0xed, 0x98, 0x27, 0x8a, 0xf5, 0xe3, 0x9e, 0xdf, 0x99, 0x17, 0x7f, 0xf0, - 0x9f, 0x3f, 0xfb, 0x83, 0xdc, 0xac, 0x59, 0x6e, 0x8c, 0x96, 0x1b, 0xfb, 0xa3, 0x06, 0x0d, 0xb2, - 0x0f, 0x8c, 0x5b, 0xe8, 0x63, 0xc8, 0x6f, 0x1d, 0x84, 0x28, 0xf3, 0xe9, 0x62, 0x3d, 0xfb, 0x45, - 0x9e, 0x79, 0x9e, 0x12, 0x9d, 0x31, 0x81, 0x13, 0x1d, 0x1e, 0x84, 0x84, 0xe4, 0xf7, 0xa0, 0xa4, - 0xbe, 0xa7, 0x3b, 0xf1, 0x3d, 0x63, 0xfd, 0xe4, 0xb7, 0x7a, 0xe6, 0x55, 0xca, 0xea, 0xa2, 0x89, - 0x38, 0x2b, 0xf6, 0xe2, 0x4f, 0x9d, 0x45, 0xfb, 0xd0, 0x45, 0x99, 0xaf, 0x1d, 0xeb, 0xd9, 0xcf, - 0xf7, 0x52, 0xb3, 0x08, 0x0f, 0x5d, 0x42, 0xf2, 0xbb, 0xfc, 0x9d, 0x5e, 0x27, 0x44, 0xd7, 0x34, - 0x0f, 0xad, 0xd4, 0x07, 0x44, 0xf5, 0x85, 0x6c, 0x04, 0xce, 0xe4, 0x0a, 0x65, 0x72, 0xc1, 0x9c, - 0xe5, 0x4c, 0x3a, 0x11, 0xca, 0x03, 0xe3, 0xd6, 0x52, 0x07, 0x26, 0x68, 0x81, 0x1a, 0xbd, 0x10, - 0x1f, 0x75, 0x4d, 0xe9, 0x3f, 0xc3, 0xd0, 0xb1, 0xd2, 0xb6, 0x39, 0x47, 0x19, 0x55, 0xcc, 0x22, - 0x61, 0x44, 0xcb, 0xd3, 0x0f, 0x8c, 0x5b, 0x37, 0x8d, 0x3b, 0xc6, 0xd2, 0x5f, 0x4d, 0xc0, 0x04, - 0x2d, 0x84, 0xa0, 0x7d, 0x00, 0x59, 0x88, 0x4d, 0xce, 0x2e, 0x55, 0xe3, 0x4d, 0xce, 0x2e, 0x5d, - 0xc3, 0x35, 0xeb, 0x94, 0xe9, 0x9c, 0x39, 0x43, 0x98, 0xd2, 0xfa, 0x4a, 0x83, 0x96, 0x93, 0x88, - 0x1e, 0x7f, 0x6c, 0xf0, 0x8a, 0x10, 0xdb, 0x66, 0x48, 0x47, 0x2d, 0x56, 0x84, 0x4d, 0x2e, 0x07, - 0x4d, 0xdd, 0xd5, 0xbc, 0x4f, 0x19, 0x36, 0xcc, 0xaa, 0x64, 0xe8, 0x53, 0x8c, 0x07, 0xc6, 0xad, - 0x17, 0x35, 0xf3, 0x1c, 0xd7, 0x72, 0x02, 0x82, 0xbe, 0x0f, 0x95, 0x78, 0xb9, 0x10, 0x5d, 0xd7, - 0xf0, 0x4a, 0x96, 0x1f, 0xeb, 0x6f, 0x1e, 0x8f, 0xc4, 0x65, 0x9a, 0xa7, 0x32, 0x71, 0xe6, 0x8c, - 0xf3, 0x3e, 0xc6, 0x43, 0x9b, 0x20, 0x71, 0x1b, 0xa0, 0x3f, 0x31, 0x78, 0xc5, 0x57, 0x56, 0xfb, - 0x90, 0x8e, 0x7a, 0xaa, 0xa8, 0x58, 0xbf, 0x71, 0x02, 0x16, 0x17, 0xe2, 0x03, 0x2a, 0xc4, 0xfb, - 0xe6, 0x9c, 0x14, 0x22, 0x74, 0x06, 0x38, 0xf4, 0xb8, 0x14, 0x2f, 0xae, 0x98, 0x17, 0x63, 0xca, - 0x89, 0x41, 0xa5, 0xb1, 0x58, 0x55, 0x4e, 0x6b, 0xac, 0x58, 0xe1, 0x4f, 0x6b, 0xac, 0x78, 0x49, - 0x4f, 0x67, 0x2c, 0x5e, 0x83, 0xd3, 0x18, 0x2b, 0x82, 0x2c, 0xfd, 0xdf, 0x38, 0x14, 0x56, 0xd8, - 0xff, 0xf7, 0x41, 0x1e, 0x14, 0xa3, 0x3a, 0x15, 0x9a, 0xd7, 0xa5, 0xc2, 0xe5, 0x55, 0xae, 0x7e, - 0x2d, 0x13, 0xce, 0x05, 0x7a, 0x83, 0x0a, 0x74, 0xd9, 0xbc, 0x40, 0x38, 0xf3, 0xff, 0x52, 0xd4, - 0x60, 0x09, 0xd3, 0x86, 0xdd, 0xed, 0x12, 0x45, 0xfc, 0x06, 0x94, 0xd5, 0xaa, 0x11, 0x7a, 0x43, - 0x9b, 0x7e, 0x57, 0x4b, 0x50, 0x75, 0xf3, 0x38, 0x14, 0xce, 0xf9, 0x4d, 0xca, 0x79, 0xde, 0xbc, - 0xa4, 0xe1, 0xec, 0x53, 0xd4, 0x18, 0x73, 0x56, 0xde, 0xd1, 0x33, 0x8f, 0xd5, 0x91, 0xf4, 0xcc, - 0xe3, 0xd5, 0xa1, 0x63, 0x99, 0x1f, 0x50, 0x54, 0xc2, 0x3c, 0x00, 0x90, 0xf5, 0x17, 0xa4, 0xd5, - 0xa5, 0x72, 0x61, 0x4d, 0x3a, 0x87, 0x74, 0xe9, 0xc6, 0x34, 0x29, 0x5b, 0xbe, 0xee, 0x12, 0x6c, - 0xfb, 0x4e, 0x10, 0xb2, 0x8d, 0x39, 0x1d, 0xab, 0x9e, 0x20, 0xed, 0x7c, 0xe2, 0xc5, 0x98, 0xfa, - 0xf5, 0x63, 0x71, 0x38, 0xf7, 0x1b, 0x94, 0xfb, 0x35, 0xb3, 0xae, 0xe1, 0x3e, 0x64, 0xb8, 0x64, - 0xb1, 0x7d, 0x5e, 0x80, 0xd2, 0x53, 0xdb, 0x71, 0x43, 0xec, 0xda, 0x6e, 0x07, 0xa3, 0x5d, 0x98, - 0xa0, 0xb1, 0x3b, 0xe9, 0x88, 0xd5, 0x62, 0x41, 0xd2, 0x11, 0xc7, 0xb2, 0xe5, 0xe6, 0x02, 0x65, - 0x5c, 0x37, 0xcf, 0x13, 0xc6, 0x03, 0x49, 0xba, 0xc1, 0xf2, 0xec, 0xc6, 0x2d, 0xf4, 0x12, 0x26, - 0x79, 0x95, 0x3c, 0x41, 0x28, 0x96, 0x54, 0xab, 0x5f, 0xd1, 0x03, 0x75, 0x6b, 0x59, 0x65, 0x13, - 0x50, 0x3c, 0xc2, 0x67, 0x04, 0x20, 0x8b, 0x3e, 0x49, 0x8b, 0xa6, 0x8a, 0x45, 0xf5, 0x85, 0x6c, - 0x04, 0x9d, 0x4e, 0x55, 0x9e, 0xdd, 0x08, 0x97, 0xf0, 0xfd, 0x0e, 0x8c, 0x3f, 0xb6, 0x83, 0x3d, - 0x94, 0x88, 0xbd, 0xca, 0xa3, 0xd6, 0x7a, 0x5d, 0x07, 0xe2, 0x5c, 0xae, 0x51, 0x2e, 0x97, 0x98, - 0x2b, 0x53, 0xb9, 0xd0, 0x67, 0x9b, 0x4c, 0x7f, 0xec, 0x45, 0x6b, 0x52, 0x7f, 0xb1, 0xe7, 0xb1, - 0x49, 0xfd, 0xc5, 0x1f, 0xc1, 0x66, 0xeb, 0x8f, 0x70, 0xd9, 0x1f, 0x11, 0x3e, 0x43, 0x98, 0x12, - 0x6f, 0x3f, 0x51, 0xe2, 0xc5, 0x4c, 0xe2, 0xc1, 0x68, 0x7d, 0x3e, 0x0b, 0xcc, 0xb9, 0x5d, 0xa7, - 0xdc, 0xae, 0x9a, 0xb5, 0x94, 0xb5, 0x38, 0xe6, 0x03, 0xe3, 0xd6, 0x1d, 0x03, 0x7d, 0x1f, 0x40, - 0xd6, 0xc5, 0x52, 0x7b, 0x30, 0x59, 0x6b, 0x4b, 0xed, 0xc1, 0x54, 0x49, 0xcd, 0x5c, 0xa4, 0x7c, - 0x6f, 0x9a, 0xd7, 0x93, 0x7c, 0x43, 0xdf, 0x76, 0x83, 0x97, 0xd8, 0xbf, 0xcd, 0x52, 0xeb, 0xc1, - 0x9e, 0x33, 0x24, 0x53, 0xf6, 0xa1, 0x18, 0x15, 0x12, 0x92, 0xfe, 0x36, 0x59, 0x60, 0x49, 0xfa, - 0xdb, 0x54, 0xbd, 0x23, 0xee, 0x78, 0x62, 0xeb, 0x45, 0xa0, 0x92, 0x2d, 0xf8, 0xe7, 0x55, 0x18, - 0x27, 0x47, 0x72, 0x72, 0x3c, 0x91, 0xe9, 0x9e, 0xe4, 0xec, 0x53, 0x19, 0xeb, 0xe4, 0xec, 0xd3, - 0x99, 0xa2, 0xf8, 0xf1, 0x84, 0x5c, 0xd7, 0x1a, 0x2c, 0x8f, 0x42, 0x66, 0xea, 0x41, 0x49, 0x49, - 0x03, 0x21, 0x0d, 0xb1, 0x78, 0x06, 0x3c, 0x19, 0xf0, 0x34, 0x39, 0x24, 0xf3, 0x32, 0xe5, 0x77, - 0x9e, 0x05, 0x3c, 0xca, 0xaf, 0xcb, 0x30, 0x08, 0x43, 0x3e, 0x3b, 0xbe, 0xf3, 0x35, 0xb3, 0x8b, - 0xef, 0xfe, 0x85, 0x6c, 0x84, 0xcc, 0xd9, 0xc9, 0xad, 0xff, 0x0a, 0xca, 0x6a, 0xea, 0x07, 0x69, - 0x84, 0x4f, 0xe4, 0xe8, 0x93, 0x91, 0x44, 0x97, 0x39, 0x8a, 0xfb, 0x36, 0xca, 0xd2, 0x56, 0xd0, - 0x08, 0xe3, 0x3e, 0x14, 0x78, 0x0a, 0x48, 0xa7, 0xd2, 0x78, 0x1a, 0x5f, 0xa7, 0xd2, 0x44, 0xfe, - 0x28, 0x7e, 0x7e, 0xa6, 0x1c, 0xc9, 0x55, 0x54, 0x44, 0x6b, 0xce, 0xed, 0x11, 0x0e, 0xb3, 0xb8, - 0xc9, 0xb4, 0x6d, 0x16, 0x37, 0x25, 0x43, 0x90, 0xc5, 0xad, 0x87, 0x43, 0xee, 0x0f, 0xc4, 0xf5, - 0x1a, 0x65, 0x10, 0x53, 0x23, 0xa4, 0x79, 0x1c, 0x8a, 0xee, 0x7a, 0x23, 0x19, 0x8a, 0xf0, 0x78, - 0x08, 0x20, 0xd3, 0x51, 0xc9, 0x33, 0xab, 0xb6, 0x52, 0x90, 0x3c, 0xb3, 0xea, 0x33, 0x5a, 0x71, - 0x1f, 0x2b, 0xf9, 0xb2, 0xdb, 0x15, 0xe1, 0xfc, 0x85, 0x01, 0x28, 0x9d, 0xb0, 0x42, 0xef, 0xe8, - 0xa9, 0x6b, 0xab, 0x0e, 0xf5, 0x77, 0x5f, 0x0f, 0x59, 0xe7, 0x90, 0xa5, 0x48, 0x1d, 0x8a, 0x3d, - 0x7c, 0x45, 0x84, 0xfa, 0xdc, 0x80, 0xe9, 0x58, 0x92, 0x0b, 0xbd, 0x95, 0x61, 0xd3, 0x44, 0xe9, - 0xa1, 0xfe, 0x8d, 0x13, 0xf1, 0x74, 0x87, 0x79, 0x65, 0x05, 0x88, 0x5b, 0xcd, 0x6f, 0x1b, 0x50, - 0x89, 0xe7, 0xc2, 0x50, 0x06, 0xed, 0x54, 0xc5, 0xa2, 0x7e, 0xf3, 0x64, 0xc4, 0xe3, 0xcd, 0x23, - 0x2f, 0x34, 0x7d, 0x28, 0xf0, 0xa4, 0x99, 0x6e, 0xe1, 0xc7, 0x4b, 0x1c, 0xba, 0x85, 0x9f, 0xc8, - 0xb8, 0x69, 0x16, 0xbe, 0xef, 0xf5, 0xb1, 0xb2, 0xcd, 0x78, 0x2e, 0x2d, 0x8b, 0xdb, 0xf1, 0xdb, - 0x2c, 0x91, 0x88, 0xcb, 0xe2, 0x26, 0xb7, 0x99, 0x48, 0x99, 0xa1, 0x0c, 0x62, 0x27, 0x6c, 0xb3, - 0x64, 0xc6, 0x4d, 0xb3, 0xcd, 0x28, 0x43, 0x65, 0x9b, 0xc9, 0x54, 0x96, 0x6e, 0x9b, 0xa5, 0xaa, - 0x31, 0xba, 0x6d, 0x96, 0xce, 0x86, 0x69, 0xec, 0x48, 0xf9, 0xc6, 0xb6, 0xd9, 0x39, 0x4d, 0xb2, - 0x0b, 0xbd, 0x9b, 0xa1, 0x44, 0x6d, 0x6d, 0xa7, 0x7e, 0xfb, 0x35, 0xb1, 0x33, 0xd7, 0x38, 0x53, - 0xbf, 0x58, 0xe3, 0x7f, 0x68, 0xc0, 0x9c, 0x2e, 0x3f, 0x86, 0x32, 0xf8, 0x64, 0x94, 0x82, 0xea, - 0x8b, 0xaf, 0x8b, 0x7e, 0xbc, 0xb6, 0xa2, 0x55, 0xff, 0xb0, 0xf7, 0x45, 0xb3, 0xf1, 0xe2, 0x1a, - 0x5c, 0x85, 0xc9, 0xe6, 0xd0, 0x79, 0x82, 0x8f, 0xd0, 0xb9, 0xa9, 0x5c, 0x7d, 0x9a, 0xd0, 0xf5, - 0x7c, 0xe7, 0x33, 0xfa, 0xc3, 0x12, 0x0b, 0xb9, 0xdd, 0x32, 0x40, 0x84, 0x30, 0xf6, 0xaf, 0x5f, - 0xce, 0x1b, 0xff, 0xf1, 0xe5, 0xbc, 0xf1, 0xdf, 0x5f, 0xce, 0x1b, 0x3f, 0xfd, 0xdf, 0xf9, 0xb1, - 0x17, 0xd7, 0x7b, 0x1e, 0x15, 0x6b, 0xd1, 0xf1, 0x1a, 0xf2, 0xc7, 0x2e, 0x96, 0x1b, 0xaa, 0xa8, - 0xbb, 0x93, 0xf4, 0xd7, 0x29, 0x96, 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x97, 0xb0, 0x67, 0xa9, - 0x74, 0x43, 0x00, 0x00, + // 4574 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x5d, 0x6f, 0x1b, 0x57, + 0x76, 0x1a, 0x92, 0x12, 0xc9, 0xc3, 0x0f, 0xd1, 0xd7, 0xb2, 0x4d, 0xd3, 0xb6, 0xac, 0x8c, 0xed, + 0xc4, 0x71, 0x62, 0xd1, 0x96, 0xec, 0x64, 0xeb, 0x22, 0xe9, 0xd2, 0x12, 0x63, 0x6b, 0x2d, 0x4b, + 0xca, 0x88, 0x76, 0x36, 0x2e, 0xb0, 0xea, 0x88, 0xbc, 0xa6, 0x66, 0x45, 0xce, 0x70, 0x67, 0x86, + 0xb4, 0x94, 0x3e, 0x6c, 0xba, 0xed, 0x76, 0xb1, 0x2d, 0xb0, 0x40, 0x53, 0xa0, 0x58, 0x14, 0xed, + 0x4b, 0x5b, 0xa0, 0x7d, 0x68, 0x8b, 0xf6, 0xa1, 0x0f, 0x45, 0x0b, 0xf4, 0xa1, 0x7d, 0x68, 0x1f, + 0x0a, 0x14, 0xe8, 0x1f, 0x68, 0xd3, 0x7d, 0xea, 0xaf, 0x58, 0xdc, 0xaf, 0xb9, 0x77, 0xbe, 0x24, + 0x67, 0xa5, 0x60, 0x5f, 0x62, 0xce, 0x3d, 0x9f, 0xf7, 0x9c, 0x7b, 0xcf, 0xb9, 0xf7, 0x9c, 0x1b, + 0x41, 0xd1, 0x1d, 0x75, 0x17, 0x47, 0xae, 0xe3, 0x3b, 0xa8, 0x8c, 0xfd, 0x6e, 0xcf, 0xc3, 0xee, + 0x04, 0xbb, 0xa3, 0xdd, 0xc6, 0x5c, 0xdf, 0xe9, 0x3b, 0x14, 0xd0, 0x24, 0xbf, 0x18, 0x4e, 0xa3, + 0x4e, 0x70, 0x9a, 0xe6, 0xc8, 0x6a, 0x0e, 0x27, 0xdd, 0xee, 0x68, 0xb7, 0xb9, 0x3f, 0xe1, 0x90, + 0x46, 0x00, 0x31, 0xc7, 0xfe, 0xde, 0x68, 0x97, 0xfe, 0xc3, 0x61, 0x0b, 0x01, 0x6c, 0x82, 0x5d, + 0xcf, 0x72, 0xec, 0xd1, 0xae, 0xf8, 0xc5, 0x31, 0x2e, 0xf7, 0x1d, 0xa7, 0x3f, 0xc0, 0x8c, 0xde, + 0xb6, 0x1d, 0xdf, 0xf4, 0x2d, 0xc7, 0xf6, 0x38, 0x94, 0xfd, 0xd3, 0xbd, 0xdd, 0xc7, 0xf6, 0x6d, + 0x67, 0x84, 0x6d, 0x73, 0x64, 0x4d, 0x96, 0x9a, 0xce, 0x88, 0xe2, 0xc4, 0xf1, 0xf5, 0x9f, 0x68, + 0x50, 0x35, 0xb0, 0x37, 0x72, 0x6c, 0x0f, 0x3f, 0xc6, 0x66, 0x0f, 0xbb, 0xe8, 0x0a, 0x40, 0x77, + 0x30, 0xf6, 0x7c, 0xec, 0xee, 0x58, 0xbd, 0xba, 0xb6, 0xa0, 0xdd, 0xcc, 0x19, 0x45, 0x3e, 0xb2, + 0xd6, 0x43, 0x97, 0xa0, 0x38, 0xc4, 0xc3, 0x5d, 0x06, 0xcd, 0x50, 0x68, 0x81, 0x0d, 0xac, 0xf5, + 0x50, 0x03, 0x0a, 0x2e, 0x9e, 0x58, 0x44, 0xdd, 0x7a, 0x76, 0x41, 0xbb, 0x99, 0x35, 0x82, 0x6f, + 0x42, 0xe8, 0x9a, 0x2f, 0xfd, 0x1d, 0x1f, 0xbb, 0xc3, 0x7a, 0x8e, 0x11, 0x92, 0x81, 0x0e, 0x76, + 0x87, 0x0f, 0xf2, 0x3f, 0xf8, 0x87, 0x7a, 0x76, 0x79, 0xf1, 0x8e, 0xfe, 0xaf, 0xd3, 0x50, 0x36, + 0x4c, 0xbb, 0x8f, 0x0d, 0xfc, 0xbd, 0x31, 0xf6, 0x7c, 0x54, 0x83, 0xec, 0x3e, 0x3e, 0xa4, 0x7a, + 0x94, 0x0d, 0xf2, 0x93, 0x31, 0xb2, 0xfb, 0x78, 0x07, 0xdb, 0x4c, 0x83, 0x32, 0x61, 0x64, 0xf7, + 0x71, 0xdb, 0xee, 0xa1, 0x39, 0x98, 0x1e, 0x58, 0x43, 0xcb, 0xe7, 0xe2, 0xd9, 0x47, 0x48, 0xaf, + 0x5c, 0x44, 0xaf, 0x15, 0x00, 0xcf, 0x71, 0xfd, 0x1d, 0xc7, 0xed, 0x61, 0xb7, 0x3e, 0xbd, 0xa0, + 0xdd, 0xac, 0x2e, 0x5d, 0x5f, 0x54, 0x3d, 0xbc, 0xa8, 0x2a, 0xb4, 0xb8, 0xed, 0xb8, 0xfe, 0x26, + 0xc1, 0x35, 0x8a, 0x9e, 0xf8, 0x89, 0x3e, 0x82, 0x12, 0x65, 0xe2, 0x9b, 0x6e, 0x1f, 0xfb, 0xf5, + 0x19, 0xca, 0xe5, 0xc6, 0x31, 0x5c, 0x3a, 0x14, 0xd9, 0xa0, 0xe2, 0xd9, 0x6f, 0xa4, 0x43, 0xd9, + 0xc3, 0xae, 0x65, 0x0e, 0xac, 0xcf, 0xcc, 0xdd, 0x01, 0xae, 0xe7, 0x17, 0xb4, 0x9b, 0x05, 0x23, + 0x34, 0x46, 0xe6, 0xbf, 0x8f, 0x0f, 0xbd, 0x1d, 0xc7, 0x1e, 0x1c, 0xd6, 0x0b, 0x14, 0xa1, 0x40, + 0x06, 0x36, 0xed, 0xc1, 0x21, 0xf5, 0x9e, 0x33, 0xb6, 0x7d, 0x06, 0x2d, 0x52, 0x68, 0x91, 0x8e, + 0x50, 0xf0, 0x5d, 0xa8, 0x0d, 0x2d, 0x7b, 0x67, 0xe8, 0xf4, 0x76, 0x02, 0x83, 0x00, 0x31, 0xc8, + 0xc3, 0xfc, 0xef, 0x51, 0x0f, 0xdc, 0x35, 0xaa, 0x43, 0xcb, 0x7e, 0xea, 0xf4, 0x0c, 0x61, 0x1f, + 0x42, 0x62, 0x1e, 0x84, 0x49, 0x4a, 0x51, 0x12, 0xf3, 0x40, 0x25, 0x79, 0x1f, 0xce, 0x12, 0x29, + 0x5d, 0x17, 0x9b, 0x3e, 0x96, 0x54, 0xe5, 0x30, 0xd5, 0x99, 0xa1, 0x65, 0xaf, 0x50, 0x94, 0x10, + 0xa1, 0x79, 0x10, 0x23, 0xac, 0x44, 0x09, 0xcd, 0x83, 0x30, 0xa1, 0xfe, 0x3e, 0x14, 0x03, 0xbf, + 0xa0, 0x02, 0xe4, 0x36, 0x36, 0x37, 0xda, 0xb5, 0x29, 0x04, 0x30, 0xd3, 0xda, 0x5e, 0x69, 0x6f, + 0xac, 0xd6, 0x34, 0x54, 0x82, 0xfc, 0x6a, 0x9b, 0x7d, 0x64, 0x1a, 0xf9, 0x2f, 0xf8, 0x7a, 0x7b, + 0x02, 0x20, 0x5d, 0x81, 0xf2, 0x90, 0x7d, 0xd2, 0xfe, 0xb4, 0x36, 0x45, 0x90, 0x9f, 0xb7, 0x8d, + 0xed, 0xb5, 0xcd, 0x8d, 0x9a, 0x46, 0xb8, 0xac, 0x18, 0xed, 0x56, 0xa7, 0x5d, 0xcb, 0x10, 0x8c, + 0xa7, 0x9b, 0xab, 0xb5, 0x2c, 0x2a, 0xc2, 0xf4, 0xf3, 0xd6, 0xfa, 0xb3, 0x76, 0x2d, 0x17, 0x30, + 0x93, 0xab, 0xf8, 0x4f, 0x34, 0xa8, 0x70, 0x77, 0xb3, 0xbd, 0x85, 0xee, 0xc1, 0xcc, 0x1e, 0xdd, + 0x5f, 0x74, 0x25, 0x97, 0x96, 0x2e, 0x47, 0xd6, 0x46, 0x68, 0x0f, 0x1a, 0x1c, 0x17, 0xe9, 0x90, + 0xdd, 0x9f, 0x78, 0xf5, 0xcc, 0x42, 0xf6, 0x66, 0x69, 0xa9, 0xb6, 0xc8, 0x22, 0xc9, 0xe2, 0x13, + 0x7c, 0xf8, 0xdc, 0x1c, 0x8c, 0xb1, 0x41, 0x80, 0x08, 0x41, 0x6e, 0xe8, 0xb8, 0x98, 0x2e, 0xf8, + 0x82, 0x41, 0x7f, 0x93, 0x5d, 0x40, 0x7d, 0xce, 0x17, 0x3b, 0xfb, 0x90, 0xea, 0xfd, 0xa7, 0x06, + 0xb0, 0x35, 0xf6, 0xd3, 0xb7, 0xd8, 0x1c, 0x4c, 0x4f, 0x88, 0x04, 0xbe, 0xbd, 0xd8, 0x07, 0xdd, + 0x5b, 0xd8, 0xf4, 0x70, 0xb0, 0xb7, 0xc8, 0x07, 0x5a, 0x80, 0xfc, 0xc8, 0xc5, 0x93, 0x9d, 0xfd, + 0x09, 0x95, 0x56, 0x90, 0x7e, 0x9a, 0x21, 0xe3, 0x4f, 0x26, 0xe8, 0x16, 0x94, 0xad, 0xbe, 0xed, + 0xb8, 0x78, 0x87, 0x31, 0x9d, 0x56, 0xd1, 0x96, 0x8c, 0x12, 0x03, 0xd2, 0x29, 0x29, 0xb8, 0x4c, + 0xd4, 0x4c, 0x22, 0xee, 0x3a, 0x81, 0xc9, 0xf9, 0x7c, 0xae, 0x41, 0x89, 0xce, 0xe7, 0x44, 0xc6, + 0x5e, 0x92, 0x13, 0xc9, 0x50, 0xb2, 0x98, 0xc1, 0x63, 0x53, 0x93, 0x2a, 0xd8, 0x80, 0x56, 0xf1, + 0x00, 0xfb, 0xf8, 0x24, 0xc1, 0x4b, 0x31, 0x65, 0x36, 0xd1, 0x94, 0x52, 0xde, 0x5f, 0x68, 0x70, + 0x36, 0x24, 0xf0, 0x44, 0x53, 0xaf, 0x43, 0xbe, 0x47, 0x99, 0x31, 0x9d, 0xb2, 0x86, 0xf8, 0x44, + 0xf7, 0xa0, 0xc0, 0x55, 0xf2, 0xea, 0xd9, 0xe4, 0x65, 0x28, 0xb5, 0xcc, 0x33, 0x2d, 0x3d, 0xa9, + 0xe6, 0x3f, 0x65, 0xa0, 0xc8, 0x8d, 0xb1, 0x39, 0x42, 0x2d, 0xa8, 0xb8, 0xec, 0x63, 0x87, 0xce, + 0x99, 0xeb, 0xd8, 0x48, 0x8f, 0x93, 0x8f, 0xa7, 0x8c, 0x32, 0x27, 0xa1, 0xc3, 0xe8, 0x57, 0xa1, + 0x24, 0x58, 0x8c, 0xc6, 0x3e, 0x77, 0x54, 0x3d, 0xcc, 0x40, 0x2e, 0xed, 0xc7, 0x53, 0x06, 0x70, + 0xf4, 0xad, 0xb1, 0x8f, 0x3a, 0x30, 0x27, 0x88, 0xd9, 0xfc, 0xb8, 0x1a, 0x59, 0xca, 0x65, 0x21, + 0xcc, 0x25, 0xee, 0xce, 0xc7, 0x53, 0x06, 0xe2, 0xf4, 0x0a, 0x10, 0xad, 0x4a, 0x95, 0xfc, 0x03, + 0x96, 0x5f, 0x62, 0x2a, 0x75, 0x0e, 0x6c, 0xce, 0x44, 0x58, 0x6b, 0x59, 0xd1, 0xad, 0x73, 0x60, + 0x07, 0x26, 0x7b, 0x58, 0x84, 0x3c, 0x1f, 0xd6, 0xff, 0x23, 0x03, 0x20, 0x3c, 0xb6, 0x39, 0x42, + 0xab, 0x50, 0x75, 0xf9, 0x57, 0xc8, 0x7e, 0x97, 0x12, 0xed, 0xc7, 0x1d, 0x3d, 0x65, 0x54, 0x04, + 0x11, 0x53, 0xf7, 0x43, 0x28, 0x07, 0x5c, 0xa4, 0x09, 0x2f, 0x26, 0x98, 0x30, 0xe0, 0x50, 0x12, + 0x04, 0xc4, 0x88, 0x9f, 0xc0, 0xb9, 0x80, 0x3e, 0xc1, 0x8a, 0x6f, 0x1c, 0x61, 0xc5, 0x80, 0xe1, + 0x59, 0xc1, 0x41, 0xb5, 0xe3, 0x23, 0x45, 0x31, 0x69, 0xc8, 0x8b, 0x09, 0x86, 0x64, 0x48, 0xaa, + 0x25, 0x03, 0x0d, 0x43, 0xa6, 0x04, 0x92, 0xf6, 0xd9, 0xb8, 0xfe, 0x57, 0x39, 0xc8, 0xaf, 0x38, + 0xc3, 0x91, 0xe9, 0x92, 0x45, 0x34, 0xe3, 0x62, 0x6f, 0x3c, 0xf0, 0xa9, 0x01, 0xab, 0x4b, 0xd7, + 0xc2, 0x32, 0x38, 0x9a, 0xf8, 0xd7, 0xa0, 0xa8, 0x06, 0x27, 0x21, 0xc4, 0x3c, 0xcb, 0x67, 0x5e, + 0x83, 0x98, 0xe7, 0x78, 0x4e, 0x22, 0x02, 0x42, 0x56, 0x06, 0x84, 0x06, 0xe4, 0xf9, 0x01, 0x8f, + 0x05, 0xeb, 0xc7, 0x53, 0x86, 0x18, 0x40, 0x6f, 0xc3, 0x6c, 0x34, 0x15, 0x4e, 0x73, 0x9c, 0x6a, + 0x37, 0x9c, 0x39, 0xaf, 0x41, 0x39, 0x94, 0xa1, 0x67, 0x38, 0x5e, 0x69, 0xa8, 0xe4, 0xe5, 0xf3, + 0x22, 0xac, 0x93, 0x63, 0x45, 0xf9, 0xf1, 0x94, 0x08, 0xec, 0x57, 0x45, 0x60, 0x2f, 0xa8, 0x89, + 0x96, 0xd8, 0x95, 0xc7, 0xf8, 0xeb, 0x6a, 0xd4, 0xfa, 0x26, 0x21, 0x0e, 0x90, 0x64, 0xf8, 0xd2, + 0x0d, 0xa8, 0x84, 0x4c, 0x46, 0x72, 0x64, 0xfb, 0xe3, 0x67, 0xad, 0x75, 0x96, 0x50, 0x1f, 0xd1, + 0x1c, 0x6a, 0xd4, 0x34, 0x92, 0xa0, 0xd7, 0xdb, 0xdb, 0xdb, 0xb5, 0x0c, 0x3a, 0x0f, 0xc5, 0x8d, + 0xcd, 0xce, 0x0e, 0xc3, 0xca, 0x36, 0xf2, 0x7f, 0xcc, 0x22, 0x89, 0xcc, 0xcf, 0x9f, 0x06, 0x3c, + 0x79, 0x8a, 0x56, 0x32, 0xf3, 0x94, 0x92, 0x99, 0x35, 0x91, 0x99, 0x33, 0x32, 0x33, 0x67, 0x11, + 0x82, 0xe9, 0xf5, 0x76, 0x6b, 0x9b, 0x26, 0x69, 0xc6, 0x7a, 0x39, 0x9e, 0xad, 0x1f, 0x56, 0xa1, + 0xcc, 0xdc, 0xb3, 0x33, 0xb6, 0xc9, 0x61, 0xe2, 0xaf, 0x35, 0x00, 0xb9, 0x61, 0x51, 0x13, 0xf2, + 0x5d, 0xa6, 0x42, 0x5d, 0xa3, 0x11, 0xf0, 0x5c, 0xa2, 0xc7, 0x0d, 0x81, 0x85, 0xee, 0x42, 0xde, + 0x1b, 0x77, 0xbb, 0xd8, 0x13, 0x99, 0xfb, 0x42, 0x34, 0x08, 0xf3, 0x80, 0x68, 0x08, 0x3c, 0x42, + 0xf2, 0xd2, 0xb4, 0x06, 0x63, 0x9a, 0xc7, 0x8f, 0x26, 0xe1, 0x78, 0x32, 0xc6, 0xfe, 0x99, 0x06, + 0x25, 0x65, 0x5b, 0xfc, 0x82, 0x29, 0xe0, 0x32, 0x14, 0xa9, 0x32, 0xb8, 0xc7, 0x93, 0x40, 0xc1, + 0x90, 0x03, 0xe8, 0x3d, 0x28, 0x8a, 0x9d, 0x24, 0xf2, 0x40, 0x3d, 0x99, 0xed, 0xe6, 0xc8, 0x90, + 0xa8, 0x52, 0xc9, 0x0e, 0x9c, 0xa1, 0x76, 0xea, 0x92, 0xdb, 0x87, 0xb0, 0xac, 0x7a, 0x2c, 0xd7, + 0x22, 0xc7, 0xf2, 0x06, 0x14, 0x46, 0x7b, 0x87, 0x9e, 0xd5, 0x35, 0x07, 0x5c, 0x9d, 0xe0, 0x5b, + 0x72, 0xdd, 0x06, 0xa4, 0x72, 0x3d, 0x89, 0x01, 0x24, 0xd3, 0xf3, 0x50, 0x7a, 0x6c, 0x7a, 0x7b, + 0x5c, 0x49, 0x39, 0x7e, 0x0f, 0x2a, 0x64, 0xfc, 0xc9, 0xf3, 0xd7, 0x50, 0x5f, 0x50, 0x2d, 0xeb, + 0xff, 0xac, 0x41, 0x55, 0x90, 0x9d, 0xc8, 0x41, 0x08, 0x72, 0x7b, 0xa6, 0xb7, 0x47, 0x8d, 0x51, + 0x31, 0xe8, 0x6f, 0xf4, 0x36, 0xd4, 0xba, 0x6c, 0xfe, 0x3b, 0x91, 0x7b, 0xd7, 0x2c, 0x1f, 0x0f, + 0xf6, 0xfe, 0xbb, 0x50, 0x21, 0x24, 0x3b, 0xe1, 0x7b, 0x90, 0xd8, 0xc6, 0xef, 0x19, 0xe5, 0x3d, + 0x3a, 0xe7, 0xa8, 0xfa, 0x26, 0x94, 0x99, 0x31, 0x4e, 0x5b, 0x77, 0x69, 0xd7, 0x06, 0xcc, 0x6e, + 0xdb, 0xe6, 0xc8, 0xdb, 0x73, 0xfc, 0x88, 0xcd, 0x97, 0xf5, 0xbf, 0xd7, 0xa0, 0x26, 0x81, 0x27, + 0xd2, 0xe1, 0x2d, 0x98, 0x75, 0xf1, 0xd0, 0xb4, 0x6c, 0xcb, 0xee, 0xef, 0xec, 0x1e, 0xfa, 0xd8, + 0xe3, 0xd7, 0xd7, 0x6a, 0x30, 0xfc, 0x90, 0x8c, 0x12, 0x65, 0x77, 0x07, 0xce, 0x2e, 0x0f, 0xd2, + 0xf4, 0x37, 0x7a, 0x23, 0x1c, 0xa5, 0x8b, 0xd2, 0x6e, 0x62, 0x5c, 0xea, 0xfc, 0xd3, 0x0c, 0x94, + 0x3f, 0x31, 0xfd, 0xae, 0x58, 0x41, 0x68, 0x0d, 0xaa, 0x41, 0x18, 0xa7, 0x23, 0x5c, 0xef, 0xc8, + 0x81, 0x83, 0xd2, 0x88, 0x7b, 0x8d, 0x38, 0x70, 0x54, 0xba, 0xea, 0x00, 0x65, 0x65, 0xda, 0x5d, + 0x3c, 0x08, 0x58, 0x65, 0xd2, 0x59, 0x51, 0x44, 0x95, 0x95, 0x3a, 0x80, 0xbe, 0x0d, 0xb5, 0x91, + 0xeb, 0xf4, 0x5d, 0xec, 0x79, 0x01, 0x33, 0x96, 0xc2, 0xf5, 0x04, 0x66, 0x5b, 0x1c, 0x35, 0x72, + 0x8a, 0xb9, 0xf7, 0x78, 0xca, 0x98, 0x1d, 0x85, 0x61, 0x32, 0xb0, 0xce, 0xca, 0xf3, 0x1e, 0x8b, + 0xac, 0x3f, 0xca, 0x02, 0x8a, 0x4f, 0xf3, 0xab, 0x1e, 0x93, 0x6f, 0x40, 0xd5, 0xf3, 0x4d, 0x37, + 0xb6, 0xe6, 0x2b, 0x74, 0x34, 0x58, 0xf1, 0x6f, 0x41, 0xa0, 0xd9, 0x8e, 0xed, 0xf8, 0xd6, 0xcb, + 0x43, 0x76, 0x41, 0x31, 0xaa, 0x62, 0x78, 0x83, 0x8e, 0xa2, 0x0d, 0xc8, 0xbf, 0xb4, 0x06, 0x3e, + 0x76, 0xbd, 0xfa, 0xf4, 0x42, 0xf6, 0x66, 0x75, 0xe9, 0x9d, 0xe3, 0x1c, 0xb3, 0xf8, 0x11, 0xc5, + 0xef, 0x1c, 0x8e, 0xd4, 0xd3, 0x2f, 0x67, 0xa2, 0x1e, 0xe3, 0x67, 0x92, 0x6f, 0x44, 0x3a, 0x14, + 0x5e, 0x11, 0xa6, 0x3b, 0x56, 0x8f, 0xe6, 0xe2, 0x60, 0x1f, 0xde, 0x33, 0xf2, 0x14, 0xb0, 0xd6, + 0x43, 0xd7, 0xa0, 0xf0, 0xd2, 0x35, 0xfb, 0x43, 0x6c, 0xfb, 0xec, 0x96, 0x2f, 0x71, 0x02, 0x80, + 0xbe, 0x08, 0x20, 0x55, 0x21, 0x99, 0x6f, 0x63, 0x73, 0xeb, 0x59, 0xa7, 0x36, 0x85, 0xca, 0x50, + 0xd8, 0xd8, 0x5c, 0x6d, 0xaf, 0xb7, 0x49, 0x6e, 0x14, 0x39, 0xef, 0xae, 0xdc, 0x74, 0x2d, 0xe1, + 0x88, 0xd0, 0x9a, 0x50, 0xf5, 0xd2, 0xc2, 0x97, 0x6e, 0xa1, 0x97, 0x60, 0x71, 0x57, 0xbf, 0x0a, + 0x73, 0x49, 0x4b, 0x43, 0x20, 0xdc, 0xd3, 0xff, 0x2d, 0x03, 0x15, 0xbe, 0x11, 0x4e, 0xb4, 0x73, + 0x2f, 0x2a, 0x5a, 0xf1, 0xeb, 0x89, 0x30, 0x52, 0x1d, 0xf2, 0x6c, 0x83, 0xf4, 0xf8, 0xfd, 0x57, + 0x7c, 0x92, 0xe0, 0xcc, 0xd6, 0x3b, 0xee, 0x71, 0xb7, 0x07, 0xdf, 0x89, 0x61, 0x73, 0x3a, 0x35, + 0x6c, 0x06, 0x1b, 0xce, 0xf4, 0xf8, 0xc1, 0xaa, 0x28, 0x5d, 0x51, 0x16, 0x9b, 0x8a, 0x00, 0x43, + 0x3e, 0xcb, 0xa7, 0xf8, 0x0c, 0xdd, 0x80, 0x19, 0x3c, 0xc1, 0xb6, 0xef, 0xd5, 0x4b, 0x34, 0x91, + 0x56, 0xc4, 0x85, 0xaa, 0x4d, 0x46, 0x0d, 0x0e, 0x94, 0xae, 0xfa, 0x10, 0xce, 0xd0, 0xfb, 0xee, + 0x23, 0xd7, 0xb4, 0xd5, 0x3b, 0x7b, 0xa7, 0xb3, 0xce, 0xd3, 0x0e, 0xf9, 0x89, 0xaa, 0x90, 0x59, + 0x5b, 0xe5, 0xf6, 0xc9, 0xac, 0xad, 0x4a, 0xfa, 0xdf, 0xd7, 0x00, 0xa9, 0x0c, 0x4e, 0xe4, 0x8b, + 0x88, 0x14, 0xa1, 0x47, 0x56, 0xea, 0x31, 0x07, 0xd3, 0xd8, 0x75, 0x1d, 0x97, 0x05, 0x4a, 0x83, + 0x7d, 0x48, 0x6d, 0x6e, 0x73, 0x65, 0x0c, 0x3c, 0x71, 0xf6, 0x83, 0x08, 0xc0, 0xd8, 0x6a, 0x71, + 0xe5, 0x3b, 0x70, 0x36, 0x84, 0x7e, 0x3a, 0x29, 0x7e, 0x13, 0x66, 0x29, 0xd7, 0x95, 0x3d, 0xdc, + 0xdd, 0x1f, 0x39, 0x96, 0x1d, 0xd3, 0x00, 0x5d, 0x23, 0xb1, 0x4b, 0xa4, 0x0b, 0x32, 0x45, 0x36, + 0xe7, 0x72, 0x30, 0xd8, 0xe9, 0xac, 0xcb, 0xa5, 0xbe, 0x0b, 0xe7, 0x23, 0x0c, 0xc5, 0xcc, 0x7e, + 0x0d, 0x4a, 0xdd, 0x60, 0xd0, 0xe3, 0x27, 0xc8, 0x2b, 0x61, 0x75, 0xa3, 0xa4, 0x2a, 0x85, 0x94, + 0xf1, 0x6d, 0xb8, 0x10, 0x93, 0x71, 0x1a, 0xe6, 0xb8, 0xa7, 0xdf, 0x81, 0x73, 0x94, 0xf3, 0x13, + 0x8c, 0x47, 0xad, 0x81, 0x35, 0x39, 0xde, 0x2d, 0x87, 0x7c, 0xbe, 0x0a, 0xc5, 0xd7, 0xbb, 0xac, + 0xa4, 0xe8, 0x36, 0x17, 0xdd, 0xb1, 0x86, 0xb8, 0xe3, 0xac, 0xa7, 0x6b, 0x4b, 0x12, 0xf9, 0x3e, + 0x3e, 0xf4, 0xf8, 0xf1, 0x91, 0xfe, 0x96, 0xd1, 0xeb, 0x6f, 0x35, 0x6e, 0x4e, 0x95, 0xcf, 0xd7, + 0xbc, 0x35, 0xe6, 0x01, 0xfa, 0x64, 0x0f, 0xe2, 0x1e, 0x01, 0xb0, 0xda, 0x9c, 0x32, 0x12, 0x28, + 0x4c, 0xb2, 0x50, 0x39, 0xaa, 0xf0, 0x15, 0xbe, 0x71, 0xe8, 0x7f, 0xbc, 0xd8, 0x49, 0xe9, 0x4d, + 0x28, 0x51, 0xc8, 0xb6, 0x6f, 0xfa, 0x63, 0x2f, 0xcd, 0x73, 0xcb, 0xfa, 0x8f, 0x34, 0xbe, 0xa3, + 0x04, 0x9f, 0x13, 0xcd, 0xf9, 0x2e, 0xcc, 0xd0, 0x1b, 0xa2, 0xb8, 0xe9, 0x5c, 0x4c, 0x58, 0xd8, + 0x4c, 0x23, 0x83, 0x23, 0x2a, 0xe7, 0x24, 0x0d, 0x66, 0x9e, 0xd2, 0xce, 0x81, 0xa2, 0x6d, 0x4e, + 0x78, 0xce, 0x36, 0x87, 0xac, 0xfc, 0x58, 0x34, 0xe8, 0x6f, 0x7a, 0x21, 0xc0, 0xd8, 0x7d, 0x66, + 0xac, 0xb3, 0x1b, 0x48, 0xd1, 0x08, 0xbe, 0x89, 0x61, 0xbb, 0x03, 0x0b, 0xdb, 0x3e, 0x85, 0xe6, + 0x28, 0x54, 0x19, 0x41, 0x37, 0xa0, 0x68, 0x79, 0xeb, 0xd8, 0x74, 0x6d, 0x5e, 0xe2, 0x57, 0x02, + 0xb3, 0x84, 0xc8, 0x35, 0xf6, 0x1d, 0xa8, 0x31, 0xcd, 0x5a, 0xbd, 0x9e, 0x72, 0xda, 0x0f, 0xe4, + 0x6b, 0x11, 0xf9, 0x21, 0xfe, 0x99, 0xe3, 0xf9, 0xff, 0x9d, 0x06, 0x67, 0x14, 0x01, 0x27, 0x72, + 0xc1, 0xbb, 0x30, 0xc3, 0xfa, 0x2f, 0xfc, 0x28, 0x38, 0x17, 0xa6, 0x62, 0x62, 0x0c, 0x8e, 0x83, + 0x16, 0x21, 0xcf, 0x7e, 0x89, 0x6b, 0x5c, 0x32, 0xba, 0x40, 0x92, 0x2a, 0x2f, 0xc2, 0x59, 0x0e, + 0xc3, 0x43, 0x27, 0x69, 0xcf, 0xe5, 0xc2, 0x11, 0xe2, 0x87, 0x1a, 0xcc, 0x85, 0x09, 0x4e, 0x34, + 0x4b, 0x45, 0xef, 0xcc, 0x57, 0xd2, 0xfb, 0x5b, 0x42, 0xef, 0x67, 0xa3, 0x9e, 0x72, 0xe4, 0x8c, + 0xae, 0x38, 0xd5, 0xbb, 0x99, 0xb0, 0x77, 0x25, 0xaf, 0x9f, 0x04, 0x73, 0x12, 0xcc, 0x4e, 0x34, + 0xa7, 0xf7, 0x5f, 0x6b, 0x4e, 0xca, 0x11, 0x2c, 0x36, 0xb9, 0x35, 0xb1, 0x8c, 0xd6, 0x2d, 0x2f, + 0xc8, 0x38, 0xef, 0x40, 0x79, 0x60, 0xd9, 0xd8, 0x74, 0x79, 0x0f, 0x49, 0x53, 0xd7, 0xe3, 0x7d, + 0x23, 0x04, 0x94, 0xac, 0x7e, 0x5b, 0x03, 0xa4, 0xf2, 0xfa, 0xe5, 0x78, 0xab, 0x29, 0x0c, 0xbc, + 0xe5, 0x3a, 0x43, 0xc7, 0x3f, 0x6e, 0x99, 0xdd, 0xd3, 0x7f, 0x57, 0x83, 0x73, 0x11, 0x8a, 0x5f, + 0x86, 0xe6, 0xf7, 0xf4, 0xcb, 0x70, 0x66, 0x15, 0x8b, 0x33, 0x5e, 0xac, 0x76, 0xb0, 0x0d, 0x48, + 0x85, 0x9e, 0xce, 0x29, 0xe6, 0x1b, 0x70, 0xe6, 0xa9, 0x33, 0x21, 0x81, 0x9c, 0x80, 0x65, 0x98, + 0x62, 0xc5, 0xac, 0xc0, 0x5e, 0xc1, 0xb7, 0x0c, 0xbd, 0xdb, 0x80, 0x54, 0xca, 0xd3, 0x50, 0x67, + 0x59, 0xff, 0x5f, 0x0d, 0xca, 0xad, 0x81, 0xe9, 0x0e, 0x85, 0x2a, 0x1f, 0xc2, 0x0c, 0xab, 0xcc, + 0xf0, 0x32, 0xeb, 0x9b, 0x61, 0x7e, 0x2a, 0x2e, 0xfb, 0x68, 0xb1, 0x3a, 0x0e, 0xa7, 0x22, 0x53, + 0xe1, 0x9d, 0xe5, 0xd5, 0x48, 0xa7, 0x79, 0x15, 0xdd, 0x86, 0x69, 0x93, 0x90, 0xd0, 0xf4, 0x5a, + 0x8d, 0x96, 0xcb, 0x28, 0x37, 0x72, 0x25, 0x32, 0x18, 0x96, 0xfe, 0x01, 0x94, 0x14, 0x09, 0x28, + 0x0f, 0xd9, 0x47, 0x6d, 0x7e, 0x4d, 0x6a, 0xad, 0x74, 0xd6, 0x9e, 0xb3, 0x12, 0x62, 0x15, 0x60, + 0xb5, 0x1d, 0x7c, 0x67, 0x12, 0x1a, 0x7b, 0x26, 0xe7, 0xc3, 0xf3, 0x96, 0xaa, 0xa1, 0x96, 0xa6, + 0x61, 0xe6, 0x75, 0x34, 0x94, 0x22, 0x7e, 0x4b, 0x83, 0x0a, 0x37, 0xcd, 0x49, 0x53, 0x33, 0xe5, + 0x9c, 0x92, 0x9a, 0x95, 0x69, 0x18, 0x1c, 0x51, 0xea, 0xf0, 0x2f, 0x1a, 0xd4, 0x56, 0x9d, 0x57, + 0x76, 0xdf, 0x35, 0x7b, 0xc1, 0x1e, 0xfc, 0x28, 0xe2, 0xce, 0xc5, 0x48, 0xa5, 0x3f, 0x82, 0x2f, + 0x07, 0x22, 0x6e, 0xad, 0xcb, 0x5a, 0x0a, 0xcb, 0xef, 0xe2, 0x53, 0xff, 0x26, 0xcc, 0x46, 0x88, + 0x88, 0x83, 0x9e, 0xb7, 0xd6, 0xd7, 0x56, 0x89, 0x43, 0x68, 0xbd, 0xb7, 0xbd, 0xd1, 0x7a, 0xb8, + 0xde, 0xe6, 0x5d, 0xd9, 0xd6, 0xc6, 0x4a, 0x7b, 0x5d, 0x3a, 0xea, 0xbe, 0x98, 0xc1, 0x7d, 0x7d, + 0x00, 0x67, 0x14, 0x85, 0x4e, 0xda, 0x1c, 0x4b, 0xd6, 0x57, 0x4a, 0xfb, 0x06, 0x5c, 0x0a, 0xa4, + 0x3d, 0x67, 0xc0, 0x0e, 0xf6, 0xd4, 0xcb, 0xda, 0x84, 0x0b, 0x2d, 0x1a, 0xe4, 0xa7, 0xa0, 0x7c, + 0x4f, 0xaf, 0x43, 0x85, 0x9f, 0x8f, 0xa2, 0x21, 0xe3, 0xcf, 0x73, 0x50, 0x15, 0xa0, 0xaf, 0x47, + 0x7f, 0x74, 0x1e, 0x66, 0x7a, 0xbb, 0xdb, 0xd6, 0x67, 0xa2, 0xa3, 0xcb, 0xbf, 0xc8, 0xf8, 0x80, + 0xc9, 0x61, 0xef, 0x34, 0xf8, 0x17, 0xba, 0xcc, 0x9e, 0x70, 0xac, 0xd9, 0x3d, 0x7c, 0x40, 0x8f, + 0x51, 0x39, 0x43, 0x0e, 0xd0, 0x72, 0x28, 0x7f, 0xcf, 0x41, 0x6f, 0xc9, 0xca, 0xfb, 0x0e, 0xb4, + 0x0c, 0x35, 0xf2, 0xbb, 0x35, 0x1a, 0x0d, 0x2c, 0xdc, 0x63, 0x0c, 0xc8, 0x05, 0x39, 0x27, 0xcf, + 0x49, 0x31, 0x04, 0x74, 0x15, 0x66, 0xe8, 0xe5, 0xd1, 0xab, 0x17, 0x48, 0x46, 0x96, 0xa8, 0x7c, + 0x18, 0xbd, 0x0d, 0x25, 0xa6, 0xf1, 0x9a, 0xfd, 0xcc, 0xc3, 0xf4, 0xb5, 0x83, 0x52, 0x49, 0x51, + 0x61, 0xe1, 0x13, 0x1a, 0xa4, 0x9d, 0xd0, 0x50, 0x13, 0xaa, 0x9e, 0xef, 0xb8, 0x66, 0x5f, 0xb8, + 0x91, 0x3e, 0x75, 0x50, 0xca, 0x7d, 0x11, 0xb0, 0x54, 0xe1, 0xe3, 0xb1, 0xe3, 0x9b, 0xe1, 0x27, + 0x0e, 0xef, 0x19, 0x2a, 0x0c, 0x7d, 0x0b, 0x2a, 0x3d, 0xb1, 0x48, 0xd6, 0xec, 0x97, 0x0e, 0x7d, + 0xd6, 0x10, 0xeb, 0xde, 0xad, 0xaa, 0x28, 0x92, 0x53, 0x98, 0x54, 0xbd, 0xc9, 0x56, 0x42, 0x14, + 0xc4, 0xdb, 0xd8, 0x26, 0xa9, 0x9d, 0x55, 0x70, 0x0a, 0x86, 0xf8, 0x44, 0xd7, 0xa1, 0xc2, 0x32, + 0xc1, 0xf3, 0xd0, 0x6a, 0x08, 0x0f, 0x92, 0x3c, 0xd6, 0x1a, 0xfb, 0x7b, 0x6d, 0x4a, 0x14, 0x5b, + 0x94, 0x57, 0x00, 0x11, 0xe8, 0xaa, 0xe5, 0x25, 0x82, 0x39, 0x71, 0xe2, 0x8a, 0xbe, 0xaf, 0x6f, + 0xc0, 0x59, 0x02, 0xc5, 0xb6, 0x6f, 0x75, 0x95, 0xa3, 0x98, 0x38, 0xec, 0x6b, 0x91, 0xc3, 0xbe, + 0xe9, 0x79, 0xaf, 0x1c, 0xb7, 0xc7, 0xd5, 0x0c, 0xbe, 0xa5, 0xb4, 0x7f, 0xd4, 0x98, 0x36, 0xcf, + 0xbc, 0xd0, 0x41, 0xfd, 0x2b, 0xf2, 0x43, 0xbf, 0x02, 0x79, 0xfe, 0x40, 0x8a, 0xd7, 0x3f, 0xcf, + 0x2f, 0xb2, 0x87, 0x59, 0x8b, 0x9c, 0xf1, 0x26, 0x83, 0x2a, 0x35, 0x3a, 0x8e, 0x4f, 0x96, 0xcb, + 0x9e, 0xe9, 0xed, 0xe1, 0xde, 0x96, 0x60, 0x1e, 0xaa, 0x0e, 0xdf, 0x37, 0x22, 0x60, 0xa9, 0xfb, + 0x5d, 0xa9, 0xfa, 0x23, 0xec, 0x1f, 0xa1, 0xba, 0xda, 0x7f, 0x38, 0x27, 0x48, 0x78, 0xdb, 0xf4, + 0x75, 0xa8, 0x7e, 0xac, 0xc1, 0x15, 0x41, 0xb6, 0xb2, 0x67, 0xda, 0x7d, 0x2c, 0x94, 0xf9, 0x45, + 0xed, 0x15, 0x9f, 0x74, 0xf6, 0x35, 0x27, 0xfd, 0x04, 0xea, 0xc1, 0xa4, 0x69, 0x2d, 0xca, 0x19, + 0xa8, 0x93, 0x18, 0x7b, 0x41, 0x90, 0xa4, 0xbf, 0xc9, 0x98, 0xeb, 0x0c, 0x82, 0x6b, 0x20, 0xf9, + 0x2d, 0x99, 0xad, 0xc3, 0x45, 0xc1, 0x8c, 0x17, 0x87, 0xc2, 0xdc, 0x62, 0x73, 0x3a, 0x92, 0x1b, + 0xf7, 0x07, 0xe1, 0x71, 0xf4, 0x52, 0x4a, 0x24, 0x09, 0xbb, 0x90, 0x4a, 0xd1, 0x92, 0xa4, 0xcc, + 0xb3, 0x1d, 0x40, 0x74, 0x56, 0x4e, 0xec, 0x31, 0x38, 0x61, 0x99, 0x08, 0xe7, 0x4b, 0x80, 0xc0, + 0x63, 0x4b, 0x20, 0x5d, 0x2a, 0x86, 0xf9, 0x40, 0x51, 0x62, 0xf6, 0x2d, 0xec, 0x0e, 0x2d, 0xcf, + 0x53, 0x1a, 0x71, 0x49, 0xe6, 0x7a, 0x13, 0x72, 0x23, 0xcc, 0x8f, 0x2f, 0xa5, 0x25, 0x24, 0xf6, + 0x84, 0x42, 0x4c, 0xe1, 0x52, 0xcc, 0x10, 0xae, 0x0a, 0x31, 0xcc, 0x21, 0x89, 0x72, 0xa2, 0x6a, + 0x8a, 0xe2, 0x7f, 0x26, 0xa5, 0xf8, 0x9f, 0x0d, 0x17, 0xff, 0x43, 0x47, 0x6a, 0x35, 0x50, 0x9d, + 0xce, 0x91, 0xba, 0xc3, 0x1c, 0x10, 0xc4, 0xb7, 0xd3, 0xe1, 0xfa, 0x07, 0x3c, 0x50, 0x9d, 0x56, + 0x3a, 0x17, 0x01, 0x3e, 0x13, 0x0e, 0xf0, 0x3a, 0x94, 0x89, 0x93, 0x0c, 0xb5, 0x2b, 0x92, 0x33, + 0x42, 0x63, 0x32, 0x18, 0xef, 0xc3, 0x5c, 0x38, 0x18, 0x9f, 0x48, 0xa9, 0x39, 0x98, 0xf6, 0x9d, + 0x7d, 0x2c, 0x72, 0x0a, 0xfb, 0x88, 0x99, 0x35, 0x08, 0xd4, 0xa7, 0x63, 0xd6, 0xef, 0x4a, 0xae, + 0x74, 0x03, 0x9e, 0x74, 0x06, 0x64, 0x39, 0x8a, 0xdb, 0x3f, 0xfb, 0x90, 0xb2, 0x3e, 0x81, 0xf3, + 0xd1, 0xe0, 0x7b, 0x3a, 0x93, 0xd8, 0x61, 0x9b, 0x33, 0x29, 0x3c, 0x9f, 0x8e, 0x80, 0x17, 0x32, + 0x4e, 0x2a, 0x41, 0xf7, 0x74, 0x78, 0xff, 0x3a, 0x34, 0x92, 0x62, 0xf0, 0xa9, 0xee, 0xc5, 0x20, + 0x24, 0x9f, 0x0e, 0xd7, 0x1f, 0x6a, 0x92, 0xad, 0xba, 0x6a, 0x3e, 0xf8, 0x2a, 0x6c, 0x45, 0xae, + 0xbb, 0x13, 0x2c, 0x9f, 0x66, 0x10, 0x2d, 0xb3, 0xc9, 0xd1, 0x52, 0x92, 0x50, 0x44, 0xb1, 0xff, + 0x64, 0xa8, 0xff, 0x3a, 0x57, 0x2f, 0x17, 0x26, 0xf3, 0xce, 0x49, 0x85, 0x91, 0xf4, 0x1c, 0x08, + 0xa3, 0x1f, 0xb1, 0xad, 0xa2, 0x26, 0xa9, 0xd3, 0x71, 0xdd, 0x6f, 0xc8, 0x04, 0x13, 0xcb, 0x63, + 0xa7, 0x23, 0xc1, 0x84, 0x85, 0xf4, 0x14, 0x76, 0x2a, 0x22, 0x6e, 0xb5, 0xa0, 0x18, 0xdc, 0xfd, + 0x95, 0x97, 0xca, 0x25, 0xc8, 0x6f, 0x6c, 0x6e, 0x6f, 0xb5, 0x56, 0xc8, 0xd5, 0x76, 0x0e, 0xf2, + 0x2b, 0x9b, 0x86, 0xf1, 0x6c, 0xab, 0x43, 0xee, 0xb6, 0xd1, 0x87, 0x4b, 0x4b, 0x3f, 0xcb, 0x42, + 0xe6, 0xc9, 0x73, 0xf4, 0x29, 0x4c, 0xb3, 0x87, 0x73, 0x47, 0xbc, 0x9f, 0x6c, 0x1c, 0xf5, 0x36, + 0x50, 0xbf, 0xf0, 0x83, 0xff, 0xfe, 0xd9, 0x1f, 0x66, 0xce, 0xe8, 0xe5, 0xe6, 0x64, 0xb9, 0xb9, + 0x3f, 0x69, 0xd2, 0x24, 0xfb, 0x40, 0xbb, 0x85, 0x3e, 0x86, 0xec, 0xd6, 0xd8, 0x47, 0xa9, 0xef, + 0x2a, 0x1b, 0xe9, 0xcf, 0x05, 0xf5, 0x73, 0x94, 0xe9, 0xac, 0x0e, 0x9c, 0xe9, 0x68, 0xec, 0x13, + 0x96, 0xdf, 0x83, 0x92, 0xfa, 0xd8, 0xef, 0xd8, 0xc7, 0x96, 0x8d, 0xe3, 0x1f, 0x12, 0xea, 0x57, + 0xa8, 0xa8, 0x0b, 0x3a, 0xe2, 0xa2, 0xd8, 0x73, 0x44, 0x75, 0x16, 0x9d, 0x03, 0x1b, 0xa5, 0x3e, + 0xc5, 0x6c, 0xa4, 0xbf, 0x2d, 0x8c, 0xcd, 0xc2, 0x3f, 0xb0, 0x09, 0xcb, 0xef, 0xf2, 0x47, 0x84, + 0x5d, 0x1f, 0x5d, 0x4d, 0x78, 0x05, 0xa6, 0xbe, 0x6e, 0x6a, 0x2c, 0xa4, 0x23, 0x70, 0x21, 0x97, + 0xa9, 0x90, 0xf3, 0xfa, 0x19, 0x2e, 0xa4, 0x1b, 0xa0, 0x3c, 0xd0, 0x6e, 0x2d, 0x75, 0x61, 0x9a, + 0x76, 0xcf, 0xd1, 0x0b, 0xf1, 0xa3, 0x91, 0xf0, 0x2e, 0x21, 0xc5, 0xd1, 0xa1, 0xbe, 0xbb, 0x3e, + 0x47, 0x05, 0x55, 0xf5, 0x22, 0x11, 0x44, 0x7b, 0xe7, 0x0f, 0xb4, 0x5b, 0x37, 0xb5, 0x3b, 0xda, + 0xd2, 0xdf, 0x4c, 0xc3, 0x34, 0xed, 0xd2, 0xa0, 0x7d, 0x00, 0xd9, 0x25, 0x8e, 0xce, 0x2e, 0xd6, + 0x80, 0x8e, 0xce, 0x2e, 0xde, 0x60, 0xd6, 0x1b, 0x54, 0xe8, 0x9c, 0x3e, 0x4b, 0x84, 0xd2, 0xe6, + 0x4f, 0x93, 0xf6, 0xba, 0x88, 0x1d, 0x7f, 0xac, 0xf1, 0x76, 0x15, 0xdb, 0x66, 0x28, 0x89, 0x5b, + 0xa8, 0x43, 0x1c, 0x5d, 0x0e, 0x09, 0x4d, 0x61, 0xfd, 0x3e, 0x15, 0xd8, 0xd4, 0x6b, 0x52, 0xa0, + 0x4b, 0x31, 0x1e, 0x68, 0xb7, 0x5e, 0xd4, 0xf5, 0xb3, 0xdc, 0xca, 0x11, 0x08, 0xfa, 0x3e, 0x54, + 0xc3, 0xbd, 0x4c, 0x74, 0x2d, 0x41, 0x56, 0xb4, 0x37, 0xda, 0xb8, 0x7e, 0x34, 0x12, 0xd7, 0x69, + 0x9e, 0xea, 0xc4, 0x85, 0x33, 0xc9, 0xfb, 0x18, 0x8f, 0x4c, 0x82, 0xc4, 0x7d, 0x80, 0xfe, 0x54, + 0xe3, 0xed, 0x68, 0xd9, 0x8a, 0x44, 0x49, 0xdc, 0x63, 0x1d, 0xcf, 0xc6, 0x8d, 0x63, 0xb0, 0xb8, + 0x12, 0x1f, 0x50, 0x25, 0xde, 0xd7, 0xe7, 0xa4, 0x12, 0xbe, 0x35, 0xc4, 0xbe, 0xc3, 0xb5, 0x78, + 0x71, 0x59, 0xbf, 0x10, 0x32, 0x4e, 0x08, 0x2a, 0x9d, 0xc5, 0x5a, 0x86, 0x89, 0xce, 0x0a, 0x75, + 0x25, 0x13, 0x9d, 0x15, 0xee, 0x37, 0x26, 0x39, 0x8b, 0x37, 0x08, 0x13, 0x9c, 0x15, 0x40, 0x96, + 0xfe, 0x3f, 0x07, 0xf9, 0x15, 0xf6, 0x3f, 0x23, 0x21, 0x07, 0x8a, 0x41, 0x13, 0x0d, 0xcd, 0x27, + 0xd5, 0xe9, 0xe5, 0x55, 0xae, 0x71, 0x35, 0x15, 0xce, 0x15, 0x7a, 0x83, 0x2a, 0x74, 0x49, 0x3f, + 0x4f, 0x24, 0xf3, 0xff, 0xdf, 0xa9, 0xc9, 0xaa, 0xb9, 0x4d, 0xb3, 0xd7, 0x23, 0x86, 0xf8, 0x4d, + 0x28, 0xab, 0x2d, 0x2d, 0xf4, 0x46, 0x62, 0x6f, 0x40, 0xed, 0x8f, 0x35, 0xf4, 0xa3, 0x50, 0xb8, + 0xe4, 0xeb, 0x54, 0xf2, 0xbc, 0x7e, 0x31, 0x41, 0xb2, 0x4b, 0x51, 0x43, 0xc2, 0x59, 0xef, 0x29, + 0x59, 0x78, 0xa8, 0xc9, 0x95, 0x2c, 0x3c, 0xdc, 0xba, 0x3a, 0x52, 0xf8, 0x98, 0xa2, 0x12, 0xe1, + 0x1e, 0x80, 0x6c, 0x0e, 0xa1, 0x44, 0x5b, 0x2a, 0x17, 0xd6, 0x68, 0x70, 0x88, 0xf7, 0x95, 0x74, + 0x9d, 0x8a, 0xe5, 0xeb, 0x2e, 0x22, 0x76, 0x60, 0x79, 0x3e, 0xdb, 0x98, 0x95, 0x50, 0x6b, 0x07, + 0x25, 0xce, 0x27, 0xdc, 0x29, 0x6a, 0x5c, 0x3b, 0x12, 0x87, 0x4b, 0xbf, 0x41, 0xa5, 0x5f, 0xd5, + 0x1b, 0x09, 0xd2, 0x47, 0x0c, 0x97, 0x2c, 0xb6, 0xcf, 0xf3, 0x50, 0x7a, 0x6a, 0x5a, 0xb6, 0x8f, + 0x6d, 0xd3, 0xee, 0x62, 0xb4, 0x0b, 0xd3, 0x34, 0x77, 0x47, 0x03, 0xb1, 0xda, 0xc9, 0x88, 0x06, + 0xe2, 0x50, 0x29, 0x5f, 0x5f, 0xa0, 0x82, 0x1b, 0xfa, 0x39, 0x22, 0x78, 0x28, 0x59, 0x37, 0x59, + 0x13, 0x40, 0xbb, 0x85, 0x5e, 0xc2, 0x0c, 0x6f, 0xe1, 0x47, 0x18, 0x85, 0x8a, 0x6a, 0x8d, 0xcb, + 0xc9, 0xc0, 0xa4, 0xb5, 0xac, 0x8a, 0xf1, 0x28, 0x1e, 0x91, 0x33, 0x01, 0x90, 0x1d, 0xa9, 0xa8, + 0x47, 0x63, 0x9d, 0xac, 0xc6, 0x42, 0x3a, 0x42, 0x92, 0x4d, 0x55, 0x99, 0xbd, 0x00, 0x97, 0xc8, + 0xfd, 0x0e, 0xe4, 0x1e, 0x9b, 0xde, 0x1e, 0x8a, 0xe4, 0x5e, 0xe5, 0xc5, 0x6d, 0xa3, 0x91, 0x04, + 0xe2, 0x52, 0xae, 0x52, 0x29, 0x17, 0x59, 0x28, 0x53, 0xa5, 0xd0, 0x37, 0xa5, 0xcc, 0x7e, 0xec, + 0xb9, 0x6d, 0xd4, 0x7e, 0xa1, 0xb7, 0xbb, 0x51, 0xfb, 0x85, 0x5f, 0xe8, 0xa6, 0xdb, 0x8f, 0x48, + 0xd9, 0x9f, 0x10, 0x39, 0x23, 0x28, 0x88, 0x87, 0xa9, 0x28, 0xf2, 0x9c, 0x27, 0xf2, 0x9a, 0xb5, + 0x31, 0x9f, 0x06, 0xe6, 0xd2, 0xae, 0x51, 0x69, 0x57, 0xf4, 0x7a, 0xcc, 0x5b, 0x1c, 0xf3, 0x81, + 0x76, 0xeb, 0x8e, 0x86, 0xbe, 0x0f, 0x20, 0x9b, 0x76, 0xb1, 0x3d, 0x18, 0x6d, 0x04, 0xc6, 0xf6, + 0x60, 0xac, 0xdf, 0xa7, 0x2f, 0x52, 0xb9, 0x37, 0xf5, 0x6b, 0x51, 0xb9, 0xbe, 0x6b, 0xda, 0xde, + 0x4b, 0xec, 0xde, 0x66, 0x75, 0x7f, 0x6f, 0xcf, 0x1a, 0x91, 0x29, 0xbb, 0x50, 0x0c, 0x6a, 0xcd, + 0xd1, 0x78, 0x1b, 0xed, 0xfe, 0x44, 0xe3, 0x6d, 0xac, 0x19, 0x13, 0x0e, 0x3c, 0xa1, 0xf5, 0x22, + 0x50, 0xc9, 0x16, 0xfc, 0xcb, 0x1a, 0xe4, 0xc8, 0x91, 0x9c, 0x1c, 0x4f, 0x64, 0xb9, 0x27, 0x3a, + 0xfb, 0x58, 0xc5, 0x3a, 0x3a, 0xfb, 0x78, 0xa5, 0x28, 0x7c, 0x3c, 0x21, 0xd7, 0xb5, 0x26, 0xab, + 0xa3, 0x90, 0x99, 0x3a, 0x50, 0x52, 0xca, 0x40, 0x28, 0x81, 0x59, 0xb8, 0x02, 0x1e, 0x4d, 0x78, + 0x09, 0x35, 0x24, 0xfd, 0x12, 0x95, 0x77, 0x8e, 0x25, 0x3c, 0x2a, 0xaf, 0xc7, 0x30, 0x88, 0x40, + 0x3e, 0x3b, 0xbe, 0xf3, 0x13, 0x66, 0x17, 0xde, 0xfd, 0x0b, 0xe9, 0x08, 0xa9, 0xb3, 0x93, 0x5b, + 0xff, 0x15, 0x94, 0xd5, 0xd2, 0x0f, 0x4a, 0x50, 0x3e, 0x52, 0xa3, 0x8f, 0x66, 0x92, 0xa4, 0xca, + 0x51, 0x38, 0xb6, 0x51, 0x91, 0xa6, 0x82, 0x46, 0x04, 0x0f, 0x20, 0xcf, 0x4b, 0x40, 0x49, 0x26, + 0x0d, 0x97, 0xf1, 0x93, 0x4c, 0x1a, 0xa9, 0x1f, 0x85, 0xcf, 0xcf, 0x54, 0x22, 0xb9, 0x8a, 0x8a, + 0x6c, 0xcd, 0xa5, 0x3d, 0xc2, 0x7e, 0x9a, 0x34, 0x59, 0xb6, 0x4d, 0x93, 0xa6, 0x54, 0x08, 0xd2, + 0xa4, 0xf5, 0xb1, 0xcf, 0xe3, 0x81, 0xb8, 0x5e, 0xa3, 0x14, 0x66, 0x6a, 0x86, 0xd4, 0x8f, 0x42, + 0x49, 0xba, 0xde, 0x48, 0x81, 0x22, 0x3d, 0x1e, 0x00, 0xc8, 0x72, 0x54, 0xf4, 0xcc, 0x9a, 0xd8, + 0x29, 0x88, 0x9e, 0x59, 0x93, 0x2b, 0x5a, 0xe1, 0x18, 0x2b, 0xe5, 0xb2, 0xdb, 0x15, 0x91, 0xfc, + 0x85, 0x06, 0x28, 0x5e, 0xb0, 0x42, 0xef, 0x24, 0x73, 0x4f, 0xec, 0x3a, 0x34, 0xde, 0x7d, 0x3d, + 0xe4, 0xa4, 0x80, 0x2c, 0x55, 0xea, 0x52, 0xec, 0xd1, 0x2b, 0xa2, 0xd4, 0xe7, 0x1a, 0x54, 0x42, + 0x45, 0x2e, 0xf4, 0x66, 0x8a, 0x4f, 0x23, 0xad, 0x87, 0xc6, 0x5b, 0xc7, 0xe2, 0x25, 0x1d, 0xe6, + 0x95, 0x15, 0x20, 0x6e, 0x35, 0xbf, 0xa3, 0x41, 0x35, 0x5c, 0x0b, 0x43, 0x29, 0xbc, 0x63, 0x1d, + 0x8b, 0xc6, 0xcd, 0xe3, 0x11, 0x8f, 0x76, 0x8f, 0xbc, 0xd0, 0x0c, 0x20, 0xcf, 0x8b, 0x66, 0x49, + 0x0b, 0x3f, 0xdc, 0xe2, 0x48, 0x5a, 0xf8, 0x91, 0x8a, 0x5b, 0xc2, 0xc2, 0x77, 0x9d, 0x01, 0x56, + 0xb6, 0x19, 0xaf, 0xa5, 0xa5, 0x49, 0x3b, 0x7a, 0x9b, 0x45, 0x0a, 0x71, 0x69, 0xd2, 0xe4, 0x36, + 0x13, 0x25, 0x33, 0x94, 0xc2, 0xec, 0x98, 0x6d, 0x16, 0xad, 0xb8, 0x25, 0x6c, 0x33, 0x2a, 0x50, + 0xd9, 0x66, 0xb2, 0x94, 0x95, 0xb4, 0xcd, 0x62, 0xdd, 0x98, 0xa4, 0x6d, 0x16, 0xaf, 0x86, 0x25, + 0xf8, 0x91, 0xca, 0x0d, 0x6d, 0xb3, 0xb3, 0x09, 0xc5, 0x2e, 0xf4, 0x6e, 0x8a, 0x11, 0x13, 0x7b, + 0x3b, 0x8d, 0xdb, 0xaf, 0x89, 0x9d, 0xba, 0xc6, 0x99, 0xf9, 0xc5, 0x1a, 0xff, 0x23, 0x0d, 0xe6, + 0x92, 0xea, 0x63, 0x28, 0x45, 0x4e, 0x4a, 0x2b, 0xa8, 0xb1, 0xf8, 0xba, 0xe8, 0x47, 0x5b, 0x2b, + 0x58, 0xf5, 0x0f, 0xfb, 0x5f, 0xb4, 0x9a, 0x2f, 0xae, 0xc2, 0x15, 0x98, 0x69, 0x8d, 0xac, 0x27, + 0xf8, 0x10, 0x9d, 0x2d, 0x64, 0x1a, 0x15, 0xc2, 0xd7, 0x71, 0xad, 0xcf, 0xe8, 0x5f, 0xbd, 0x58, + 0xc8, 0xec, 0x96, 0x01, 0x02, 0x84, 0xa9, 0x7f, 0xff, 0x72, 0x5e, 0xfb, 0xaf, 0x2f, 0xe7, 0xb5, + 0xff, 0xf9, 0x72, 0x5e, 0xfb, 0xe9, 0xff, 0xcd, 0x4f, 0xbd, 0xb8, 0xd6, 0x77, 0xa8, 0x5a, 0x8b, + 0x96, 0xd3, 0x94, 0x7f, 0x89, 0x63, 0xb9, 0xa9, 0xaa, 0xba, 0x3b, 0x43, 0xff, 0x74, 0xc6, 0xf2, + 0xcf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x82, 0x9b, 0xab, 0xde, 0x11, 0x44, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -11648,6 +11719,18 @@ func (m *StatusResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.DowngradeInfo != nil { + { + size, err := m.DowngradeInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintRpc(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + } if m.DbSizeQuota != 0 { i = encodeVarintRpc(dAtA, i, uint64(m.DbSizeQuota)) i-- @@ -11731,6 +11814,50 @@ func (m *StatusResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *DowngradeInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DowngradeInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DowngradeInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.TargetVersion) > 0 { + i -= len(m.TargetVersion) + copy(dAtA[i:], m.TargetVersion) + i = encodeVarintRpc(dAtA, i, uint64(len(m.TargetVersion))) + i-- + dAtA[i] = 0x12 + } + if m.Enabled { + i-- + if m.Enabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *AuthEnableRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -14598,6 +14725,29 @@ func (m *StatusResponse) Size() (n int) { if m.DbSizeQuota != 0 { n += 1 + sovRpc(uint64(m.DbSizeQuota)) } + if m.DowngradeInfo != nil { + l = m.DowngradeInfo.Size() + n += 1 + l + sovRpc(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *DowngradeInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Enabled { + n += 2 + } + l = len(m.TargetVersion) + if l > 0 { + n += 1 + l + sovRpc(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -22853,6 +23003,145 @@ func (m *StatusResponse) Unmarshal(dAtA []byte) error { break } } + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DowngradeInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRpc + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DowngradeInfo == nil { + m.DowngradeInfo = &DowngradeInfo{} + } + if err := m.DowngradeInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRpc(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRpc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DowngradeInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DowngradeInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DowngradeInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Enabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Enabled = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRpc + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TargetVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRpc(dAtA[iNdEx:]) diff --git a/api/etcdserverpb/rpc.proto b/api/etcdserverpb/rpc.proto index 604be785f763..983dc01725f4 100644 --- a/api/etcdserverpb/rpc.proto +++ b/api/etcdserverpb/rpc.proto @@ -507,7 +507,9 @@ message RangeResponse { repeated mvccpb.KeyValue kvs = 2; // more indicates if there are more keys to return in the requested range. bool more = 3; - // count is set to the number of keys within the range when requested. + // count is set to the actual number of keys within the range when requested. + // Unlike Kvs, it is unaffected by limits and filters (e.g., Min/Max, Create/Modify, Revisions) + // and reflects the full count within the specified range. int64 count = 4; } @@ -1194,10 +1196,19 @@ message StatusResponse { int64 dbSizeInUse = 9 [(versionpb.etcd_version_field)="3.4"]; // isLearner indicates if the member is raft learner. bool isLearner = 10 [(versionpb.etcd_version_field)="3.4"]; - // storageVersion is the version of the db file. It might be get updated with delay in relationship to the target cluster version. + // storageVersion is the version of the db file. It might be updated with delay in relationship to the target cluster version. string storageVersion = 11 [(versionpb.etcd_version_field)="3.6"]; // dbSizeQuota is the configured etcd storage quota in bytes (the value passed to etcd instance by flag --quota-backend-bytes) int64 dbSizeQuota = 12 [(versionpb.etcd_version_field)="3.6"]; + // downgradeInfo indicates if there is downgrade process. + DowngradeInfo downgradeInfo = 13 [(versionpb.etcd_version_field)="3.6"]; +} + +message DowngradeInfo { + // enabled indicates whether the cluster is enabled to downgrade. + bool enabled = 1; + // targetVersion is the target downgrade version. + string targetVersion = 2; } message AuthEnableRequest { diff --git a/api/go.mod b/api/go.mod index 9eba0482db9d..492b6f145534 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,30 +1,30 @@ module go.etcd.io/etcd/api/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/coreos/go-semver v0.3.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/stretchr/testify v1.10.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f - google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.4 + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 + google.golang.org/grpc v1.72.2 + google.golang.org/protobuf v1.36.6 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/api/go.sum b/api/go.sum index 6fd065bd76c9..40f931532542 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,7 +1,7 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,38 +10,38 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -51,20 +51,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -73,14 +73,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/api/v3rpc/rpctypes/error.go b/api/v3rpc/rpctypes/error.go index 28f8d7e435fe..781c73b7bff5 100644 --- a/api/v3rpc/rpctypes/error.go +++ b/api/v3rpc/rpctypes/error.go @@ -212,7 +212,7 @@ var ( ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt) ErrClusterIDMismatch = Error(ErrGRPCClusterIDMismatch) //revive:disable:var-naming - // Deprecated: Please use ErrGRPCClusterIDMismatch. + // Deprecated: Please use ErrClusterIDMismatch. ErrClusterIdMismatch = ErrClusterIDMismatch //revive:enable:var-naming diff --git a/api/version/version.go b/api/version/version.go index ef3c09e7c115..31ca2f14d8a0 100644 --- a/api/version/version.go +++ b/api/version/version.go @@ -26,7 +26,7 @@ import ( var ( // MinClusterVersion is the min cluster version this etcd binary is compatible with. MinClusterVersion = "3.0.0" - Version = "3.6.0-alpha.0" + Version = "3.7.0-alpha.0" APIVersion = "unknown" // Git SHA Value will be set during build @@ -43,6 +43,7 @@ var ( V3_5 = semver.Version{Major: 3, Minor: 5} V3_6 = semver.Version{Major: 3, Minor: 6} V3_7 = semver.Version{Major: 3, Minor: 7} + V3_8 = semver.Version{Major: 3, Minor: 8} V4_0 = semver.Version{Major: 4, Minor: 0} // AllVersions keeps all the versions in ascending order. diff --git a/bill-of-materials.json b/bill-of-materials.json index 16fd5131fd83..68b8c20553c5 100644 --- a/bill-of-materials.json +++ b/bill-of-materials.json @@ -10,6 +10,15 @@ }, { "project": "github.com/anishathalye/porcupine", + "licenses": [ + { + "type": "MIT License", + "confidence": 0.96875 + } + ] + }, + { + "project": "github.com/antithesishq/antithesis-sdk-go", "licenses": [ { "type": "MIT License", @@ -36,7 +45,7 @@ ] }, { - "project": "github.com/cenkalti/backoff/v4", + "project": "github.com/cenkalti/backoff/v5", "licenses": [ { "type": "MIT License", @@ -261,34 +270,25 @@ ] }, { - "project": "github.com/klauspost/compress", - "licenses": [ - { - "type": "Apache License 2.0", - "confidence": 0.9376299376299376 - } - ] - }, - { - "project": "github.com/klauspost/compress/internal/snapref", + "project": "github.com/mattn/go-colorable", "licenses": [ { - "type": "BSD 3-clause \"New\" or \"Revised\" License", - "confidence": 0.9663865546218487 + "type": "MIT License", + "confidence": 1 } ] }, { - "project": "github.com/klauspost/compress/zstd/internal/xxhash", + "project": "github.com/mattn/go-isatty", "licenses": [ { "type": "MIT License", - "confidence": 1 + "confidence": 0.9587628865979382 } ] }, { - "project": "github.com/mattn/go-colorable", + "project": "github.com/mattn/go-runewidth", "licenses": [ { "type": "MIT License", @@ -297,16 +297,16 @@ ] }, { - "project": "github.com/mattn/go-isatty", + "project": "github.com/munnerz/goautoneg", "licenses": [ { - "type": "MIT License", - "confidence": 0.9587628865979382 + "type": "BSD 3-clause \"New\" or \"Revised\" License", + "confidence": 0.9794238683127572 } ] }, { - "project": "github.com/mattn/go-runewidth", + "project": "github.com/olekukonko/errors", "licenses": [ { "type": "MIT License", @@ -315,11 +315,11 @@ ] }, { - "project": "github.com/munnerz/goautoneg", + "project": "github.com/olekukonko/ll", "licenses": [ { - "type": "BSD 3-clause \"New\" or \"Revised\" License", - "confidence": 0.9794238683127572 + "type": "MIT License", + "confidence": 1 } ] }, diff --git a/client/internal/v2/client_test.go b/client/internal/v2/client_test.go index 19374ec93624..66e5671a3f7b 100644 --- a/client/internal/v2/client_test.go +++ b/client/internal/v2/client_test.go @@ -131,7 +131,7 @@ func TestSimpleHTTPClientDoSuccess(t *testing.T) { Body: io.NopCloser(strings.NewReader("foo")), } - resp, body, err := c.Do(context.Background(), &fakeAction{}) + resp, body, err := c.Do(t.Context(), &fakeAction{}) require.NoErrorf(t, err, "incorrect error value") wantCode := http.StatusTeapot require.Equalf(t, wantCode, resp.StatusCode, "invalid response code: want=%d got=%d", wantCode, resp.StatusCode) @@ -146,7 +146,7 @@ func TestSimpleHTTPClientDoError(t *testing.T) { tr.errchan <- errors.New("fixture") - _, _, err := c.Do(context.Background(), &fakeAction{}) + _, _, err := c.Do(t.Context(), &fakeAction{}) assert.Errorf(t, err, "expected non-nil error, got nil") } @@ -162,7 +162,7 @@ func TestSimpleHTTPClientDoNilRequest(t *testing.T) { tr.errchan <- errors.New("fixture") - _, _, err := c.Do(context.Background(), &nilAction{}) + _, _, err := c.Do(t.Context(), &nilAction{}) require.ErrorIsf(t, err, ErrNoRequest, "expected non-nil error, got nil") } @@ -173,7 +173,7 @@ func TestSimpleHTTPClientDoCancelContext(t *testing.T) { tr.startCancel <- struct{}{} tr.finishCancel <- struct{}{} - _, _, err := c.Do(context.Background(), &fakeAction{}) + _, _, err := c.Do(t.Context(), &fakeAction{}) assert.Errorf(t, err, "expected non-nil error, got nil") } @@ -195,7 +195,7 @@ func TestSimpleHTTPClientDoCancelContextResponseBodyClosed(t *testing.T) { c := &simpleHTTPClient{transport: tr} // create an already-cancelled context - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) cancel() body := &checkableReadCloser{ReadCloser: io.NopCloser(strings.NewReader("foo"))} @@ -232,7 +232,7 @@ func TestSimpleHTTPClientDoCancelContextResponseBodyClosedWithBlockingBody(t *te tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) body := &checkableReadCloser{ReadCloser: &blockingBody{c: make(chan struct{})}} go func() { tr.respchan <- &http.Response{Body: body} @@ -252,7 +252,7 @@ func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) { c := &simpleHTTPClient{transport: tr} donechan := make(chan struct{}) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) go func() { c.Do(ctx, &fakeAction{}) close(donechan) @@ -285,7 +285,7 @@ func TestSimpleHTTPClientDoHeaderTimeout(t *testing.T) { errc := make(chan error, 1) go func() { - _, _, err := c.Do(context.Background(), &fakeAction{}) + _, _, err := c.Do(t.Context(), &fakeAction{}) errc <- err }() @@ -407,7 +407,7 @@ func TestHTTPClusterClientDo(t *testing.T) { ), rand: rand.New(rand.NewSource(0)), }, - ctx: context.WithValue(context.Background(), &oneShotCtxValue, &oneShotCtxValue), + ctx: context.WithValue(t.Context(), &oneShotCtxValue, &oneShotCtxValue), wantErr: errors.New("client: etcd member returns server error [Bad Gateway]"), wantPinned: 1, }, @@ -415,7 +415,7 @@ func TestHTTPClusterClientDo(t *testing.T) { for i, tt := range tests { if tt.ctx == nil { - tt.ctx = context.Background() + tt.ctx = t.Context() } resp, _, err := tt.client.Do(tt.ctx, nil) if (tt.wantErr == nil && !errors.Is(err, tt.wantErr)) || (tt.wantErr != nil && tt.wantErr.Error() != err.Error()) { @@ -450,7 +450,7 @@ func TestHTTPClusterClientDoDeadlineExceedContext(t *testing.T) { errc := make(chan error, 1) go func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _, _, err := c.Do(ctx, &fakeAction{}) errc <- err @@ -722,7 +722,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) { for i, tt := range tests { client := &redirectFollowingHTTPClient{client: tt.client, checkRedirect: tt.checkRedirect} - resp, _, err := client.Do(context.Background(), nil) + resp, _, err := client.Do(t.Context(), nil) if (tt.wantErr == nil && !errors.Is(err, tt.wantErr)) || (tt.wantErr != nil && tt.wantErr.Error() != err.Error()) { t.Errorf("#%d: got err=%v, want=%v", i, err, tt.wantErr) continue @@ -781,7 +781,7 @@ func TestHTTPClusterClientSync(t *testing.T) { got := hc.Endpoints() require.Truef(t, reflect.DeepEqual(want, got), "incorrect endpoints: want=%#v got=%#v", want, got) - err = hc.Sync(context.Background()) + err = hc.Sync(t.Context()) require.NoErrorf(t, err, "unexpected error during Sync: %#v", err) want = []string{"http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002", "http://127.0.0.1:4003"} @@ -813,7 +813,7 @@ func TestHTTPClusterClientSyncFail(t *testing.T) { got := hc.Endpoints() require.Truef(t, reflect.DeepEqual(want, got), "incorrect endpoints: want=%#v got=%#v", want, got) - err = hc.Sync(context.Background()) + err = hc.Sync(t.Context()) require.Errorf(t, err, "got nil error during Sync") got = hc.Endpoints() @@ -835,7 +835,7 @@ func TestHTTPClusterClientAutoSyncCancelContext(t *testing.T) { err := hc.SetEndpoints([]string{"http://127.0.0.1:2379"}) require.NoErrorf(t, err, "unexpected error during setup") - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) cancel() err = hc.AutoSync(ctx, time.Hour) @@ -854,7 +854,7 @@ func TestHTTPClusterClientAutoSyncFail(t *testing.T) { err := hc.SetEndpoints([]string{"http://127.0.0.1:2379"}) require.NoErrorf(t, err, "unexpected error during setup") - err = hc.AutoSync(context.Background(), time.Hour) + err = hc.AutoSync(t.Context(), time.Hour) require.Truef(t, strings.HasPrefix(err.Error(), ErrClusterUnavailable.Error()), "incorrect error value: want=%v got=%v", ErrClusterUnavailable, err) } @@ -874,7 +874,7 @@ func TestHTTPClusterClientGetVersion(t *testing.T) { err := hc.SetEndpoints([]string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"}) require.NoErrorf(t, err, "unexpected error during setup") - actual, err := hc.GetVersion(context.Background()) + actual, err := hc.GetVersion(t.Context()) if err != nil { t.Errorf("non-nil error: %#v", err) } @@ -911,7 +911,7 @@ func TestHTTPClusterClientSyncPinEndpoint(t *testing.T) { pinnedEndpoint := hc.endpoints[hc.pinned] for i := 0; i < 3; i++ { - err = hc.Sync(context.Background()) + err = hc.Sync(t.Context()) require.NoErrorf(t, err, "#%d: unexpected error during Sync", i) if g := hc.endpoints[hc.pinned]; g != pinnedEndpoint { @@ -947,7 +947,7 @@ func TestHTTPClusterClientSyncUnpinEndpoint(t *testing.T) { wants := []string{"http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"} for i := 0; i < 3; i++ { - err = hc.Sync(context.Background()) + err = hc.Sync(t.Context()) require.NoErrorf(t, err, "#%d: unexpected error during Sync", i) if g := hc.endpoints[hc.pinned]; g.String() != wants[i] { @@ -988,7 +988,7 @@ func TestHTTPClusterClientSyncPinLeaderEndpoint(t *testing.T) { wants := []string{"http://127.0.0.1:4003", "http://127.0.0.1:4002"} for i, want := range wants { - err := hc.Sync(context.Background()) + err := hc.Sync(t.Context()) require.NoErrorf(t, err, "#%d: unexpected error during Sync", i) pinned := hc.endpoints[hc.pinned].String() diff --git a/client/internal/v2/go.mod b/client/internal/v2/go.mod index 1c3a0b2c3ef6..c7013cf1f6cb 100644 --- a/client/internal/v2/go.mod +++ b/client/internal/v2/go.mod @@ -1,21 +1,21 @@ module go.etcd.io/etcd/client/v2 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 ) require ( github.com/coreos/go-semver v0.3.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/kr/text v0.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/client/internal/v2/go.sum b/client/internal/v2/go.sum index f3a0fb38708a..5e88bc955410 100644 --- a/client/internal/v2/go.sum +++ b/client/internal/v2/go.sum @@ -1,16 +1,16 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -18,5 +18,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= diff --git a/client/internal/v2/keys_test.go b/client/internal/v2/keys_test.go index 1bc6927b68e8..7bc264a7b83f 100644 --- a/client/internal/v2/keys_test.go +++ b/client/internal/v2/keys_test.go @@ -15,7 +15,6 @@ package client import ( - "context" "errors" "fmt" "io" @@ -875,7 +874,7 @@ func TestHTTPWatcherNextWaitAction(t *testing.T) { nextWait: initAction, } - resp, err := watcher.Next(context.Background()) + resp, err := watcher.Next(t.Context()) if err != nil { t.Errorf("non-nil error: %#v", err) } @@ -925,7 +924,7 @@ func TestHTTPWatcherNextFail(t *testing.T) { nextWait: act, } - resp, err := watcher.Next(context.Background()) + resp, err := watcher.Next(t.Context()) if err == nil { t.Errorf("#%d: expected non-nil error", i) } @@ -1073,7 +1072,7 @@ func TestHTTPKeysAPISetAction(t *testing.T) { for i, tt := range tests { client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction} kAPI := httpKeysAPI{client: client} - kAPI.Set(context.Background(), tt.key, tt.value, tt.opts) + kAPI.Set(t.Context(), tt.key, tt.value, tt.opts) } } @@ -1102,7 +1101,7 @@ func TestHTTPKeysAPISetError(t *testing.T) { for i, tt := range tests { kAPI := httpKeysAPI{client: tt} - resp, err := kAPI.Set(context.Background(), "/foo", "bar", nil) + resp, err := kAPI.Set(t.Context(), "/foo", "bar", nil) if err == nil { t.Errorf("#%d: received nil error", i) } @@ -1129,7 +1128,7 @@ func TestHTTPKeysAPISetResponse(t *testing.T) { } kAPI := &httpKeysAPI{client: client, prefix: "/pants"} - resp, err := kAPI.Set(context.Background(), "/foo/bar/baz", "snarf", nil) + resp, err := kAPI.Set(t.Context(), "/foo/bar/baz", "snarf", nil) if err != nil { t.Errorf("non-nil error: %#v", err) } @@ -1184,7 +1183,7 @@ func TestHTTPKeysAPIGetAction(t *testing.T) { for i, tt := range tests { client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction} kAPI := httpKeysAPI{client: client} - kAPI.Get(context.Background(), tt.key, tt.opts) + kAPI.Get(t.Context(), tt.key, tt.opts) } } @@ -1213,7 +1212,7 @@ func TestHTTPKeysAPIGetError(t *testing.T) { for i, tt := range tests { kAPI := httpKeysAPI{client: tt} - resp, err := kAPI.Get(context.Background(), "/foo", nil) + resp, err := kAPI.Get(t.Context(), "/foo", nil) if err == nil { t.Errorf("#%d: received nil error", i) } @@ -1246,7 +1245,7 @@ func TestHTTPKeysAPIGetResponse(t *testing.T) { } kAPI := &httpKeysAPI{client: client, prefix: "/pants"} - resp, err := kAPI.Get(context.Background(), "/foo/bar", &GetOptions{Recursive: true}) + resp, err := kAPI.Get(t.Context(), "/foo/bar", &GetOptions{Recursive: true}) if err != nil { t.Errorf("non-nil error: %#v", err) } @@ -1303,7 +1302,7 @@ func TestHTTPKeysAPIDeleteAction(t *testing.T) { for i, tt := range tests { client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction} kAPI := httpKeysAPI{client: client} - kAPI.Delete(context.Background(), tt.key, tt.opts) + kAPI.Delete(t.Context(), tt.key, tt.opts) } } @@ -1332,7 +1331,7 @@ func TestHTTPKeysAPIDeleteError(t *testing.T) { for i, tt := range tests { kAPI := httpKeysAPI{client: tt} - resp, err := kAPI.Delete(context.Background(), "/foo", nil) + resp, err := kAPI.Delete(t.Context(), "/foo", nil) if err == nil { t.Errorf("#%d: received nil error", i) } @@ -1359,7 +1358,7 @@ func TestHTTPKeysAPIDeleteResponse(t *testing.T) { } kAPI := &httpKeysAPI{client: client, prefix: "/pants"} - resp, err := kAPI.Delete(context.Background(), "/foo/bar/baz", nil) + resp, err := kAPI.Delete(t.Context(), "/foo/bar/baz", nil) if err != nil { t.Errorf("non-nil error: %#v", err) } @@ -1379,7 +1378,7 @@ func TestHTTPKeysAPICreateAction(t *testing.T) { } kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}} - kAPI.Create(context.Background(), "/foo", "bar") + kAPI.Create(t.Context(), "/foo", "bar") } func TestHTTPKeysAPICreateInOrderAction(t *testing.T) { @@ -1389,7 +1388,7 @@ func TestHTTPKeysAPICreateInOrderAction(t *testing.T) { TTL: 0, } kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}} - kAPI.CreateInOrder(context.Background(), "/foo", "bar", nil) + kAPI.CreateInOrder(t.Context(), "/foo", "bar", nil) } func TestHTTPKeysAPIUpdateAction(t *testing.T) { @@ -1403,7 +1402,7 @@ func TestHTTPKeysAPIUpdateAction(t *testing.T) { } kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}} - kAPI.Update(context.Background(), "/foo", "bar") + kAPI.Update(t.Context(), "/foo", "bar") } func TestNodeTTLDuration(t *testing.T) { diff --git a/client/internal/v2/members_test.go b/client/internal/v2/members_test.go index 4d3114a49097..e93b55c476f2 100644 --- a/client/internal/v2/members_test.go +++ b/client/internal/v2/members_test.go @@ -15,7 +15,6 @@ package client import ( - "context" "encoding/json" "errors" "net/http" @@ -340,7 +339,7 @@ func TestHTTPMembersAPIAddSuccess(t *testing.T) { PeerURLs: []string{"http://127.0.0.1:7002"}, } - m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002") + m, err := mAPI.Add(t.Context(), "http://127.0.0.1:7002") if err != nil { t.Errorf("got non-nil err: %#v", err) } @@ -415,7 +414,7 @@ func TestHTTPMembersAPIAddError(t *testing.T) { for i, tt := range tests { mAPI := &httpMembersAPI{client: tt.client} - m, err := mAPI.Add(context.Background(), tt.peerURL) + m, err := mAPI.Add(t.Context(), tt.peerURL) if err == nil { t.Errorf("#%d: got nil err", i) } @@ -443,7 +442,7 @@ func TestHTTPMembersAPIRemoveSuccess(t *testing.T) { }, } - if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil { + if err := mAPI.Remove(t.Context(), "94088180e21eb87b"); err != nil { t.Errorf("got non-nil err: %#v", err) } } @@ -465,7 +464,7 @@ func TestHTTPMembersAPIRemoveFail(t *testing.T) { for i, tt := range tests { mAPI := &httpMembersAPI{client: tt} - if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil { + if err := mAPI.Remove(t.Context(), "94088180e21eb87b"); err == nil { t.Errorf("#%d: got nil err", i) } } @@ -493,7 +492,7 @@ func TestHTTPMembersAPIListSuccess(t *testing.T) { }, } - m, err := mAPI.List(context.Background()) + m, err := mAPI.List(t.Context()) if err != nil { t.Errorf("got non-nil err: %#v", err) } @@ -523,7 +522,7 @@ func TestHTTPMembersAPIListError(t *testing.T) { for i, tt := range tests { mAPI := &httpMembersAPI{client: tt} - ms, err := mAPI.List(context.Background()) + ms, err := mAPI.List(t.Context()) if err == nil { t.Errorf("#%d: got nil err", i) } @@ -553,7 +552,7 @@ func TestHTTPMembersAPILeaderSuccess(t *testing.T) { ClientURLs: []string{"http://127.0.0.1:4002"}, } - m, err := mAPI.Leader(context.Background()) + m, err := mAPI.Leader(t.Context()) if err != nil { t.Errorf("err = %v, want %v", err, nil) } @@ -583,7 +582,7 @@ func TestHTTPMembersAPILeaderError(t *testing.T) { for i, tt := range tests { mAPI := &httpMembersAPI{client: tt} - m, err := mAPI.Leader(context.Background()) + m, err := mAPI.Leader(t.Context()) if err == nil { t.Errorf("#%d: err = nil, want not nil", i) } diff --git a/client/pkg/fileutil/lock_test.go b/client/pkg/fileutil/lock_test.go index 0c5da9855ad7..ea449e1cff3a 100644 --- a/client/pkg/fileutil/lock_test.go +++ b/client/pkg/fileutil/lock_test.go @@ -23,7 +23,7 @@ import ( ) func TestLockAndUnlock(t *testing.T) { - f, err := os.CreateTemp("", "lock") + f, err := os.CreateTemp(t.TempDir(), "lock") require.NoError(t, err) f.Close() defer func() { diff --git a/client/pkg/go.mod b/client/pkg/go.mod index 0f3b7e11717f..634655bdae06 100644 --- a/client/pkg/go.mod +++ b/client/pkg/go.mod @@ -1,21 +1,21 @@ module go.etcd.io/etcd/client/pkg/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/coreos/go-systemd/v22 v22.5.0 github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 - golang.org/x/sys v0.29.0 + golang.org/x/sys v0.33.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect go.uber.org/multierr v1.11.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/client/pkg/go.sum b/client/pkg/go.sum index 16108eeebdb9..b7f7b48973e3 100644 --- a/client/pkg/go.sum +++ b/client/pkg/go.sum @@ -1,8 +1,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -12,11 +12,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -25,8 +25,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/client/pkg/testutil/before.go b/client/pkg/testutil/before.go index 155a491ad405..6259fccfca19 100644 --- a/client/pkg/testutil/before.go +++ b/client/pkg/testutil/before.go @@ -19,9 +19,6 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.etcd.io/etcd/client/pkg/v3/verify" ) @@ -31,15 +28,12 @@ func BeforeTest(tb testing.TB) { revertVerifyFunc := verify.EnableAllVerifications() - path, err := os.Getwd() - require.NoError(tb, err) tempDir := tb.TempDir() - require.NoError(tb, os.Chdir(tempDir)) + tb.Chdir(tempDir) tb.Logf("Changing working directory to: %s", tempDir) tb.Cleanup(func() { revertVerifyFunc() - assert.NoError(tb, os.Chdir(path)) }) } diff --git a/client/pkg/types/urls.go b/client/pkg/types/urls.go index 49a38967e64d..7dca16c0482c 100644 --- a/client/pkg/types/urls.go +++ b/client/pkg/types/urls.go @@ -47,7 +47,6 @@ func NewURLs(strs []string) (URLs, error) { return nil, fmt.Errorf("URL must not contain a path: %s", in) } case "unix", "unixs": - break default: return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in) } diff --git a/client/v3/client.go b/client/v3/client.go index 24f5988986d3..998f5ae03f75 100644 --- a/client/v3/client.go +++ b/client/v3/client.go @@ -43,6 +43,7 @@ import ( var ( ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints") ErrOldCluster = errors.New("etcdclient: old cluster version") + ErrMutuallyExclusiveCfg = errors.New("Username/Password and Token configurations are mutually exclusive") ) // Client provides and manages an etcd v3 client session. @@ -69,7 +70,10 @@ type Client struct { // Username is a user name for authentication. Username string // Password is a password for authentication. - Password string + Password string + // Token is a JWT used for authentication instead of a password. + Token string + authTokenBundle credentials.PerRPCCredentialsBundle callOpts []grpc.CallOption @@ -288,6 +292,11 @@ func (c *Client) Dial(ep string) (*grpc.ClientConn, error) { func (c *Client) getToken(ctx context.Context) error { var err error // return last error in a case of fail + if c.Token != "" { + c.authTokenBundle.UpdateAuthToken(c.Token) + return nil + } + if c.Username == "" || c.Password == "" { return nil } @@ -376,6 +385,10 @@ func newClient(cfg *Config) (*Client, error) { creds = credentials.NewTransportCredential(cfg.TLS) } + if cfg.Token != "" && (cfg.Username != "" || cfg.Password != "") { + return nil, ErrMutuallyExclusiveCfg + } + // use a temporary skeleton client to bootstrap first connection baseCtx := context.TODO() if cfg.Context != nil { @@ -414,6 +427,12 @@ func newClient(cfg *Config) (*Client, error) { client.Password = cfg.Password client.authTokenBundle = credentials.NewPerRPCCredentialBundle() } + + if cfg.Token != "" { + client.Token = cfg.Token + client.authTokenBundle = credentials.NewPerRPCCredentialBundle() + } + if cfg.MaxCallSendMsgSize > 0 || cfg.MaxCallRecvMsgSize > 0 { if cfg.MaxCallRecvMsgSize > 0 && cfg.MaxCallSendMsgSize > cfg.MaxCallRecvMsgSize { return nil, fmt.Errorf("gRPC message recv limit (%d bytes) must be greater than send limit (%d bytes)", cfg.MaxCallRecvMsgSize, cfg.MaxCallSendMsgSize) diff --git a/client/v3/client_test.go b/client/v3/client_test.go index cbe94a411b36..474a48203691 100644 --- a/client/v3/client_test.go +++ b/client/v3/client_test.go @@ -196,18 +196,18 @@ func TestBackoffJitterFraction(t *testing.T) { func TestIsHaltErr(t *testing.T) { assert.Truef(t, - isHaltErr(context.TODO(), errors.New("etcdserver: some etcdserver error")), + isHaltErr(t.Context(), errors.New("etcdserver: some etcdserver error")), "error created by errors.New should be unavailable error", ) assert.Falsef(t, - isHaltErr(context.TODO(), rpctypes.ErrGRPCStopped), + isHaltErr(t.Context(), rpctypes.ErrGRPCStopped), `error "%v" should not be halt error`, rpctypes.ErrGRPCStopped, ) assert.Falsef(t, - isHaltErr(context.TODO(), rpctypes.ErrGRPCNoLeader), + isHaltErr(t.Context(), rpctypes.ErrGRPCNoLeader), `error "%v" should not be halt error`, rpctypes.ErrGRPCNoLeader, ) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) assert.Falsef(t, isHaltErr(ctx, nil), "no error and active context should be halt error", @@ -221,18 +221,18 @@ func TestIsHaltErr(t *testing.T) { func TestIsUnavailableErr(t *testing.T) { assert.Falsef(t, - isUnavailableErr(context.TODO(), errors.New("etcdserver: some etcdserver error")), + isUnavailableErr(t.Context(), errors.New("etcdserver: some etcdserver error")), "error created by errors.New should not be unavailable error", ) assert.Truef(t, - isUnavailableErr(context.TODO(), rpctypes.ErrGRPCStopped), + isUnavailableErr(t.Context(), rpctypes.ErrGRPCStopped), `error "%v" should be unavailable error`, rpctypes.ErrGRPCStopped, ) assert.Falsef(t, - isUnavailableErr(context.TODO(), rpctypes.ErrGRPCNotCapable), + isUnavailableErr(t.Context(), rpctypes.ErrGRPCNotCapable), "error %v should not be unavailable error", rpctypes.ErrGRPCNotCapable, ) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) assert.Falsef(t, isUnavailableErr(ctx, nil), "no error and active context should not be unavailable error", @@ -245,7 +245,7 @@ func TestIsUnavailableErr(t *testing.T) { } func TestCloseCtxClient(t *testing.T) { - ctx := context.Background() + ctx := t.Context() c := NewCtxClient(ctx) err := c.Close() // Close returns ctx.toErr, a nil error means an open Done channel @@ -255,7 +255,7 @@ func TestCloseCtxClient(t *testing.T) { } func TestWithLogger(t *testing.T) { - ctx := context.Background() + ctx := t.Context() c := NewCtxClient(ctx) if c.lg == nil { t.Errorf("unexpected nil in *zap.Logger") @@ -268,7 +268,7 @@ func TestWithLogger(t *testing.T) { } func TestZapWithLogger(t *testing.T) { - ctx := context.Background() + ctx := t.Context() lg := zap.NewNop() c := NewCtxClient(ctx, WithZapLogger(lg)) @@ -317,6 +317,75 @@ func TestAuthTokenBundleNoOverwrite(t *testing.T) { } } +func TestNewWithOnlyJWT(t *testing.T) { + // This call in particular changes working directory to the tmp dir of + // the test. The `etcd-auth-test:1` can be created in local directory, + // not exceeding the longest allowed path on OsX. + testutil.BeforeTest(t) + + // Create a mock AuthServer to handle Authenticate RPCs. + lis, err := net.Listen("unix", "etcd-auth-test:1") + if err != nil { + t.Fatal(err) + } + defer lis.Close() + addr := "unix://" + lis.Addr().String() + srv := grpc.NewServer() + // Having a token removes the need to ever call Authenticate on the + // server. If that happens then this will cause a connection failure. + etcdserverpb.RegisterAuthServer(srv, mockFailingAuthServer{}) + go srv.Serve(lis) + defer srv.Stop() + + c, err := NewClient(t, Config{ + DialTimeout: 5 * time.Second, + Endpoints: []string{addr}, + Token: "foo", + }) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + meta, err := c.authTokenBundle.PerRPCCredentials().GetRequestMetadata(context.Background(), "") + if err != nil { + t.Errorf("Error building request metadata: %s", err) + } + + if tok, ok := meta[rpctypes.TokenFieldNameGRPC]; !ok { + t.Error("Token was not successfuly set in the auth bundle") + } else if tok != "foo" { + t.Errorf("Incorrect token set in auth bundle, got '%s', expected 'foo'", tok) + } +} + +func TestNewOnlyJWTExclusivity(t *testing.T) { + testutil.BeforeTest(t) + + // Create a mock AuthServer to handle Authenticate RPCs. + lis, err := net.Listen("unix", "etcd-auth-test:1") + if err != nil { + t.Fatal(err) + } + defer lis.Close() + addr := "unix://" + lis.Addr().String() + srv := grpc.NewServer() + // Having a token removes the need to ever call Authenticate on the + // server. If that happens then this will cause a connection failure. + etcdserverpb.RegisterAuthServer(srv, mockFailingAuthServer{}) + go srv.Serve(lis) + defer srv.Stop() + + _, err = NewClient(t, Config{ + DialTimeout: 5 * time.Second, + Endpoints: []string{addr}, + Token: "foo", + Username: "user", + Password: "pass", + }) + require.ErrorIs(t, ErrMutuallyExclusiveCfg, err) +} + func TestSyncFiltersMembers(t *testing.T) { c, _ := NewClient(t, Config{Endpoints: []string{"http://254.0.0.1:12345"}}) defer c.Close() @@ -327,7 +396,7 @@ func TestSyncFiltersMembers(t *testing.T) { {ID: 2, Name: "isStartedAndNotLearner", ClientURLs: []string{"http://254.0.0.3:12345"}, IsLearner: false}, }, } - c.Sync(context.Background()) + c.Sync(t.Context()) endpoints := c.Endpoints() if len(endpoints) != 1 || endpoints[0] != "http://254.0.0.3:12345" { @@ -421,7 +490,7 @@ func TestClientRejectOldCluster(t *testing.T) { endpointToVersion[tt.endpoints[j]] = tt.versions[j] } c := &Client{ - ctx: context.Background(), + ctx: t.Context(), endpoints: tt.endpoints, epMu: new(sync.RWMutex), Maintenance: &mockMaintenance{ @@ -476,6 +545,14 @@ func (mm mockMaintenance) Downgrade(ctx context.Context, action DowngradeAction, return nil, nil } +type mockFailingAuthServer struct { + *etcdserverpb.UnimplementedAuthServer +} + +func (mockFailingAuthServer) Authenticate(context.Context, *etcdserverpb.AuthenticateRequest) (*etcdserverpb.AuthenticateResponse, error) { + return nil, errors.New("this auth server always fails") +} + type mockAuthServer struct { *etcdserverpb.UnimplementedAuthServer } diff --git a/client/v3/config.go b/client/v3/config.go index 8351828d2f90..61d093760749 100644 --- a/client/v3/config.go +++ b/client/v3/config.go @@ -66,6 +66,9 @@ type Config struct { // Password is a password for authentication. Password string `json:"password"` + // Token is a JWT used for authentication instead of a password. + Token string `json:"token"` + // RejectOldCluster when set will refuse to create a client against an outdated cluster. RejectOldCluster bool `json:"reject-old-cluster"` @@ -130,6 +133,7 @@ type SecureConfig struct { type AuthConfig struct { Username string `json:"username"` Password string `json:"password"` + Token string `json:"token"` } func (cs *ConfigSpec) Clone() *ConfigSpec { @@ -157,7 +161,7 @@ func (cs *ConfigSpec) Clone() *ConfigSpec { } func (cfg AuthConfig) Empty() bool { - return cfg.Username == "" && cfg.Password == "" + return cfg.Username == "" && cfg.Password == "" && cfg.Token == "" } // NewClientConfig creates a Config based on the provided ConfigSpec. @@ -180,6 +184,7 @@ func NewClientConfig(confSpec *ConfigSpec, lg *zap.Logger) (*Config, error) { if confSpec.Auth != nil { cfg.Username = confSpec.Auth.Username cfg.Password = confSpec.Auth.Password + cfg.Token = confSpec.Auth.Token } return cfg, nil diff --git a/client/v3/config_test.go b/client/v3/config_test.go index 1fe2fb2d391e..eb3c8bcaacd0 100644 --- a/client/v3/config_test.go +++ b/client/v3/config_test.go @@ -71,6 +71,25 @@ func TestNewClientConfig(t *testing.T) { Password: "changeme", }, }, + { + name: "JWT specified", + spec: ConfigSpec{ + Endpoints: []string{"http://192.168.0.12:2379"}, + DialTimeout: 1 * time.Second, + KeepAliveTime: 4 * time.Second, + KeepAliveTimeout: 6 * time.Second, + Auth: &AuthConfig{ + Token: "test", + }, + }, + expectedConf: Config{ + Endpoints: []string{"http://192.168.0.12:2379"}, + DialTimeout: 1 * time.Second, + DialKeepAliveTime: 4 * time.Second, + DialKeepAliveTimeout: 6 * time.Second, + Token: "test", + }, + }, { name: "default secure transport", spec: ConfigSpec{ diff --git a/client/v3/credentials/credentials_test.go b/client/v3/credentials/credentials_test.go index 0db241e3c413..ca2bd0cbf1d1 100644 --- a/client/v3/credentials/credentials_test.go +++ b/client/v3/credentials/credentials_test.go @@ -15,7 +15,6 @@ package credentials import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -25,7 +24,7 @@ import ( func TestUpdateAuthToken(t *testing.T) { bundle := NewPerRPCCredentialBundle() - ctx := context.TODO() + ctx := t.Context() metadataBeforeUpdate, _ := bundle.PerRPCCredentials().GetRequestMetadata(ctx) assert.Empty(t, metadataBeforeUpdate) diff --git a/client/v3/ctx_test.go b/client/v3/ctx_test.go index 2df734e4ea2d..ea0870236481 100644 --- a/client/v3/ctx_test.go +++ b/client/v3/ctx_test.go @@ -15,7 +15,6 @@ package clientv3 import ( - "context" "reflect" "testing" @@ -27,7 +26,7 @@ import ( ) func TestMetadataWithRequireLeader(t *testing.T) { - ctx := context.TODO() + ctx := t.Context() _, ok := metadata.FromOutgoingContext(ctx) require.Falsef(t, ok, "expected no outgoing metadata ctx key") @@ -48,7 +47,7 @@ func TestMetadataWithRequireLeader(t *testing.T) { } func TestMetadataWithClientAPIVersion(t *testing.T) { - ctx := withVersion(WithRequireLeader(context.TODO())) + ctx := withVersion(WithRequireLeader(t.Context())) md, ok := metadata.FromOutgoingContext(ctx) require.Truef(t, ok, "expected outgoing metadata ctx key") diff --git a/client/v3/go.mod b/client/v3/go.mod index 4e86a11081b5..e3ff6912919a 100644 --- a/client/v3/go.mod +++ b/client/v3/go.mod @@ -1,19 +1,19 @@ module go.etcd.io/etcd/client/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/coreos/go-semver v0.3.1 github.com/dustin/go-humanize v1.0.1 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.70.0 + google.golang.org/grpc v1.72.2 sigs.k8s.io/yaml v1.4.0 ) @@ -21,26 +21,25 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/protobuf v1.36.4 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/client/v3/go.sum b/client/v3/go.sum index 82fce51ce70c..b27f9d5ef829 100644 --- a/client/v3/go.sum +++ b/client/v3/go.sum @@ -6,8 +6,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -20,20 +20,20 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -42,34 +42,34 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -85,20 +85,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -107,14 +107,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/client/v3/internal/resolver/resolver.go b/client/v3/internal/resolver/resolver.go index b5c9de00786e..c7f9fb1aee29 100644 --- a/client/v3/internal/resolver/resolver.go +++ b/client/v3/internal/resolver/resolver.go @@ -60,16 +60,28 @@ func (r *EtcdManualResolver) SetEndpoints(endpoints []string) { } func (r EtcdManualResolver) updateState() { - if r.CC != nil { - addresses := make([]resolver.Address, len(r.endpoints)) + if getCC(r) != nil { + eps := make([]resolver.Endpoint, len(r.endpoints)) for i, ep := range r.endpoints { addr, serverName := endpoint.Interpret(ep) - addresses[i] = resolver.Address{Addr: addr, ServerName: serverName} + eps[i] = resolver.Endpoint{Addresses: []resolver.Address{ + {Addr: addr, ServerName: serverName}, + }} } state := resolver.State{ - Addresses: addresses, + Endpoints: eps, ServiceConfig: r.serviceConfig, } r.UpdateState(state) } } + +func getCC(r EtcdManualResolver) (cc resolver.ClientConn) { + defer func() { + if rec := recover(); rec != nil { + cc = nil + } + }() + + return r.CC() +} diff --git a/client/v3/kv.go b/client/v3/kv.go index 8d0c595d1e43..6b94b8196015 100644 --- a/client/v3/kv.go +++ b/client/v3/kv.go @@ -134,7 +134,7 @@ func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*C if err != nil { return nil, ContextError(ctx, err) } - return (*CompactResponse)(resp), err + return (*CompactResponse)(resp), nil } func (kv *kv) Txn(ctx context.Context) Txn { diff --git a/client/v3/naming/endpoints/endpoints.go b/client/v3/naming/endpoints/endpoints.go index bcc96b14f165..322b6c2860b1 100644 --- a/client/v3/naming/endpoints/endpoints.go +++ b/client/v3/naming/endpoints/endpoints.go @@ -32,6 +32,8 @@ type Endpoint struct { // Metadata is the information associated with Addr, which may be used // to make load balancing decision. // Since etcd 3.1 + // + // Deprecated: The field is deprecated and will be removed in 3.7. Metadata any } diff --git a/client/v3/naming/resolver/resolver.go b/client/v3/naming/resolver/resolver.go index f42b2b0b6e74..f8629aaaa2f0 100644 --- a/client/v3/naming/resolver/resolver.go +++ b/client/v3/naming/resolver/resolver.go @@ -100,22 +100,26 @@ func (r *resolver) watch() { } } - addrs := convertToGRPCAddress(allUps) - r.cc.UpdateState(gresolver.State{Addresses: addrs}) + eps := convertToGRPCEndpoint(allUps) + r.cc.UpdateState(gresolver.State{Endpoints: eps}) } } } -func convertToGRPCAddress(ups map[string]*endpoints.Update) []gresolver.Address { - var addrs []gresolver.Address +func convertToGRPCEndpoint(ups map[string]*endpoints.Update) []gresolver.Endpoint { + var eps []gresolver.Endpoint for _, up := range ups { - addr := gresolver.Address{ - Addr: up.Endpoint.Addr, - Metadata: up.Endpoint.Metadata, + ep := gresolver.Endpoint{ + Addresses: []gresolver.Address{ + { + Addr: up.Endpoint.Addr, + Metadata: up.Endpoint.Metadata, + }, + }, } - addrs = append(addrs, addr) + eps = append(eps, ep) } - return addrs + return eps } // ResolveNow is a no-op here. diff --git a/client/v3/ordering/kv_test.go b/client/v3/ordering/kv_test.go index cb6d0d3909e4..679d74771ad0 100644 --- a/client/v3/ordering/kv_test.go +++ b/client/v3/ordering/kv_test.go @@ -76,7 +76,7 @@ func TestKvOrdering(t *testing.T) { tt.prevRev, sync.RWMutex{}, } - res, err := kv.Get(context.TODO(), "mockKey") + res, err := kv.Get(t.Context(), "mockKey") if err != nil { t.Errorf("#%d: expected response %+v, got error %+v", i, tt.response, err) } @@ -131,9 +131,9 @@ func TestTxnOrdering(t *testing.T) { sync.RWMutex{}, } txn := &txnOrdering{ - kv.Txn(context.Background()), + kv.Txn(t.Context()), kv, - context.Background(), + t.Context(), sync.Mutex{}, []clientv3.Cmp{}, []clientv3.Op{}, diff --git a/client/v3/retry_interceptor.go b/client/v3/retry_interceptor.go index 2b9301a5808d..7703e673b061 100644 --- a/client/v3/retry_interceptor.go +++ b/client/v3/retry_interceptor.go @@ -351,11 +351,11 @@ func isContextError(err error) bool { func contextErrToGRPCErr(err error) error { switch { case errors.Is(err, context.DeadlineExceeded): - return status.Errorf(codes.DeadlineExceeded, err.Error()) + return status.Error(codes.DeadlineExceeded, err.Error()) case errors.Is(err, context.Canceled): - return status.Errorf(codes.Canceled, err.Error()) + return status.Error(codes.Canceled, err.Error()) default: - return status.Errorf(codes.Unknown, err.Error()) + return status.Error(codes.Unknown, err.Error()) } } diff --git a/client/v3/txn_test.go b/client/v3/txn_test.go index de7aec3b8e98..60687219d600 100644 --- a/client/v3/txn_test.go +++ b/client/v3/txn_test.go @@ -15,7 +15,6 @@ package clientv3 import ( - "context" "testing" "time" @@ -44,7 +43,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).If(cmp).If(cmp) + kv.Txn(t.Context()).If(cmp).If(cmp) }, err: "cannot call If twice!", @@ -52,7 +51,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).Then(op).If(cmp) + kv.Txn(t.Context()).Then(op).If(cmp) }, err: "cannot call If after Then!", @@ -60,7 +59,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).Else(op).If(cmp) + kv.Txn(t.Context()).Else(op).If(cmp) }, err: "cannot call If after Else!", @@ -68,7 +67,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).Then(op).Then(op) + kv.Txn(t.Context()).Then(op).Then(op) }, err: "cannot call Then twice!", @@ -76,7 +75,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).Else(op).Then(op) + kv.Txn(t.Context()).Else(op).Then(op) }, err: "cannot call Then after Else!", @@ -84,7 +83,7 @@ func TestTxnPanics(t *testing.T) { { f: func(errc chan string) { defer df(errc) - kv.Txn(context.TODO()).Else(op).Else(op) + kv.Txn(t.Context()).Else(op).Else(op) }, err: "cannot call Else twice!", diff --git a/client/v3/watch_test.go b/client/v3/watch_test.go index 721fc4a8a237..ca0b86d26107 100644 --- a/client/v3/watch_test.go +++ b/client/v3/watch_test.go @@ -72,7 +72,7 @@ func TestStreamKeyFromCtx(t *testing.T) { }{ { name: "multiple keys", - ctx: metadata.NewOutgoingContext(context.Background(), metadata.MD{ + ctx: metadata.NewOutgoingContext(t.Context(), metadata.MD{ "key1": []string{"value1"}, "key2": []string{"value2a", "value2b"}, }), @@ -80,19 +80,19 @@ func TestStreamKeyFromCtx(t *testing.T) { }, { name: "no keys", - ctx: metadata.NewOutgoingContext(context.Background(), metadata.MD{}), + ctx: metadata.NewOutgoingContext(t.Context(), metadata.MD{}), expected: "map[]", }, { name: "only one key", - ctx: metadata.NewOutgoingContext(context.Background(), metadata.MD{ + ctx: metadata.NewOutgoingContext(t.Context(), metadata.MD{ "key1": []string{"value1", "value1a"}, }), expected: "map[key1:[value1 value1a]]", }, { name: "no metadata", - ctx: context.Background(), + ctx: t.Context(), expected: "", }, } diff --git a/client/v3/yaml/config_test.go b/client/v3/yaml/config_test.go index 4d23f27494e1..f4c68c999dfe 100644 --- a/client/v3/yaml/config_test.go +++ b/client/v3/yaml/config_test.go @@ -73,7 +73,7 @@ func TestConfigFromFile(t *testing.T) { } for i, tt := range tests { - tmpfile, err := os.CreateTemp("", "clientcfg") + tmpfile, err := os.CreateTemp(t.TempDir(), "clientcfg") if err != nil { log.Fatal(err) } diff --git a/etcdctl/ctlv3/command/global.go b/etcdctl/ctlv3/command/global.go index 019b638d3940..602f2776d2d0 100644 --- a/etcdctl/ctlv3/command/global.go +++ b/etcdctl/ctlv3/command/global.go @@ -58,6 +58,7 @@ type GlobalFlags struct { User string Password string + Token string Debug bool } @@ -290,13 +291,22 @@ func authCfgFromCmd(cmd *cobra.Command) *clientv3.AuthConfig { if err != nil { cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) } + tokenFlag, err := cmd.Flags().GetString("auth-jwt-token") + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitBadArgs, err) + } - if userFlag == "" { + if userFlag == "" && tokenFlag == "" { return nil } var cfg clientv3.AuthConfig + if tokenFlag != "" { + cfg.Token = tokenFlag + return &cfg + } + if passwordFlag == "" { splitted := strings.SplitN(userFlag, ":", 2) if len(splitted) < 2 { diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index 90a76ebfc31b..12460259eb33 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -17,6 +17,7 @@ package command import ( "errors" "fmt" + "strconv" "strings" "github.com/dustin/go-humanize" @@ -220,7 +221,7 @@ func makeEndpointHealthTable(healthList []epHealth) (hdr []string, rows [][]stri func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]string) { hdr = []string{ "endpoint", "ID", "version", "storage version", "db size", "in use", "percentage not in use", "quota", "is leader", "is learner", "raft term", - "raft index", "raft applied index", "errors", + "raft index", "raft applied index", "errors", "downgrade target version", "downgrade enabled", } for _, status := range statusList { rows = append(rows, []string{ @@ -238,6 +239,8 @@ func makeEndpointStatusTable(statusList []epStatus) (hdr []string, rows [][]stri fmt.Sprint(status.Resp.RaftIndex), fmt.Sprint(status.Resp.RaftAppliedIndex), fmt.Sprint(strings.Join(status.Resp.Errors, ", ")), + status.Resp.DowngradeInfo.GetTargetVersion(), + strconv.FormatBool(status.Resp.DowngradeInfo.GetEnabled()), }) } return hdr, rows diff --git a/etcdctl/ctlv3/command/printer_fields.go b/etcdctl/ctlv3/command/printer_fields.go index 86f1c70d71e7..1ca4c876e04a 100644 --- a/etcdctl/ctlv3/command/printer_fields.go +++ b/etcdctl/ctlv3/command/printer_fields.go @@ -203,6 +203,8 @@ func (p *fieldsPrinter) EndpointStatus(eps []epStatus) { fmt.Println(`"RaftAppliedIndex" :`, ep.Resp.RaftAppliedIndex) fmt.Println(`"Errors" :`, ep.Resp.Errors) fmt.Printf("\"Endpoint\" : %q\n", ep.Ep) + fmt.Printf("\"DowngradeTargetVersion\" : %q\n", ep.Resp.DowngradeInfo.GetTargetVersion()) + fmt.Println(`"DowngradeEnabled" :`, ep.Resp.DowngradeInfo.GetEnabled()) fmt.Println() } } diff --git a/etcdctl/ctlv3/command/printer_table.go b/etcdctl/ctlv3/command/printer_table.go index c576231ed5b0..fcab69fb0639 100644 --- a/etcdctl/ctlv3/command/printer_table.go +++ b/etcdctl/ctlv3/command/printer_table.go @@ -18,6 +18,7 @@ import ( "os" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" v3 "go.etcd.io/etcd/client/v3" ) @@ -26,44 +27,44 @@ type tablePrinter struct{ printer } func (tp *tablePrinter) MemberList(r v3.MemberListResponse) { hdr, rows := makeMemberListTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) EndpointHealth(r []epHealth) { hdr, rows := makeEndpointHealthTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) EndpointStatus(r []epStatus) { hdr, rows := makeEndpointStatusTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) EndpointHashKV(r []epHashKV) { hdr, rows := makeEndpointHashKVTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index 6686940cec7c..e992fa4fa0c8 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -23,6 +23,7 @@ import ( "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/etcdctl/v3/ctlv3/command" + "go.etcd.io/etcd/etcdctl/v3/util" "go.etcd.io/etcd/pkg/v3/cobrautl" ) @@ -69,6 +70,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.TrustedCAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle") + rootCmd.PersistentFlags().StringVar(&globalFlags.Token, "auth-jwt-token", "", "JWT token used for authentication (if this option is used, --user and --password should not be set)") rootCmd.PersistentFlags().StringVar(&globalFlags.User, "user", "", "username[:password] for authentication (prompt if password is not supplied)") rootCmd.PersistentFlags().StringVar(&globalFlags.Password, "password", "", "password for authentication (if this option is used, --user option shouldn't include password)") rootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints") @@ -102,7 +104,7 @@ func init() { } func usageFunc(c *cobra.Command) error { - return cobrautl.UsageFunc(c, version.Version, version.APIVersion) + return util.UsageFunc(c, version.Version, version.APIVersion) } func Start() error { diff --git a/etcdctl/go.mod b/etcdctl/go.mod index 4e2c3f4a41c8..d91f852c4862 100644 --- a/etcdctl/go.mod +++ b/etcdctl/go.mod @@ -1,15 +1,15 @@ module go.etcd.io/etcd/etcdctl/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/bgentry/speakeasy v0.2.0 - github.com/cheggaaa/pb/v3 v3.1.6 + github.com/cheggaaa/pb/v3 v3.1.7 github.com/dustin/go-humanize v1.0.1 - github.com/olekukonko/tablewriter v0.0.5 - github.com/spf13/cobra v1.8.1 + github.com/olekukonko/tablewriter v1.0.6 + github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 @@ -17,33 +17,34 @@ require ( go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 go.uber.org/zap v1.27.0 - golang.org/x/time v0.9.0 - google.golang.org/grpc v1.70.0 + golang.org/x/time v0.11.0 + google.golang.org/grpc v1.72.2 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/protobuf v1.36.4 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/etcdctl/go.sum b/etcdctl/go.sum index 2bf1d2bfdef4..8df757018e3f 100644 --- a/etcdctl/go.sum +++ b/etcdctl/go.sum @@ -6,15 +6,15 @@ github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE5 github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.1.6 h1:h0x+vd7EiUohAJ29DJtJy+SNAc55t/elW3jCD086EXk= -github.com/cheggaaa/pb/v3 v3.1.6/go.mod h1:urxmfVtaxT+9aWk92DbsvXFZtNSWQSO5TRAp+MJ3l1s= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -28,22 +28,20 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -52,32 +50,34 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= +github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -86,16 +86,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -111,8 +111,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -120,14 +120,14 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -136,14 +136,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/etcdctl/util/help.go b/etcdctl/util/help.go new file mode 100644 index 000000000000..0e8035313571 --- /dev/null +++ b/etcdctl/util/help.go @@ -0,0 +1,180 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +// copied from https://github.com/rkt/rkt/blob/master/rkt/help.go + +package util + +import ( + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + "text/template" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + commandUsageTemplate *template.Template + templFuncs = template.FuncMap{ + "descToLines": func(s string) []string { + // trim leading/trailing whitespace and split into slice of lines + return strings.Split(strings.Trim(s, "\n\t "), "\n") + }, + "cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string { + parts := []string{cmd.Name()} + for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() { + cmd = cmd.Parent() + parts = append([]string{cmd.Name()}, parts...) + } + return strings.Join(parts, " ") + }, + "indent": func(s string) string { + pad := strings.Repeat(" ", 2) + return pad + strings.Replace(s, "\n", "\n"+pad, -1) + }, + } +) + +func init() { + commandUsage := ` +{{ $cmd := .Cmd }}\ +{{ $cmdname := cmdName .Cmd .Cmd.Root }}\ +NAME: +{{if not .Cmd.HasParent}}\ +{{printf "%s - %s" .Cmd.Name .Cmd.Short | indent}} +{{else}}\ +{{printf "%s - %s" $cmdname .Cmd.Short | indent}} +{{end}}\ + +USAGE: +{{printf "%s" .Cmd.UseLine | indent}} +{{ if not .Cmd.HasParent }}\ + +VERSION: +{{printf "%s" .Version | indent}} +{{end}}\ +{{if .Cmd.HasSubCommands}}\ + +API VERSION: +{{.APIVersion | indent}} +{{end}}\ +{{if .Cmd.HasExample}}\ + +Examples: +{{.Cmd.Example}} +{{end}}\ +{{if .Cmd.HasSubCommands}}\ + +COMMANDS: +{{range .SubCommands}}\ +{{ $cmdname := cmdName . $cmd }}\ +{{ if .Runnable }}\ +{{printf "%s\t%s" $cmdname .Short | indent}} +{{end}}\ +{{end}}\ +{{end}}\ +{{ if .Cmd.Long }}\ + +DESCRIPTION: +{{range $line := descToLines .Cmd.Long}}{{printf "%s" $line | indent}} +{{end}}\ +{{end}}\ +{{if .Cmd.HasLocalFlags}}\ + +OPTIONS: +{{.LocalFlags}}\ +{{end}}\ +{{if .Cmd.HasInheritedFlags}}\ + +GLOBAL OPTIONS: +{{.GlobalFlags}}\ +{{end}} +`[1:] + + commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.ReplaceAll(commandUsage, "\\\n", ""))) +} + +func etcdFlagUsages(flagSet *pflag.FlagSet) string { + x := new(strings.Builder) + + flagSet.VisitAll(func(flag *pflag.Flag) { + if len(flag.Deprecated) > 0 { + return + } + var format string + if len(flag.Shorthand) > 0 { + format = " -%s, --%s" + } else { + format = " %s --%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "[" + } + if flag.Value.Type() == "string" { + // put quotes on the value + format = format + "=%q" + } else { + format = format + "=%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "]" + } + format = format + "\t%s\n" + shorthand := flag.Shorthand + fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage) + }) + + return x.String() +} + +func getSubCommands(cmd *cobra.Command) []*cobra.Command { + var subCommands []*cobra.Command + for _, subCmd := range cmd.Commands() { + subCommands = append(subCommands, subCmd) + subCommands = append(subCommands, getSubCommands(subCmd)...) + } + return subCommands +} + +func UsageFunc(cmd *cobra.Command, version, APIVersion string) error { + subCommands := getSubCommands(cmd) + tabOut := getTabOutWithWriter(os.Stdout) + commandUsageTemplate.Execute(tabOut, struct { + Cmd *cobra.Command + LocalFlags string + GlobalFlags string + SubCommands []*cobra.Command + Version string + APIVersion string + }{ + cmd, + etcdFlagUsages(cmd.LocalFlags()), + etcdFlagUsages(cmd.InheritedFlags()), + subCommands, + version, + APIVersion, + }) + tabOut.Flush() + return nil +} + +func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer { + aTabOut := new(tabwriter.Writer) + aTabOut.Init(writer, 0, 8, 1, '\t', 0) + return aTabOut +} diff --git a/etcdutl/etcdutl/migrate_command.go b/etcdutl/etcdutl/migrate_command.go index 0a114268524f..a7f3d849f168 100644 --- a/etcdutl/etcdutl/migrate_command.go +++ b/etcdutl/etcdutl/migrate_command.go @@ -97,7 +97,7 @@ func (o *migrateOptions) Config() (*migrateConfig, error) { type migrateConfig struct { lg *zap.Logger targetVersion *semver.Version - walVersion schema.WALVersion + walVersion wal.Version dataDir string force bool } diff --git a/etcdutl/etcdutl/printer_table.go b/etcdutl/etcdutl/printer_table.go index ec66ea38f767..8558d5a0bea2 100644 --- a/etcdutl/etcdutl/printer_table.go +++ b/etcdutl/etcdutl/printer_table.go @@ -18,6 +18,7 @@ import ( "os" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" "go.etcd.io/etcd/etcdutl/v3/snapshot" ) @@ -26,22 +27,22 @@ type tablePrinter struct{ printer } func (tp *tablePrinter) DBStatus(r snapshot.Status) { hdr, rows := makeDBStatusTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } func (tp *tablePrinter) DBHashKV(r HashKV) { hdr, rows := makeDBHashKVTable(r) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader(hdr) + cfgBuilder := tablewriter.NewConfigBuilder().WithRowAlignment(tw.AlignRight) + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header(hdr) for _, row := range rows { table.Append(row) } - table.SetAlignment(tablewriter.ALIGN_RIGHT) table.Render() } diff --git a/etcdutl/go.mod b/etcdutl/go.mod index a6579c1c962b..6b3b8c592eb7 100644 --- a/etcdutl/go.mod +++ b/etcdutl/go.mod @@ -1,8 +1,8 @@ module go.etcd.io/etcd/etcdutl/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 replace ( go.etcd.io/etcd/api/v3 => ../api @@ -24,8 +24,8 @@ replace ( require ( github.com/coreos/go-semver v0.3.1 github.com/dustin/go-humanize v1.0.1 - github.com/olekukonko/tablewriter v0.0.5 - github.com/spf13/cobra v1.8.1 + github.com/olekukonko/tablewriter v1.0.6 + github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 go.etcd.io/bbolt v1.4.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 @@ -39,31 +39,35 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fatih/color v1.18.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -73,26 +77,26 @@ require ( github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.9.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.70.0 // indirect - google.golang.org/protobuf v1.36.4 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/grpc v1.72.2 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/etcdutl/go.sum b/etcdutl/go.sum index 9aebbd0c72a9..a59cf56a8384 100644 --- a/etcdutl/go.sum +++ b/etcdutl/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= @@ -10,12 +10,15 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -24,69 +27,76 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= +github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -105,24 +115,24 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -132,8 +142,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -141,25 +151,26 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -168,14 +179,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -184,7 +195,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/etcdutl/snapshot/v3_snapshot_test.go b/etcdutl/snapshot/v3_snapshot_test.go index 1971eefe2fc2..0fcc3540bd70 100644 --- a/etcdutl/snapshot/v3_snapshot_test.go +++ b/etcdutl/snapshot/v3_snapshot_test.go @@ -15,7 +15,6 @@ package snapshot import ( - "context" "errors" "path/filepath" "strconv" @@ -44,7 +43,7 @@ func TestSnapshotStatus(t *testing.T) { status, err := NewV3(zap.NewNop()).Status(dbpath) require.NoError(t, err) - assert.Equal(t, uint32(0x62132b4d), status.Hash) + assert.Equal(t, uint32(0xe7a6e44b), status.Hash) assert.Equal(t, int64(11), status.Revision) } @@ -136,7 +135,7 @@ func TestSnapshotStatusTotalKey(t *testing.T) { Key: []byte(key), Value: val, } - _, err := srv.Put(context.TODO(), &req) + _, err := srv.Put(t.Context(), &req) require.NoError(t, err) } } @@ -150,10 +149,10 @@ func TestSnapshotStatusTotalKey(t *testing.T) { key := []byte("key1") for i := 0; i < 3; i++ { if i < 2 { - _, err := srv.Put(context.TODO(), &etcdserverpb.PutRequest{Key: key, Value: []byte(strconv.Itoa(i))}) + _, err := srv.Put(t.Context(), &etcdserverpb.PutRequest{Key: key, Value: []byte(strconv.Itoa(i))}) require.NoError(t, err) } else { - _, err := srv.DeleteRange(context.TODO(), &etcdserverpb.DeleteRangeRequest{Key: key}) + _, err := srv.DeleteRange(t.Context(), &etcdserverpb.DeleteRangeRequest{Key: key}) require.NoError(t, err) } } @@ -166,9 +165,9 @@ func TestSnapshotStatusTotalKey(t *testing.T) { // key1: create -> delete -> re-create -> delete key := []byte("key1") for i := 0; i < 2; i++ { - _, err := srv.Put(context.TODO(), &etcdserverpb.PutRequest{Key: key, Value: make([]byte, 1)}) + _, err := srv.Put(t.Context(), &etcdserverpb.PutRequest{Key: key, Value: make([]byte, 1)}) require.NoError(t, err) - _, err = srv.DeleteRange(context.TODO(), &etcdserverpb.DeleteRangeRequest{Key: key}) + _, err = srv.DeleteRange(t.Context(), &etcdserverpb.DeleteRangeRequest{Key: key}) require.NoError(t, err) } }, @@ -181,10 +180,10 @@ func TestSnapshotStatusTotalKey(t *testing.T) { key := []byte("key1") for i := 0; i < 5; i++ { if i%2 == 0 { - _, err := srv.Put(context.TODO(), &etcdserverpb.PutRequest{Key: key, Value: make([]byte, 1)}) + _, err := srv.Put(t.Context(), &etcdserverpb.PutRequest{Key: key, Value: make([]byte, 1)}) require.NoError(t, err) } else { - _, err := srv.DeleteRange(context.TODO(), &etcdserverpb.DeleteRangeRequest{Key: key}) + _, err := srv.DeleteRange(t.Context(), &etcdserverpb.DeleteRangeRequest{Key: key}) require.NoError(t, err) } } @@ -195,11 +194,11 @@ func TestSnapshotStatusTotalKey(t *testing.T) { name: "mixed deletions", prepare: func(srv *etcdserver.EtcdServer) { // Put("key1") -> Put("key2")-> Delete("key1") - _, err := srv.Put(context.TODO(), &etcdserverpb.PutRequest{Key: []byte("key1"), Value: make([]byte, 1)}) + _, err := srv.Put(t.Context(), &etcdserverpb.PutRequest{Key: []byte("key1"), Value: make([]byte, 1)}) require.NoError(t, err) - _, err = srv.Put(context.TODO(), &etcdserverpb.PutRequest{Key: []byte("key2"), Value: make([]byte, 1)}) + _, err = srv.Put(t.Context(), &etcdserverpb.PutRequest{Key: []byte("key2"), Value: make([]byte, 1)}) require.NoError(t, err) - _, err = srv.DeleteRange(context.TODO(), &etcdserverpb.DeleteRangeRequest{Key: []byte("key1")}) + _, err = srv.DeleteRange(t.Context(), &etcdserverpb.DeleteRangeRequest{Key: []byte("key1")}) require.NoError(t, err) }, expected: 1, @@ -228,7 +227,7 @@ func insertKeys(t *testing.T, numKeys, valueSize int) func(*etcdserver.EtcdServe Key: []byte(strconv.Itoa(i)), Value: val, } - _, err := srv.Put(context.TODO(), &req) + _, err := srv.Put(t.Context(), &req) require.NoError(t, err) } } diff --git a/go.mod b/go.mod index d3479a985c45..37f5b62949bd 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module go.etcd.io/etcd/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 replace ( go.etcd.io/etcd/api/v3 => ./api @@ -18,10 +18,10 @@ replace ( require ( github.com/bgentry/speakeasy v0.2.0 - github.com/cheggaaa/pb/v3 v3.1.6 + github.com/cheggaaa/pb/v3 v3.1.7 github.com/coreos/go-semver v0.3.1 github.com/dustin/go-humanize v1.0.1 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 go.etcd.io/bbolt v1.4.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 @@ -35,45 +35,46 @@ require ( go.etcd.io/etcd/tests/v3 v3.0.0-00010101000000-000000000000 go.etcd.io/raft/v3 v3.6.0 go.uber.org/zap v1.27.0 - golang.org/x/time v0.9.0 - google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.4 + golang.org/x/time v0.11.0 + google.golang.org/grpc v1.72.2 + google.golang.org/protobuf v1.36.6 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect + github.com/olekukonko/tablewriter v1.0.6 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -83,23 +84,23 @@ require ( github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/gofail v0.2.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2a1f38eaf378..5601a88de705 100644 --- a/go.sum +++ b/go.sum @@ -6,13 +6,13 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.1.6 h1:h0x+vd7EiUohAJ29DJtJy+SNAc55t/elW3jCD086EXk= -github.com/cheggaaa/pb/v3 v3.1.6/go.mod h1:urxmfVtaxT+9aWk92DbsvXFZtNSWQSO5TRAp+MJ3l1s= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= @@ -21,10 +21,11 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -44,11 +45,11 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -59,28 +60,28 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -93,40 +94,43 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= +github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -150,24 +154,24 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -180,8 +184,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -197,16 +201,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -214,14 +218,14 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -239,19 +243,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -263,7 +267,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/adt/interval_tree.go b/pkg/adt/interval_tree.go index 3c1c3ea8322a..85542020f592 100644 --- a/pkg/adt/interval_tree.go +++ b/pkg/adt/interval_tree.go @@ -434,46 +434,27 @@ func (ivt *intervalTree) createIntervalNode(ivl Interval, val any) *intervalNode } } -// TODO: make this consistent with textbook implementation -// -// "Introduction to Algorithms" (Cormen et al, 3rd ed.), chapter 13.3, p315 -// -// RB-INSERT(T, z) -// -// y = T.nil -// x = T.root -// -// while x ≠ T.nil -// y = x -// if z.key < x.key -// x = x.left -// else -// x = x.right -// -// z.p = y -// -// if y == T.nil -// T.root = z -// else if z.key < y.key -// y.left = z -// else -// y.right = z -// -// z.left = T.nil -// z.right = T.nil -// z.color = RED -// -// RB-INSERT-FIXUP(T, z) - // Insert adds a node with the given interval into the tree. +// +// Cormen "Introduction to Algorithms", Chapter 14 Exercise 14.3.5. +// The algorithm follows Cormen "Introduction to Algorithms", Chapter 14 Exercise 14.3.5. +// for modifying an interval tree structure to support exact interval matching. func (ivt *intervalTree) Insert(ivl Interval, val any) { y := ivt.sentinel z := ivt.createIntervalNode(ivl, val) x := ivt.root for x != ivt.sentinel { y = x - if z.iv.Ivl.Begin.Compare(x.iv.Ivl.Begin) < 0 { + // Split on left endpoint. If left endpoints match, instead split on right endpoint. + beginCompare := z.iv.Ivl.Begin.Compare(x.iv.Ivl.Begin) + if beginCompare < 0 { x = x.left + } else if beginCompare == 0 { + if z.iv.Ivl.End.Compare(x.iv.Ivl.End) < 0 { + x = x.left + } else { + x = x.right + } } else { x = x.right } @@ -483,8 +464,15 @@ func (ivt *intervalTree) Insert(ivl Interval, val any) { if y == ivt.sentinel { ivt.root = z } else { - if z.iv.Ivl.Begin.Compare(y.iv.Ivl.Begin) < 0 { + beginCompare := z.iv.Ivl.Begin.Compare(y.iv.Ivl.Begin) + if beginCompare < 0 { y.left = z + } else if beginCompare == 0 { + if z.iv.Ivl.End.Compare(y.iv.Ivl.End) < 0 { + y.left = z + } else { + y.right = z + } } else { y.right = z } @@ -699,18 +687,34 @@ func (ivt *intervalTree) Visit(ivl Interval, ivv IntervalVisitor) { ivt.root.visit(&ivl, ivt.sentinel, func(n *intervalNode) bool { return ivv(&n.iv) }) } -// find the exact node for a given interval +// find the exact node for a given interval. The implementation follows +// Cormen "Introduction to Algorithms", Chapter 14 Exercise 14.3.5. for +// exact interval matching. The search runs in O(log n) time on an n-node +// interval tree. func (ivt *intervalTree) find(ivl Interval) *intervalNode { - ret := ivt.sentinel - f := func(n *intervalNode) bool { - if n.iv.Ivl != ivl { - return true + x := ivt.root + // Search until hit sentinel or exact match. + for x != ivt.sentinel { + beginCompare := ivl.Begin.Compare(x.iv.Ivl.Begin) + endCompare := ivl.End.Compare(x.iv.Ivl.End) + if beginCompare == 0 && endCompare == 0 { + return x + } + // Split on left endpoint. If left endpoints match, + // instead split on right endpoints. + if beginCompare < 0 { + x = x.left + } else if beginCompare == 0 { + if endCompare < 0 { + x = x.left + } else { + x = x.right + } + } else { + x = x.right } - ret = n - return false } - ivt.root.visit(&ivl, ivt.sentinel, f) - return ret + return x } // Find gets the IntervalValue for the node matching the given interval diff --git a/pkg/adt/interval_tree_test.go b/pkg/adt/interval_tree_test.go index 8eb0246ad971..012e044e0e40 100644 --- a/pkg/adt/interval_tree_test.go +++ b/pkg/adt/interval_tree_test.go @@ -269,6 +269,36 @@ func TestIntervalTreeDelete(t *testing.T) { require.Truef(t, reflect.DeepEqual(expectedAfterDelete11, visitsAfterDelete11), "level order after deleting '11' expected %v, got %v", expectedAfterDelete11, visitsAfterDelete11) } +func TestIntervalTreeFind(t *testing.T) { + ivt := NewIntervalTree() + ivl1 := NewInt64Interval(3, 6) + val := 123 + assert.Nilf(t, ivt.Find(ivl1), "find for %v expected nil on empty tree", ivl1) + // insert interval [3, 6) into tree + ivt.Insert(ivl1, val) + // check cases of expected find matches and non-matches + assert.NotNilf(t, ivt.Find(ivl1), "find expected not-nil on exact-matched interval %v", ivl1) + assert.Equalf(t, ivl1, ivt.Find(ivl1).Ivl, "find expected to return exact-matched interval %v", ivl1) + ivl2 := NewInt64Interval(3, 7) + assert.Nilf(t, ivt.Find(ivl2), "find expected nil on matched start, different end %v", ivl2) + ivl3 := NewInt64Interval(2, 6) + assert.Nilf(t, ivt.Find(ivl3), "find expected nil on different start, matched end %v", ivl3) + ivl4 := NewInt64Interval(10, 20) + assert.Nilf(t, ivt.Find(ivl4), "find expected nil on different start, different end %v", ivl4) + // insert the additional intervals into the tree, and check they can each be found. + ivls := []Interval{ivl2, ivl3, ivl4} + for _, ivl := range ivls { + ivt.Insert(ivl, val) + assert.NotNilf(t, ivt.Find(ivl), "find expected not-nil on exact-matched interval %v", ivl) + assert.Equalf(t, ivl, ivt.Find(ivl).Ivl, "find expected to return exact-matched interval %v", ivl) + } + // check additional intervals no longer found after deletion + for _, ivl := range ivls { + assert.Truef(t, ivt.Delete(ivl), "expected successful delete on %v", ivl) + assert.Nilf(t, ivt.Find(ivl), "find expected nil after deleted interval %v", ivl) + } +} + func TestIntervalTreeIntersects(t *testing.T) { ivt := NewIntervalTree() ivt.Insert(NewStringInterval("1", "3"), 123) @@ -341,8 +371,14 @@ func TestIntervalTreeRandom(t *testing.T) { require.NotEmptyf(t, ivt.Stab(NewInt64Point(v)), "expected %v stab non-zero for [%+v)", v, xy) require.Truef(t, ivt.Intersects(NewInt64Point(v)), "did not get %d as expected for [%+v)", v, xy) } - assert.Truef(t, ivt.Delete(NewInt64Interval(ab.x, ab.y)), "did not delete %v as expected", ab) + ivl := NewInt64Interval(ab.x, ab.y) + iv := ivt.Find(ivl) + assert.NotNilf(t, iv, "expected find non-nil on %v", ab) + assert.Equalf(t, ivl, iv.Ivl, "find did not get matched interval %v", ab) + assert.Truef(t, ivt.Delete(ivl), "did not delete %v as expected", ab) delete(ivs, ab) + ivAfterDel := ivt.Find(ivl) + assert.Nilf(t, ivAfterDel, "expected find nil after deletion on %v", ab) } assert.Equalf(t, 0, ivt.Len(), "got ivt.Len() = %v, expected 0", ivt.Len()) diff --git a/pkg/cobrautl/help.go b/pkg/cobrautl/help.go index 574578199e70..d4c4896b3277 100644 --- a/pkg/cobrautl/help.go +++ b/pkg/cobrautl/help.go @@ -151,6 +151,8 @@ func getSubCommands(cmd *cobra.Command) []*cobra.Command { return subCommands } +// UsageFunc is the usage function for the cobra command. +// Deprecated: Please use go.etcd.io/etcd/etcdctl/v3/util instead. func UsageFunc(cmd *cobra.Command, version, APIVersion string) error { subCommands := getSubCommands(cmd) tabOut := getTabOutWithWriter(os.Stdout) diff --git a/pkg/expect/expect_test.go b/pkg/expect/expect_test.go index 3d621b9aacbd..95862ddc5500 100644 --- a/pkg/expect/expect_test.go +++ b/pkg/expect/expect_test.go @@ -31,7 +31,7 @@ func TestExpectFunc(t *testing.T) { ep, err := NewExpect("echo", "hello world") require.NoError(t, err) wstr := "hello world\r\n" - l, eerr := ep.ExpectFunc(context.Background(), func(a string) bool { return len(a) > 10 }) + l, eerr := ep.ExpectFunc(t.Context(), func(a string) bool { return len(a) > 10 }) require.NoError(t, eerr) require.Equalf(t, l, wstr, `got "%v", expected "%v"`, l, wstr) require.NoError(t, ep.Close()) @@ -49,7 +49,7 @@ func TestExpectFuncTimeout(t *testing.T) { } }() - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond) defer cancel() _, err = ep.ExpectFunc(ctx, func(a string) bool { return false }) @@ -66,7 +66,7 @@ func TestExpectFuncExitFailure(t *testing.T) { ep, err := NewExpect("tail", "-x") require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond) defer cancel() _, err = ep.ExpectFunc(ctx, func(s string) bool { @@ -81,7 +81,7 @@ func TestExpectFuncExitFailureStop(t *testing.T) { ep, err := NewExpect("tail", "-x") require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond) defer cancel() _, err = ep.ExpectFunc(ctx, func(s string) bool { @@ -101,7 +101,7 @@ func TestExpectFuncExitFailureStop(t *testing.T) { func TestEcho(t *testing.T) { ep, err := NewExpect("echo", "hello world") require.NoError(t, err) - ctx := context.Background() + ctx := t.Context() l, eerr := ep.ExpectWithContext(ctx, ExpectedResponse{Value: "world"}) require.NoError(t, eerr) wstr := "hello world" @@ -115,7 +115,7 @@ func TestLineCount(t *testing.T) { ep, err := NewExpect("printf", "1\n2\n3") require.NoError(t, err) wstr := "3" - l, eerr := ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: wstr}) + l, eerr := ep.ExpectWithContext(t.Context(), ExpectedResponse{Value: wstr}) require.NoError(t, eerr) require.Equalf(t, l, wstr, `got "%v", expected "%v"`, l, wstr) require.Equalf(t, 3, ep.LineCount(), "got %d, expected 3", ep.LineCount()) @@ -127,7 +127,7 @@ func TestSend(t *testing.T) { require.NoError(t, err) err = ep.Send("a\r") require.NoError(t, err) - _, err = ep.ExpectWithContext(context.Background(), ExpectedResponse{Value: "b"}) + _, err = ep.ExpectWithContext(t.Context(), ExpectedResponse{Value: "b"}) require.NoError(t, err) require.NoError(t, ep.Stop()) } @@ -208,7 +208,7 @@ func TestResponseMatchRegularExpr(t *testing.T) { ep, err := NewExpect("echo", "-n", tc.mockOutput) require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() l, err := ep.ExpectWithContext(ctx, tc.expectedResp) diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go index c899616d92a6..2f3eaf5585d4 100644 --- a/pkg/featuregate/feature_gate.go +++ b/pkg/featuregate/feature_gate.go @@ -112,8 +112,6 @@ type MutableFeatureGate interface { Add(features map[Feature]FeatureSpec) error // GetAll returns a copy of the map of known feature names to feature specs. GetAll() map[Feature]FeatureSpec - // AddMetrics adds feature enablement metrics - AddMetrics() // OverrideDefault sets a local override for the registered default value of a named // feature. If the feature has not been previously registered (e.g. by a call to Add), has a // locked default, or if the gate has already registered itself with a FlagSet, a non-nil @@ -168,7 +166,7 @@ func setUnsetBetaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, // Set, String, and Type implement pflag.Value var _ pflag.Value = &featureGate{} -func New(name string, lg *zap.Logger) *featureGate { +func New(name string, lg *zap.Logger) MutableFeatureGate { if lg == nil { lg = zap.NewNop() } @@ -363,10 +361,6 @@ func (f *featureGate) AddFlag(fs *flag.FlagSet, flagName string) { "Options are:\n"+strings.Join(known, "\n")) } -func (f *featureGate) AddMetrics() { - // TODO(henrybear327): implement this. -} - // KnownFeatures returns a slice of strings describing the FeatureGate's known features. // Deprecated and GA features are hidden from the list. func (f *featureGate) KnownFeatures() []string { diff --git a/pkg/featuregate/feature_gate_test.go b/pkg/featuregate/feature_gate_test.go index 5dc5a86537d7..d70f20cf9a1b 100644 --- a/pkg/featuregate/feature_gate_test.go +++ b/pkg/featuregate/feature_gate_test.go @@ -219,7 +219,7 @@ func TestFeatureGateFlag(t *testing.T) { t.Errorf("%d: Parse() Expected nil, Got %v", i, err) } for k, v := range test.expect { - actual := f.enabled.Load().(map[Feature]bool)[k] + actual := f.Enabled(k) assert.Equalf(t, actual, v, "%d: expected %s=%v, Got %v", i, k, v, actual) } }) diff --git a/pkg/go.mod b/pkg/go.mod index 9180151681d6..581d95fe2899 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -1,33 +1,34 @@ module go.etcd.io/etcd/pkg/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/creack/pty v1.1.18 github.com/dustin/go-humanize v1.0.1 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.70.0 + google.golang.org/grpc v1.72.2 ) require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/protobuf v1.36.4 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/go.sum b/pkg/go.sum index 6d0bccbe3dea..ec6fe47cbab8 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -1,10 +1,10 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -14,8 +14,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -24,48 +24,47 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/netutil/netutil_test.go b/pkg/netutil/netutil_test.go index 47a9a4df0988..f3953caed94f 100644 --- a/pkg/netutil/netutil_test.go +++ b/pkg/netutil/netutil_test.go @@ -134,7 +134,7 @@ func TestResolveTCPAddrs(t *testing.T) { } return &net.TCPAddr{IP: net.ParseIP(tt.hostMap[host]), Port: i, Zone: ""}, nil } - ctx, cancel := context.WithTimeout(context.TODO(), time.Second) + ctx, cancel := context.WithTimeout(t.Context(), time.Second) urls, err := resolveTCPAddrs(ctx, zaptest.NewLogger(t), tt.urls) cancel() if tt.hasError { @@ -305,7 +305,7 @@ func TestURLsEqual(t *testing.T) { } for i, test := range tests { - result, err := urlsEqual(context.TODO(), zaptest.NewLogger(t), test.a, test.b) + result, err := urlsEqual(t.Context(), zaptest.NewLogger(t), test.a, test.b) assert.Equalf(t, result, test.expect, "idx=%d #%d: a:%v b:%v, expected %v but %v", i, test.n, test.a, test.b, test.expect, result) if test.err != nil { if err.Error() != test.err.Error() { @@ -342,7 +342,7 @@ func TestURLStringsEqual(t *testing.T) { for idx, c := range cases { t.Logf("TestURLStringsEqual, case #%d", idx) resolveTCPAddr = c.resolver - result, err := URLStringsEqual(context.TODO(), zaptest.NewLogger(t), c.urlsA, c.urlsB) + result, err := URLStringsEqual(t.Context(), zaptest.NewLogger(t), c.urlsA, c.urlsB) assert.Truef(t, result, "unexpected result %v", result) assert.NoErrorf(t, err, "unexpected error %v", err) } diff --git a/pkg/proxy/server_test.go b/pkg/proxy/server_test.go index 2739c76858c0..2a7e622a558f 100644 --- a/pkg/proxy/server_test.go +++ b/pkg/proxy/server_test.go @@ -16,7 +16,6 @@ package proxy import ( "bytes" - "context" "crypto/tls" "fmt" "io" @@ -613,7 +612,7 @@ func send(t *testing.T, data []byte, scheme, addr string, tlsInfo transport.TLSI if !tlsInfo.Empty() { tp, terr := transport.NewTransport(tlsInfo, 3*time.Second) require.NoError(t, terr) - out, err = tp.DialContext(context.Background(), scheme, addr) + out, err = tp.DialContext(t.Context(), scheme, addr) } else { out, err = net.Dial(scheme, addr) } diff --git a/pkg/traceutil/trace_test.go b/pkg/traceutil/trace_test.go index 7b4ae28e61ce..5080671977d5 100644 --- a/pkg/traceutil/trace_test.go +++ b/pkg/traceutil/trace_test.go @@ -38,12 +38,12 @@ func TestGet(t *testing.T) { }{ { name: "When the context does not have trace", - inputCtx: context.TODO(), + inputCtx: t.Context(), outputTrace: TODO(), }, { name: "When the context has trace", - inputCtx: context.WithValue(context.Background(), TraceKey{}, traceForTest), + inputCtx: context.WithValue(t.Context(), TraceKey{}, traceForTest), outputTrace: traceForTest, }, } diff --git a/scripts/codecov_upload.sh b/scripts/codecov_upload.sh index b516b3bf1cb5..7dbad265b92c 100755 --- a/scripts/codecov_upload.sh +++ b/scripts/codecov_upload.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash -# Script used to collect and upload test coverage (mostly by travis). -# Usage ./test_coverage_upload.sh [log_file] +# Script used to collect and upload test coverage. set -o pipefail -LOG_FILE=${1:-test-coverage.log} - -# We collect the coverage -COVERDIR=covdir PASSES='build cov' ./scripts/test.sh 2>&1 | tee "${LOG_FILE}" -test_success="$?" - # We try to upload whatever we have: -bash <(curl -s https://codecov.io/bash) -f ./covdir/all.coverprofile -cF all || exit 2 +mkdir -p bin +curl -sf -o ./bin/codecov.sh https://codecov.io/bash -# Expose the original status of the test coverage execution. -exit ${test_success} +bash ./bin/codecov.sh -f "${COVERDIR}/all.coverprofile" \ + -cF all \ + -C "${PULL_PULL_SHA:-${PULL_BASE_SHA}}" \ + -r "${REPO_OWNER}/${REPO_NAME}" \ + -P "${PULL_NUMBER}" \ + -b "${BUILD_ID}" \ + -B "${PULL_BASE_REF}" \ + -N "${PULL_BASE_SHA}" diff --git a/scripts/etcd_version_annotations.txt b/scripts/etcd_version_annotations.txt index 10c02b888083..48071fb66d1d 100644 --- a/scripts/etcd_version_annotations.txt +++ b/scripts/etcd_version_annotations.txt @@ -150,6 +150,9 @@ etcdserverpb.DeleteRangeResponse: "3.0" etcdserverpb.DeleteRangeResponse.deleted: "" etcdserverpb.DeleteRangeResponse.header: "" etcdserverpb.DeleteRangeResponse.prev_kvs: "3.1" +etcdserverpb.DowngradeInfo: "" +etcdserverpb.DowngradeInfo.enabled: "" +etcdserverpb.DowngradeInfo.targetVersion: "" etcdserverpb.DowngradeRequest: "3.5" etcdserverpb.DowngradeRequest.CANCEL: "" etcdserverpb.DowngradeRequest.DowngradeAction: "3.5" @@ -382,6 +385,7 @@ etcdserverpb.StatusResponse: "3.0" etcdserverpb.StatusResponse.dbSize: "" etcdserverpb.StatusResponse.dbSizeInUse: "3.4" etcdserverpb.StatusResponse.dbSizeQuota: "3.6" +etcdserverpb.StatusResponse.downgradeInfo: "3.6" etcdserverpb.StatusResponse.errors: "3.4" etcdserverpb.StatusResponse.header: "" etcdserverpb.StatusResponse.isLearner: "3.4" diff --git a/scripts/markdown_diff_lint.sh b/scripts/markdown_diff_lint.sh new file mode 100755 index 000000000000..4649593cae31 --- /dev/null +++ b/scripts/markdown_diff_lint.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# This script runs markdownlint-cli2 on changed files. +# Usage: ./markdown_lint.sh + +ETCD_ROOT_DIR=$(git rev-parse --show-toplevel) + +# We source ./scripts/test_utils.sh, it sets the log functions and color variables. +source ./scripts/test_utils.sh + +# When we source ./scripts/test_utils.sh, it has the line set -u which treats unset variables as errors. +# We need to unset the variable to avoid the error. +set +u -eo pipefail + +if ! command markdownlint-cli2 dummy.md &>/dev/null; then + log_error "markdownlint-cli2 needs to be installed." + log_error "Please refer to https://github.com/DavidAnson/markdownlint-cli2?tab=readme-ov-file#install for installation instructions." + exit 1 +fi + + +if [ -z "${PULL_BASE_SHA}" ]; then + echo "Empty base reference (\$PULL_BASE_SHA), assuming: main" + PULL_BASE_SHA=main +fi + +if [ -z "${PULL_PULL_SHA}" ]; then + PULL_PULL_SHA="$(git rev-parse HEAD)" + echo "Empty pull reference (\$PULL_PULL_SHA), assuming: ${PULL_PULL_SHA}" +fi + +MD_LINT_URL_PREFIX="https://github.com/DavidAnson/markdownlint/blob/main/doc/" + +mapfile -t changed_files < <(git diff "${PULL_BASE_SHA}" --name-only) +declare -A files_with_failures start_ranges end_ranges + +for file in "${changed_files[@]}"; do + if ! [[ "$file" =~ .md$ ]]; then + continue + fi + + # Find start and end ranges from changed files. + start_ranges=() + end_ranges=() + # From https://github.com/paleite/eslint-plugin-diff/blob/46c5bcf296e9928db19333288457bf2805aad3b9/src/git.ts#L8-L27 + ranges=$(git diff "${PULL_BASE_SHA}" \ + --diff-algorithm=histogram \ + --diff-filter=ACM \ + --find-renames=100% \ + --no-ext-diff \ + --relative \ + --unified=0 -- "${file}" | \ + gawk 'match($0, /^@@\s-[0-9,]+\s\+([0-9]+)(,([0-9]+))?/, m) { \ + print m[1] ":" m[1] + ((m[3] == "") ? "0" : m[3]) }') + i=0 + for range in ${ranges}; do + start_ranges["${i}"]=$(echo "${range}" | awk -F: '{print $1}') + end_ranges["${i}"]=$(echo "${range}" | awk -F: '{print $2}') + i=$((1 + i)) + done + if [ -z "${ranges}" ]; then + start_ranges[0]=0 + end_ranges[0]=0 + fi + + i=0 + # Run markdownlint-cli2 with the changed file and print only the summary (stdout). + markdownlint-cli2 "${file}" --config "${ETCD_ROOT_DIR}/tools/.markdownlint.jsonc" 2>/dev/null || true + while IFS= read -r line; do + line_number=$(echo "${line}" | awk -F: '{print $2}' | awk '{print $1}') + + while [ "${i}" -lt "${#end_ranges[@]}" ] && [ "${line_number}" -gt "${end_ranges["${i}"]}" ]; do + i=$((1 + i)) + done + rule=$(echo "${line}" | gawk 'match($2, /([^\/]+)/, m) {print tolower(m[1])}') + lint_error="${line} (${MD_LINT_URL_PREFIX}${rule}.md)" + + if [ "${i}" -lt "${#start_ranges[@]}" ] && [ "${line_number}" -ge "${start_ranges["${i}"]}" ] && [ "${line_number}" -le "${end_ranges["${i}"]}" ]; then + # Inside range with changes, raise an error. + log_error "${lint_error}" + files_with_failures["${file}"]=1 + else + # Outside of range, raise a warning. + log_warning "${lint_error}" + fi + done < <(markdownlint-cli2 "${file}" --config "${ETCD_ROOT_DIR}/tools/.markdownlint.jsonc" 2>&1 >/dev/null || true) +done + +echo "Finished linting" + +for file in "${!files_with_failures[@]}"; do + log_error "${file} has linting issues" +done +if [ "${#files_with_failures[@]}" -gt "0" ]; then + exit 1 +fi + diff --git a/scripts/release.sh b/scripts/release.sh index aaa1e612c433..22f2fe0e178c 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -184,7 +184,7 @@ main() { fi # Tag release. - if [ "$(git tag --list | grep -c "${RELEASE_VERSION}")" -gt 0 ]; then + if git tag --list | grep --quiet "^${RELEASE_VERSION}$"; then log_callout "Skipping tag step. git tag ${RELEASE_VERSION} already exists." else log_callout "Tagging release..." @@ -254,7 +254,7 @@ main() { # Upload artifacts. if [ "${DRY_RUN}" == "true" ] || [ "${NO_UPLOAD}" == 1 ]; then - log_callout "Skipping artifact upload to gs://etcd. --no-upload flat is set." + log_callout "Skipping artifact upload to gs://etcd. --no-upload flag is set." else read -p "Upload etcd ${RELEASE_VERSION} release artifacts to gs://etcd [y/N]? " -r confirm [[ "${confirm,,}" == "y" ]] || exit 1 @@ -266,7 +266,7 @@ main() { # Push images. if [ "${DRY_RUN}" == "true" ] || [ "${NO_DOCKER_PUSH}" == 1 ]; then - log_callout "Skipping docker push. --no-docker-push flat is set." + log_callout "Skipping docker push. --no-docker-push flag is set." else read -p "Publish etcd ${RELEASE_VERSION} docker images to quay.io [y/N]? " -r confirm [[ "${confirm,,}" == "y" ]] || exit 1 @@ -364,7 +364,8 @@ main() { release_notes_temp_file=$(mktemp) local release_version=${RELEASE_VERSION#v} # Remove the v prefix from the release version (i.e., v3.6.1 -> 3.6.1) - local release_version_major_minor=${release_version%.*} # Remove the patch from the version (i.e., 3.6) + local release_version_major_minor + release_version_major_minor=$(echo "${release_version}" | cut -d. -f1-2) # Remove the patch from the version (i.e., 3.6) local release_version_major=${release_version_major_minor%.*} # Extract the major (i.e., 3) local release_version_minor=${release_version_major_minor/*./} # Extract the minor (i.e., 6) diff --git a/scripts/release_mod.sh b/scripts/release_mod.sh index da6e63cc2e29..d2783f28ef06 100755 --- a/scripts/release_mod.sh +++ b/scripts/release_mod.sh @@ -31,7 +31,7 @@ function update_module_version() { local v2version="${2}" local modules run go mod tidy - modules=$(run go list -f '{{if not .Main}}{{if not .Indirect}}{{.Path}}{{end}}{{end}}' -m all) + modules=$(go mod edit -json | jq -r '.Require[] | select(.Indirect | not) | .Path') v3deps=$(echo "${modules}" | grep -E "${ROOT_MODULE}/.*/v3") for dep in ${v3deps}; do @@ -97,16 +97,16 @@ function push_mod_tags_cmd { # Any module ccan be used for this local main_version - main_version=$(go list -f '{{.Version}}' -m "${ROOT_MODULE}/api/v3") + main_version=$(go mod edit -json | jq -r '.Require[] | select(.Path == "'"${ROOT_MODULE}"'/api/v3") | .Version') local tags=() keyid=$(get_gpg_key) || return 2 for module in $(modules); do local version - version=$(go list -f '{{.Version}}' -m "${module}") + version=$(go mod edit -json | jq -r '.Require[] | select(.Path == "'"${module}"'") | .Version') local path - path=$(go list -f '{{.Path}}' -m "${module}") + path=$(go mod edit -json | jq -r '.Require[] | select(.Path == "'"${module}"'") | .Path') local subdir="${path//${ROOT_MODULE}\//}" local tag if [ -z "${version}" ]; then @@ -121,7 +121,7 @@ function push_mod_tags_cmd { # consider main-module's tag as the latest. run sleep 2 run git tag --local-user "${keyid}" --sign "${tag}" --message "${version}" - tags=("${tags[@]}" "${tag}") + tags+=("${tag}") done maybe_run git push -f "${REMOTE_REPO}" "${tags[@]}" } diff --git a/scripts/release_notes.tpl.txt b/scripts/release_notes.tpl.txt index 67701d9886e6..3fe1284fdea6 100644 --- a/scripts/release_notes.tpl.txt +++ b/scripts/release_notes.tpl.txt @@ -16,7 +16,7 @@ rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1 +tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1 --no-same-owner rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz /tmp/etcd-download-test/etcd --version diff --git a/scripts/test.sh b/scripts/test.sh index b7932bbad177..7a13765486fa 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -67,6 +67,18 @@ if [ -z "${GOARCH:-}" ]; then GOARCH=$(go env GOARCH); fi +if [ -z "${OS:-}" ]; then + OS=$(uname -s | tr '[:upper:]' '[:lower:]') +fi + +if [ -z "${ARCH:-}" ]; then + ARCH=$(uname -m) + + if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" + fi +fi + # determine whether target supports race detection if [ -z "${RACE:-}" ] ; then if [ "$GOARCH" == "amd64" ] || [ "$GOARCH" == "arm64" ]; then @@ -331,7 +343,7 @@ function shellcheck_pass { SHELLCHECK=shellcheck if ! tool_exists "shellcheck" "https://github.com/koalaman/shellcheck#installing"; then log_callout "Installing shellcheck $SHELLCHECK_VERSION" - wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar -xJv -C /tmp/ --strip-components=1 + wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.${OS}.${ARCH}.tar.xz" | tar -xJv -C /tmp/ --strip-components=1 mkdir -p ./bin mv /tmp/shellcheck ./bin/ SHELLCHECK=./bin/shellcheck @@ -356,7 +368,14 @@ function markdown_marker_pass { # TODO: check other markdown files when marker handles headers with '[]' if ! tool_exists "$marker" "https://crates.io/crates/marker"; then log_callout "Installing markdown marker $MARKDOWN_MARKER_VERSION" - wget -qO- "https://github.com/crawford/marker/releases/download/${MARKDOWN_MARKER_VERSION}/marker-${MARKDOWN_MARKER_VERSION}-x86_64-unknown-linux-musl.tar.gz" | tar -xzv -C /tmp/ --strip-components=1 >/dev/null + MARKER_OS=$OS + if [ "$OS" = "darwin" ]; then + MARKER_OS="apple-darwin" + elif [ "$OS" = "linux" ]; then + MARKER_OS="unknown-linux-musl" + fi + + wget -qO- "https://github.com/crawford/marker/releases/download/${MARKDOWN_MARKER_VERSION}/marker-${MARKDOWN_MARKER_VERSION}-${ARCH}-${MARKER_OS}.tar.gz" | tar -xzv -C /tmp/ --strip-components=1 >/dev/null mkdir -p ./bin mv /tmp/marker ./bin/ marker=./bin/marker @@ -483,7 +502,10 @@ function bom_pass { run cp go.sum go.sum.tmp || return 2 run cp go.mod go.mod.tmp || return 2 - output=$(GOFLAGS=-mod=mod run_go_tool github.com/appscodelabs/license-bill-of-materials \ + # BOM file should be generated for linux. Otherwise running this command on other operating systems such as OSX + # results in certain dependencies being excluded from the BOM file, such as procfs. + # For more info, https://github.com/etcd-io/etcd/issues/19665 + output=$(GOOS=linux GOFLAGS=-mod=mod run_go_tool github.com/appscodelabs/license-bill-of-materials \ --override-file ./bill-of-materials.override.json \ "${modules[@]}") code="$?" @@ -508,7 +530,7 @@ function bom_pass { function dump_deps_of_module() { local module - if ! module=$(run go list -m); then + if ! module=$(run go mod edit -json | jq -r .Module.Path); then return 255 fi run go mod edit -json | jq -r '.Require[] | .Path+","+.Version+","+if .Indirect then " (indirect)" else "" end+",'"${module}"'"' @@ -596,11 +618,39 @@ function release_pass { ;; esac - tar xzvf "/tmp/$file" -C /tmp/ --strip-components=1 + tar xzvf "/tmp/$file" -C /tmp/ --strip-components=1 --no-same-owner mkdir -p ./bin mv /tmp/etcd ./bin/etcd-last-release } +function release_tests_pass { + if [ -z "${VERSION:-}" ]; then + VERSION=$(go list -m go.etcd.io/etcd/api/v3 2>/dev/null | \ + awk '{split(substr($2,2), a, "."); print a[1]"."a[2]".99"}') + fi + + if [ -n "${CI:-}" ]; then + git config user.email "prow@etcd.io" + git config user.name "Prow" + + gpg --batch --gen-key <&2 echo -n -e "${COLOR_BOLD}${COLOR_RED}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -function log_warning { - >&2 echo -n -e "${COLOR_ORANGE}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -function log_callout { - >&2 echo -n -e "${COLOR_LIGHTCYAN}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -function log_cmd { - >&2 echo -n -e "${COLOR_BLUE}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -function log_success { - >&2 echo -n -e "${COLOR_GREEN}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -function log_info { - >&2 echo -n -e "${COLOR_NONE}" - >&2 echo "$@" - >&2 echo -n -e "${COLOR_NONE}" -} - -# From http://stackoverflow.com/a/12498485 -function relativePath { - # both $1 and $2 are absolute paths beginning with / - # returns relative path to $2 from $1 - local source=$1 - local target=$2 - - local commonPart=$source - local result="" - - while [[ "${target#"$commonPart"}" == "${target}" ]]; do - # no match, means that candidate common part is not correct - # go up one level (reduce common part) - commonPart="$(dirname "$commonPart")" - # and record that we went back, with correct / handling - if [[ -z $result ]]; then - result=".." - else - result="../$result" - fi - done - - if [[ $commonPart == "/" ]]; then - # special case for root (no common path) - result="$result/" - fi - - # since we now have identified the common part, - # compute the non-common part - local forwardPart="${target#"$commonPart"}" - - # and now stick all parts together - if [[ -n $result ]] && [[ -n $forwardPart ]]; then - result="$result$forwardPart" - elif [[ -n $forwardPart ]]; then - # extra slash removal - result="${forwardPart:1}" - fi - - echo "$result" -} - #### Discovery of files/packages within a go module ##### # go_srcs_in_module @@ -408,7 +320,7 @@ function tool_pkg_dir { # tool_get_bin [tool] function run_go_tool { local cmdbin - if ! cmdbin=$(GOARCH="" tool_get_bin "${1}"); then + if ! cmdbin=$(GOARCH="" GOOS="" tool_get_bin "${1}"); then log_warning "Failed to install tool '${1}'" return 2 fi diff --git a/scripts/test_utils.sh b/scripts/test_utils.sh new file mode 100644 index 000000000000..5925fc7a09ea --- /dev/null +++ b/scripts/test_utils.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +#### Convenient IO methods ##### + +export COLOR_RED='\033[0;31m' +export COLOR_ORANGE='\033[0;33m' +export COLOR_GREEN='\033[0;32m' +export COLOR_LIGHTCYAN='\033[0;36m' +export COLOR_BLUE='\033[0;94m' +export COLOR_BOLD='\033[1m' +export COLOR_MAGENTA='\033[95m' +export COLOR_NONE='\033[0m' # No Color + + +function log_error { + >&2 echo -n -e "${COLOR_BOLD}${COLOR_RED}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +function log_warning { + >&2 echo -n -e "${COLOR_ORANGE}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +function log_callout { + >&2 echo -n -e "${COLOR_LIGHTCYAN}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +function log_cmd { + >&2 echo -n -e "${COLOR_BLUE}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +function log_success { + >&2 echo -n -e "${COLOR_GREEN}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +function log_info { + >&2 echo -n -e "${COLOR_NONE}" + >&2 echo "$@" + >&2 echo -n -e "${COLOR_NONE}" +} + +# From http://stackoverflow.com/a/12498485 +function relativePath { + # both $1 and $2 are absolute paths beginning with / + # returns relative path to $2 from $1 + local source=$1 + local target=$2 + + local commonPart=$source + local result="" + + while [[ "${target#"$commonPart"}" == "${target}" ]]; do + # no match, means that candidate common part is not correct + # go up one level (reduce common part) + commonPart="$(dirname "$commonPart")" + # and record that we went back, with correct / handling + if [[ -z $result ]]; then + result=".." + else + result="../$result" + fi + done + + if [[ $commonPart == "/" ]]; then + # special case for root (no common path) + result="$result/" + fi + + # since we now have identified the common part, + # compute the non-common part + local forwardPart="${target#"$commonPart"}" + + # and now stick all parts together + if [[ -n $result ]] && [[ -n $forwardPart ]]; then + result="$result$forwardPart" + elif [[ -n $forwardPart ]]; then + # extra slash removal + result="${forwardPart:1}" + fi + + echo "$result" +} diff --git a/scripts/updatebom.sh b/scripts/updatebom.sh index 2c6bf8720b0d..a1de4a8763b2 100755 --- a/scripts/updatebom.sh +++ b/scripts/updatebom.sh @@ -14,7 +14,10 @@ function bom_fixlet { # shellcheck disable=SC2207 modules=($(modules_for_bom)) - if GOFLAGS=-mod=mod run_go_tool "github.com/appscodelabs/license-bill-of-materials" \ + # BOM file should be generated for linux. Otherwise running this command on other operating systems such as OSX + # results in certain dependencies being excluded from the BOM file, such as procfs. + # For more info, https://github.com/etcd-io/etcd/issues/19665 + if GOOS=linux GOFLAGS=-mod=mod run_go_tool "github.com/appscodelabs/license-bill-of-materials" \ --override-file ./bill-of-materials.override.json \ "${modules[@]}" > ./bill-of-materials.json.tmp; then cp ./bill-of-materials.json.tmp ./bill-of-materials.json diff --git a/server/auth/jwt.go b/server/auth/jwt.go index e6aad1857d05..cab3f3eb9035 100644 --- a/server/auth/jwt.go +++ b/server/auth/jwt.go @@ -121,7 +121,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) zap.Uint64("revision", revision), zap.String("token", token), ) - return token, err + return token, nil } func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) { diff --git a/server/auth/jwt_test.go b/server/auth/jwt_test.go index e987b94ab4af..3ecb3328f0d9 100644 --- a/server/auth/jwt_test.go +++ b/server/auth/jwt_test.go @@ -15,7 +15,6 @@ package auth import ( - "context" "fmt" "maps" "testing" @@ -96,7 +95,7 @@ func testJWTInfo(t *testing.T, opts map[string]string) { t.Fatal(err) } - ctx := context.TODO() + ctx := t.Context() token, aerr := jwt.assign(ctx, "abc", 123) if aerr != nil { @@ -196,7 +195,7 @@ func TestJWTTokenWithMissingFields(t *testing.T) { // verify the token jwtProvider, err := newTokenProviderJWT(zap.NewNop(), optsMap) require.NoError(t, err) - ai, ok := jwtProvider.info(context.TODO(), token, 123) + ai, ok := jwtProvider.info(t.Context(), token, 123) require.Equal(t, tc.expectValid, ok) if ok { diff --git a/server/auth/simple_token_test.go b/server/auth/simple_token_test.go index 13db76efe4a3..8b8340764df7 100644 --- a/server/auth/simple_token_test.go +++ b/server/auth/simple_token_test.go @@ -31,7 +31,7 @@ func TestSimpleTokenDisabled(t *testing.T) { explicitlyDisabled.disable() for _, tp := range []*tokenSimple{initialState, explicitlyDisabled} { - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") token, err := tp.assign(ctx, "user1", 0) if err != nil { t.Fatal(err) @@ -51,7 +51,7 @@ func TestSimpleTokenAssign(t *testing.T) { tp := newTokenProviderSimple(zaptest.NewLogger(t), dummyIndexWaiter, simpleTokenTTLDefault) tp.enable() defer tp.disable() - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") token, err := tp.assign(ctx, "user1", 0) if err != nil { t.Fatal(err) @@ -63,7 +63,7 @@ func TestSimpleTokenAssign(t *testing.T) { tp.invalidateUser("user1") - _, ok = tp.info(context.TODO(), token, 0) + _, ok = tp.info(t.Context(), token, 0) if ok { t.Errorf("expected ok == false after user is invalidated") } diff --git a/server/auth/store_test.go b/server/auth/store_test.go index bf708805cd63..df13fbc297d0 100644 --- a/server/auth/store_test.go +++ b/server/auth/store_test.go @@ -280,7 +280,7 @@ func TestUserChangePassword(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) - ctx1 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx1 := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err := as.Authenticate(ctx1, "foo", "bar") if err != nil { t.Fatal(err) @@ -291,7 +291,7 @@ func TestUserChangePassword(t *testing.T) { t.Fatal(err) } - ctx2 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx2 := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err = as.Authenticate(ctx2, "foo", "baz") if err != nil { t.Fatal(err) @@ -811,38 +811,38 @@ func TestAuthInfoFromCtx(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) - ctx := context.Background() + ctx := t.Context() ai, err := as.AuthInfoFromCtx(ctx) if err != nil && ai != nil { t.Errorf("expected (nil, nil), got (%v, %v)", ai, err) } // as if it came from RPC - ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{"tokens": "dummy"})) + ctx = metadata.NewIncomingContext(t.Context(), metadata.New(map[string]string{"tokens": "dummy"})) ai, err = as.AuthInfoFromCtx(ctx) if err != nil && ai != nil { t.Errorf("expected (nil, nil), got (%v, %v)", ai, err) } - ctx = context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx = context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") resp, err := as.Authenticate(ctx, "foo", "bar") if err != nil { t.Error(err) } - ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid Token"})) + ctx = metadata.NewIncomingContext(t.Context(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid Token"})) _, err = as.AuthInfoFromCtx(ctx) if !errors.Is(err, ErrInvalidAuthToken) { t.Errorf("expected %v, got %v", ErrInvalidAuthToken, err) } - ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid.Token"})) + ctx = metadata.NewIncomingContext(t.Context(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid.Token"})) _, err = as.AuthInfoFromCtx(ctx) if !errors.Is(err, ErrInvalidAuthToken) { t.Errorf("expected %v, got %v", ErrInvalidAuthToken, err) } - ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: resp.Token})) + ctx = metadata.NewIncomingContext(t.Context(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: resp.Token})) ai, err = as.AuthInfoFromCtx(ctx) if err != nil { t.Error(err) @@ -857,7 +857,7 @@ func TestAuthDisable(t *testing.T) { defer tearDown(t) as.AuthDisable() - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err := as.Authenticate(ctx, "foo", "bar") if !errors.Is(err, ErrAuthNotEnabled) { t.Errorf("expected %v, got %v", ErrAuthNotEnabled, err) @@ -879,7 +879,7 @@ func TestIsAuthEnabled(t *testing.T) { as.AuthEnable() status := as.IsAuthEnabled() - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, _ = as.Authenticate(ctx, "foo", "bar") if status != true { t.Errorf("expected %v, got %v", true, false) @@ -907,7 +907,7 @@ func TestAuthInfoFromCtxRace(t *testing.T) { donec := make(chan struct{}) go func() { defer close(donec) - ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "test"})) + ctx := metadata.NewIncomingContext(t.Context(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "test"})) as.AuthInfoFromCtx(ctx) }() as.UserAdd(&pb.AuthUserAddRequest{Name: "test", Options: &authpb.UserAddOptions{NoPassword: false}}) @@ -1025,7 +1025,7 @@ func TestHammerSimpleAuthenticate(t *testing.T) { go func(user string) { defer wg.Done() token := fmt.Sprintf("%s(%d)", user, i) - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, token) + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, token) if _, err := as.Authenticate(ctx, user, "123"); err != nil { t.Error(err) } @@ -1106,7 +1106,7 @@ func testAuthInfoFromCtxWithRoot(t *testing.T, opts string) { t.Fatal(err) } - ctx := context.Background() + ctx := t.Context() ctx = as.WithRoot(ctx) ai, aerr := as.AuthInfoFromCtx(ctx) @@ -1130,7 +1130,7 @@ func TestUserNoPasswordAdd(t *testing.T) { t.Fatal(err) } - ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err = as.Authenticate(ctx, username, "") require.ErrorIsf(t, err, ErrAuthFailed, "expected %v, got %v", ErrAuthFailed, err) } @@ -1150,7 +1150,7 @@ func TestUserChangePasswordWithOldLog(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) - ctx1 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx1 := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err := as.Authenticate(ctx1, "foo", "bar") if err != nil { t.Fatal(err) @@ -1161,7 +1161,7 @@ func TestUserChangePasswordWithOldLog(t *testing.T) { t.Fatal(err) } - ctx2 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") + ctx2 := context.WithValue(context.WithValue(t.Context(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err = as.Authenticate(ctx2, "foo", "baz") if err != nil { t.Fatal(err) diff --git a/server/config/config.go b/server/config/config.go index 8621feb80039..8f57112b3c60 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -167,10 +167,10 @@ type ServerConfig struct { EnableGRPCGateway bool - // ExperimentalEnableDistributedTracing enables distributed tracing using OpenTelemetry protocol. - ExperimentalEnableDistributedTracing bool - // ExperimentalTracerOptions are options for OpenTelemetry gRPC interceptor. - ExperimentalTracerOptions []otelgrpc.Option + // EnableDistributedTracing enables distributed tracing using OpenTelemetry protocol. + EnableDistributedTracing bool + // TracerOptions are options for OpenTelemetry gRPC interceptor. + TracerOptions []otelgrpc.Option WatchProgressNotifyInterval time.Duration @@ -188,10 +188,6 @@ type ServerConfig struct { // be refined to mlock in-use area of bbolt only. MemoryMlock bool `json:"memory-mlock"` - // ExperimentalTxnModeWriteWithSharedBuffer enable write transaction to use - // a shared buffer in its readonly check operations. - ExperimentalTxnModeWriteWithSharedBuffer bool `json:"experimental-txn-mode-write-with-shared-buffer"` - // BootstrapDefragThresholdMegabytes is the minimum number of megabytes needed to be freed for etcd server to // consider running defrag during bootstrap. Needs to be set to non-zero value to take effect. BootstrapDefragThresholdMegabytes uint `json:"bootstrap-defrag-threshold-megabytes"` @@ -202,8 +198,8 @@ type ServerConfig struct { // V2Deprecation defines a phase of v2store deprecation process. V2Deprecation V2DeprecationEnum `json:"v2-deprecation"` - // ExperimentalLocalAddress is the local IP address to use when communicating with a peer. - ExperimentalLocalAddress string `json:"experimental-local-address"` + // LocalAddress is the local IP address to use when communicating with a peer. + LocalAddress string `json:"local-address"` // ServerFeatureGate is a server level feature gate ServerFeatureGate featuregate.FeatureGate diff --git a/server/embed/auth_test.go b/server/embed/auth_test.go index a09e618f66c8..83a3ccfda1f9 100644 --- a/server/embed/auth_test.go +++ b/server/embed/auth_test.go @@ -15,7 +15,6 @@ package embed import ( - "context" "testing" "go.etcd.io/etcd/server/v3/etcdserver/api/v3client" @@ -33,19 +32,19 @@ func TestEnableAuth(t *testing.T) { client := v3client.New(e.Server) defer client.Close() - _, err = client.RoleAdd(context.TODO(), "root") + _, err = client.RoleAdd(t.Context(), "root") if err != nil { t.Fatal(err) } - _, err = client.UserAdd(context.TODO(), "root", "root") + _, err = client.UserAdd(t.Context(), "root", "root") if err != nil { t.Fatal(err) } - _, err = client.UserGrantRole(context.TODO(), "root", "root") + _, err = client.UserGrantRole(t.Context(), "root", "root") if err != nil { t.Fatal(err) } - _, err = client.AuthEnable(context.TODO()) + _, err = client.AuthEnable(t.Context()) if err != nil { t.Fatal(err) } diff --git a/server/embed/config.go b/server/embed/config.go index 05813b39a81a..5b4aceeb19c3 100644 --- a/server/embed/config.go +++ b/server/embed/config.go @@ -101,12 +101,10 @@ const ( // Compress = false // compress the rotated log in gzip format DefaultLogRotationConfig = `{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}` - // ExperimentalDistributedTracingAddress is the default collector address. - ExperimentalDistributedTracingAddress = "localhost:4317" - // ExperimentalDistributedTracingServiceName is the default etcd service name. - ExperimentalDistributedTracingServiceName = "etcd" - - DefaultExperimentalTxnModeWriteWithSharedBuffer = true + // DefaultDistributedTracingAddress is the default collector address. + DefaultDistributedTracingAddress = "localhost:4317" + // DefaultDistributedTracingServiceName is the default etcd service name. + DefaultDistributedTracingServiceName = "etcd" // DefaultStrictReconfigCheck is the default value for "--strict-reconfig-check" flag. // It's enabled by default. @@ -135,23 +133,6 @@ var ( // indirection for testing getCluster = srv.GetCluster - - // in 3.6, we are migration all the --experimental flags to feature gate and flags without the prefix. - // This is the mapping from the non boolean `experimental-` to the new flags. - // TODO: delete in v3.7 - experimentalFlagMigrationMap = map[string]string{ - "experimental-compact-hash-check-time": "compact-hash-check-time", - "experimental-corrupt-check-time": "corrupt-check-time", - "experimental-compaction-batch-limit": "compaction-batch-limit", - "experimental-watch-progress-notify-interval": "watch-progress-notify-interval", - "experimental-warning-apply-duration": "warning-apply-duration", - "experimental-bootstrap-defrag-threshold-megabytes": "bootstrap-defrag-threshold-megabytes", - "experimental-max-learners": "max-learners", - "experimental-memory-mlock": "memory-mlock", - "experimental-compaction-sleep-interval": "compaction-sleep-interval", - "experimental-downgrade-check-time": "downgrade-check-time", - "experimental-peer-skip-client-san-verification": "peer-skip-client-san-verification", - } ) var ( @@ -182,19 +163,21 @@ type Config struct { //revive:disable-next-line:var-naming WalDir string `json:"wal-dir"` - // SnapshotCount is deprecated in v3.6 and will be decommissioned in v3.7. + // SnapshotCount is the number of committed transactions that trigger a snapshot to disk. // TODO: remove it in 3.7. + // Deprecated: Will be decommissioned in v3.7. SnapshotCount uint64 `json:"snapshot-count"` - // SnapshotCatchUpEntries is the number of entries for a slow follower + // SnapshotCatchUpEntries is the number of entires for a slow follower // to catch-up after compacting the raft storage entries. // We expect the follower has a millisecond level latency with the leader. // The max throughput is around 10K. Keep a 5K entries is enough for helping // follower to catch up. - SnapshotCatchUpEntries uint64 `json:"experimental-snapshot-catch-up-entries"` + SnapshotCatchUpEntries uint64 `json:"snapshot-catchup-entries"` - // MaxSnapFiles is deprecated in v3.6 and will be decommissioned in v3.7. + // MaxSnapFiles is the maximum number of snapshot files. // TODO: remove it in 3.7. + // Deprecated: Will be removed in v3.7. MaxSnapFiles uint `json:"max-snapshots"` //revive:disable-next-line:var-naming MaxWalFiles uint `json:"max-wals"` @@ -258,11 +241,6 @@ type Config struct { PeerTLSInfo transport.TLSInfo PeerAutoTLS bool - // ExperimentalSetMemberLocalAddr enables using the first specified and - // non-loopback local address from initial-advertise-peer-urls as the local - // address when communicating with a peer. - ExperimentalSetMemberLocalAddr bool `json:"experimental-set-member-localaddr"` - // SelfSignedCertValidity specifies the validity period of the client and peer certificates // that are automatically generated by etcd when you specify ClientAutoTLS and PeerAutoTLS, // the unit is year, and the default is 1 @@ -380,67 +358,26 @@ type Config struct { // AuthTokenTTL in seconds of the simple token AuthTokenTTL uint `json:"auth-token-ttl"` - ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"` - // ExperimentalCorruptCheckTime is the duration of time between cluster corruption check passes. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: delete in v3.7 - ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"` - CorruptCheckTime time.Duration `json:"corrupt-check-time"` - // ExperimentalCompactHashCheckEnabled enables leader to periodically check followers compaction hashes. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: delete in v3.7 - ExperimentalCompactHashCheckEnabled bool `json:"experimental-compact-hash-check-enabled"` - // ExperimentalCompactHashCheckTime is the duration of time between leader checks followers compaction hashes. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: delete in v3.7 - ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time"` - CompactHashCheckTime time.Duration `json:"compact-hash-check-time"` - - // ExperimentalEnableLeaseCheckpoint enables leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change. - ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"` - // ExperimentalEnableLeaseCheckpointPersist enables persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. - // Requires experimental-enable-lease-checkpoint to be enabled. - // Deprecated in v3.6. - // TODO: Delete in v3.7 - ExperimentalEnableLeaseCheckpointPersist bool `json:"experimental-enable-lease-checkpoint-persist"` - // ExperimentalCompactionBatchLimit Sets the maximum revisions deleted in each compaction batch. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit"` - CompactionBatchLimit int `json:"compaction-batch-limit"` - // ExperimentalCompactionSleepInterval is the sleep interval between every etcd compaction loop. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalCompactionSleepInterval time.Duration `json:"experimental-compaction-sleep-interval"` + // CorruptCheckTime is the duration of time between cluster corruption check passes. + CorruptCheckTime time.Duration `json:"corrupt-check-time"` + + // CompactHashCheckTime is the duration of time between leader checks followers compaction hashes. + CompactHashCheckTime time.Duration `json:"compact-hash-check-time"` + // CompactionBatchLimit Sets the maximum revisions deleted in each compaction batch. + CompactionBatchLimit int `json:"compaction-batch-limit"` // CompactionSleepInterval is the sleep interval between every etcd compaction loop. CompactionSleepInterval time.Duration `json:"compaction-sleep-interval"` - // ExperimentalWatchProgressNotifyInterval is the time duration of periodic watch progress notifications. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval"` - WatchProgressNotifyInterval time.Duration `json:"watch-progress-notify-interval"` - // ExperimentalWarningApplyDuration is the time duration after which a warning is generated if applying request - // takes more time than this value. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration"` - WarningApplyDuration time.Duration `json:"warning-apply-duration"` - // ExperimentalBootstrapDefragThresholdMegabytes is the minimum number of megabytes needed to be freed for etcd server to - // consider running defrag during bootstrap. Needs to be set to non-zero value to take effect. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes"` - BootstrapDefragThresholdMegabytes uint `json:"bootstrap-defrag-threshold-megabytes"` + // WatchProgressNotifyInterval is the time duration of periodic watch progress notifications. + WatchProgressNotifyInterval time.Duration `json:"watch-progress-notify-interval"` + // WarningApplyDuration is the time duration after which a warning is generated if applying request + WarningApplyDuration time.Duration `json:"warning-apply-duration"` + // BootstrapDefragThresholdMegabytes is the minimum number of megabytes needed to be freed for etcd server to + BootstrapDefragThresholdMegabytes uint `json:"bootstrap-defrag-threshold-megabytes"` // WarningUnaryRequestDuration is the time duration after which a warning is generated if applying // unary request takes more time than this value. WarningUnaryRequestDuration time.Duration `json:"warning-unary-request-duration"` - // ExperimentalWarningUnaryRequestDuration is deprecated, please use WarningUnaryRequestDuration instead. - ExperimentalWarningUnaryRequestDuration time.Duration `json:"experimental-warning-unary-request-duration"` - // ExperimentalMaxLearners sets a limit to the number of learner members that can exist in the cluster membership. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalMaxLearners int `json:"experimental-max-learners"` - MaxLearners int `json:"max-learners"` + // MaxLearners sets a limit to the number of learner members that can exist in the cluster membership. + MaxLearners int `json:"max-learners"` // ForceNewCluster starts a new cluster even if previously started; unsafe. ForceNewCluster bool `json:"force-new-cluster"` @@ -450,28 +387,22 @@ type Config struct { ListenMetricsUrls []url.URL ListenMetricsUrlsJSON string `json:"listen-metrics-urls"` - // ExperimentalEnableDistributedTracing indicates if experimental tracing using OpenTelemetry is enabled. - ExperimentalEnableDistributedTracing bool `json:"experimental-enable-distributed-tracing"` - // ExperimentalDistributedTracingAddress is the address of the OpenTelemetry Collector. - // Can only be set if ExperimentalEnableDistributedTracing is true. - ExperimentalDistributedTracingAddress string `json:"experimental-distributed-tracing-address"` - // ExperimentalDistributedTracingServiceName is the name of the service. - // Can only be used if ExperimentalEnableDistributedTracing is true. - ExperimentalDistributedTracingServiceName string `json:"experimental-distributed-tracing-service-name"` - // ExperimentalDistributedTracingServiceInstanceID is the ID key of the service. + // EnableDistributedTracing indicates if tracing using OpenTelemetry is enabled. + EnableDistributedTracing bool `json:"enable-distributed-tracing"` + // DistributedTracingAddress is the address of the OpenTelemetry Collector. + // Can only be set if EnableDistributedTracing is true. + DistributedTracingAddress string `json:"distributed-tracing-address"` + // DistributedTracingServiceName is the name of the service. + // Can only be used if EnableDistributedTracing is true. + DistributedTracingServiceName string `json:"distributed-tracing-service-name"` + // DistributedTracingServiceInstanceID is the ID key of the service. // This ID must be unique, as helps to distinguish instances of the same service // that exist at the same time. - // Can only be used if ExperimentalEnableDistributedTracing is true. - ExperimentalDistributedTracingServiceInstanceID string `json:"experimental-distributed-tracing-instance-id"` - // ExperimentalDistributedTracingSamplingRatePerMillion is the number of samples to collect per million spans. + // Can only be used if EnableDistributedTracing is true. + DistributedTracingServiceInstanceID string `json:"distributed-tracing-instance-id"` + // DistributedTracingSamplingRatePerMillion is the number of samples to collect per million spans. // Defaults to 0. - ExperimentalDistributedTracingSamplingRatePerMillion int `json:"experimental-distributed-tracing-sampling-rate"` - - // ExperimentalPeerSkipClientSanVerification determines whether to skip verification of SAN field - // in client certificate for peer connections. - // Deprecated in v3.6 and will be decommissioned in v3.7. - // TODO: Delete in v3.7 - ExperimentalPeerSkipClientSanVerification bool `json:"experimental-peer-skip-client-san-verification"` + DistributedTracingSamplingRatePerMillion int `json:"distributed-tracing-sampling-rate"` // Logger is logger options: currently only supports "zap". // "capnslog" is removed in v3.5. @@ -507,10 +438,6 @@ type Config struct { // Setting this is unsafe and will cause data loss. UnsafeNoFsync bool `json:"unsafe-no-fsync"` - // ExperimentalDowngradeCheckTime is the duration between two downgrade status checks (in seconds). - // Deprecated in v3.6 and scheduled to be removed in v3.7. - // TODO: Delete `ExperimentalDowngradeCheckTime` in v3.7. - ExperimentalDowngradeCheckTime time.Duration `json:"experimental-downgrade-check-time"` // DowngradeCheckTime is the duration between two downgrade status checks (in seconds). DowngradeCheckTime time.Duration `json:"downgrade-check-time"` @@ -522,20 +449,10 @@ type Config struct { // be refined to mlock in-use area of bbolt only. MemoryMlock bool `json:"memory-mlock"` - // ExperimentalMemoryMlock enables mlocking of etcd owned memory pages. - // Deprecated in v3.6 and will be decommissioned in v3.7. Use MemoryMlock instead. - // TODO: Delete in v3.7 - ExperimentalMemoryMlock bool `json:"experimental-memory-mlock"` - - // ExperimentalTxnModeWriteWithSharedBuffer enables write transaction to use a shared buffer in its readonly check operations. - ExperimentalTxnModeWriteWithSharedBuffer bool `json:"experimental-txn-mode-write-with-shared-buffer"` - - // ExperimentalStopGRPCServiceOnDefrag enables etcd gRPC service to stop serving client requests on defragmentation. - ExperimentalStopGRPCServiceOnDefrag bool `json:"experimental-stop-grpc-service-on-defrag"` - // V2Deprecation describes phase of API & Storage V2 support. - // Deprecated and scheduled for removal in v3.8. // Do not set this field for embedded use cases, as it has no effect. However, setting it will not cause any harm. + // TODO: Delete in v3.8 + // Deprecated: The default value is enforced, to be removed in v3.8. V2Deprecation config.V2DeprecationEnum `json:"v2-deprecation"` // ServerFeatureGate is a server level feature gate @@ -645,23 +562,14 @@ func NewConfig() *Config { LogRotationConfigJSON: DefaultLogRotationConfig, EnableGRPCGateway: true, - ExperimentalDowngradeCheckTime: DefaultDowngradeCheckTime, - DowngradeCheckTime: DefaultDowngradeCheckTime, - MemoryMlock: false, - // TODO: delete in v3.7 - ExperimentalMemoryMlock: false, - ExperimentalStopGRPCServiceOnDefrag: false, - MaxLearners: membership.DefaultMaxLearners, - // TODO: delete in v3.7 - ExperimentalMaxLearners: membership.DefaultMaxLearners, + DowngradeCheckTime: DefaultDowngradeCheckTime, + MemoryMlock: false, + MaxLearners: membership.DefaultMaxLearners, - ExperimentalTxnModeWriteWithSharedBuffer: DefaultExperimentalTxnModeWriteWithSharedBuffer, - ExperimentalDistributedTracingAddress: ExperimentalDistributedTracingAddress, - ExperimentalDistributedTracingServiceName: ExperimentalDistributedTracingServiceName, + DistributedTracingAddress: DefaultDistributedTracingAddress, + DistributedTracingServiceName: DefaultDistributedTracingServiceName, CompactHashCheckTime: DefaultCompactHashCheckTime, - // TODO: delete in v3.7 - ExperimentalCompactHashCheckTime: DefaultCompactHashCheckTime, V2Deprecation: config.V2DeprDefault, @@ -741,7 +649,6 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) { "initial-advertise-peer-urls", "List of this member's peer URLs to advertise to the rest of the cluster.", ) - fs.BoolVar(&cfg.ExperimentalSetMemberLocalAddr, "experimental-set-member-localaddr", false, "Enable to have etcd use the first specified and non-loopback host from initial-advertise-peer-urls as the local address when communicating with a peer.") fs.Var( flags.NewUniqueURLsWithExceptions(DefaultAdvertiseClientURLs, ""), @@ -800,7 +707,6 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) { fs.Var(flags.NewStringsValue(""), "peer-cert-allowed-cn", "Comma-separated list of allowed CNs for inter-peer TLS authentication.") fs.Var(flags.NewStringsValue(""), "peer-cert-allowed-hostname", "Comma-separated list of allowed SAN hostnames for inter-peer TLS authentication.") fs.Var(flags.NewStringsValue(""), "cipher-suites", "Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).") - fs.BoolVar(&cfg.ExperimentalPeerSkipClientSanVerification, "experimental-peer-skip-client-san-verification", false, "Skip verification of SAN field in client certificate for peer connections.Deprecated in v3.6 and will be decommissioned in v3.7. Use peer-skip-client-san-verification instead") fs.BoolVar(&cfg.PeerTLSInfo.SkipClientSANVerify, "peer-skip-client-san-verification", false, "Skip verification of SAN field in client certificate for peer connections.") fs.StringVar(&cfg.TlsMinVersion, "tls-min-version", string(tlsutil.TLSVersion12), "Minimum TLS version supported by etcd. Possible values: TLS1.2, TLS1.3.") fs.StringVar(&cfg.TlsMaxVersion, "tls-max-version", string(tlsutil.TLSVersionDefault), "Maximum TLS version supported by etcd. Possible values: TLS1.2, TLS1.3 (empty defers to Go).") @@ -829,12 +735,11 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) { // additional metrics fs.StringVar(&cfg.Metrics, "metrics", cfg.Metrics, "Set level of detail for exported metrics, specify 'extensive' to include server side grpc histogram metrics") - // experimental distributed tracing - fs.BoolVar(&cfg.ExperimentalEnableDistributedTracing, "experimental-enable-distributed-tracing", false, "Enable experimental distributed tracing using OpenTelemetry Tracing.") - fs.StringVar(&cfg.ExperimentalDistributedTracingAddress, "experimental-distributed-tracing-address", ExperimentalDistributedTracingAddress, "Address for distributed tracing used for OpenTelemetry Tracing (if enabled with experimental-enable-distributed-tracing flag).") - fs.StringVar(&cfg.ExperimentalDistributedTracingServiceName, "experimental-distributed-tracing-service-name", ExperimentalDistributedTracingServiceName, "Configures service name for distributed tracing to be used to define service name for OpenTelemetry Tracing (if enabled with experimental-enable-distributed-tracing flag). 'etcd' is the default service name. Use the same service name for all instances of etcd.") - fs.StringVar(&cfg.ExperimentalDistributedTracingServiceInstanceID, "experimental-distributed-tracing-instance-id", "", "Configures service instance ID for distributed tracing to be used to define service instance ID key for OpenTelemetry Tracing (if enabled with experimental-enable-distributed-tracing flag). There is no default value set. This ID must be unique per etcd instance.") - fs.IntVar(&cfg.ExperimentalDistributedTracingSamplingRatePerMillion, "experimental-distributed-tracing-sampling-rate", 0, "Number of samples to collect per million spans for OpenTelemetry Tracing (if enabled with experimental-enable-distributed-tracing flag).") + fs.BoolVar(&cfg.EnableDistributedTracing, "enable-distributed-tracing", false, "Enable distributed tracing using OpenTelemetry Tracing.") + fs.StringVar(&cfg.DistributedTracingAddress, "distributed-tracing-address", cfg.DistributedTracingAddress, "Address for distributed tracing used for OpenTelemetry Tracing (if enabled with enable-distributed-tracing flag).") + fs.StringVar(&cfg.DistributedTracingServiceName, "distributed-tracing-service-name", cfg.DistributedTracingServiceName, "Configures service name for distributed tracing to be used to define service name for OpenTelemetry Tracing (if enabled with enable-distributed-tracing flag). 'etcd' is the default service name. Use the same service name for all instances of etcd.") + fs.StringVar(&cfg.DistributedTracingServiceInstanceID, "distributed-tracing-instance-id", "", "Configures service instance ID for distributed tracing to be used to define service instance ID key for OpenTelemetry Tracing (if enabled with enable-distributed-tracing flag). There is no default value set. This ID must be unique per etcd instance.") + fs.IntVar(&cfg.DistributedTracingSamplingRatePerMillion, "distributed-tracing-sampling-rate", 0, "Number of samples to collect per million spans for OpenTelemetry Tracing (if enabled with enable-distributed-tracing flag).") // auth fs.StringVar(&cfg.AuthToken, "auth-token", cfg.AuthToken, "Specify auth token specific options.") @@ -843,49 +748,19 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) { // gateway fs.BoolVar(&cfg.EnableGRPCGateway, "enable-grpc-gateway", cfg.EnableGRPCGateway, "Enable GRPC gateway.") - - // experimental - fs.BoolVar(&cfg.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.") - // TODO: delete in v3.7 - fs.DurationVar(&cfg.ExperimentalCorruptCheckTime, "experimental-corrupt-check-time", cfg.ExperimentalCorruptCheckTime, "Duration of time between cluster corruption check passes. Deprecated in v3.6 and will be decommissioned in v3.7. Use --corrupt-check-time instead") fs.DurationVar(&cfg.CorruptCheckTime, "corrupt-check-time", cfg.CorruptCheckTime, "Duration of time between cluster corruption check passes.") - // TODO: delete in v3.7 - fs.BoolVar(&cfg.ExperimentalCompactHashCheckEnabled, "experimental-compact-hash-check-enabled", cfg.ExperimentalCompactHashCheckEnabled, "Enable leader to periodically check followers compaction hashes. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=CompactHashCheck=true' instead") - fs.DurationVar(&cfg.ExperimentalCompactHashCheckTime, "experimental-compact-hash-check-time", cfg.ExperimentalCompactHashCheckTime, "Duration of time between leader checks followers compaction hashes. Deprecated in v3.6 and will be decommissioned in v3.7. Use --compact-hash-check-time instead.") - fs.DurationVar(&cfg.CompactHashCheckTime, "compact-hash-check-time", cfg.CompactHashCheckTime, "Duration of time between leader checks followers compaction hashes.") - fs.BoolVar(&cfg.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change.") - // TODO: delete in v3.7 - fs.BoolVar(&cfg.ExperimentalEnableLeaseCheckpointPersist, "experimental-enable-lease-checkpoint-persist", false, "Enable persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. Requires experimental-enable-lease-checkpoint to be enabled.") - // TODO: delete in v3.7 - fs.IntVar(&cfg.ExperimentalCompactionBatchLimit, "experimental-compaction-batch-limit", cfg.ExperimentalCompactionBatchLimit, "Sets the maximum revisions deleted in each compaction batch. Deprecated in v3.6 and will be decommissioned in v3.7. Use --compaction-batch-limit instead.") fs.IntVar(&cfg.CompactionBatchLimit, "compaction-batch-limit", cfg.CompactionBatchLimit, "Sets the maximum revisions deleted in each compaction batch.") - fs.DurationVar(&cfg.ExperimentalCompactionSleepInterval, "experimental-compaction-sleep-interval", cfg.ExperimentalCompactionSleepInterval, "Sets the sleep interval between each compaction batch. Deprecated in v3.6 and will be decommissioned in v3.7. Use --compaction-sleep-interval instead.") fs.DurationVar(&cfg.CompactionSleepInterval, "compaction-sleep-interval", cfg.CompactionSleepInterval, "Sets the sleep interval between each compaction batch.") - // TODO: delete in v3.7 - fs.DurationVar(&cfg.ExperimentalWatchProgressNotifyInterval, "experimental-watch-progress-notify-interval", cfg.ExperimentalWatchProgressNotifyInterval, "Duration of periodic watch progress notifications. Deprecated in v3.6 and will be decommissioned in v3.7. Use --watch-progress-notify-interval instead.") fs.DurationVar(&cfg.WatchProgressNotifyInterval, "watch-progress-notify-interval", cfg.WatchProgressNotifyInterval, "Duration of periodic watch progress notifications.") fs.DurationVar(&cfg.DowngradeCheckTime, "downgrade-check-time", cfg.DowngradeCheckTime, "Duration of time between two downgrade status checks.") - // TODO: delete in v3.7 - fs.DurationVar(&cfg.ExperimentalDowngradeCheckTime, "experimental-downgrade-check-time", cfg.ExperimentalDowngradeCheckTime, "Duration of time between two downgrade status checks. Deprecated in v3.6 and will be decommissioned in v3.7. Use --downgrade-check-time instead.") - // TODO: delete in v3.7 - fs.DurationVar(&cfg.ExperimentalWarningApplyDuration, "experimental-warning-apply-duration", cfg.ExperimentalWarningApplyDuration, "Time duration after which a warning is generated if request takes more time. Deprecated in v3.6 and will be decommissioned in v3.7. Use --warning-watch-progress-duration instead.") fs.DurationVar(&cfg.WarningApplyDuration, "warning-apply-duration", cfg.WarningApplyDuration, "Time duration after which a warning is generated if watch progress takes more time.") fs.DurationVar(&cfg.WarningUnaryRequestDuration, "warning-unary-request-duration", cfg.WarningUnaryRequestDuration, "Time duration after which a warning is generated if a unary request takes more time.") - fs.DurationVar(&cfg.ExperimentalWarningUnaryRequestDuration, "experimental-warning-unary-request-duration", cfg.ExperimentalWarningUnaryRequestDuration, "Time duration after which a warning is generated if a unary request takes more time. It's deprecated, and will be decommissioned in v3.7. Use --warning-unary-request-duration instead.") - // TODO: delete in v3.7 - fs.BoolVar(&cfg.ExperimentalMemoryMlock, "experimental-memory-mlock", cfg.ExperimentalMemoryMlock, "Enable to enforce etcd pages (in particular bbolt) to stay in RAM.") fs.BoolVar(&cfg.MemoryMlock, "memory-mlock", cfg.MemoryMlock, "Enable to enforce etcd pages (in particular bbolt) to stay in RAM.") - fs.BoolVar(&cfg.ExperimentalTxnModeWriteWithSharedBuffer, "experimental-txn-mode-write-with-shared-buffer", true, "Enable the write transaction to use a shared buffer in its readonly check operations.") - fs.BoolVar(&cfg.ExperimentalStopGRPCServiceOnDefrag, "experimental-stop-grpc-service-on-defrag", cfg.ExperimentalStopGRPCServiceOnDefrag, "Enable etcd gRPC service to stop serving client requests on defragmentation.") - // TODO: delete in v3.7 - fs.UintVar(&cfg.ExperimentalBootstrapDefragThresholdMegabytes, "experimental-bootstrap-defrag-threshold-megabytes", 0, "Enable the defrag during etcd server bootstrap on condition that it will free at least the provided threshold of disk space. Needs to be set to non-zero value to take effect. It's deprecated, and will be decommissioned in v3.7. Use --bootstrap-defrag-threshold-megabytes instead.") fs.UintVar(&cfg.BootstrapDefragThresholdMegabytes, "bootstrap-defrag-threshold-megabytes", 0, "Enable the defrag during etcd server bootstrap on condition that it will free at least the provided threshold of disk space. Needs to be set to non-zero value to take effect.") - // TODO: delete in v3.7 - fs.IntVar(&cfg.ExperimentalMaxLearners, "experimental-max-learners", membership.DefaultMaxLearners, "Sets the maximum number of learners that can be available in the cluster membership. Deprecated in v3.6 and will be decommissioned in v3.7. Use --max-learners instead.") fs.IntVar(&cfg.MaxLearners, "max-learners", membership.DefaultMaxLearners, "Sets the maximum number of learners that can be available in the cluster membership.") - fs.Uint64Var(&cfg.SnapshotCatchUpEntries, "experimental-snapshot-catchup-entries", cfg.SnapshotCatchUpEntries, "Number of entries for a slow follower to catch up after compacting the raft storage entries.") + fs.Uint64Var(&cfg.SnapshotCatchUpEntries, "snapshot-catchup-entries", cfg.SnapshotCatchUpEntries, "Number of entries for a slow follower to catch up after compacting the raft storage entries.") // unsafe fs.BoolVar(&cfg.UnsafeNoFsync, "unsafe-no-fsync", false, "Disables fsync, unsafe, will cause data loss.") @@ -944,19 +819,6 @@ func (cfg *configYAML) configFromFile(path string) error { } } - getBoolFlagVal := func(flagName string) *bool { - flagVal, ok := cfgMap[flagName] - if !ok { - return nil - } - boolVal := flagVal.(bool) - return &boolVal - } - err = SetFeatureGatesFromExperimentalFlags(cfg.ServerFeatureGate, getBoolFlagVal, cfg.configJSON.ServerFeatureGatesJSON) - if err != nil { - return err - } - if cfg.configJSON.ListenPeerURLs != "" { u, err := types.NewURLs(strings.Split(cfg.configJSON.ListenPeerURLs, ",")) if err != nil { @@ -1050,36 +912,6 @@ func (cfg *configYAML) configFromFile(path string) error { return cfg.Validate() } -// SetFeatureGatesFromExperimentalFlags sets the feature gate values if the feature gate is not explicitly set -// while their corresponding experimental flags are explicitly set, for all the features in ExperimentalFlagToFeatureMap. -// TODO: remove after all experimental flags are deprecated. -func SetFeatureGatesFromExperimentalFlags(fg featuregate.FeatureGate, getExperimentalFlagVal func(string) *bool, featureGatesVal string) error { - m := make(map[featuregate.Feature]bool) - // verify that the feature gate and its experimental flag are not both set at the same time. - for expFlagName, featureName := range features.ExperimentalFlagToFeatureMap { - flagVal := getExperimentalFlagVal(expFlagName) - if flagVal == nil { - continue - } - if strings.Contains(featureGatesVal, string(featureName)) { - return fmt.Errorf("cannot specify both flags: --%s=%v and --%s=%s=%v at the same time, please just use --%s=%s=%v", - expFlagName, *flagVal, ServerFeatureGateFlagName, featureName, fg.Enabled(featureName), ServerFeatureGateFlagName, featureName, fg.Enabled(featureName)) - } - m[featureName] = *flagVal - } - - // filter out unknown features for fg, because we could use SetFeatureGatesFromExperimentalFlags both for - // server and cluster level feature gates. - allFeatures := fg.(featuregate.MutableFeatureGate).GetAll() - mFiltered := make(map[string]bool) - for k, v := range m { - if _, ok := allFeatures[k]; ok { - mFiltered[string(k)] = v - } - } - return fg.(featuregate.MutableFeatureGate).SetFromMap(mFiltered) -} - func updateCipherSuites(tls *transport.TLSInfo, ss []string) error { if len(tls.CipherSuites) > 0 && len(ss) > 0 { return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss) @@ -1107,14 +939,6 @@ func updateMinMaxVersions(info *transport.TLSInfo, min, max string) { // Validate ensures that '*embed.Config' fields are properly configured. func (cfg *Config) Validate() error { - // make sure there is no conflict in the flag settings in the ExperimentalNonBoolFlagMigrationMap - // TODO: delete in v3.7 - for oldFlag, newFlag := range experimentalFlagMigrationMap { - if cfg.FlagsExplicitlySet[oldFlag] && cfg.FlagsExplicitlySet[newFlag] { - return fmt.Errorf("cannot set --%s and --%s at the same time, please use --%s only", oldFlag, newFlag, newFlag) - } - } - if err := cfg.setupLogging(); err != nil { return err } @@ -1206,8 +1030,8 @@ func (cfg *Config) Validate() error { } // Validate distributed tracing configuration but only if enabled. - if cfg.ExperimentalEnableDistributedTracing { - if err := validateTracingConfig(cfg.ExperimentalDistributedTracingSamplingRatePerMillion); err != nil { + if cfg.EnableDistributedTracing { + if err := validateTracingConfig(cfg.DistributedTracingSamplingRatePerMillion); err != nil { return fmt.Errorf("distributed tracing configurition is not valid: (%w)", err) } } @@ -1219,10 +1043,7 @@ func (cfg *Config) Validate() error { if cfg.ServerFeatureGate.Enabled(features.LeaseCheckpointPersist) && !cfg.ServerFeatureGate.Enabled(features.LeaseCheckpoint) { return fmt.Errorf("enabling feature gate LeaseCheckpointPersist requires enabling feature gate LeaseCheckpoint") } - // TODO: delete in v3.7 - if cfg.ExperimentalCompactHashCheckTime <= 0 { - return fmt.Errorf("--experimental-compact-hash-check-time must be >0 (set to %v)", cfg.ExperimentalCompactHashCheckTime) - } + if cfg.CompactHashCheckTime <= 0 { return fmt.Errorf("--compact-hash-check-time must be >0 (set to %v)", cfg.CompactHashCheckTime) } @@ -1383,7 +1204,7 @@ func (cfg *Config) InitialClusterFromName(name string) (ret string) { // non-loopback address. Otherwise, it defaults to empty string and the // LocalAddr used will be the default for the Golang HTTP client. func (cfg *Config) InferLocalAddr() string { - if !cfg.ExperimentalSetMemberLocalAddr { + if !cfg.ServerFeatureGate.Enabled(features.SetMemberLocalAddr) { return "" } diff --git a/server/embed/config_logging.go b/server/embed/config_logging.go index ddf19cdbb525..c9da6260d519 100644 --- a/server/embed/config_logging.go +++ b/server/embed/config_logging.go @@ -216,12 +216,6 @@ func NewZapLoggerBuilder(lg *zap.Logger) func(*Config) error { } } -// NewZapCoreLoggerBuilder - is a deprecated setter for the logger. -// Deprecated: Use simpler NewZapLoggerBuilder. To be removed in etcd-3.6. -func NewZapCoreLoggerBuilder(lg *zap.Logger, _ zapcore.Core, _ zapcore.WriteSyncer) func(*Config) error { - return NewZapLoggerBuilder(lg) -} - // SetupGlobalLoggers configures 'global' loggers (grpc, zapGlobal) based on the cfg. // // The method is not executed by embed server by default (since 3.5) to diff --git a/server/embed/config_test.go b/server/embed/config_test.go index bb098719c924..729595aab22c 100644 --- a/server/embed/config_test.go +++ b/server/embed/config_test.go @@ -22,7 +22,6 @@ import ( "net" "net/url" "os" - "strconv" "testing" "time" @@ -97,21 +96,14 @@ func TestConfigFileOtherFields(t *testing.T) { func TestConfigFileFeatureGates(t *testing.T) { testCases := []struct { - name string - serverFeatureGatesJSON string - experimentalStopGRPCServiceOnDefrag string - experimentalInitialCorruptCheck string - experimentalCompactHashCheckEnabled string - experimentalTxnModeWriteWithSharedBuffer string - experimentalEnableLeaseCheckpoint string - experimentalEnableLeaseCheckpointPersist string - expectErr bool - expectedFeatures map[featuregate.Feature]bool + name string + serverFeatureGatesJSON string + expectErr bool + expectedFeatures map[featuregate.Feature]bool }{ { name: "default", expectedFeatures: map[featuregate.Feature]bool{ - features.DistributedTracing: false, features.StopGRPCServiceOnDefrag: false, features.InitialCorruptCheck: false, features.TxnModeWriteWithSharedBuffer: true, @@ -119,93 +111,6 @@ func TestConfigFileFeatureGates(t *testing.T) { features.LeaseCheckpointPersist: false, }, }, - { - name: "cannot set both experimental flag and feature gate flag for StopGRPCServiceOnDefrag", - serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true", - experimentalStopGRPCServiceOnDefrag: "false", - expectErr: true, - }, - { - name: "cannot set both experimental flag and feature gate flag for InitialCorruptCheck", - serverFeatureGatesJSON: "InitialCorruptCheck=true", - experimentalInitialCorruptCheck: "false", - expectErr: true, - }, - { - name: "cannot set both experimental flag and feature gate flag for TxnModeWriteWithSharedBuffer", - serverFeatureGatesJSON: "TxnModeWriteWithSharedBuffer=true", - experimentalTxnModeWriteWithSharedBuffer: "false", - expectErr: true, - }, - { - name: "ok to set different experimental flag and feature gate flag", - serverFeatureGatesJSON: "DistributedTracing=true", - experimentalStopGRPCServiceOnDefrag: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.DistributedTracing: true, - features.StopGRPCServiceOnDefrag: true, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "ok to set different multiple experimental flags and feature gate flags", - serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true,TxnModeWriteWithSharedBuffer=true,LeaseCheckpoint=true", - experimentalCompactHashCheckEnabled: "true", - experimentalInitialCorruptCheck: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - features.CompactHashCheck: true, - features.InitialCorruptCheck: true, - features.TxnModeWriteWithSharedBuffer: true, - features.LeaseCheckpoint: true, - }, - }, - { - name: "can set feature gate StopGRPCServiceOnDefrag to true from experimental flag", - experimentalStopGRPCServiceOnDefrag: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate StopGRPCServiceOnDefrag to false from experimental flag", - experimentalStopGRPCServiceOnDefrag: "false", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: false, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate experimentalInitialCorruptCheck to true from experimental flag", - experimentalInitialCorruptCheck: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.InitialCorruptCheck: true, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate experimentalInitialCorruptCheck to false from experimental flag", - experimentalInitialCorruptCheck: "false", - expectedFeatures: map[featuregate.Feature]bool{ - features.InitialCorruptCheck: false, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate TxnModeWriteWithSharedBuffer to true from experimental flag", - experimentalTxnModeWriteWithSharedBuffer: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate TxnModeWriteWithSharedBuffer to false from experimental flag", - experimentalTxnModeWriteWithSharedBuffer: "false", - expectedFeatures: map[featuregate.Feature]bool{ - features.TxnModeWriteWithSharedBuffer: false, - }, - }, { name: "can set feature gate StopGRPCServiceOnDefrag to true from feature gate flag", serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true", @@ -244,28 +149,6 @@ func TestConfigFileFeatureGates(t *testing.T) { features.TxnModeWriteWithSharedBuffer: false, }, }, - { - name: "cannot set both experimental flag and feature gate flag for ExperimentalCompactHashCheckEnabled", - serverFeatureGatesJSON: "CompactHashCheck=true", - experimentalCompactHashCheckEnabled: "false", - expectErr: true, - }, - { - name: "can set feature gate experimentalCompactHashCheckEnabled to true from experimental flag", - experimentalCompactHashCheckEnabled: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.CompactHashCheck: true, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, - { - name: "can set feature gate experimentalCompactHashCheckEnabled to false from experimental flag", - experimentalCompactHashCheckEnabled: "false", - expectedFeatures: map[featuregate.Feature]bool{ - features.CompactHashCheck: false, - features.TxnModeWriteWithSharedBuffer: true, - }, - }, { name: "can set feature gate CompactHashCheck to true from feature gate flag", serverFeatureGatesJSON: "CompactHashCheck=true", @@ -274,17 +157,6 @@ func TestConfigFileFeatureGates(t *testing.T) { features.TxnModeWriteWithSharedBuffer: true, }, }, - { - name: "can set feature gate experimentalEnableLeaseCheckpoint and experimentalEnableLeaseCheckpointPersist to true from experimental flag", - experimentalEnableLeaseCheckpoint: "true", - experimentalEnableLeaseCheckpointPersist: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.CompactHashCheck: false, - features.TxnModeWriteWithSharedBuffer: true, - features.LeaseCheckpoint: true, - features.LeaseCheckpointPersist: true, - }, - }, { name: "can set feature gate LeaseCheckpoint and LeaseCheckpointPersist to true from feature gate flag", serverFeatureGatesJSON: "LeaseCheckpointPersist=true,LeaseCheckpoint=true", @@ -294,75 +166,15 @@ func TestConfigFileFeatureGates(t *testing.T) { features.LeaseCheckpointPersist: true, }, }, - { - name: "cannot set feature gate experimentalEnableLeaseCheckpoint=false and experimentalEnableLeaseCheckpointPersist=true", - experimentalEnableLeaseCheckpoint: "false", - experimentalEnableLeaseCheckpointPersist: "true", - expectErr: true, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { yc := struct { - ExperimentalStopGRPCServiceOnDefrag *bool `json:"experimental-stop-grpc-service-on-defrag,omitempty"` - ExperimentalInitialCorruptCheck *bool `json:"experimental-initial-corrupt-check,omitempty"` - ExperimentalCompactHashCheckEnabled *bool `json:"experimental-compact-hash-check-enabled,omitempty"` - ExperimentalTxnModeWriteWithSharedBuffer *bool `json:"experimental-txn-mode-write-with-shared-buffer,omitempty"` - ExperimentalEnableLeaseCheckpoint *bool `json:"experimental-enable-lease-checkpoint,omitempty"` - ExperimentalEnableLeaseCheckpointPersist *bool `json:"experimental-enable-lease-checkpoint-persist,omitempty"` - ServerFeatureGatesJSON string `json:"feature-gates"` + ServerFeatureGatesJSON string `json:"feature-gates"` }{ ServerFeatureGatesJSON: tc.serverFeatureGatesJSON, } - if tc.experimentalInitialCorruptCheck != "" { - experimentalInitialCorruptCheck, err := strconv.ParseBool(tc.experimentalInitialCorruptCheck) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalInitialCorruptCheck = &experimentalInitialCorruptCheck - } - - if tc.experimentalTxnModeWriteWithSharedBuffer != "" { - experimentalTxnModeWriteWithSharedBuffer, err := strconv.ParseBool(tc.experimentalTxnModeWriteWithSharedBuffer) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalTxnModeWriteWithSharedBuffer = &experimentalTxnModeWriteWithSharedBuffer - } - - if tc.experimentalStopGRPCServiceOnDefrag != "" { - experimentalStopGRPCServiceOnDefrag, err := strconv.ParseBool(tc.experimentalStopGRPCServiceOnDefrag) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalStopGRPCServiceOnDefrag = &experimentalStopGRPCServiceOnDefrag - } - - if tc.experimentalCompactHashCheckEnabled != "" { - experimentalCompactHashCheckEnabled, err := strconv.ParseBool(tc.experimentalCompactHashCheckEnabled) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalCompactHashCheckEnabled = &experimentalCompactHashCheckEnabled - } - - if tc.experimentalEnableLeaseCheckpoint != "" { - experimentalEnableLeaseCheckpoint, err := strconv.ParseBool(tc.experimentalEnableLeaseCheckpoint) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalEnableLeaseCheckpoint = &experimentalEnableLeaseCheckpoint - } - - if tc.experimentalEnableLeaseCheckpointPersist != "" { - experimentalEnableLeaseCheckpointPersist, err := strconv.ParseBool(tc.experimentalEnableLeaseCheckpointPersist) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalEnableLeaseCheckpointPersist = &experimentalEnableLeaseCheckpointPersist - } - b, err := yaml.Marshal(&yc) if err != nil { t.Fatal(err) @@ -440,117 +252,117 @@ func TestInferLocalAddr(t *testing.T) { tests := []struct { name string advertisePeerURLs []string - setMemberLocalAddr bool + serverFeatureGates string expectedLocalAddr string }{ { - "defaults, ExperimentalSetMemberLocalAddr=false ", + "defaults, SetMemberLocalAddr=false ", []string{DefaultInitialAdvertisePeerURLs}, - false, + "SetMemberLocalAddr=false", "", }, { - "IPv4 address, ExperimentalSetMemberLocalAddr=false ", + "IPv4 address, SetMemberLocalAddr=false ", []string{"https://192.168.100.110:2380"}, - false, + "SetMemberLocalAddr=false", "", }, { - "defaults, ExperimentalSetMemberLocalAddr=true", + "defaults, SetMemberLocalAddr=true", []string{DefaultInitialAdvertisePeerURLs}, - true, + "SetMemberLocalAddr=true", "", }, { - "IPv4 unspecified address, ExperimentalSetMemberLocalAddr=true", + "IPv4 unspecified address, SetMemberLocalAddr=true", []string{"https://0.0.0.0:2380"}, - true, + "SetMemberLocalAddr=true", "", }, { - "IPv6 unspecified address, ExperimentalSetMemberLocalAddr=true", + "IPv6 unspecified address, SetMemberLocalAddr=true", []string{"https://[::]:2380"}, - true, + "SetMemberLocalAddr=true", "", }, { - "IPv4 loopback address, ExperimentalSetMemberLocalAddr=true", + "IPv4 loopback address, SetMemberLocalAddr=true", []string{"https://127.0.0.1:2380"}, - true, + "SetMemberLocalAddr=true", "", }, { - "IPv6 loopback address, ExperimentalSetMemberLocalAddr=true", + "IPv6 loopback address, SetMemberLocalAddr=true", []string{"https://[::1]:2380"}, - true, + "SetMemberLocalAddr=true", "", }, { - "IPv4 address, ExperimentalSetMemberLocalAddr=true", + "IPv4 address, SetMemberLocalAddr=true", []string{"https://192.168.100.110:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "Hostname only, ExperimentalSetMemberLocalAddr=true", + "Hostname only, SetMemberLocalAddr=true", []string{"https://123-host-3.corp.internal:2380"}, - true, + "SetMemberLocalAddr=true", "", }, { - "Hostname and IPv4 address, ExperimentalSetMemberLocalAddr=true", + "Hostname and IPv4 address, SetMemberLocalAddr=true", []string{"https://123-host-3.corp.internal:2380", "https://192.168.100.110:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "IPv4 address and Hostname, ExperimentalSetMemberLocalAddr=true", + "IPv4 address and Hostname, SetMemberLocalAddr=true", []string{"https://192.168.100.110:2380", "https://123-host-3.corp.internal:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "IPv4 and IPv6 addresses, ExperimentalSetMemberLocalAddr=true", + "IPv4 and IPv6 addresses, SetMemberLocalAddr=true", []string{"https://192.168.100.110:2380", "https://[2001:db8:85a3::8a2e:370:7334]:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "IPv6 and IPv4 addresses, ExperimentalSetMemberLocalAddr=true", + "IPv6 and IPv4 addresses, SetMemberLocalAddr=true", // IPv4 addresses will always sort before IPv6 ones anyway []string{"https://[2001:db8:85a3::8a2e:370:7334]:2380", "https://192.168.100.110:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "Hostname, IPv4 and IPv6 addresses, ExperimentalSetMemberLocalAddr=true", + "Hostname, IPv4 and IPv6 addresses, SetMemberLocalAddr=true", []string{"https://123-host-3.corp.internal:2380", "https://192.168.100.110:2380", "https://[2001:db8:85a3::8a2e:370:7334]:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "Hostname, IPv6 and IPv4 addresses, ExperimentalSetMemberLocalAddr=true", + "Hostname, IPv6 and IPv4 addresses, SetMemberLocalAddr=true", // IPv4 addresses will always sort before IPv6 ones anyway []string{"https://123-host-3.corp.internal:2380", "https://[2001:db8:85a3::8a2e:370:7334]:2380", "https://192.168.100.110:2380"}, - true, + "SetMemberLocalAddr=true", "192.168.100.110", }, { - "IPv6 address, ExperimentalSetMemberLocalAddr=true", + "IPv6 address, SetMemberLocalAddr=true", []string{"https://[2001:db8:85a3::8a2e:370:7334]:2380"}, - true, + "SetMemberLocalAddr=true", "2001:db8:85a3::8a2e:370:7334", }, { - "Hostname and IPv6 address, ExperimentalSetMemberLocalAddr=true", + "Hostname and IPv6 address, SetMemberLocalAddr=true", []string{"https://123-host-3.corp.internal:2380", "https://[2001:db8:85a3::8a2e:370:7334]:2380"}, - true, + "SetMemberLocalAddr=true", "2001:db8:85a3::8a2e:370:7334", }, { - "IPv6 address and Hostname, ExperimentalSetMemberLocalAddr=true", + "IPv6 address and Hostname, SetMemberLocalAddr=true", []string{"https://[2001:db8:85a3::8a2e:370:7334]:2380", "https://123-host-3.corp.internal:2380"}, - true, + "SetMemberLocalAddr=true", "2001:db8:85a3::8a2e:370:7334", }, } @@ -559,7 +371,7 @@ func TestInferLocalAddr(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := NewConfig() cfg.AdvertisePeerUrls = types.MustNewURLs(tt.advertisePeerURLs) - cfg.ExperimentalSetMemberLocalAddr = tt.setMemberLocalAddr + cfg.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(tt.serverFeatureGates) require.NoError(t, cfg.Validate()) require.Equal(t, tt.expectedLocalAddr, cfg.InferLocalAddr()) @@ -567,6 +379,29 @@ func TestInferLocalAddr(t *testing.T) { } } +func TestSetMemberLocalAddrValidate(t *testing.T) { + tcs := []struct { + name string + serverFeatureGates string + }{ + { + name: "Default config should pass", + }, + { + name: "Enabling SetMemberLocalAddr should pass", + serverFeatureGates: "SetMemberLocalAddr=true", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + cfg := *NewConfig() + cfg.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(tc.serverFeatureGates) + err := cfg.Validate() + require.NoError(t, err) + }) + } +} + func (s *securityConfig) equals(t *transport.TLSInfo) bool { return s.CertFile == t.CertFile && s.CertAuth == t.ClientCertAuth && @@ -591,7 +426,7 @@ func compareSlices(slice1, slice2 []string) bool { } func mustCreateCfgFile(t *testing.T, b []byte) *os.File { - tmpfile, err := os.CreateTemp("", "servercfg") + tmpfile, err := os.CreateTemp(t.TempDir(), "servercfg") if err != nil { t.Fatal(err) } @@ -819,7 +654,7 @@ func TestLogRotation(t *testing.T) { logOutputs: []string{"/tmp/path"}, logRotationConfig: `{"maxsize": true}`, wantErr: true, - wantErrMsg: errors.New("invalid log rotation config: json: cannot unmarshal bool into Go struct field logRotationConfig.maxsize of type int"), + wantErrMsg: errors.New("invalid log rotation config: json: cannot unmarshal bool into Go struct field logRotationConfig.Logger.maxsize of type int"), }, { name: "improperly formatted logger config", @@ -937,104 +772,6 @@ func TestUndefinedAutoCompactionModeValidate(t *testing.T) { require.Error(t, err) } -func TestSetFeatureGatesFromExperimentalFlags(t *testing.T) { - testCases := []struct { - name string - featureGatesFlag string - experimentalStopGRPCServiceOnDefrag string - expectErr bool - expectedFeatures map[featuregate.Feature]bool - }{ - { - name: "default", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: false, - "TestAlpha": false, - "TestBeta": true, - }, - }, - { - name: "cannot set experimental flag and feature gate to true at the same time", - featureGatesFlag: "StopGRPCServiceOnDefrag=true", - experimentalStopGRPCServiceOnDefrag: "true", - expectErr: true, - }, - { - name: "cannot set experimental flag and feature gate to false at the same time", - featureGatesFlag: "StopGRPCServiceOnDefrag=false", - experimentalStopGRPCServiceOnDefrag: "false", - expectErr: true, - }, - { - name: "cannot set experimental flag and feature gate to different values at the same time", - featureGatesFlag: "StopGRPCServiceOnDefrag=true", - experimentalStopGRPCServiceOnDefrag: "false", - expectErr: true, - }, - { - name: "can set experimental flag and other feature gates", - featureGatesFlag: "TestAlpha=true,TestBeta=false", - experimentalStopGRPCServiceOnDefrag: "true", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - "TestAlpha": true, - "TestBeta": false, - }, - }, - { - name: "can set feature gate when its experimental flag is not explicitly set", - featureGatesFlag: "TestAlpha=true,StopGRPCServiceOnDefrag=true", - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - "TestAlpha": true, - "TestBeta": true, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - fg := features.NewDefaultServerFeatureGate("test", nil) - err := fg.(featuregate.MutableFeatureGate).Add( - map[featuregate.Feature]featuregate.FeatureSpec{ - "TestAlpha": {Default: false, PreRelease: featuregate.Alpha}, - "TestBeta": {Default: true, PreRelease: featuregate.Beta}, - }) - require.NoError(t, err) - - fg.(featuregate.MutableFeatureGate).Set(tc.featureGatesFlag) - var getExperimentalFlagVal func(flagName string) *bool - if tc.experimentalStopGRPCServiceOnDefrag == "" { - // experimental flag is not explicitly set - getExperimentalFlagVal = func(flagName string) *bool { - return nil - } - } else { - // mexperimental flag is explicitly set - getExperimentalFlagVal = func(flagName string) *bool { - // only the experimental-stop-grpc-service-on-defrag flag can be set in this test. - if flagName != "experimental-stop-grpc-service-on-defrag" { - return nil - } - flagVal, parseErr := strconv.ParseBool(tc.experimentalStopGRPCServiceOnDefrag) - require.NoError(t, parseErr) - return &flagVal - } - } - err = SetFeatureGatesFromExperimentalFlags(fg, getExperimentalFlagVal, tc.featureGatesFlag) - if tc.expectErr { - require.Error(t, err) - return - } - require.NoError(t, err) - for k, v := range tc.expectedFeatures { - if fg.Enabled(k) != v { - t.Errorf("expected feature gate %s=%v, got %v", k, v, fg.Enabled(k)) - } - } - }) - } -} - func TestMatchNewConfigAddFlags(t *testing.T) { cfg := NewConfig() fs := flag.NewFlagSet("etcd", flag.ContinueOnError) diff --git a/server/embed/config_tracing.go b/server/embed/config_tracing.go index 7fd86e8610f0..0ca90fdc52af 100644 --- a/server/embed/config_tracing.go +++ b/server/embed/config_tracing.go @@ -49,7 +49,7 @@ type tracingExporter struct { func newTracingExporter(ctx context.Context, cfg *Config) (*tracingExporter, error) { exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), - otlptracegrpc.WithEndpoint(cfg.ExperimentalDistributedTracingAddress), + otlptracegrpc.WithEndpoint(cfg.DistributedTracingAddress), ) if err != nil { return nil, err @@ -57,14 +57,14 @@ func newTracingExporter(ctx context.Context, cfg *Config) (*tracingExporter, err res, err := resource.New(ctx, resource.WithAttributes( - semconv.ServiceNameKey.String(cfg.ExperimentalDistributedTracingServiceName), + semconv.ServiceNameKey.String(cfg.DistributedTracingServiceName), ), ) if err != nil { return nil, err } - if resWithIDKey := determineResourceWithIDKey(cfg.ExperimentalDistributedTracingServiceInstanceID); resWithIDKey != nil { + if resWithIDKey := determineResourceWithIDKey(cfg.DistributedTracingServiceInstanceID); resWithIDKey != nil { // Merge resources into a new // resource in case of duplicates. res, err = resource.Merge(res, resWithIDKey) @@ -77,7 +77,7 @@ func newTracingExporter(ctx context.Context, cfg *Config) (*tracingExporter, err tracesdk.WithBatcher(exporter), tracesdk.WithResource(res), tracesdk.WithSampler( - tracesdk.ParentBased(determineSampler(cfg.ExperimentalDistributedTracingSamplingRatePerMillion)), + tracesdk.ParentBased(determineSampler(cfg.DistributedTracingSamplingRatePerMillion)), ), ) @@ -95,10 +95,10 @@ func newTracingExporter(ctx context.Context, cfg *Config) (*tracingExporter, err cfg.logger.Debug( "distributed tracing enabled", - zap.String("address", cfg.ExperimentalDistributedTracingAddress), - zap.String("service-name", cfg.ExperimentalDistributedTracingServiceName), - zap.String("service-instance-id", cfg.ExperimentalDistributedTracingServiceInstanceID), - zap.Int("sampling-rate", cfg.ExperimentalDistributedTracingSamplingRatePerMillion), + zap.String("address", cfg.DistributedTracingAddress), + zap.String("service-name", cfg.DistributedTracingServiceName), + zap.String("service-instance-id", cfg.DistributedTracingServiceInstanceID), + zap.Int("sampling-rate", cfg.DistributedTracingSamplingRatePerMillion), ) return &tracingExporter{ diff --git a/server/embed/etcd.go b/server/embed/etcd.go index 0e22388ad7b2..6a5b2f1c356d 100644 --- a/server/embed/etcd.go +++ b/server/embed/etcd.go @@ -180,66 +180,66 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { backendFreelistType := parseBackendFreelistType(cfg.BackendFreelistType) srvcfg := config.ServerConfig{ - Name: cfg.Name, - ClientURLs: cfg.AdvertiseClientUrls, - PeerURLs: cfg.AdvertisePeerUrls, - DataDir: cfg.Dir, - DedicatedWALDir: cfg.WalDir, - SnapshotCount: cfg.SnapshotCount, - SnapshotCatchUpEntries: cfg.SnapshotCatchUpEntries, - MaxSnapFiles: cfg.MaxSnapFiles, - MaxWALFiles: cfg.MaxWalFiles, - InitialPeerURLsMap: urlsmap, - InitialClusterToken: token, - DiscoveryURL: cfg.Durl, - DiscoveryProxy: cfg.Dproxy, - DiscoveryCfg: cfg.DiscoveryCfg, - NewCluster: cfg.IsNewCluster(), - PeerTLSInfo: cfg.PeerTLSInfo, - TickMs: cfg.TickMs, - ElectionTicks: cfg.ElectionTicks(), - InitialElectionTickAdvance: cfg.InitialElectionTickAdvance, - AutoCompactionRetention: autoCompactionRetention, - AutoCompactionMode: cfg.AutoCompactionMode, - QuotaBackendBytes: cfg.QuotaBackendBytes, - BackendBatchLimit: cfg.BackendBatchLimit, - BackendFreelistType: backendFreelistType, - BackendBatchInterval: cfg.BackendBatchInterval, - MaxTxnOps: cfg.MaxTxnOps, - MaxRequestBytes: cfg.MaxRequestBytes, - MaxConcurrentStreams: cfg.MaxConcurrentStreams, - SocketOpts: cfg.SocketOpts, - StrictReconfigCheck: cfg.StrictReconfigCheck, - ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth, - AuthToken: cfg.AuthToken, - BcryptCost: cfg.BcryptCost, - TokenTTL: cfg.AuthTokenTTL, - CORS: cfg.CORS, - HostWhitelist: cfg.HostWhitelist, - CorruptCheckTime: cfg.CorruptCheckTime, - CompactHashCheckTime: cfg.CompactHashCheckTime, - PreVote: cfg.PreVote, - Logger: cfg.logger, - ForceNewCluster: cfg.ForceNewCluster, - EnableGRPCGateway: cfg.EnableGRPCGateway, - ExperimentalEnableDistributedTracing: cfg.ExperimentalEnableDistributedTracing, - UnsafeNoFsync: cfg.UnsafeNoFsync, - CompactionBatchLimit: cfg.CompactionBatchLimit, - CompactionSleepInterval: cfg.CompactionSleepInterval, - WatchProgressNotifyInterval: cfg.WatchProgressNotifyInterval, - DowngradeCheckTime: cfg.DowngradeCheckTime, - WarningApplyDuration: cfg.WarningApplyDuration, - WarningUnaryRequestDuration: cfg.WarningUnaryRequestDuration, - MemoryMlock: cfg.MemoryMlock, - BootstrapDefragThresholdMegabytes: cfg.BootstrapDefragThresholdMegabytes, - MaxLearners: cfg.MaxLearners, - V2Deprecation: cfg.V2DeprecationEffective(), - ExperimentalLocalAddress: cfg.InferLocalAddr(), - ServerFeatureGate: cfg.ServerFeatureGate, - Metrics: cfg.Metrics, - } - - if srvcfg.ExperimentalEnableDistributedTracing { + Name: cfg.Name, + ClientURLs: cfg.AdvertiseClientUrls, + PeerURLs: cfg.AdvertisePeerUrls, + DataDir: cfg.Dir, + DedicatedWALDir: cfg.WalDir, + SnapshotCount: cfg.SnapshotCount, + SnapshotCatchUpEntries: cfg.SnapshotCatchUpEntries, + MaxSnapFiles: cfg.MaxSnapFiles, + MaxWALFiles: cfg.MaxWalFiles, + InitialPeerURLsMap: urlsmap, + InitialClusterToken: token, + DiscoveryURL: cfg.Durl, + DiscoveryProxy: cfg.Dproxy, + DiscoveryCfg: cfg.DiscoveryCfg, + NewCluster: cfg.IsNewCluster(), + PeerTLSInfo: cfg.PeerTLSInfo, + TickMs: cfg.TickMs, + ElectionTicks: cfg.ElectionTicks(), + InitialElectionTickAdvance: cfg.InitialElectionTickAdvance, + AutoCompactionRetention: autoCompactionRetention, + AutoCompactionMode: cfg.AutoCompactionMode, + QuotaBackendBytes: cfg.QuotaBackendBytes, + BackendBatchLimit: cfg.BackendBatchLimit, + BackendFreelistType: backendFreelistType, + BackendBatchInterval: cfg.BackendBatchInterval, + MaxTxnOps: cfg.MaxTxnOps, + MaxRequestBytes: cfg.MaxRequestBytes, + MaxConcurrentStreams: cfg.MaxConcurrentStreams, + SocketOpts: cfg.SocketOpts, + StrictReconfigCheck: cfg.StrictReconfigCheck, + ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth, + AuthToken: cfg.AuthToken, + BcryptCost: cfg.BcryptCost, + TokenTTL: cfg.AuthTokenTTL, + CORS: cfg.CORS, + HostWhitelist: cfg.HostWhitelist, + CorruptCheckTime: cfg.CorruptCheckTime, + CompactHashCheckTime: cfg.CompactHashCheckTime, + PreVote: cfg.PreVote, + Logger: cfg.logger, + ForceNewCluster: cfg.ForceNewCluster, + EnableGRPCGateway: cfg.EnableGRPCGateway, + EnableDistributedTracing: cfg.EnableDistributedTracing, + UnsafeNoFsync: cfg.UnsafeNoFsync, + CompactionBatchLimit: cfg.CompactionBatchLimit, + CompactionSleepInterval: cfg.CompactionSleepInterval, + WatchProgressNotifyInterval: cfg.WatchProgressNotifyInterval, + DowngradeCheckTime: cfg.DowngradeCheckTime, + WarningApplyDuration: cfg.WarningApplyDuration, + WarningUnaryRequestDuration: cfg.WarningUnaryRequestDuration, + MemoryMlock: cfg.MemoryMlock, + BootstrapDefragThresholdMegabytes: cfg.BootstrapDefragThresholdMegabytes, + MaxLearners: cfg.MaxLearners, + V2Deprecation: cfg.V2DeprecationEffective(), + LocalAddress: cfg.InferLocalAddr(), + ServerFeatureGate: cfg.ServerFeatureGate, + Metrics: cfg.Metrics, + } + + if srvcfg.EnableDistributedTracing { tctx := context.Background() tracingExporter, terr := newTracingExporter(tctx, cfg) if terr != nil { @@ -248,14 +248,14 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { e.tracingExporterShutdown = func() { tracingExporter.Close(tctx) } - srvcfg.ExperimentalTracerOptions = tracingExporter.opts + srvcfg.TracerOptions = tracingExporter.opts e.cfg.logger.Info( "distributed tracing setup enabled", ) } - srvcfg.PeerTLSInfo.LocalAddr = srvcfg.ExperimentalLocalAddress + srvcfg.PeerTLSInfo.LocalAddr = srvcfg.LocalAddress print(e.cfg.logger, *cfg, srvcfg, memberInitialized) @@ -348,8 +348,7 @@ func print(lg *zap.Logger, ec Config, sc config.ServerConfig, memberInitialized zap.Strings("advertise-client-urls", ec.getAdvertiseClientURLs()), zap.Strings("listen-client-urls", ec.getListenClientURLs()), zap.Strings("listen-metrics-urls", ec.getMetricsURLs()), - zap.Bool("experimental-set-member-localaddr", ec.ExperimentalSetMemberLocalAddr), - zap.String("experimental-local-address", sc.ExperimentalLocalAddress), + zap.String("local-address", sc.LocalAddress), zap.Strings("cors", cors), zap.Strings("host-whitelist", hss), zap.String("initial-cluster", sc.InitialPeerURLsMap.String()), diff --git a/server/etcdmain/config.go b/server/etcdmain/config.go index 9905009df8d1..9968cae3499e 100644 --- a/server/etcdmain/config.go +++ b/server/etcdmain/config.go @@ -62,18 +62,6 @@ var ( "snapshot-count": "--snapshot-count is deprecated in 3.6 and will be decommissioned in 3.7.", "max-snapshots": "--max-snapshots is deprecated in 3.6 and will be decommissioned in 3.7.", "v2-deprecation": "--v2-deprecation is deprecated and scheduled for removal in v3.8. The default value is enforced, ignoring user input.", - "experimental-compact-hash-check-enabled": "--experimental-compact-hash-check-enabled is deprecated in 3.6 and will be decommissioned in 3.7. Use '--feature-gates=CompactHashCheck=true' instead.", - "experimental-compact-hash-check-time": "--experimental-compact-hash-check-time is deprecated in 3.6 and will be decommissioned in 3.7. Use '--compact-hash-check-time' instead.", - "experimental-txn-mode-write-with-shared-buffer": "--experimental-txn-mode-write-with-shared-buffer is deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=TxnModeWriteWithSharedBuffer=true' instead.", - "experimental-corrupt-check-time": "--experimental-corrupt-check-time is deprecated in v3.6 and will be decommissioned in v3.7. Use '--corrupt-check-time' instead.", - "experimental-compaction-batch-limit": "--experimental-compaction-batch-limit is deprecated in v3.6 and will be decommissioned in v3.7. Use '--compaction-batch-limit' instead.", - "experimental-watch-progress-notify-interval": "--experimental-watch-progress-notify-interval is deprecated in v3.6 and will be decommissioned in v3.7. Use '--watch-progress-notify-interval' instead.", - "experimental-warning-apply-duration": "--experimental-warning-apply-duration is deprecated in v3.6 and will be decommissioned in v3.7. Use '--warning-apply-duration' instead.", - "experimental-bootstrap-defrag-threshold-megabytes": "--experimental-bootstrap-defrag-threshold-megabytes is deprecated in v3.6 and will be decommissioned in v3.7. Use '--bootstrap-defrag-threshold-megabytes' instead.", - "experimental-max-learners": "--experimental-max-learners is deprecated in v3.6 and will be decommissioned in v3.7. Use '--max-learners' instead.", - "experimental-memory-mlock": "--experimental-memory-mlock is deprecated in v3.6 and will be decommissioned in v3.7. Use '--memory-mlock' instead.", - "experimental-compaction-sleep-interval": "--experimental-compaction-sleep-interval is deprecated in v3.6 and will be decommissioned in v3.7. Use 'compaction-sleep-interval' instead.", - "experimental-downgrade-check-time": "--experimental-downgrade-check-time is deprecated in v3.6 and will be decommissioned in v3.7. Use '--downgrade-check-time' instead.", } ) @@ -177,58 +165,10 @@ func (cfg *config) parse(arguments []string) error { err = cfg.configFromCmdLine() } - // params related to experimental flag deprecation - // TODO: delete in v3.7 - if cfg.ec.FlagsExplicitlySet["experimental-compact-hash-check-time"] { - cfg.ec.CompactHashCheckTime = cfg.ec.ExperimentalCompactHashCheckTime - } - - if cfg.ec.FlagsExplicitlySet["experimental-corrupt-check-time"] { - cfg.ec.CorruptCheckTime = cfg.ec.ExperimentalCorruptCheckTime - } - - if cfg.ec.FlagsExplicitlySet["experimental-compaction-batch-limit"] { - cfg.ec.CompactionBatchLimit = cfg.ec.ExperimentalCompactionBatchLimit - } - - if cfg.ec.FlagsExplicitlySet["experimental-watch-progress-notify-interval"] { - cfg.ec.WatchProgressNotifyInterval = cfg.ec.ExperimentalWatchProgressNotifyInterval - } - - if cfg.ec.FlagsExplicitlySet["experimental-warning-apply-duration"] { - cfg.ec.WarningApplyDuration = cfg.ec.ExperimentalWarningApplyDuration - } - - if cfg.ec.FlagsExplicitlySet["experimental-bootstrap-defrag-threshold-megabytes"] { - cfg.ec.BootstrapDefragThresholdMegabytes = cfg.ec.ExperimentalBootstrapDefragThresholdMegabytes - } - if cfg.ec.FlagsExplicitlySet["experimental-peer-skip-client-san-verification"] { - cfg.ec.PeerTLSInfo.SkipClientSANVerify = cfg.ec.ExperimentalPeerSkipClientSanVerification - } - - if cfg.ec.FlagsExplicitlySet["experimental-max-learners"] { - cfg.ec.MaxLearners = cfg.ec.ExperimentalMaxLearners - } - - if cfg.ec.FlagsExplicitlySet["experimental-memory-mlock"] { - cfg.ec.MemoryMlock = cfg.ec.ExperimentalMemoryMlock - } - - if cfg.ec.FlagsExplicitlySet["experimental-compaction-sleep-interval"] { - cfg.ec.CompactionSleepInterval = cfg.ec.ExperimentalCompactionSleepInterval - } - - if cfg.ec.FlagsExplicitlySet["experimental-downgrade-check-time"] { - cfg.ec.DowngradeCheckTime = cfg.ec.ExperimentalDowngradeCheckTime - } - // `V2Deprecation` (--v2-deprecation) is deprecated and scheduled for removal in v3.8. The default value is enforced, ignoring user input. cfg.ec.V2Deprecation = cconfig.V2DeprDefault - cfg.ec.WarningUnaryRequestDuration, perr = cfg.parseWarningUnaryRequestDuration() - if perr != nil { - return perr - } + cfg.ec.WarningUnaryRequestDuration = cfg.parseWarningUnaryRequestDuration() // Check for deprecated options from both command line and config file var warningsForDeprecatedOpts []string @@ -323,21 +263,6 @@ func (cfg *config) configFromCmdLine() error { cfg.ec.FlagsExplicitlySet[f.Name] = true }) - getBoolFlagVal := func(flagName string) *bool { - boolVal, parseErr := flags.GetBoolFlagVal(cfg.cf.flagSet, flagName) - if parseErr != nil { - panic(parseErr) - } - return boolVal - } - - // SetFeatureGatesFromExperimentalFlags validates that cmd line flags for experimental feature and their feature gates are not explicitly set simultaneously, - // and passes the values of cmd line flags for experimental feature to the server feature gate. - err = embed.SetFeatureGatesFromExperimentalFlags(cfg.ec.ServerFeatureGate, getBoolFlagVal, cfg.cf.flagSet.Lookup(embed.ServerFeatureGateFlagName).Value.String()) - if err != nil { - return err - } - return cfg.validate() } @@ -358,23 +283,10 @@ func (cfg *config) validate() error { return cfg.ec.Validate() } -func (cfg *config) parseWarningUnaryRequestDuration() (time.Duration, error) { - if cfg.ec.ExperimentalWarningUnaryRequestDuration != 0 && cfg.ec.WarningUnaryRequestDuration != 0 { - return 0, errors.New( - "both --experimental-warning-unary-request-duration and --warning-unary-request-duration flags are set. " + - "Use only --warning-unary-request-duration") - } - +func (cfg *config) parseWarningUnaryRequestDuration() time.Duration { if cfg.ec.WarningUnaryRequestDuration != 0 { - return cfg.ec.WarningUnaryRequestDuration, nil - } - - if cfg.ec.ExperimentalWarningUnaryRequestDuration != 0 { - cfg.ec.GetLogger().Warn( - "--experimental-warning-unary-request-duration is deprecated, and will be decommissioned in v3.7. " + - "Use --warning-unary-request-duration instead.") - return cfg.ec.ExperimentalWarningUnaryRequestDuration, nil + return cfg.ec.WarningUnaryRequestDuration } - return embed.DefaultWarningUnaryRequestDuration, nil + return embed.DefaultWarningUnaryRequestDuration } diff --git a/server/etcdmain/config_test.go b/server/etcdmain/config_test.go index aa4aea00768c..643e89715568 100644 --- a/server/etcdmain/config_test.go +++ b/server/etcdmain/config_test.go @@ -21,10 +21,8 @@ import ( "net/url" "os" "reflect" - "strconv" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,7 +41,7 @@ func TestConfigParsingMemberFlags(t *testing.T) { "-max-wals=10", "-max-snapshots=10", "-snapshot-count=10", - "-experimental-snapshot-catchup-entries=1000", + "-snapshot-catchup-entries=1000", "-listen-peer-urls=http://localhost:8000,https://localhost:8001", "-listen-client-urls=http://localhost:7000,https://localhost:7001", "-listen-client-http-urls=http://localhost:7002,https://localhost:7003", @@ -67,7 +65,7 @@ func TestConfigFileMemberFields(t *testing.T) { MaxWALFiles uint `json:"max-wals"` Name string `json:"name"` SnapshotCount uint64 `json:"snapshot-count"` - SnapshotCatchUpEntries uint64 `json:"experimental-snapshot-catch-up-entries"` + SnapshotCatchUpEntries uint64 `json:"snapshot-catchup-entries"` ListenPeerURLs string `json:"listen-peer-urls"` ListenClientURLs string `json:"listen-client-urls"` ListenClientHTTPURLs string `json:"listen-client-http-urls"` @@ -412,46 +410,16 @@ func TestParseFeatureGateFlags(t *testing.T) { name: "default", expectedFeatures: map[featuregate.Feature]bool{ features.StopGRPCServiceOnDefrag: false, - features.DistributedTracing: false, - }, - }, - { - name: "cannot set both experimental flag and feature gate flag", - args: []string{ - "--experimental-stop-grpc-service-on-defrag=false", - "--feature-gates=StopGRPCServiceOnDefrag=true", - }, - expectErr: true, - }, - { - name: "ok to set different experimental flag and feature gate flag", - args: []string{ - "--experimental-stop-grpc-service-on-defrag=true", - "--feature-gates=DistributedTracing=true", - }, - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - features.DistributedTracing: true, - }, - }, - { - name: "can set feature gate from experimental flag", - args: []string{ - "--experimental-stop-grpc-service-on-defrag=true", - }, - expectedFeatures: map[featuregate.Feature]bool{ - features.StopGRPCServiceOnDefrag: true, - features.DistributedTracing: false, }, }, { name: "can set feature gate from feature gate flag", args: []string{ - "--feature-gates=StopGRPCServiceOnDefrag=true,DistributedTracing=true", + "--feature-gates=StopGRPCServiceOnDefrag=true,InitialCorruptCheck=true", }, expectedFeatures: map[featuregate.Feature]bool{ features.StopGRPCServiceOnDefrag: true, - features.DistributedTracing: true, + features.InitialCorruptCheck: true, }, }, } @@ -476,707 +444,8 @@ func TestParseFeatureGateFlags(t *testing.T) { } } -// TestDowngradeCheckTimeFlagMigration tests the migration from -// --experimental-downgrade-check-time to --downgrade-check-time -func TestDowngradeCheckTimeFlagMigration(t *testing.T) { - testCases := []struct { - name string - downgradeCheckTime string - experimentalDowngradeCheckTime string - wantErr bool - wantConfig time.Duration - }{ - { - name: "default", - wantConfig: embed.DefaultDowngradeCheckTime, - }, - { - name: "cannot set both experimental flag and non experimental flag", - experimentalDowngradeCheckTime: "1m", - downgradeCheckTime: "2m", - wantErr: true, - }, - { - name: "can set experimental flag", - experimentalDowngradeCheckTime: "1m", - wantConfig: time.Minute, - }, - { - name: "can set non-experimental flag", - downgradeCheckTime: "2m", - wantConfig: 2 * time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalDowngradeCheckTime time.Duration `json:"experimental-downgrade-check-time,omitempty"` - DowngradeCheckTime time.Duration `json:"downgrade-check-time,omitempty"` - }{} - - if tc.downgradeCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--downgrade-check-time=%s", tc.downgradeCheckTime)) - downgradeCheckTime, err := time.ParseDuration(tc.downgradeCheckTime) - require.NoError(t, err) - yc.DowngradeCheckTime = downgradeCheckTime - } - - if tc.experimentalDowngradeCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-downgrade-check-time=%s", tc.experimentalDowngradeCheckTime)) - experimentalDowngradeCheckTime, err := time.ParseDuration(tc.experimentalDowngradeCheckTime) - require.NoError(t, err) - yc.ExperimentalDowngradeCheckTime = experimentalDowngradeCheckTime - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.wantErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.wantConfig, cfgFromCmdLine.ec.DowngradeCheckTime) - require.Equal(t, tc.wantConfig, cfgFromFile.ec.DowngradeCheckTime) - }) - } -} - -// TestCompactHashCheckTimeFlagMigration tests the migration from -// --experimental-compact-hash-check-time to --compact-hash-check-time -// TODO: delete in v3.7 -func TestCompactHashCheckTimeFlagMigration(t *testing.T) { - testCases := []struct { - name string - compactHashCheckTime string - experimentalCompactHashCheckTime string - expectErr bool - expectedCompactHashCheckTime time.Duration - }{ - { - name: "default", - expectedCompactHashCheckTime: time.Minute, - }, - { - name: "cannot set both experimental flag and non experimental flag", - compactHashCheckTime: "2m", - experimentalCompactHashCheckTime: "3m", - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalCompactHashCheckTime: "3m", - expectedCompactHashCheckTime: 3 * time.Minute, - }, - { - name: "can set non experimental flag", - compactHashCheckTime: "2m", - expectedCompactHashCheckTime: 2 * time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time,omitempty"` - CompactHashCheckTime time.Duration `json:"compact-hash-check-time,omitempty"` - }{} - - if tc.compactHashCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--compact-hash-check-time=%s", tc.compactHashCheckTime)) - compactHashCheckTime, err := time.ParseDuration(tc.compactHashCheckTime) - require.NoError(t, err) - yc.CompactHashCheckTime = compactHashCheckTime - } - - if tc.experimentalCompactHashCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-compact-hash-check-time=%s", tc.experimentalCompactHashCheckTime)) - experimentalCompactHashCheckTime, err := time.ParseDuration(tc.experimentalCompactHashCheckTime) - require.NoError(t, err) - yc.ExperimentalCompactHashCheckTime = experimentalCompactHashCheckTime - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedCompactHashCheckTime, cfgFromCmdLine.ec.CompactHashCheckTime) - require.Equal(t, tc.expectedCompactHashCheckTime, cfgFromFile.ec.CompactHashCheckTime) - }) - } -} - -// TestCompactionSleepIntervalFlagMigration tests the migration from -// --experimental-compaction-sleep-interval to --compaction-sleep-interval -func TestCompactionSleepIntervalFlagMigration(t *testing.T) { - testCases := []struct { - name string - compactionSleepInterval string - experimentalCompactionSleepInterval string - wantErr bool - wantConfig time.Duration - }{ - { - name: "default", - wantConfig: time.Duration(0), - }, - { - name: "cannot set both experimental flag and non experimental flag", - experimentalCompactionSleepInterval: "30s", - compactionSleepInterval: "15s", - wantErr: true, - }, - { - name: "can set experimental flag", - experimentalCompactionSleepInterval: "30s", - wantConfig: 30 * time.Second, - }, - { - name: "can set non-experimental flag", - compactionSleepInterval: "1m", - wantConfig: time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalCompactionSleepInterval time.Duration `json:"experimental-compaction-sleep-interval,omitempty"` - CompactionSleepInterval time.Duration `json:"compaction-sleep-interval,omitempty"` - }{} - - if tc.compactionSleepInterval != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--compaction-sleep-interval=%s", tc.compactionSleepInterval)) - compactionSleepInterval, err := time.ParseDuration(tc.compactionSleepInterval) - require.NoError(t, err) - yc.CompactionSleepInterval = compactionSleepInterval - } - - if tc.experimentalCompactionSleepInterval != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-compaction-sleep-interval=%s", tc.experimentalCompactionSleepInterval)) - experimentalCompactionSleepInterval, err := time.ParseDuration(tc.experimentalCompactionSleepInterval) - require.NoError(t, err) - yc.ExperimentalCompactionSleepInterval = experimentalCompactionSleepInterval - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.wantErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.wantConfig, cfgFromCmdLine.ec.CompactionSleepInterval) - require.Equal(t, tc.wantConfig, cfgFromFile.ec.CompactionSleepInterval) - }) - } -} - -// TestCorruptCheckTimeFlagMigration tests the migration from -// --experimental-corrupt-check-time to --corrupt-check-time -// TODO: delete in v3.7 -func TestCorruptCheckTimeFlagMigration(t *testing.T) { - testCases := []struct { - name string - corruptCheckTime string - experimentalCorruptCheckTime string - expectErr bool - expectedCorruptCheckTime time.Duration - }{ - { - name: "cannot set both experimental flag and non experimental flag", - corruptCheckTime: "2m", - experimentalCorruptCheckTime: "3m", - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalCorruptCheckTime: "3m", - expectedCorruptCheckTime: 3 * time.Minute, - }, - { - name: "can set non experimental flag", - corruptCheckTime: "2m", - expectedCorruptCheckTime: 2 * time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time,omitempty"` - CorruptCheckTime time.Duration `json:"corrupt-check-time,omitempty"` - }{} - - if tc.corruptCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--corrupt-check-time=%s", tc.corruptCheckTime)) - corruptCheckTime, err := time.ParseDuration(tc.corruptCheckTime) - require.NoError(t, err) - yc.CorruptCheckTime = corruptCheckTime - } - - if tc.experimentalCorruptCheckTime != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-corrupt-check-time=%s", tc.experimentalCorruptCheckTime)) - experimentalCorruptCheckTime, err := time.ParseDuration(tc.experimentalCorruptCheckTime) - require.NoError(t, err) - yc.ExperimentalCorruptCheckTime = experimentalCorruptCheckTime - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedCorruptCheckTime, cfgFromCmdLine.ec.CorruptCheckTime) - require.Equal(t, tc.expectedCorruptCheckTime, cfgFromFile.ec.CorruptCheckTime) - }) - } -} - -// TestCompactionBatchLimitFlagMigration tests the migration from -// --experimental-compaction-batch-limit to --compaction-batch-limit -// TODO: delete in v3.7 -func TestCompactionBatchLimitFlagMigration(t *testing.T) { - testCases := []struct { - name string - compactionBatchLimit int - experimentalCompactionBatchLimit int - expectErr bool - expectedCompactionBatchLimit int - }{ - { - name: "cannot set both experimental flag and non experimental flag", - compactionBatchLimit: 1, - experimentalCompactionBatchLimit: 2, - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalCompactionBatchLimit: 2, - expectedCompactionBatchLimit: 2, - }, - { - name: "can set non experimental flag", - compactionBatchLimit: 1, - expectedCompactionBatchLimit: 1, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit,omitempty"` - CompactionBatchLimit int `json:"compaction-batch-limit,omitempty"` - }{} - - if tc.compactionBatchLimit != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--compaction-batch-limit=%d", tc.compactionBatchLimit)) - yc.CompactionBatchLimit = tc.compactionBatchLimit - } - - if tc.experimentalCompactionBatchLimit != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-compaction-batch-limit=%d", tc.experimentalCompactionBatchLimit)) - yc.ExperimentalCompactionBatchLimit = tc.experimentalCompactionBatchLimit - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedCompactionBatchLimit, cfgFromCmdLine.ec.CompactionBatchLimit) - require.Equal(t, tc.expectedCompactionBatchLimit, cfgFromFile.ec.CompactionBatchLimit) - }) - } -} - -// TestWatchProgressNotifyInterval tests the migration from -// --experimental-watch-progress-notify-interval to --watch-progress-notify-interval -// TODO: delete in v3.7 -func TestWatchProgressNotifyInterval(t *testing.T) { - testCases := []struct { - name string - watchProgressNotifyInterval string - experimentalWatchProgressNotifyInterval string - expectErr bool - expectedWatchProgressNotifyInterval time.Duration - }{ - { - name: "cannot set both experimental flag and non experimental flag", - watchProgressNotifyInterval: "2m", - experimentalWatchProgressNotifyInterval: "3m", - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalWatchProgressNotifyInterval: "3m", - expectedWatchProgressNotifyInterval: 3 * time.Minute, - }, - { - name: "can set non experimental flag", - watchProgressNotifyInterval: "2m", - expectedWatchProgressNotifyInterval: 2 * time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval,omitempty"` - WatchProgressNotifyInterval time.Duration `json:"watch-progress-notify-interval,omitempty"` - }{} - - if tc.watchProgressNotifyInterval != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--watch-progress-notify-interval=%s", tc.watchProgressNotifyInterval)) - watchProgressNotifyInterval, err := time.ParseDuration(tc.watchProgressNotifyInterval) - require.NoError(t, err) - yc.WatchProgressNotifyInterval = watchProgressNotifyInterval - } - - if tc.experimentalWatchProgressNotifyInterval != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-watch-progress-notify-interval=%s", tc.experimentalWatchProgressNotifyInterval)) - experimentalWatchProgressNotifyInterval, err := time.ParseDuration(tc.experimentalWatchProgressNotifyInterval) - require.NoError(t, err) - yc.ExperimentalWatchProgressNotifyInterval = experimentalWatchProgressNotifyInterval - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedWatchProgressNotifyInterval, cfgFromCmdLine.ec.WatchProgressNotifyInterval) - require.Equal(t, tc.expectedWatchProgressNotifyInterval, cfgFromFile.ec.WatchProgressNotifyInterval) - }) - } -} - -// TestWarningApplyDuration tests the migration from -// --experimental-warning-apply-duration to --warning-apply-duration -// TODO: delete in v3.7 -func TestWarningApplyDuration(t *testing.T) { - testCases := []struct { - name string - warningApplyDuration string - experimentalWarningApplyDuration string - expectErr bool - expectedWarningApplyDuration time.Duration - }{ - { - name: "cannot set both experimental flag and non experimental flag", - warningApplyDuration: "2m", - experimentalWarningApplyDuration: "3m", - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalWarningApplyDuration: "3m", - expectedWarningApplyDuration: 3 * time.Minute, - }, - { - name: "can set non experimental flag", - warningApplyDuration: "2m", - expectedWarningApplyDuration: 2 * time.Minute, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration,omitempty"` - WarningApplyDuration time.Duration `json:"warning-apply-duration,omitempty"` - }{} - - if tc.warningApplyDuration != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--warning-apply-duration=%s", tc.warningApplyDuration)) - warningApplyDuration, err := time.ParseDuration(tc.warningApplyDuration) - require.NoError(t, err) - yc.WarningApplyDuration = warningApplyDuration - } - - if tc.experimentalWarningApplyDuration != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-warning-apply-duration=%s", tc.experimentalWarningApplyDuration)) - experimentalWarningApplyDuration, err := time.ParseDuration(tc.experimentalWarningApplyDuration) - require.NoError(t, err) - yc.ExperimentalWarningApplyDuration = experimentalWarningApplyDuration - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedWarningApplyDuration, cfgFromCmdLine.ec.WarningApplyDuration) - require.Equal(t, tc.expectedWarningApplyDuration, cfgFromFile.ec.WarningApplyDuration) - }) - } -} - -// TestBootstrapDefragThresholdMegabytesFlagMigration tests the migration from -// --experimental-bootstrap-defrag-threshold-megabytes to --bootstrap-defrag-threshold-megabytes -// TODO: delete in v3.7 -func TestBootstrapDefragThresholdMegabytesFlagMigration(t *testing.T) { - testCases := []struct { - name string - bootstrapDefragThresholdMegabytes uint - experimentalBootstrapDefragThresholdMegabytes uint - expectErr bool - expectedBootstrapDefragThresholdMegabytes uint - }{ - { - name: "cannot set both experimental flag and non experimental flag", - bootstrapDefragThresholdMegabytes: 100, - experimentalBootstrapDefragThresholdMegabytes: 200, - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalBootstrapDefragThresholdMegabytes: 200, - expectedBootstrapDefragThresholdMegabytes: 200, - }, - { - name: "can set non experimental flag", - bootstrapDefragThresholdMegabytes: 100, - expectedBootstrapDefragThresholdMegabytes: 100, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes,omitempty"` - BootstrapDefragThresholdMegabytes uint `json:"bootstrap-defrag-threshold-megabytes,omitempty"` - }{} - - if tc.bootstrapDefragThresholdMegabytes != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--bootstrap-defrag-threshold-megabytes=%d", tc.bootstrapDefragThresholdMegabytes)) - yc.BootstrapDefragThresholdMegabytes = tc.bootstrapDefragThresholdMegabytes - } - - if tc.experimentalBootstrapDefragThresholdMegabytes != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-bootstrap-defrag-threshold-megabytes=%d", tc.experimentalBootstrapDefragThresholdMegabytes)) - yc.ExperimentalBootstrapDefragThresholdMegabytes = tc.experimentalBootstrapDefragThresholdMegabytes - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedBootstrapDefragThresholdMegabytes, cfgFromCmdLine.ec.BootstrapDefragThresholdMegabytes) - require.Equal(t, tc.expectedBootstrapDefragThresholdMegabytes, cfgFromFile.ec.BootstrapDefragThresholdMegabytes) - }) - } -} - -// TestMaxLearnersFlagMigration tests the migration from -// --experimental-max-learners to --max-learners -// TODO: delete in v3.7 -func TestMaxLearnersFlagMigration(t *testing.T) { - testCases := []struct { - name string - maxLearners int - experimentalMaxLearners int - expectErr bool - expectedMaxLearners int - }{ - { - name: "cannot set both experimental flag and non experimental flag", - maxLearners: 1, - experimentalMaxLearners: 2, - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalMaxLearners: 2, - expectedMaxLearners: 2, - }, - { - name: "can set non experimental flag", - maxLearners: 1, - expectedMaxLearners: 1, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - ExperimentalMaxLearners int `json:"experimental-max-learners,omitempty"` - MaxLearners int `json:"max-learners,omitempty"` - }{} - - if tc.maxLearners != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--max-learners=%d", tc.maxLearners)) - yc.MaxLearners = tc.maxLearners - } - - if tc.experimentalMaxLearners != 0 { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-max-learners=%d", tc.experimentalMaxLearners)) - yc.ExperimentalMaxLearners = tc.experimentalMaxLearners - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - require.Equal(t, tc.expectedMaxLearners, cfgFromCmdLine.ec.MaxLearners) - require.Equal(t, tc.expectedMaxLearners, cfgFromFile.ec.MaxLearners) - }) - } -} - -// TestMemoryMlockFlagMigration tests the migration from -// --experimental-memory-mlock to --memory-mlock -// TODO: delete in v3.7 -func TestMemoryMlockFlagMigration(t *testing.T) { - testCases := []struct { - name string - memoryMlock bool - experimentalMemoryMlock bool - expectedMemoryMlock bool - expectErr bool - }{ - { - name: "default", - expectedMemoryMlock: false, - }, - { - name: "cannot set both experimental flag and non experimental flag", - memoryMlock: true, - experimentalMemoryMlock: true, - expectErr: true, - }, - { - name: "can set experimental flag", - experimentalMemoryMlock: true, - expectedMemoryMlock: true, - }, - { - name: "can set non experimental flag", - memoryMlock: true, - expectedMemoryMlock: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cmdLineArgs := []string{} - yc := struct { - MemoryMlock bool `json:"memory-mlock,omitempty"` - ExperimentalMemoryMlock bool `json:"experimental-memory-mlock,omitempty"` - }{} - - if tc.memoryMlock { - cmdLineArgs = append(cmdLineArgs, "--memory-mlock") - yc.MemoryMlock = tc.memoryMlock - } - - if tc.experimentalMemoryMlock { - cmdLineArgs = append(cmdLineArgs, "--experimental-memory-mlock") - yc.ExperimentalMemoryMlock = tc.experimentalMemoryMlock - } - - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatal("expect parse error") - } - return - } - - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - - if cfgFromCmdLine.ec.MemoryMlock != tc.expectedMemoryMlock { - t.Errorf("expected MemoryMlock=%v, got %v", tc.expectedMemoryMlock, cfgFromCmdLine.ec.MemoryMlock) - } - if cfgFromFile.ec.MemoryMlock != tc.expectedMemoryMlock { - t.Errorf("expected MemoryMlock=%v, got %v", tc.expectedMemoryMlock, cfgFromFile.ec.MemoryMlock) - } - }) - } -} - -// TODO delete in v3.7 -func generateCfgsFromFileAndCmdLine(t *testing.T, yc any, cmdLineArgs []string) (*config, error, *config, error) { - b, err := yaml.Marshal(&yc) - require.NoError(t, err) - - tmpfile := mustCreateCfgFile(t, b) - defer os.Remove(tmpfile.Name()) - - cfgFromCmdLine := newConfig() - errFromCmdLine := cfgFromCmdLine.parse(cmdLineArgs) - - cfgFromFile := newConfig() - errFromFile := cfgFromFile.parse([]string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}) - return cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile -} - func mustCreateCfgFile(t *testing.T, b []byte) *os.File { - tmpfile, err := os.CreateTemp("", "servercfg") + tmpfile, err := os.CreateTemp(t.TempDir(), "servercfg") if err != nil { t.Fatal(err) } @@ -1263,19 +532,8 @@ func validateClusteringFlags(t *testing.T, cfg *config) { func TestConfigFileDeprecatedOptions(t *testing.T) { // Define a minimal config struct with only the fields we need type configFileYAML struct { - SnapshotCount uint64 `json:"snapshot-count,omitempty"` - MaxSnapFiles uint `json:"max-snapshots,omitempty"` - ExperimentalCompactHashCheckEnabled bool `json:"experimental-compact-hash-check-enabled,omitempty"` - ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time,omitempty"` - ExperimentalWarningUnaryRequestDuration time.Duration `json:"experimental-warning-unary-request-duration,omitempty"` - ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time,omitempty"` - ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit,omitempty"` - ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval,omitempty"` - ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration,omitempty"` - ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes,omitempty"` - ExperimentalMaxLearners int `json:"experimental-max-learners,omitempty"` - ExperimentalCompactionSleepInterval time.Duration `json:"experimental-compaction-sleep-interval,omitempty"` - ExperimentalDowngradeCheckTime time.Duration `json:"experimental-downgrade-check-time,omitempty"` + SnapshotCount uint64 `json:"snapshot-count,omitempty"` + MaxSnapFiles uint `json:"max-snapshots,omitempty"` } testCases := []struct { @@ -1288,34 +546,6 @@ func TestConfigFileDeprecatedOptions(t *testing.T) { configFileYAML: configFileYAML{}, expectedFlags: map[string]struct{}{}, }, - { - name: "deprecated experimental options", - configFileYAML: configFileYAML{ - ExperimentalCompactHashCheckEnabled: true, - ExperimentalCompactHashCheckTime: 2 * time.Minute, - ExperimentalWarningUnaryRequestDuration: time.Second, - ExperimentalCorruptCheckTime: time.Minute, - ExperimentalCompactionBatchLimit: 1, - ExperimentalWatchProgressNotifyInterval: 3 * time.Minute, - ExperimentalWarningApplyDuration: 3 * time.Minute, - ExperimentalBootstrapDefragThresholdMegabytes: 100, - ExperimentalMaxLearners: 1, - ExperimentalCompactionSleepInterval: 30 * time.Second, - ExperimentalDowngradeCheckTime: 1 * time.Minute, - }, - expectedFlags: map[string]struct{}{ - "experimental-compact-hash-check-enabled": {}, - "experimental-compact-hash-check-time": {}, - "experimental-corrupt-check-time": {}, - "experimental-compaction-batch-limit": {}, - "experimental-watch-progress-notify-interval": {}, - "experimental-warning-apply-duration": {}, - "experimental-bootstrap-defrag-threshold-megabytes": {}, - "experimental-max-learners": {}, - "experimental-compaction-sleep-interval": {}, - "experimental-downgrade-check-time": {}, - }, - }, { name: "deprecated snapshot options", configFileYAML: configFileYAML{ @@ -1358,109 +588,6 @@ func TestConfigFileDeprecatedOptions(t *testing.T) { // Compare sets of flags assert.Equalf(t, tc.expectedFlags, foundFlags, "deprecated flags mismatch - expected: %v, got: %v", tc.expectedFlags, foundFlags) - - // Note: experimental-warning-unary-request-duration deprecation is handled - // through a separate mechanism in embed.Config - if tc.configFileYAML.ExperimentalWarningUnaryRequestDuration != 0 { - assert.Equalf(t, cfg.ec.WarningUnaryRequestDuration, - tc.configFileYAML.ExperimentalWarningUnaryRequestDuration, - "experimental warning duration mismatch - expected: %v, got: %v", - tc.configFileYAML.ExperimentalWarningUnaryRequestDuration, - cfg.ec.WarningUnaryRequestDuration) - } - }) - } -} - -// TestPeerSkipClientSanVerificationFlagMigration tests the migration from -// --experimental-peer-skip-client-san-verification to --peer-skip-client-san-verification -// TODO: delete in v3.7 -func TestPeerSkipClientSanVerificationFlagMigration(t *testing.T) { - testCases := []struct { - name string - peerSkipClientSanVerification string - experimentalPeerSkipClientSanVerification string - useConfigFile bool - expectErr bool - expectedPeerSkipClientSanVerification bool - }{ - { - name: "cannot set both experimental flag and non experimental flag", - peerSkipClientSanVerification: "true", - experimentalPeerSkipClientSanVerification: "true", - expectErr: true, - }, - { - name: "can set experimental flag to true", - experimentalPeerSkipClientSanVerification: "true", - expectedPeerSkipClientSanVerification: true, - }, - { - name: "can set experimental flag to false", - experimentalPeerSkipClientSanVerification: "false", - expectedPeerSkipClientSanVerification: false, - }, - { - name: "can set non experimental flag to true", - peerSkipClientSanVerification: "true", - expectedPeerSkipClientSanVerification: true, - }, - { - name: "can set non experimental flag to false", - peerSkipClientSanVerification: "false", - expectedPeerSkipClientSanVerification: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - type securityConfig struct { - SkipClientSanVerification bool `json:"skip-client-san-verification,omitempty"` - } - cmdLineArgs := []string{} - yc := struct { - ExperimentalPeerSkipClientSanVerification bool `json:"experimental-peer-skip-client-san-verification,omitempty"` - PeerSecurityJSON securityConfig `json:"peer-transport-security"` - }{} - - if tc.peerSkipClientSanVerification != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--peer-skip-client-san-verification=%s", tc.peerSkipClientSanVerification)) - val, err := strconv.ParseBool(tc.peerSkipClientSanVerification) - if err != nil { - t.Fatal(err) - } - yc.PeerSecurityJSON.SkipClientSanVerification = val - } - - if tc.experimentalPeerSkipClientSanVerification != "" { - cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-peer-skip-client-san-verification=%s", tc.experimentalPeerSkipClientSanVerification)) - val, err := strconv.ParseBool(tc.experimentalPeerSkipClientSanVerification) - if err != nil { - t.Fatal(err) - } - yc.ExperimentalPeerSkipClientSanVerification = val - } - cfgFromCmdLine, errFromCmdLine, cfgFromFile, errFromFile := generateCfgsFromFileAndCmdLine(t, yc, cmdLineArgs) - - if tc.expectErr { - if errFromCmdLine == nil || errFromFile == nil { - t.Fatalf("expect parse error, got errFromCmdLine=%v, errFromFile=%v", errFromCmdLine, errFromFile) - } - return - } - if errFromCmdLine != nil || errFromFile != nil { - t.Fatal("error parsing config") - } - if cfgFromCmdLine.ec.PeerTLSInfo.SkipClientSANVerify != tc.expectedPeerSkipClientSanVerification { - t.Errorf("expected SkipClientSANVerify=%v, got %v", - tc.expectedPeerSkipClientSanVerification, - cfgFromCmdLine.ec.PeerTLSInfo.SkipClientSANVerify) - } - if cfgFromFile.ec.PeerTLSInfo.SkipClientSANVerify != tc.expectedPeerSkipClientSanVerification { - t.Errorf("expected SkipClientSANVerify=%v, got %v", - tc.expectedPeerSkipClientSanVerification, - cfgFromFile.ec.PeerTLSInfo.SkipClientSANVerify) - } }) } } diff --git a/server/etcdmain/etcd.go b/server/etcdmain/etcd.go index 16bba3736fd2..cb633e7e5d05 100644 --- a/server/etcdmain/etcd.go +++ b/server/etcdmain/etcd.go @@ -64,8 +64,7 @@ func startEtcdOrProxyV2(args []string) { lg.Info("Running: ", zap.Strings("args", args)) if err != nil { lg.Warn("failed to verify flags", zap.Error(err)) - switch { - case errorspkg.Is(err, embed.ErrUnsetAdvertiseClientURLsFlag): + if errorspkg.Is(err, embed.ErrUnsetAdvertiseClientURLsFlag) { lg.Warn("advertise client URLs are not set", zap.Error(err)) } os.Exit(1) diff --git a/server/etcdmain/help.go b/server/etcdmain/help.go index b1c1af9e701e..bf03c34ed547 100644 --- a/server/etcdmain/help.go +++ b/server/etcdmain/help.go @@ -113,8 +113,6 @@ Member: Clustering: --initial-advertise-peer-urls 'http://localhost:2380' List of this member's peer URLs to advertise to the rest of the cluster. - --experimental-set-member-localaddr 'false' - Enable using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer. --initial-cluster 'default=http://localhost:2380' Initial cluster configuration for bootstrapping. --initial-cluster-state 'new' @@ -262,79 +260,41 @@ Logging: --warning-unary-request-duration '300ms' Set time duration after which a warning is logged if a unary request takes more than this duration. -Experimental distributed tracing: - --experimental-enable-distributed-tracing 'false' - Enable experimental distributed tracing. - --experimental-distributed-tracing-address 'localhost:4317' +Distributed tracing: + --enable-distributed-tracing 'false' + Enable distributed tracing. + --distributed-tracing-address 'localhost:4317' Distributed tracing collector address. - --experimental-distributed-tracing-service-name 'etcd' + --distributed-tracing-service-name 'etcd' Distributed tracing service name, must be same across all etcd instances. - --experimental-distributed-tracing-instance-id '' + --distributed-tracing-instance-id '' Distributed tracing instance ID, must be unique per each etcd instance. - --experimental-distributed-tracing-sampling-rate '0' - Number of samples to collect per million spans for distributed tracing. Disabled by default. + --distributed-tracing-sampling-rate '0' + Number of samples to collect per million spans for distributed tracing. -Experimental feature: - --experimental-initial-corrupt-check 'false'. It's deprecated, and will be decommissioned in v3.7. Use '--feature-gates=InitialCorruptCheck=true' instead. - Enable to check data corruption before serving any client/peer traffic. - --experimental-corrupt-check-time '0s' - Duration of time between cluster corruption check passes. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'corrupt-check-time' instead. +Features: --corrupt-check-time '0s' Duration of time between cluster corruption check passes. - --experimental-compact-hash-check-enabled 'false'. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=CompactHashCheck=true' instead. - Enable leader to periodically check followers compaction hashes. - --experimental-compact-hash-check-time '1m' - Duration of time between leader checks followers compaction hashes. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--compact-hash-check-time' instead. --compact-hash-check-time '1m' Duration of time between leader checks followers compaction hashes. - --experimental-enable-lease-checkpoint 'false' - ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=LeaseCheckpoint=true' instead. - --experimental-compaction-batch-limit 1000 - ExperimentalCompactionBatchLimit sets the maximum revisions deleted in each compaction batch. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'compaction-batch-limit' instead. --compaction-batch-limit 1000 CompactionBatchLimit sets the maximum revisions deleted in each compaction batch. - --experimental-peer-skip-client-san-verification 'false' - Skip verification of SAN field in client certificate for peer connections. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'peer-skip-client-san-verification' instead. --peer-skip-client-san-verification 'false' Skip verification of SAN field in client certificate for peer connections. - --experimental-watch-progress-notify-interval '10m' - Duration of periodical watch progress notification. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'watch-progress-notify-interval' instead. --watch-progress-notify-interval '10m' Duration of periodical watch progress notification. - --experimental-warning-apply-duration '100ms' - Warning is generated if requests take more than this duration. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'warning-apply-duration' instead. --warning-apply-duration '100ms' Warning is generated if requests take more than this duration. - --experimental-txn-mode-write-with-shared-buffer 'true'. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=TxnModeWriteWithSharedBuffer=true' instead. - Enable the write transaction to use a shared buffer in its readonly check operations. - --experimental-bootstrap-defrag-threshold-megabytes - Enable the defrag during etcd server bootstrap on condition that it will free at least the provided threshold of disk space. Needs to be set to non-zero value to take effect. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'bootstrap-defrag-threshold-megabytes' instead. --bootstrap-defrag-threshold-megabytes Enable the defrag during etcd server bootstrap on condition that it will free at least the provided threshold of disk space. Needs to be set to non-zero value to take effect. - --experimental-warning-unary-request-duration '300ms' - Set time duration after which a warning is generated if a unary request takes more than this duration. It's deprecated, and will be decommissioned in v3.7. Use --warning-unary-request-duration instead. - --experimental-max-learners '1' - Set the max number of learner members allowed in the cluster membership. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'max-learners' instead. --max-learners '1' Set the max number of learner members allowed in the cluster membership. - --experimental-snapshot-catch-up-entries '5000' - Number of entries for a slow follower to catch up after compacting the raft storage entries. - --experimental-compaction-sleep-interval - Sets the sleep interval between each compaction batch. Deprecated in v3.6 and will be decommissioned in v3.7. Use 'compaction-sleep-interval' instead. --compaction-sleep-interval Sets the sleep interval between each compaction batch. - --experimental-downgrade-check-time - Duration of time between two downgrade status checks. Deprecated in v3.6 and will be decommissioned in v3.7. Use "downgrade-check-time" instead. --downgrade-check-time Duration of time between two downgrade status checks. - --experimental-enable-lease-checkpoint-persist 'false' - Enable persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. Requires experimental-enable-lease-checkpoint to be enabled. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=LeaseCheckpointPersist=true' instead. - --experimental-memory-mlock - Enable to enforce etcd pages (in particular bbolt) to stay in RAM. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--memory-mlock' instead. - --experimental-snapshot-catchup-entries + --snapshot-catchup-entries Number of entries for a slow follower to catch up after compacting the raft storage entries. - --experimental-stop-grpc-service-on-defrag - Enable etcd gRPC service to stop serving client requests on defragmentation. It's deprecated, and will be decommissioned in v3.7. Use '--feature-gates=StopGRPCServiceOnDefrag=true' instead. Unsafe feature: --force-new-cluster 'false' diff --git a/server/etcdserver/api/capability.go b/server/etcdserver/api/capability.go index cf535ec4efaa..3efe6c74d704 100644 --- a/server/etcdserver/api/capability.go +++ b/server/etcdserver/api/capability.go @@ -41,6 +41,7 @@ var ( "3.4.0": {AuthCapability: true, V3rpcCapability: true}, "3.5.0": {AuthCapability: true, V3rpcCapability: true}, "3.6.0": {AuthCapability: true, V3rpcCapability: true}, + "3.7.0": {AuthCapability: true, V3rpcCapability: true}, } enableMapMu sync.RWMutex diff --git a/server/etcdserver/api/membership/cluster.go b/server/etcdserver/api/membership/cluster.go index d2c97df74bb8..93db0c78a9b1 100644 --- a/server/etcdserver/api/membership/cluster.go +++ b/server/etcdserver/api/membership/cluster.go @@ -116,6 +116,7 @@ func NewCluster(lg *zap.Logger, opts ...ClusterOption) *RaftCluster { removed: make(map[types.ID]bool), downgradeInfo: &serverversion.DowngradeInfo{Enabled: false}, maxLearners: clOpts.maxLearners, + v2store: v2store.New(), } } @@ -257,17 +258,9 @@ func (c *RaftCluster) SetVersionChangedNotifier(n *notify.Notifier) { } func (c *RaftCluster) UnsafeLoad() { - if c.be != nil { - c.version = c.be.ClusterVersionFromBackend() - c.members, c.removed = c.be.MustReadMembersFromBackend() - } else { - c.version = clusterVersionFromStore(c.lg, c.v2store) - c.members, c.removed = membersFromStore(c.lg, c.v2store) - } - - if c.be != nil { - c.downgradeInfo = c.be.DowngradeInfoFromBackend() - } + c.version = c.be.ClusterVersionFromBackend() + c.members, c.removed = c.be.MustReadMembersFromBackend() + c.downgradeInfo = c.be.DowngradeInfoFromBackend() } func (c *RaftCluster) Recover(onSet func(*zap.Logger, *semver.Version)) { @@ -313,9 +306,16 @@ func (c *RaftCluster) Recover(onSet func(*zap.Logger, *semver.Version)) { // ValidateConfigurationChange takes a proposed ConfChange and // ensures that it is still valid. -func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error { - // TODO: this must be switched to backend as well. - membersMap, removedMap := membersFromStore(c.lg, c.v2store) +func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange, shouldApplyV3 ShouldApplyV3) error { + var membersMap map[types.ID]*Member + var removedMap map[types.ID]bool + + if shouldApplyV3 { + membersMap, removedMap = c.be.MustReadMembersFromBackend() + } else { + membersMap, removedMap = membersFromStore(c.lg, c.v2store) + } + id := types.ID(cc.NodeID) if removedMap[id] { return ErrIDRemoved @@ -400,15 +400,13 @@ func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error { func (c *RaftCluster) AddMember(m *Member, shouldApplyV3 ShouldApplyV3) { c.Lock() defer c.Unlock() - if c.v2store != nil { - mustSaveMemberToStore(c.lg, c.v2store, m) - } + mustSaveMemberToStore(c.lg, c.v2store, m) if m.ID == c.localID { setIsLearnerMetric(m) } - if c.be != nil && shouldApplyV3 { + if shouldApplyV3 { c.be.MustSaveMemberToBackend(m) c.members[m.ID] = m @@ -438,10 +436,8 @@ func (c *RaftCluster) AddMember(m *Member, shouldApplyV3 ShouldApplyV3) { func (c *RaftCluster) RemoveMember(id types.ID, shouldApplyV3 ShouldApplyV3) { c.Lock() defer c.Unlock() - if c.v2store != nil { - mustDeleteMemberFromStore(c.lg, c.v2store, id) - } - if c.be != nil && shouldApplyV3 { + mustDeleteMemberFromStore(c.lg, c.v2store, id) + if shouldApplyV3 { c.be.MustDeleteMemberFromBackend(id) m, ok := c.members[id] @@ -482,10 +478,8 @@ func (c *RaftCluster) UpdateAttributes(id types.ID, attr Attributes, shouldApply if m, ok := c.members[id]; ok { m.Attributes = attr - if c.v2store != nil { - mustUpdateMemberAttrInStore(c.lg, c.v2store, m) - } - if c.be != nil && shouldApplyV3 { + mustUpdateMemberAttrInStore(c.lg, c.v2store, m) + if shouldApplyV3 { c.be.MustSaveMemberToBackend(m) } return @@ -514,17 +508,15 @@ func (c *RaftCluster) PromoteMember(id types.ID, shouldApplyV3 ShouldApplyV3) { c.Lock() defer c.Unlock() - if c.v2store != nil { - m := *(c.members[id]) - m.RaftAttributes.IsLearner = false - mustUpdateMemberInStore(c.lg, c.v2store, &m) - } + m := *(c.members[id]) + m.RaftAttributes.IsLearner = false + mustUpdateMemberInStore(c.lg, c.v2store, &m) if id == c.localID { isLearner.Set(0) } - if c.be != nil && shouldApplyV3 { + if shouldApplyV3 { c.members[id].RaftAttributes.IsLearner = false c.updateMembershipMetric(id, true) c.be.MustSaveMemberToBackend(c.members[id]) @@ -548,12 +540,11 @@ func (c *RaftCluster) UpdateRaftAttributes(id types.ID, raftAttr RaftAttributes, c.Lock() defer c.Unlock() - if c.v2store != nil { - m := *(c.members[id]) - m.RaftAttributes = raftAttr - mustUpdateMemberInStore(c.lg, c.v2store, &m) - } - if c.be != nil && shouldApplyV3 { + m := *(c.members[id]) + m.RaftAttributes = raftAttr + mustUpdateMemberInStore(c.lg, c.v2store, &m) + + if shouldApplyV3 { c.members[id].RaftAttributes = raftAttr c.be.MustSaveMemberToBackend(c.members[id]) @@ -609,10 +600,9 @@ func (c *RaftCluster) SetVersion(ver *semver.Version, onSet func(*zap.Logger, *s c.version = ver sv := semver.Must(semver.NewVersion(version.Version)) serverversion.MustDetectDowngrade(c.lg, sv, c.version) - if c.v2store != nil { - mustSaveClusterVersionToStore(c.lg, c.v2store, ver) - } - if c.be != nil && shouldApplyV3 { + mustSaveClusterVersionToStore(c.lg, c.v2store, ver) + + if shouldApplyV3 { c.be.MustSaveClusterVersionToBackend(ver) } if oldVer != nil { @@ -814,7 +804,7 @@ func (c *RaftCluster) SetDowngradeInfo(d *serverversion.DowngradeInfo, shouldApp c.Lock() defer c.Unlock() - if c.be != nil && shouldApplyV3 { + if shouldApplyV3 { c.be.MustSaveDowngradeToBackend(d) } diff --git a/server/etcdserver/api/membership/cluster_test.go b/server/etcdserver/api/membership/cluster_test.go index c07093878c9e..afe524f70465 100644 --- a/server/etcdserver/api/membership/cluster_test.go +++ b/server/etcdserver/api/membership/cluster_test.go @@ -277,7 +277,15 @@ func TestClusterValidateAndAssignIDs(t *testing.T) { } } +func TestClusterValidateConfigurationChangeV3(t *testing.T) { + testClusterValidateConfigurationChange(t, true) +} + func TestClusterValidateConfigurationChangeV2(t *testing.T) { + testClusterValidateConfigurationChange(t, false) +} + +func testClusterValidateConfigurationChange(t *testing.T, shouldApplyV3 ShouldApplyV3) { cl := NewCluster(zaptest.NewLogger(t), WithMaxLearners(1)) be := newMembershipBackend() cl.SetBackend(be) @@ -458,7 +466,7 @@ func TestClusterValidateConfigurationChangeV2(t *testing.T) { }, } for i, tt := range tests { - err := cl.ValidateConfigurationChange(tt.cc) + err := cl.ValidateConfigurationChange(tt.cc, shouldApplyV3) if !errors.Is(err, tt.werr) { t.Errorf("#%d: validateConfigurationChange error = %v, want %v", i, err, tt.werr) } @@ -652,8 +660,14 @@ func TestNodeToMember(t *testing.T) { } } -func newTestCluster(t testing.TB, membs []*Member) *RaftCluster { - c := &RaftCluster{lg: zaptest.NewLogger(t), members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)} +func newTestCluster(tb testing.TB, membs []*Member) *RaftCluster { + c := &RaftCluster{ + lg: zaptest.NewLogger(tb), + members: make(map[types.ID]*Member), + removed: make(map[types.ID]bool), + be: newMembershipBackend(), + v2store: v2store.New(), + } for _, m := range membs { c.members[m.ID] = m } diff --git a/server/etcdserver/api/membership/membership_test.go b/server/etcdserver/api/membership/membership_test.go index 77be3bba2556..03bf384ad69f 100644 --- a/server/etcdserver/api/membership/membership_test.go +++ b/server/etcdserver/api/membership/membership_test.go @@ -34,10 +34,6 @@ func TestAddRemoveMember(t *testing.T) { c.AddMember(newTestMember(18, nil, "node18", nil), true) c.RemoveMember(18, true) - // Skipping removal of already removed member - c.RemoveMember(17, true) - c.RemoveMember(18, true) - c.AddMember(newTestMember(19, nil, "node19", nil), true) // Recover from backend diff --git a/server/etcdserver/api/membership/storev2.go b/server/etcdserver/api/membership/storev2.go index f7a595ac3e1a..1ab0654d00ba 100644 --- a/server/etcdserver/api/membership/storev2.go +++ b/server/etcdserver/api/membership/storev2.go @@ -183,18 +183,3 @@ func MemberStoreKey(id types.ID) string { func MemberAttributesStorePath(id types.ID) string { return path.Join(MemberStoreKey(id), attributesSuffix) } - -func clusterVersionFromStore(lg *zap.Logger, st v2store.Store) *semver.Version { - e, err := st.Get(path.Join(storePrefix, "version"), false, false) - if err != nil { - if isKeyNotFound(err) { - return nil - } - lg.Panic( - "failed to get cluster version from store", - zap.String("path", path.Join(storePrefix, "version")), - zap.Error(err), - ) - } - return semver.Must(semver.NewVersion(*e.Node.Value)) -} diff --git a/server/etcdserver/api/rafthttp/stream.go b/server/etcdserver/api/rafthttp/stream.go index fa02f42b9b9c..23eb9c9a9db2 100644 --- a/server/etcdserver/api/rafthttp/stream.go +++ b/server/etcdserver/api/rafthttp/stream.go @@ -60,6 +60,7 @@ var ( "3.4.0": {streamTypeMsgAppV2, streamTypeMessage}, "3.5.0": {streamTypeMsgAppV2, streamTypeMessage}, "3.6.0": {streamTypeMsgAppV2, streamTypeMessage}, + "3.7.0": {streamTypeMsgAppV2, streamTypeMessage}, } ) diff --git a/server/etcdserver/api/rafthttp/stream_test.go b/server/etcdserver/api/rafthttp/stream_test.go index a50961b96f06..66dd057ee7a7 100644 --- a/server/etcdserver/api/rafthttp/stream_test.go +++ b/server/etcdserver/api/rafthttp/stream_test.go @@ -15,7 +15,6 @@ package rafthttp import ( - "context" "errors" "io" "net/http" @@ -116,7 +115,7 @@ func TestStreamReaderDialRequest(t *testing.T) { peerID: types.ID(2), tr: &Transport{streamRt: tr, ClusterID: types.ID(1), ID: types.ID(1)}, picker: mustNewURLPicker(t, []string{"http://localhost:2380"}), - ctx: context.Background(), + ctx: t.Context(), } sr.dial(tt) @@ -171,7 +170,7 @@ func TestStreamReaderDialResult(t *testing.T) { tr: &Transport{streamRt: tr, ClusterID: types.ID(1)}, picker: mustNewURLPicker(t, []string{"http://localhost:2380"}), errorc: make(chan error, 1), - ctx: context.Background(), + ctx: t.Context(), } _, err := sr.dial(streamTypeMessage) @@ -253,7 +252,7 @@ func TestStreamReaderDialDetectUnsupport(t *testing.T) { peerID: types.ID(2), tr: &Transport{streamRt: tr, ClusterID: types.ID(1)}, picker: mustNewURLPicker(t, []string{"http://localhost:2380"}), - ctx: context.Background(), + ctx: t.Context(), } _, err := sr.dial(typ) diff --git a/server/etcdserver/api/v2store/store_ttl_test.go b/server/etcdserver/api/v2store/store_ttl_test.go index a4569cc3114b..aa98b4505e7b 100644 --- a/server/etcdserver/api/v2store/store_ttl_test.go +++ b/server/etcdserver/api/v2store/store_ttl_test.go @@ -132,7 +132,7 @@ func TestStoreUpdateDirTTL(t *testing.T) { assert.False(t, e.Node.Dir) assert.Equal(t, eidx, e.EtcdIndex) e, _ = s.Get("/foo/bar", false, false) - assert.Equal(t, "", *e.Node.Value) + assert.Empty(t, *e.Node.Value) assert.Equal(t, eidx, e.EtcdIndex) fc.Advance(600 * time.Millisecond) diff --git a/server/etcdserver/api/v3alarm/alarms.go b/server/etcdserver/api/v3alarm/alarms.go index e0480da081c1..bf17929d7944 100644 --- a/server/etcdserver/api/v3alarm/alarms.go +++ b/server/etcdserver/api/v3alarm/alarms.go @@ -23,20 +23,13 @@ import ( pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/client/pkg/v3/types" "go.etcd.io/etcd/server/v3/storage/backend" + "go.etcd.io/etcd/server/v3/storage/schema" ) type BackendGetter interface { Backend() backend.Backend } -type AlarmBackend interface { - CreateAlarmBucket() - MustPutAlarm(member *pb.AlarmMember) - MustDeleteAlarm(alarm *pb.AlarmMember) - GetAllAlarms() ([]*pb.AlarmMember, error) - ForceCommit() -} - type alarmSet map[types.ID]*pb.AlarmMember // AlarmStore persists alarms to the backend. @@ -45,10 +38,10 @@ type AlarmStore struct { mu sync.Mutex types map[pb.AlarmType]alarmSet - be AlarmBackend + be schema.AlarmBackend } -func NewAlarmStore(lg *zap.Logger, be AlarmBackend) (*AlarmStore, error) { +func NewAlarmStore(lg *zap.Logger, be schema.AlarmBackend) (*AlarmStore, error) { if lg == nil { lg = zap.NewNop() } diff --git a/server/etcdserver/api/v3compactor/periodic_test.go b/server/etcdserver/api/v3compactor/periodic_test.go index 5604dabc5ef9..e8658f420fa2 100644 --- a/server/etcdserver/api/v3compactor/periodic_test.go +++ b/server/etcdserver/api/v3compactor/periodic_test.go @@ -16,6 +16,7 @@ package v3compactor import ( "errors" + "fmt" "reflect" "testing" "time" @@ -49,7 +50,7 @@ func TestPeriodicHourly(t *testing.T) { } // very first compaction - a, err := compactable.Wait(1) + a, err := waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -67,7 +68,7 @@ func TestPeriodicHourly(t *testing.T) { fc.Advance(tb.getRetryInterval()) } - a, err = compactable.Wait(1) + a, err = waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -100,7 +101,7 @@ func TestPeriodicMinutes(t *testing.T) { } // very first compaction - a, err := compactable.Wait(1) + a, err := waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -117,7 +118,7 @@ func TestPeriodicMinutes(t *testing.T) { fc.Advance(tb.getRetryInterval()) } - a, err := compactable.Wait(1) + a, err := waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -162,7 +163,7 @@ func TestPeriodicPause(t *testing.T) { fc.Advance(tb.getRetryInterval()) // T=3h6m - a, err := compactable.Wait(1) + a, err := waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -197,7 +198,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) { } // very first compaction - a, err := compactable.Wait(1) + a, err := waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -228,7 +229,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) { fc.Advance(tb.getRetryInterval()) } - a, err = compactable.Wait(1) + a, err = waitWithRetry(t, compactable) if err != nil { t.Fatal(err) } @@ -244,3 +245,23 @@ func waitOneAction(t *testing.T, r testutil.Recorder) { t.Errorf("expect 1 action, got %v instead", len(actions)) } } + +func waitWithRetry(t *testing.T, compactable *fakeCompactable) ([]testutil.Action, error) { + t.Helper() + + var lastErr error + var actions []testutil.Action + + expectedActions, maxRetries := 1, 5 + for retry := 0; retry < maxRetries; retry++ { + actions, lastErr = compactable.Wait(expectedActions) + if lastErr == nil || len(actions) >= expectedActions { + return actions, nil + } + // Exponential backoff + backoffTime := time.Duration(10*(1< CappedApplier -> Auth -> Quota -> Backend), // then dispatch() unpacks the request to a specific method (like Put), // that gets executed down the hierarchy again: // i.e. CorruptApplier.Put(CappedApplier.Put(...(BackendApplier.Put(...)))). - return a.applyV3.Apply(r, a.dispatch) + return a.applyV3.Apply(r, shouldApplyV3, a.dispatch) } // dispatch translates the request (r) into appropriate call (like Put) on // the underlying applyV3 object. -func (a *uberApplier) dispatch(r *pb.InternalRaftRequest) *Result { +func (a *uberApplier) dispatch(r *pb.InternalRaftRequest, shouldApplyV3 membership.ShouldApplyV3) *Result { op := "unknown" ar := &Result{} defer func(start time.Time) { @@ -133,6 +102,31 @@ func (a *uberApplier) dispatch(r *pb.InternalRaftRequest) *Result { } }(time.Now()) + switch { + case r.ClusterVersionSet != nil: + op = "ClusterVersionSet" // Implemented in 3.5.x + a.applyV3.ClusterVersionSet(r.ClusterVersionSet, shouldApplyV3) + return ar + case r.ClusterMemberAttrSet != nil: + op = "ClusterMemberAttrSet" // Implemented in 3.5.x + a.applyV3.ClusterMemberAttrSet(r.ClusterMemberAttrSet, shouldApplyV3) + return ar + case r.DowngradeInfoSet != nil: + op = "DowngradeInfoSet" // Implemented in 3.5.x + a.applyV3.DowngradeInfoSet(r.DowngradeInfoSet, shouldApplyV3) + return ar + case r.DowngradeVersionTest != nil: + op = "DowngradeVersionTest" // Implemented in 3.6 for test only + // do nothing, we are just to ensure etcdserver don't panic in case + // users(test cases) intentionally inject DowngradeVersionTestRequest + // into the WAL files. + return ar + default: + } + if !shouldApplyV3 { + return nil + } + switch { case r.Range != nil: op = "Range" diff --git a/server/etcdserver/apply/uber_applier_test.go b/server/etcdserver/apply/uber_applier_test.go index 086db23c6cfd..e81fe49065e1 100644 --- a/server/etcdserver/apply/uber_applier_test.go +++ b/server/etcdserver/apply/uber_applier_test.go @@ -45,6 +45,7 @@ func defaultUberApplier(t *testing.T) UberApplier { }) cluster := membership.NewCluster(lg) + cluster.SetBackend(schema.NewMembershipBackend(lg, be)) cluster.AddMember(&membership.Member{ID: memberID}, true) lessor := lease.NewLessor(lg, be, cluster, lease.LessorConfig{}) kv := mvcc.NewStore(lg, be, lessor, mvcc.StoreConfig{}) @@ -60,21 +61,22 @@ func defaultUberApplier(t *testing.T) UberApplier { bcrypt.DefaultCost, ) consistentIndex := cindex.NewConsistentIndex(be) - return NewUberApplier( - lg, - be, - kv, - alarmStore, - authStore, - lessor, - cluster, - &fakeRaftStatusGetter{}, - &fakeSnapshotServer{}, - consistentIndex, - 1*time.Hour, - false, - 16*1024*1024, // 16MB - ) + opts := ApplierOptions{ + Logger: lg, + KV: kv, + AlarmStore: alarmStore, + AuthStore: authStore, + Lessor: lessor, + Cluster: cluster, + RaftStatus: &fakeRaftStatusGetter{}, + SnapshotServer: &fakeSnapshotServer{}, + ConsistentIndex: consistentIndex, + TxnModeWriteWithSharedBuffer: false, + Backend: be, + QuotaBackendBytesCfg: 16 * 1024 * 1024, // 16MB + WarningApplyDuration: time.Hour, + } + return NewUberApplier(opts) } // TestUberApplier_Alarm_Corrupt tests the applier returns ErrCorrupt after alarm CORRUPT is activated @@ -129,13 +131,13 @@ func TestUberApplier_Alarm_Corrupt(t *testing.T) { MemberID: memberID, Alarm: pb.AlarmType_CORRUPT, }, - }) + }, membership.ApplyBoth) require.NotNil(t, result) require.NoError(t, result.Err) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - result = ua.Apply(tc.request) + result = ua.Apply(tc.request, membership.ApplyBoth) require.NotNil(t, result) require.Equalf(t, tc.expectError, result.Err, "Apply: got %v, expect: %v", result.Err, tc.expectError) }) @@ -231,13 +233,13 @@ func TestUberApplier_Alarm_Quota(t *testing.T) { MemberID: memberID, Alarm: pb.AlarmType_NOSPACE, }, - }) + }, membership.ApplyBoth) require.NotNil(t, result) require.NoError(t, result.Err) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - result = ua.Apply(tc.request) + result = ua.Apply(tc.request, membership.ApplyBoth) require.NotNil(t, result) require.Equalf(t, tc.expectError, result.Err, "Apply: got %v, expect: %v", result.Err, tc.expectError) }) @@ -254,11 +256,11 @@ func TestUberApplier_Alarm_Deactivate(t *testing.T) { MemberID: memberID, Alarm: pb.AlarmType_NOSPACE, }, - }) + }, membership.ApplyBoth) require.NotNil(t, result) require.NoError(t, result.Err) - result = ua.Apply(&pb.InternalRaftRequest{Put: &pb.PutRequest{Key: []byte(key)}}) + result = ua.Apply(&pb.InternalRaftRequest{Put: &pb.PutRequest{Key: []byte(key)}}, membership.ApplyBoth) require.NotNil(t, result) require.Equalf(t, errors.ErrNoSpace, result.Err, "Apply: got %v, expect: %v", result.Err, errors.ErrNoSpace) @@ -269,11 +271,11 @@ func TestUberApplier_Alarm_Deactivate(t *testing.T) { MemberID: memberID, Alarm: pb.AlarmType_NOSPACE, }, - }) + }, membership.ApplyBoth) require.NotNil(t, result) require.NoError(t, result.Err) - result = ua.Apply(&pb.InternalRaftRequest{Put: &pb.PutRequest{Key: []byte(key)}}) + result = ua.Apply(&pb.InternalRaftRequest{Put: &pb.PutRequest{Key: []byte(key)}}, membership.ApplyBoth) require.NotNil(t, result) assert.NoError(t, result.Err) } diff --git a/server/etcdserver/bootstrap.go b/server/etcdserver/bootstrap.go index b25b7f6e1dbb..0d4e6ff964d6 100644 --- a/server/etcdserver/bootstrap.go +++ b/server/etcdserver/bootstrap.go @@ -261,8 +261,8 @@ func maybeDefragBackend(cfg config.ServerConfig, be backend.Backend) error { zap.String("current-db-size", humanize.Bytes(uint64(size))), zap.Int64("current-db-size-in-use-bytes", sizeInUse), zap.String("current-db-size-in-use", humanize.Bytes(uint64(sizeInUse))), - zap.Uint("experimental-bootstrap-defrag-threshold-bytes", thresholdBytes), - zap.String("experimental-bootstrap-defrag-threshold", humanize.Bytes(uint64(thresholdBytes))), + zap.Uint("bootstrap-defrag-threshold-bytes", thresholdBytes), + zap.String("bootstrap-defrag-threshold", humanize.Bytes(uint64(thresholdBytes))), ) return nil } diff --git a/server/etcdserver/bootstrap_test.go b/server/etcdserver/bootstrap_test.go index ca16c5887b6b..823b9b7210bd 100644 --- a/server/etcdserver/bootstrap_test.go +++ b/server/etcdserver/bootstrap_test.go @@ -133,7 +133,7 @@ func mockBootstrapRoundTrip(members []etcdserverpb.Member) roundTripFunc { case strings.Contains(r.URL.String(), DowngradeEnabledPath): return &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`true`)), + Body: io.NopCloser(strings.NewReader(`false`)), }, nil } return nil, nil diff --git a/server/etcdserver/metrics.go b/server/etcdserver/metrics.go index 5f3c2f51368f..7176d30adbc0 100644 --- a/server/etcdserver/metrics.go +++ b/server/etcdserver/metrics.go @@ -140,6 +140,13 @@ var ( }, []string{"server_id"}, ) + serverFeatureEnabled = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "etcd_server_feature_enabled", + Help: "Whether or not a feature is enabled. 1 is enabled, 0 is not.", + }, + []string{"name", "stage"}, + ) fdUsed = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "os", Subsystem: "fd", @@ -170,6 +177,7 @@ func init() { prometheus.MustRegister(currentVersion) prometheus.MustRegister(currentGoVersion) prometheus.MustRegister(serverID) + prometheus.MustRegister(serverFeatureEnabled) prometheus.MustRegister(learnerPromoteSucceed) prometheus.MustRegister(learnerPromoteFailed) prometheus.MustRegister(fdUsed) diff --git a/server/etcdserver/raft_test.go b/server/etcdserver/raft_test.go index 2cfa4816232b..e604a88a02f4 100644 --- a/server/etcdserver/raft_test.go +++ b/server/etcdserver/raft_test.go @@ -278,11 +278,10 @@ func TestProcessDuplicatedAppRespMessage(t *testing.T) { }) s := &EtcdServer{ - lgMu: new(sync.RWMutex), - lg: zaptest.NewLogger(t), - r: *r, - cluster: cl, - SyncTicker: &time.Ticker{}, + lgMu: new(sync.RWMutex), + lg: zaptest.NewLogger(t), + r: *r, + cluster: cl, } s.start() diff --git a/server/etcdserver/server.go b/server/etcdserver/server.go index 2c45553bc833..522a3abe8f0a 100644 --- a/server/etcdserver/server.go +++ b/server/etcdserver/server.go @@ -62,7 +62,6 @@ import ( "go.etcd.io/etcd/server/v3/etcdserver/apply" "go.etcd.io/etcd/server/v3/etcdserver/cindex" "go.etcd.io/etcd/server/v3/etcdserver/errors" - "go.etcd.io/etcd/server/v3/etcdserver/txn" serverversion "go.etcd.io/etcd/server/v3/etcdserver/version" "go.etcd.io/etcd/server/v3/features" "go.etcd.io/etcd/server/v3/lease" @@ -267,7 +266,6 @@ type EtcdServer struct { stats *stats.ServerStats lstats *stats.LeaderStats - SyncTicker *time.Ticker // compactor is used to auto-compact the KV. compactor v3compactor.Compactor @@ -334,7 +332,6 @@ func NewServer(cfg config.ServerConfig) (srv *EtcdServer, err error) { cluster: b.cluster.cl, stats: sstats, lstats: lstats, - SyncTicker: time.NewTicker(500 * time.Millisecond), peerRt: b.prt, reqIDGen: idutil.NewGenerator(uint16(b.cluster.nodeID), time.Now()), AccessController: &AccessController{CORS: cfg.CORS, HostWhitelist: cfg.HostWhitelist}, @@ -342,6 +339,8 @@ func NewServer(cfg config.ServerConfig) (srv *EtcdServer, err error) { firstCommitInTerm: notify.NewNotifier(), clusterVersionChanged: notify.NewNotifier(), } + + addFeatureGateMetrics(cfg.ServerFeatureGate, serverFeatureEnabled) serverID.With(prometheus.Labels{"server_id": b.cluster.nodeID.String()}).Set(1) srv.cluster.SetVersionChangedNotifier(srv.clusterVersionChanged) @@ -830,8 +829,6 @@ func (s *EtcdServer) run() { // wait for goroutines before closing raft so wal stays open s.wg.Wait() - s.SyncTicker.Stop() - // must stop raft after scheduler-- etcdserver can leak rafthttp pipelines // by adding a peer after raft stops the transport s.r.stop() @@ -1147,8 +1144,22 @@ func (s *EtcdServer) applySnapshot(ep *etcdProgress, toApply *toApply) { } func (s *EtcdServer) NewUberApplier() apply.UberApplier { - return apply.NewUberApplier(s.lg, s.be, s.KV(), s.alarmStore, s.authStore, s.lessor, s.cluster, s, s, s.consistIndex, - s.Cfg.WarningApplyDuration, s.Cfg.ServerFeatureGate.Enabled(features.TxnModeWriteWithSharedBuffer), s.Cfg.QuotaBackendBytes) + opts := apply.ApplierOptions{ + Logger: s.lg, + KV: s.KV(), + AlarmStore: s.alarmStore, + AuthStore: s.authStore, + Lessor: s.lessor, + Cluster: s.cluster, + RaftStatus: s, + SnapshotServer: s, + ConsistentIndex: s.consistIndex, + TxnModeWriteWithSharedBuffer: s.Cfg.ServerFeatureGate.Enabled(features.TxnModeWriteWithSharedBuffer), + Backend: s.be, + QuotaBackendBytesCfg: s.Cfg.QuotaBackendBytes, + WarningApplyDuration: s.Cfg.WarningApplyDuration, + } + return apply.NewUberApplier(opts) } func verifySnapshotIndex(snapshot raftpb.Snapshot, cindex uint64) { @@ -1969,7 +1980,7 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry, shouldApplyV3 membership. if !needResult && raftReq.Txn != nil { removeNeedlessRangeReqs(raftReq.Txn) } - ar = s.applyInternalRaftRequest(&raftReq, shouldApplyV3) + ar = s.uberApply.Apply(&raftReq, shouldApplyV3) } // do not re-toApply applied entries. @@ -2005,42 +2016,6 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry, shouldApplyV3 membership. }) } -func (s *EtcdServer) applyInternalRaftRequest(r *pb.InternalRaftRequest, shouldApplyV3 membership.ShouldApplyV3) *apply.Result { - if r.ClusterVersionSet == nil && r.ClusterMemberAttrSet == nil && r.DowngradeInfoSet == nil && r.DowngradeVersionTest == nil { - if !shouldApplyV3 { - return nil - } - return s.uberApply.Apply(r) - } - membershipApplier := apply.NewApplierMembership(s.lg, s.cluster, s) - op := "unknown" - defer func(start time.Time) { - txn.ApplySecObserve("v3", op, true, time.Since(start)) - txn.WarnOfExpensiveRequest(s.lg, s.Cfg.WarningApplyDuration, start, &pb.InternalRaftStringer{Request: r}, nil, nil) - }(time.Now()) - switch { - case r.ClusterVersionSet != nil: - op = "ClusterVersionSet" // Implemented in 3.5.x - membershipApplier.ClusterVersionSet(r.ClusterVersionSet, shouldApplyV3) - return &apply.Result{} - case r.ClusterMemberAttrSet != nil: - op = "ClusterMemberAttrSet" // Implemented in 3.5.x - membershipApplier.ClusterMemberAttrSet(r.ClusterMemberAttrSet, shouldApplyV3) - case r.DowngradeInfoSet != nil: - op = "DowngradeInfoSet" // Implemented in 3.5.x - membershipApplier.DowngradeInfoSet(r.DowngradeInfoSet, shouldApplyV3) - case r.DowngradeVersionTest != nil: - op = "DowngradeVersionTest" // Implemented in 3.6 for test only - // do nothing, we are just to ensure etcdserver don't panic in case - // users(test cases) intentionally inject DowngradeVersionTestRequest - // into the WAL files. - default: - s.lg.Panic("not implemented apply", zap.Stringer("raft-request", r)) - return nil - } - return &apply.Result{} -} - func noSideEffect(r *pb.InternalRaftRequest) bool { return r.Range != nil || r.AuthUserGet != nil || r.AuthRoleGet != nil || r.AuthStatus != nil } @@ -2067,7 +2042,7 @@ func removeNeedlessRangeReqs(txn *pb.TxnRequest) { // invoked with a ConfChange that has already passed through Raft func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, confState *raftpb.ConfState, shouldApplyV3 membership.ShouldApplyV3) (bool, error) { lg := s.Logger() - if err := s.cluster.ValidateConfigurationChange(cc); err != nil { + if err := s.cluster.ValidateConfigurationChange(cc, shouldApplyV3); err != nil { lg.Error("Validation on configuration change failed", zap.Bool("shouldApplyV3", bool(shouldApplyV3)), zap.Error(err)) cc.NodeID = raft.None s.r.ApplyConfChange(cc) @@ -2519,3 +2494,15 @@ func (s *EtcdServer) getTxPostLockInsideApplyHook() func() { func (s *EtcdServer) CorruptionChecker() CorruptionChecker { return s.corruptionChecker } + +func addFeatureGateMetrics(fg featuregate.FeatureGate, guageVec *prometheus.GaugeVec) { + for feature, featureSpec := range fg.(featuregate.MutableFeatureGate).GetAll() { + var metricVal float64 + if fg.Enabled(feature) { + metricVal = 1 + } else { + metricVal = 0 + } + guageVec.With(prometheus.Labels{"name": string(feature), "stage": string(featureSpec.PreRelease)}).Set(metricVal) + } +} diff --git a/server/etcdserver/server_test.go b/server/etcdserver/server_test.go index d3f379837f40..3fcf51627d0a 100644 --- a/server/etcdserver/server_test.go +++ b/server/etcdserver/server_test.go @@ -24,12 +24,15 @@ import ( "os" "path/filepath" "reflect" + "strings" "sync" "testing" "time" "github.com/coreos/go-semver/semver" "github.com/golang/protobuf/proto" + "github.com/prometheus/client_golang/prometheus" + ptestutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -42,6 +45,7 @@ import ( "go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/types" "go.etcd.io/etcd/client/pkg/v3/verify" + "go.etcd.io/etcd/pkg/v3/featuregate" "go.etcd.io/etcd/pkg/v3/idutil" "go.etcd.io/etcd/pkg/v3/notify" "go.etcd.io/etcd/pkg/v3/pbutil" @@ -100,7 +104,6 @@ func TestApplyRepeat(t *testing.T) { v2store: st, cluster: cl, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, consistIndex: cindex.NewFakeConsistentIndex(0), uberApply: uberApplierMock{}, } @@ -145,7 +148,7 @@ func TestApplyRepeat(t *testing.T) { type uberApplierMock struct{} -func (uberApplierMock) Apply(r *pb.InternalRaftRequest) *apply2.Result { +func (uberApplierMock) Apply(r *pb.InternalRaftRequest, shouldApplyV3 membership.ShouldApplyV3) *apply2.Result { return &apply2.Result{} } @@ -786,7 +789,6 @@ func TestSnapshotOrdering(t *testing.T) { v2store: st, snapshotter: snap.New(lg, snapdir), cluster: cl, - SyncTicker: &time.Ticker{}, consistIndex: ci, beHooks: serverstorage.NewBackendHooks(lg, ci), } @@ -881,7 +883,6 @@ func TestConcurrentApplyAndSnapshotV3(t *testing.T) { v2store: st, snapshotter: snap.New(lg, testdir), cluster: cl, - SyncTicker: &time.Ticker{}, consistIndex: ci, beHooks: serverstorage.NewBackendHooks(lg, ci), firstCommitInTerm: notify.NewNotifier(), @@ -976,13 +977,12 @@ func TestAddMember(t *testing.T) { v2store: st, cluster: cl, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, consistIndex: cindex.NewFakeConsistentIndex(0), beHooks: serverstorage.NewBackendHooks(lg, nil), } s.start() m := membership.Member{ID: 1234, RaftAttributes: membership.RaftAttributes{PeerURLs: []string{"foo"}}} - _, err := s.AddMember(context.Background(), m) + _, err := s.AddMember(t.Context(), m) gaction := n.Action() s.Stop() @@ -1033,7 +1033,6 @@ func TestProcessIgnoreMismatchMessage(t *testing.T) { v2store: st, cluster: cl, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, consistIndex: cindex.NewFakeConsistentIndex(0), beHooks: serverstorage.NewBackendHooks(lg, nil), } @@ -1048,7 +1047,7 @@ func TestProcessIgnoreMismatchMessage(t *testing.T) { if types.ID(m.To) == s.MemberID() { t.Fatalf("m.To (%d) is expected to mismatch s.MemberID (%d)", m.To, s.MemberID()) } - err := s.Process(context.Background(), m) + err := s.Process(t.Context(), m) if err == nil { t.Fatalf("Must ignore the message and return an error") } @@ -1083,12 +1082,11 @@ func TestRemoveMember(t *testing.T) { v2store: st, cluster: cl, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, consistIndex: cindex.NewFakeConsistentIndex(0), beHooks: serverstorage.NewBackendHooks(lg, nil), } s.start() - _, err := s.RemoveMember(context.Background(), 1234) + _, err := s.RemoveMember(t.Context(), 1234) gaction := n.Action() s.Stop() @@ -1132,13 +1130,12 @@ func TestUpdateMember(t *testing.T) { v2store: st, cluster: cl, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, consistIndex: cindex.NewFakeConsistentIndex(0), beHooks: serverstorage.NewBackendHooks(lg, nil), } s.start() wm := membership.Member{ID: 1234, RaftAttributes: membership.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}} - _, err := s.UpdateMember(context.Background(), wm) + _, err := s.UpdateMember(t.Context(), wm) gaction := n.Action() s.Stop() @@ -1162,7 +1159,7 @@ func TestPublishV3(t *testing.T) { // simulate that request has gone through consensus ch <- &apply2.Result{} w := wait.NewWithResponse(ch) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) lg := zaptest.NewLogger(t) be, _ := betesting.NewDefaultTmpBackend(t) defer betesting.Close(t, be) @@ -1177,7 +1174,6 @@ func TestPublishV3(t *testing.T) { cluster: &membership.RaftCluster{}, w: w, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, authStore: auth.NewAuthStore(lg, schema.NewAuthBackend(lg, be), nil, 0), be: be, ctx: ctx, @@ -1204,24 +1200,23 @@ func TestPublishV3(t *testing.T) { // TestPublishV3Stopped tests that publish will be stopped if server is stopped. func TestPublishV3Stopped(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) r := newRaftNode(raftNodeConfig{ lg: zaptest.NewLogger(t), Node: newNodeNop(), transport: newNopTransporter(), }) srv := &EtcdServer{ - lgMu: new(sync.RWMutex), - lg: zaptest.NewLogger(t), - Cfg: config.ServerConfig{Logger: zaptest.NewLogger(t), TickMs: 1, SnapshotCatchUpEntries: DefaultSnapshotCatchUpEntries}, - r: *r, - cluster: &membership.RaftCluster{}, - w: mockwait.NewNop(), - done: make(chan struct{}), - stopping: make(chan struct{}), - stop: make(chan struct{}), - reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, + lgMu: new(sync.RWMutex), + lg: zaptest.NewLogger(t), + Cfg: config.ServerConfig{Logger: zaptest.NewLogger(t), TickMs: 1, SnapshotCatchUpEntries: DefaultSnapshotCatchUpEntries}, + r: *r, + cluster: &membership.RaftCluster{}, + w: mockwait.NewNop(), + done: make(chan struct{}), + stopping: make(chan struct{}), + stop: make(chan struct{}), + reqIDGen: idutil.NewGenerator(0, time.Time{}), ctx: ctx, cancel: cancel, @@ -1232,7 +1227,7 @@ func TestPublishV3Stopped(t *testing.T) { // TestPublishV3Retry tests that publish will keep retry until success. func TestPublishV3Retry(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) n := newNodeRecorderStream() lg := zaptest.NewLogger(t) @@ -1250,7 +1245,6 @@ func TestPublishV3Retry(t *testing.T) { attributes: membership.Attributes{Name: "node1", ClientURLs: []string{"http://a", "http://b"}}, cluster: &membership.RaftCluster{}, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, authStore: auth.NewAuthStore(lg, schema.NewAuthBackend(lg, be), nil, 0), be: be, ctx: ctx, @@ -1286,7 +1280,7 @@ func TestUpdateVersionV3(t *testing.T) { // simulate that request has gone through consensus ch <- &apply2.Result{} w := wait.NewWithResponse(ch) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) lg := zaptest.NewLogger(t) be, _ := betesting.NewDefaultTmpBackend(t) defer betesting.Close(t, be) @@ -1300,7 +1294,6 @@ func TestUpdateVersionV3(t *testing.T) { cluster: &membership.RaftCluster{}, w: w, reqIDGen: idutil.NewGenerator(0, time.Time{}), - SyncTicker: &time.Ticker{}, authStore: auth.NewAuthStore(lg, schema.NewAuthBackend(lg, be), nil, 0), be: be, @@ -1510,13 +1503,14 @@ func (n *nodeConfChangeCommitterRecorder) ApplyConfChange(conf raftpb.ConfChange return &raftpb.ConfState{} } -func newTestCluster(t testing.TB) *membership.RaftCluster { - return membership.NewCluster(zaptest.NewLogger(t)) +func newTestCluster(tb testing.TB) *membership.RaftCluster { + return membership.NewCluster(zaptest.NewLogger(tb)) } -func newTestClusterWithBackend(t testing.TB, membs []*membership.Member, be backend.Backend) *membership.RaftCluster { - lg := zaptest.NewLogger(t) +func newTestClusterWithBackend(tb testing.TB, membs []*membership.Member, be backend.Backend) *membership.RaftCluster { + lg := zaptest.NewLogger(tb) c := membership.NewCluster(lg) + c.SetStore(v2store.New()) c.SetBackend(schema.NewMembershipBackend(lg, be)) for _, m := range membs { c.AddMember(m, true) @@ -1683,3 +1677,30 @@ func TestIsActive(t *testing.T) { require.Equal(t, tc.expectActive, s.isActive()) } } + +func TestAddFeatureGateMetrics(t *testing.T) { + const testAlphaGate featuregate.Feature = "TestAlpha" + const testBetaGate featuregate.Feature = "TestBeta" + const testGAGate featuregate.Feature = "TestGA" + + featuremap := map[featuregate.Feature]featuregate.FeatureSpec{ + testGAGate: {Default: true, PreRelease: featuregate.GA}, + testAlphaGate: {Default: true, PreRelease: featuregate.Alpha}, + testBetaGate: {Default: false, PreRelease: featuregate.Beta}, + } + fg := featuregate.New("test", zaptest.NewLogger(t)) + fg.Add(featuremap) + + addFeatureGateMetrics(fg, serverFeatureEnabled) + + expected := `# HELP etcd_server_feature_enabled Whether or not a feature is enabled. 1 is enabled, 0 is not. + # TYPE etcd_server_feature_enabled gauge + etcd_server_feature_enabled{name="AllAlpha",stage="ALPHA"} 0 + etcd_server_feature_enabled{name="AllBeta",stage="BETA"} 0 + etcd_server_feature_enabled{name="TestAlpha",stage="ALPHA"} 1 + etcd_server_feature_enabled{name="TestBeta",stage="BETA"} 0 + etcd_server_feature_enabled{name="TestGA",stage=""} 1 + ` + err := ptestutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expected), "etcd_server_feature_enabled") + require.NoErrorf(t, err, "unexpected metric collection result: \n%s", err) +} diff --git a/server/etcdserver/txn/delete.go b/server/etcdserver/txn/delete.go new file mode 100644 index 000000000000..7d1218e8ce18 --- /dev/null +++ b/server/etcdserver/txn/delete.go @@ -0,0 +1,70 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package txn + +import ( + "context" + + "go.uber.org/zap" + + pb "go.etcd.io/etcd/api/v3/etcdserverpb" + "go.etcd.io/etcd/api/v3/mvccpb" + "go.etcd.io/etcd/pkg/v3/traceutil" + "go.etcd.io/etcd/server/v3/storage/mvcc" +) + +func DeleteRange(ctx context.Context, lg *zap.Logger, kv mvcc.KV, dr *pb.DeleteRangeRequest) (resp *pb.DeleteRangeResponse, trace *traceutil.Trace, err error) { + ctx, trace = ensureTrace(ctx, lg, "delete_range", + traceutil.Field{Key: "key", Value: string(dr.Key)}, + traceutil.Field{Key: "range_end", Value: string(dr.RangeEnd)}, + ) + txnWrite := kv.Write(trace) + defer txnWrite.End() + resp, err = deleteRange(ctx, txnWrite, dr) + return resp, trace, err +} + +func deleteRange(ctx context.Context, txnWrite mvcc.TxnWrite, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) { + resp := &pb.DeleteRangeResponse{} + resp.Header = &pb.ResponseHeader{} + end := mkGteRange(dr.RangeEnd) + + if dr.PrevKv { + rr, err := txnWrite.Range(ctx, dr.Key, end, mvcc.RangeOptions{}) + if err != nil { + return nil, err + } + if rr != nil { + resp.PrevKvs = make([]*mvccpb.KeyValue, len(rr.KVs)) + for i := range rr.KVs { + resp.PrevKvs[i] = &rr.KVs[i] + } + } + } + + resp.Deleted, resp.Header.Revision = txnWrite.DeleteRange(dr.Key, end) + return resp, nil +} + +// mkGteRange determines if the range end is a >= range. This works around grpc +// sending empty byte strings as nil; >= is encoded in the range end as '\0'. +// If it is a GTE range, then []byte{} is returned to indicate the empty byte +// string (vs nil being no byte string). +func mkGteRange(rangeEnd []byte) []byte { + if len(rangeEnd) == 1 && rangeEnd[0] == 0 { + return []byte{} + } + return rangeEnd +} diff --git a/server/etcdserver/txn/put.go b/server/etcdserver/txn/put.go new file mode 100644 index 000000000000..8c304cc59293 --- /dev/null +++ b/server/etcdserver/txn/put.go @@ -0,0 +1,114 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package txn + +import ( + "context" + + "go.uber.org/zap" + + pb "go.etcd.io/etcd/api/v3/etcdserverpb" + "go.etcd.io/etcd/pkg/v3/traceutil" + "go.etcd.io/etcd/server/v3/etcdserver/errors" + "go.etcd.io/etcd/server/v3/lease" + "go.etcd.io/etcd/server/v3/storage/mvcc" +) + +func Put(ctx context.Context, lg *zap.Logger, lessor lease.Lessor, kv mvcc.KV, p *pb.PutRequest) (resp *pb.PutResponse, trace *traceutil.Trace, err error) { + ctx, trace = ensureTrace(ctx, lg, "put", + traceutil.Field{Key: "key", Value: string(p.Key)}, + traceutil.Field{Key: "req_size", Value: p.Size()}, + ) + err = checkLease(lessor, p) + if err != nil { + return nil, trace, err + } + txnWrite := kv.Write(trace) + defer txnWrite.End() + prevKV, err := checkAndGetPrevKV(trace, txnWrite, p) + if err != nil { + return nil, trace, err + } + return put(ctx, txnWrite, p, prevKV), trace, nil +} + +func put(ctx context.Context, txnWrite mvcc.TxnWrite, p *pb.PutRequest, prevKV *mvcc.RangeResult) *pb.PutResponse { + trace := traceutil.Get(ctx) + resp := &pb.PutResponse{} + resp.Header = &pb.ResponseHeader{} + val, leaseID := p.Value, lease.LeaseID(p.Lease) + + if p.IgnoreValue { + val = prevKV.KVs[0].Value + } + if p.IgnoreLease { + leaseID = lease.LeaseID(prevKV.KVs[0].Lease) + } + if p.PrevKv { + if prevKV != nil && len(prevKV.KVs) != 0 { + resp.PrevKv = &prevKV.KVs[0] + } + } + + resp.Header.Revision = txnWrite.Put(p.Key, val, leaseID) + trace.AddField(traceutil.Field{Key: "response_revision", Value: resp.Header.Revision}) + return resp +} + +func checkPut(trace *traceutil.Trace, txnWrite mvcc.ReadView, lessor lease.Lessor, p *pb.PutRequest) error { + err := checkLease(lessor, p) + if err != nil { + return err + } + _, err = checkAndGetPrevKV(trace, txnWrite, p) + return err +} + +func checkLease(lessor lease.Lessor, p *pb.PutRequest) error { + leaseID := lease.LeaseID(p.Lease) + if leaseID != lease.NoLease { + if l := lessor.Lookup(leaseID); l == nil { + return lease.ErrLeaseNotFound + } + } + return nil +} + +func checkAndGetPrevKV(trace *traceutil.Trace, txnWrite mvcc.ReadView, p *pb.PutRequest) (prevKV *mvcc.RangeResult, err error) { + prevKV, err = getPrevKV(trace, txnWrite, p) + if err != nil { + return nil, err + } + if p.IgnoreValue || p.IgnoreLease { + if prevKV == nil || len(prevKV.KVs) == 0 { + // ignore_{lease,value} flag expects previous key-value pair + return nil, errors.ErrKeyNotFound + } + } + return prevKV, nil +} + +func getPrevKV(trace *traceutil.Trace, txnWrite mvcc.ReadView, p *pb.PutRequest) (prevKV *mvcc.RangeResult, err error) { + if p.IgnoreValue || p.IgnoreLease || p.PrevKv { + trace.StepWithFunction(func() { + prevKV, err = txnWrite.Range(context.TODO(), p.Key, nil, mvcc.RangeOptions{}) + }, "get previous kv pair") + + if err != nil { + return nil, err + } + } + return prevKV, nil +} diff --git a/server/etcdserver/txn/range.go b/server/etcdserver/txn/range.go new file mode 100644 index 000000000000..8e9fef34ad7b --- /dev/null +++ b/server/etcdserver/txn/range.go @@ -0,0 +1,203 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package txn + +import ( + "bytes" + "context" + "sort" + "time" + + "go.uber.org/zap" + + pb "go.etcd.io/etcd/api/v3/etcdserverpb" + "go.etcd.io/etcd/api/v3/mvccpb" + "go.etcd.io/etcd/pkg/v3/traceutil" + "go.etcd.io/etcd/server/v3/storage/mvcc" +) + +func Range(ctx context.Context, lg *zap.Logger, kv mvcc.KV, r *pb.RangeRequest) (resp *pb.RangeResponse, trace *traceutil.Trace, err error) { + ctx, trace = ensureTrace(ctx, lg, "range") + defer func(start time.Time) { + success := err == nil + RangeSecObserve(success, time.Since(start)) + }(time.Now()) + txnRead := kv.Read(mvcc.ConcurrentReadTxMode, trace) + defer txnRead.End() + resp, err = executeRange(ctx, lg, txnRead, r) + return resp, trace, err +} + +func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) { + trace := traceutil.Get(ctx) + + resp := &pb.RangeResponse{} + resp.Header = &pb.ResponseHeader{} + + limit := r.Limit + if r.SortOrder != pb.RangeRequest_NONE || + r.MinModRevision != 0 || r.MaxModRevision != 0 || + r.MinCreateRevision != 0 || r.MaxCreateRevision != 0 { + // fetch everything; sort and truncate afterwards + limit = 0 + } + if limit > 0 { + // fetch one extra for 'more' flag + limit = limit + 1 + } + + ro := mvcc.RangeOptions{ + Limit: limit, + Rev: r.Revision, + Count: r.CountOnly, + } + + rr, err := txnRead.Range(ctx, r.Key, mkGteRange(r.RangeEnd), ro) + if err != nil { + return nil, err + } + + if r.MaxModRevision != 0 { + f := func(kv *mvccpb.KeyValue) bool { return kv.ModRevision > r.MaxModRevision } + pruneKVs(rr, f) + } + if r.MinModRevision != 0 { + f := func(kv *mvccpb.KeyValue) bool { return kv.ModRevision < r.MinModRevision } + pruneKVs(rr, f) + } + if r.MaxCreateRevision != 0 { + f := func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision > r.MaxCreateRevision } + pruneKVs(rr, f) + } + if r.MinCreateRevision != 0 { + f := func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision < r.MinCreateRevision } + pruneKVs(rr, f) + } + + sortOrder := r.SortOrder + if r.SortTarget != pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_NONE { + // Since current mvcc.Range implementation returns results + // sorted by keys in lexiographically ascending order, + // sort ASCEND by default only when target is not 'KEY' + sortOrder = pb.RangeRequest_ASCEND + } else if r.SortTarget == pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_ASCEND { + // Since current mvcc.Range implementation returns results + // sorted by keys in lexiographically ascending order, + // don't re-sort when target is 'KEY' and order is ASCEND + sortOrder = pb.RangeRequest_NONE + } + if sortOrder != pb.RangeRequest_NONE { + var sorter sort.Interface + switch { + case r.SortTarget == pb.RangeRequest_KEY: + sorter = &kvSortByKey{&kvSort{rr.KVs}} + case r.SortTarget == pb.RangeRequest_VERSION: + sorter = &kvSortByVersion{&kvSort{rr.KVs}} + case r.SortTarget == pb.RangeRequest_CREATE: + sorter = &kvSortByCreate{&kvSort{rr.KVs}} + case r.SortTarget == pb.RangeRequest_MOD: + sorter = &kvSortByMod{&kvSort{rr.KVs}} + case r.SortTarget == pb.RangeRequest_VALUE: + sorter = &kvSortByValue{&kvSort{rr.KVs}} + default: + lg.Panic("unexpected sort target", zap.Int32("sort-target", int32(r.SortTarget))) + } + switch { + case sortOrder == pb.RangeRequest_ASCEND: + sort.Sort(sorter) + case sortOrder == pb.RangeRequest_DESCEND: + sort.Sort(sort.Reverse(sorter)) + } + } + + if r.Limit > 0 && len(rr.KVs) > int(r.Limit) { + rr.KVs = rr.KVs[:r.Limit] + resp.More = true + } + trace.Step("filter and sort the key-value pairs") + resp.Header.Revision = rr.Rev + resp.Count = int64(rr.Count) + resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs)) + for i := range rr.KVs { + if r.KeysOnly { + rr.KVs[i].Value = nil + } + resp.Kvs[i] = &rr.KVs[i] + } + trace.Step("assemble the response") + return resp, nil +} + +func checkRange(rv mvcc.ReadView, req *pb.RangeRequest) error { + switch { + case req.Revision == 0: + return nil + case req.Revision > rv.Rev(): + return mvcc.ErrFutureRev + case req.Revision < rv.FirstRev(): + return mvcc.ErrCompacted + } + return nil +} + +func pruneKVs(rr *mvcc.RangeResult, isPrunable func(*mvccpb.KeyValue) bool) { + j := 0 + for i := range rr.KVs { + rr.KVs[j] = rr.KVs[i] + if !isPrunable(&rr.KVs[i]) { + j++ + } + } + rr.KVs = rr.KVs[:j] +} + +type kvSort struct{ kvs []mvccpb.KeyValue } + +func (s *kvSort) Swap(i, j int) { + t := s.kvs[i] + s.kvs[i] = s.kvs[j] + s.kvs[j] = t +} +func (s *kvSort) Len() int { return len(s.kvs) } + +type kvSortByKey struct{ *kvSort } + +func (s *kvSortByKey) Less(i, j int) bool { + return bytes.Compare(s.kvs[i].Key, s.kvs[j].Key) < 0 +} + +type kvSortByVersion struct{ *kvSort } + +func (s *kvSortByVersion) Less(i, j int) bool { + return (s.kvs[i].Version - s.kvs[j].Version) < 0 +} + +type kvSortByCreate struct{ *kvSort } + +func (s *kvSortByCreate) Less(i, j int) bool { + return (s.kvs[i].CreateRevision - s.kvs[j].CreateRevision) < 0 +} + +type kvSortByMod struct{ *kvSort } + +func (s *kvSortByMod) Less(i, j int) bool { + return (s.kvs[i].ModRevision - s.kvs[j].ModRevision) < 0 +} + +type kvSortByValue struct{ *kvSort } + +func (s *kvSortByValue) Less(i, j int) bool { + return bytes.Compare(s.kvs[i].Value, s.kvs[j].Value) < 0 +} diff --git a/server/etcdserver/txn/txn.go b/server/etcdserver/txn/txn.go index 51f70a06a423..b0d568d28ef9 100644 --- a/server/etcdserver/txn/txn.go +++ b/server/etcdserver/txn/txn.go @@ -18,8 +18,6 @@ import ( "bytes" "context" "fmt" - "sort" - "time" "go.uber.org/zap" @@ -27,234 +25,12 @@ import ( "go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/pkg/v3/traceutil" "go.etcd.io/etcd/server/v3/auth" - "go.etcd.io/etcd/server/v3/etcdserver/errors" "go.etcd.io/etcd/server/v3/lease" "go.etcd.io/etcd/server/v3/storage/mvcc" ) -func Put(ctx context.Context, lg *zap.Logger, lessor lease.Lessor, kv mvcc.KV, p *pb.PutRequest) (resp *pb.PutResponse, trace *traceutil.Trace, err error) { - trace = traceutil.Get(ctx) - // create put tracing if the trace in context is empty - if trace.IsEmpty() { - trace = traceutil.New("put", - lg, - traceutil.Field{Key: "key", Value: string(p.Key)}, - traceutil.Field{Key: "req_size", Value: p.Size()}, - ) - ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace) - } - leaseID := lease.LeaseID(p.Lease) - if leaseID != lease.NoLease { - if l := lessor.Lookup(leaseID); l == nil { - return nil, nil, lease.ErrLeaseNotFound - } - } - txnWrite := kv.Write(trace) - defer txnWrite.End() - resp, err = put(ctx, txnWrite, p) - return resp, trace, err -} - -func put(ctx context.Context, txnWrite mvcc.TxnWrite, p *pb.PutRequest) (resp *pb.PutResponse, err error) { - trace := traceutil.Get(ctx) - resp = &pb.PutResponse{} - resp.Header = &pb.ResponseHeader{} - val, leaseID := p.Value, lease.LeaseID(p.Lease) - - var rr *mvcc.RangeResult - if p.IgnoreValue || p.IgnoreLease || p.PrevKv { - trace.StepWithFunction(func() { - rr, err = txnWrite.Range(context.TODO(), p.Key, nil, mvcc.RangeOptions{}) - }, "get previous kv pair") - - if err != nil { - return nil, err - } - } - if p.IgnoreValue || p.IgnoreLease { - if rr == nil || len(rr.KVs) == 0 { - // ignore_{lease,value} flag expects previous key-value pair - return nil, errors.ErrKeyNotFound - } - } - if p.IgnoreValue { - val = rr.KVs[0].Value - } - if p.IgnoreLease { - leaseID = lease.LeaseID(rr.KVs[0].Lease) - } - if p.PrevKv { - if rr != nil && len(rr.KVs) != 0 { - resp.PrevKv = &rr.KVs[0] - } - } - - resp.Header.Revision = txnWrite.Put(p.Key, val, leaseID) - trace.AddField(traceutil.Field{Key: "response_revision", Value: resp.Header.Revision}) - return resp, nil -} - -func DeleteRange(ctx context.Context, lg *zap.Logger, kv mvcc.KV, dr *pb.DeleteRangeRequest) (resp *pb.DeleteRangeResponse, trace *traceutil.Trace, err error) { - trace = traceutil.Get(ctx) - // create delete tracing if the trace in context is empty - if trace.IsEmpty() { - trace = traceutil.New("delete_range", - lg, - traceutil.Field{Key: "key", Value: string(dr.Key)}, - traceutil.Field{Key: "range_end", Value: string(dr.RangeEnd)}, - ) - ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace) - } - txnWrite := kv.Write(trace) - defer txnWrite.End() - resp, err = deleteRange(ctx, txnWrite, dr) - return resp, trace, err -} - -func deleteRange(ctx context.Context, txnWrite mvcc.TxnWrite, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) { - resp := &pb.DeleteRangeResponse{} - resp.Header = &pb.ResponseHeader{} - end := mkGteRange(dr.RangeEnd) - - if dr.PrevKv { - rr, err := txnWrite.Range(ctx, dr.Key, end, mvcc.RangeOptions{}) - if err != nil { - return nil, err - } - if rr != nil { - resp.PrevKvs = make([]*mvccpb.KeyValue, len(rr.KVs)) - for i := range rr.KVs { - resp.PrevKvs[i] = &rr.KVs[i] - } - } - } - - resp.Deleted, resp.Header.Revision = txnWrite.DeleteRange(dr.Key, end) - return resp, nil -} - -func Range(ctx context.Context, lg *zap.Logger, kv mvcc.KV, r *pb.RangeRequest) (resp *pb.RangeResponse, trace *traceutil.Trace, err error) { - trace = traceutil.Get(ctx) - if trace.IsEmpty() { - trace = traceutil.New("range", lg) - ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace) - } - defer func(start time.Time) { - success := err == nil - RangeSecObserve(success, time.Since(start)) - }(time.Now()) - txnRead := kv.Read(mvcc.ConcurrentReadTxMode, trace) - defer txnRead.End() - resp, err = executeRange(ctx, lg, txnRead, r) - return resp, trace, err -} - -func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) { - trace := traceutil.Get(ctx) - - resp := &pb.RangeResponse{} - resp.Header = &pb.ResponseHeader{} - - limit := r.Limit - if r.SortOrder != pb.RangeRequest_NONE || - r.MinModRevision != 0 || r.MaxModRevision != 0 || - r.MinCreateRevision != 0 || r.MaxCreateRevision != 0 { - // fetch everything; sort and truncate afterwards - limit = 0 - } - if limit > 0 { - // fetch one extra for 'more' flag - limit = limit + 1 - } - - ro := mvcc.RangeOptions{ - Limit: limit, - Rev: r.Revision, - Count: r.CountOnly, - } - - rr, err := txnRead.Range(ctx, r.Key, mkGteRange(r.RangeEnd), ro) - if err != nil { - return nil, err - } - - if r.MaxModRevision != 0 { - f := func(kv *mvccpb.KeyValue) bool { return kv.ModRevision > r.MaxModRevision } - pruneKVs(rr, f) - } - if r.MinModRevision != 0 { - f := func(kv *mvccpb.KeyValue) bool { return kv.ModRevision < r.MinModRevision } - pruneKVs(rr, f) - } - if r.MaxCreateRevision != 0 { - f := func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision > r.MaxCreateRevision } - pruneKVs(rr, f) - } - if r.MinCreateRevision != 0 { - f := func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision < r.MinCreateRevision } - pruneKVs(rr, f) - } - - sortOrder := r.SortOrder - if r.SortTarget != pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_NONE { - // Since current mvcc.Range implementation returns results - // sorted by keys in lexiographically ascending order, - // sort ASCEND by default only when target is not 'KEY' - sortOrder = pb.RangeRequest_ASCEND - } else if r.SortTarget == pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_ASCEND { - // Since current mvcc.Range implementation returns results - // sorted by keys in lexiographically ascending order, - // don't re-sort when target is 'KEY' and order is ASCEND - sortOrder = pb.RangeRequest_NONE - } - if sortOrder != pb.RangeRequest_NONE { - var sorter sort.Interface - switch { - case r.SortTarget == pb.RangeRequest_KEY: - sorter = &kvSortByKey{&kvSort{rr.KVs}} - case r.SortTarget == pb.RangeRequest_VERSION: - sorter = &kvSortByVersion{&kvSort{rr.KVs}} - case r.SortTarget == pb.RangeRequest_CREATE: - sorter = &kvSortByCreate{&kvSort{rr.KVs}} - case r.SortTarget == pb.RangeRequest_MOD: - sorter = &kvSortByMod{&kvSort{rr.KVs}} - case r.SortTarget == pb.RangeRequest_VALUE: - sorter = &kvSortByValue{&kvSort{rr.KVs}} - default: - lg.Panic("unexpected sort target", zap.Int32("sort-target", int32(r.SortTarget))) - } - switch { - case sortOrder == pb.RangeRequest_ASCEND: - sort.Sort(sorter) - case sortOrder == pb.RangeRequest_DESCEND: - sort.Sort(sort.Reverse(sorter)) - } - } - - if r.Limit > 0 && len(rr.KVs) > int(r.Limit) { - rr.KVs = rr.KVs[:r.Limit] - resp.More = true - } - trace.Step("filter and sort the key-value pairs") - resp.Header.Revision = rr.Rev - resp.Count = int64(rr.Count) - resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs)) - for i := range rr.KVs { - if r.KeysOnly { - rr.KVs[i].Value = nil - } - resp.Kvs[i] = &rr.KVs[i] - } - trace.Step("assemble the response") - return resp, nil -} - -func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWithSharedBuffer bool, kv mvcc.KV, lessor lease.Lessor) (*pb.TxnResponse, *traceutil.Trace, error) { - trace := traceutil.Get(ctx) - if trace.IsEmpty() { - trace = traceutil.New("transaction", lg) - ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace) - } +func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWithSharedBuffer bool, kv mvcc.KV, lessor lease.Lessor) (txnResp *pb.TxnResponse, trace *traceutil.Trace, err error) { + ctx, trace = ensureTrace(ctx, lg, "transaction") isWrite := !IsTxnReadonly(rt) // When the transaction contains write operations, we use ReadTx instead of // ConcurrentReadTx to avoid extra overhead of copying buffer. @@ -275,7 +51,7 @@ func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWit if isWrite { trace.AddField(traceutil.Field{Key: "read_only", Value: false}) } - _, err := checkTxn(txnRead, rt, lessor, txnPath) + _, err = checkTxn(trace, txnRead, rt, lessor, txnPath) if err != nil { txnRead.End() return nil, nil, err @@ -292,7 +68,7 @@ func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWit } else { txnWrite = mvcc.NewReadOnlyTxnWrite(txnRead) } - txnResp, err := txn(ctx, lg, txnWrite, rt, isWrite, txnPath) + txnResp, err = txn(ctx, lg, txnWrite, rt, isWrite, txnPath) txnWrite.End() trace.AddField( @@ -382,10 +158,11 @@ func executeTxn(ctx context.Context, lg *zap.Logger, txnWrite mvcc.TxnWrite, rt traceutil.Field{Key: "req_type", Value: "put"}, traceutil.Field{Key: "key", Value: string(tv.RequestPut.Key)}, traceutil.Field{Key: "req_size", Value: tv.RequestPut.Size()}) - resp, err := put(ctx, txnWrite, tv.RequestPut) + prevKV, err := getPrevKV(trace, txnWrite, tv.RequestPut) if err != nil { - return 0, fmt.Errorf("applyTxn: failed Put: %w", err) + return 0, fmt.Errorf("applyTxn: failed to get prevKV on put: %w", err) } + resp := put(ctx, txnWrite, tv.RequestPut, prevKV) respi.(*pb.ResponseOp_ResponsePut).ResponsePut = resp trace.StopSubTrace() case *pb.RequestOp_RequestDeleteRange: @@ -410,38 +187,7 @@ func executeTxn(ctx context.Context, lg *zap.Logger, txnWrite mvcc.TxnWrite, rt return txns, nil } -func checkPut(rv mvcc.ReadView, lessor lease.Lessor, req *pb.PutRequest) error { - if req.IgnoreValue || req.IgnoreLease { - // expects previous key-value, error if not exist - rr, err := rv.Range(context.TODO(), req.Key, nil, mvcc.RangeOptions{}) - if err != nil { - return err - } - if rr == nil || len(rr.KVs) == 0 { - return errors.ErrKeyNotFound - } - } - if lease.LeaseID(req.Lease) != lease.NoLease { - if l := lessor.Lookup(lease.LeaseID(req.Lease)); l == nil { - return lease.ErrLeaseNotFound - } - } - return nil -} - -func checkRange(rv mvcc.ReadView, req *pb.RangeRequest) error { - switch { - case req.Revision == 0: - return nil - case req.Revision > rv.Rev(): - return mvcc.ErrFutureRev - case req.Revision < rv.FirstRev(): - return mvcc.ErrCompacted - } - return nil -} - -func checkTxn(rv mvcc.ReadView, rt *pb.TxnRequest, lessor lease.Lessor, txnPath []bool) (int, error) { +func checkTxn(trace *traceutil.Trace, rv mvcc.ReadView, rt *pb.TxnRequest, lessor lease.Lessor, txnPath []bool) (int, error) { txnCount := 0 reqs := rt.Success if !txnPath[0] { @@ -454,10 +200,10 @@ func checkTxn(rv mvcc.ReadView, rt *pb.TxnRequest, lessor lease.Lessor, txnPath case *pb.RequestOp_RequestRange: err = checkRange(rv, tv.RequestRange) case *pb.RequestOp_RequestPut: - err = checkPut(rv, lessor, tv.RequestPut) + err = checkPut(trace, rv, lessor, tv.RequestPut) case *pb.RequestOp_RequestDeleteRange: case *pb.RequestOp_RequestTxn: - txns, err = checkTxn(rv, tv.RequestTxn, lessor, txnPath[1:]) + txns, err = checkTxn(trace, rv, tv.RequestTxn, lessor, txnPath[1:]) txnCount += txns + 1 txnPath = txnPath[txns+1:] default: @@ -470,67 +216,6 @@ func checkTxn(rv mvcc.ReadView, rt *pb.TxnRequest, lessor lease.Lessor, txnPath return txnCount, nil } -// mkGteRange determines if the range end is a >= range. This works around grpc -// sending empty byte strings as nil; >= is encoded in the range end as '\0'. -// If it is a GTE range, then []byte{} is returned to indicate the empty byte -// string (vs nil being no byte string). -func mkGteRange(rangeEnd []byte) []byte { - if len(rangeEnd) == 1 && rangeEnd[0] == 0 { - return []byte{} - } - return rangeEnd -} - -func pruneKVs(rr *mvcc.RangeResult, isPrunable func(*mvccpb.KeyValue) bool) { - j := 0 - for i := range rr.KVs { - rr.KVs[j] = rr.KVs[i] - if !isPrunable(&rr.KVs[i]) { - j++ - } - } - rr.KVs = rr.KVs[:j] -} - -type kvSort struct{ kvs []mvccpb.KeyValue } - -func (s *kvSort) Swap(i, j int) { - t := s.kvs[i] - s.kvs[i] = s.kvs[j] - s.kvs[j] = t -} -func (s *kvSort) Len() int { return len(s.kvs) } - -type kvSortByKey struct{ *kvSort } - -func (s *kvSortByKey) Less(i, j int) bool { - return bytes.Compare(s.kvs[i].Key, s.kvs[j].Key) < 0 -} - -type kvSortByVersion struct{ *kvSort } - -func (s *kvSortByVersion) Less(i, j int) bool { - return (s.kvs[i].Version - s.kvs[j].Version) < 0 -} - -type kvSortByCreate struct{ *kvSort } - -func (s *kvSortByCreate) Less(i, j int) bool { - return (s.kvs[i].CreateRevision - s.kvs[j].CreateRevision) < 0 -} - -type kvSortByMod struct{ *kvSort } - -func (s *kvSortByMod) Less(i, j int) bool { - return (s.kvs[i].ModRevision - s.kvs[j].ModRevision) < 0 -} - -type kvSortByValue struct{ *kvSort } - -func (s *kvSortByValue) Less(i, j int) bool { - return bytes.Compare(s.kvs[i].Value, s.kvs[j].Value) < 0 -} - func compareInt64(a, b int64) int { switch { case a < b: @@ -721,3 +406,15 @@ func checkTxnReqsPermission(as auth.AuthStore, ai *auth.AuthInfo, reqs []*pb.Req return nil } + +func ensureTrace(ctx context.Context, lg *zap.Logger, operation string, fields ...traceutil.Field) (context.Context, *traceutil.Trace) { + trace := traceutil.Get(ctx) + if trace.IsEmpty() { + trace = traceutil.New(operation, + lg, + fields..., + ) + ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace) + } + return ctx, trace +} diff --git a/server/etcdserver/txn/txn_test.go b/server/etcdserver/txn/txn_test.go index 850c8a95b9b6..8b73424fd75d 100644 --- a/server/etcdserver/txn/txn_test.go +++ b/server/etcdserver/txn/txn_test.go @@ -226,7 +226,7 @@ func TestCheckTxn(t *testing.T) { t.Run(tc.name, func(t *testing.T) { s, lessor := setup(t, tc.setup) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() _, _, err := Txn(ctx, zaptest.NewLogger(t), tc.txn, false, s, lessor) @@ -246,7 +246,7 @@ func TestCheckPut(t *testing.T) { t.Run(tc.name, func(t *testing.T) { s, lessor := setup(t, tc.setup) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() _, _, err := Put(ctx, zaptest.NewLogger(t), lessor, s, tc.op.GetRequestPut()) @@ -266,7 +266,7 @@ func TestCheckRange(t *testing.T) { t.Run(tc.name, func(t *testing.T) { s, _ := setup(t, tc.setup) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() _, _, err := Range(ctx, zaptest.NewLogger(t), s, tc.op.GetRequestRange()) @@ -314,7 +314,7 @@ func TestReadonlyTxnError(t *testing.T) { defer s.Close() // setup cancelled context - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) cancel() // put some data to prevent early termination in rangeKeys @@ -345,7 +345,7 @@ func TestWriteTxnPanicWithoutApply(t *testing.T) { defer s.Close() // setup cancelled context - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) cancel() // write txn that puts some data and then fails in range due to cancelled context diff --git a/server/etcdserver/version/version_test.go b/server/etcdserver/version/version_test.go index cae8e70a3b5b..3d5570352900 100644 --- a/server/etcdserver/version/version_test.go +++ b/server/etcdserver/version/version_test.go @@ -65,7 +65,7 @@ func TestDowngradeSingleNode(t *testing.T) { c.StepMonitors() assert.Equal(t, newCluster(lg, 1, version.V3_6), c) - require.NoError(t, c.Version().DowngradeEnable(context.Background(), &version.V3_5)) + require.NoError(t, c.Version().DowngradeEnable(t.Context(), &version.V3_5)) c.StepMonitors() assert.Equal(t, version.V3_5, c.clusterVersion) @@ -81,7 +81,7 @@ func TestDowngradeThreeNode(t *testing.T) { c.StepMonitors() assert.Equal(t, newCluster(lg, 3, version.V3_6), c) - require.NoError(t, c.Version().DowngradeEnable(context.Background(), &version.V3_5)) + require.NoError(t, c.Version().DowngradeEnable(t.Context(), &version.V3_5)) c.StepMonitors() assert.Equal(t, version.V3_5, c.clusterVersion) @@ -101,7 +101,7 @@ func TestNewerMemberCanReconnectDuringDowngrade(t *testing.T) { c.StepMonitors() assert.Equal(t, newCluster(lg, 3, version.V3_6), c) - require.NoError(t, c.Version().DowngradeEnable(context.Background(), &version.V3_5)) + require.NoError(t, c.Version().DowngradeEnable(t.Context(), &version.V3_5)) c.StepMonitors() assert.Equal(t, version.V3_5, c.clusterVersion) diff --git a/server/features/etcd_features.go b/server/features/etcd_features.go index 822eb1507ecc..6ff85e35779d 100644 --- a/server/features/etcd_features.go +++ b/server/features/etcd_features.go @@ -35,11 +35,6 @@ const ( // of code conflicts because changes are more likely to be scattered // across the file. - // DistributedTracing enables experimental distributed tracing using OpenTelemetry Tracing. - // owner: @dashpole - // alpha: v3.5 - // issue: https://github.com/etcd-io/etcd/issues/12460 - DistributedTracing featuregate.Feature = "DistributedTracing" // StopGRPCServiceOnDefrag enables etcd gRPC service to stop serving client requests on defragmentation. // owner: @chaochn47 // alpha: v3.6 @@ -67,36 +62,29 @@ const ( LeaseCheckpoint featuregate.Feature = "LeaseCheckpoint" // LeaseCheckpointPersist enables persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. // Requires EnableLeaseCheckpoint featuragate to be enabled. - // Deprecated in v3.6. // TODO: Delete in v3.7 // owner: @serathius // alpha: v3.6 // main PR: https://github.com/etcd-io/etcd/pull/13508 + // Deprecated: Enabled by default in v3.6, to be removed in v3.7. LeaseCheckpointPersist featuregate.Feature = "LeaseCheckpointPersist" + // SetMemberLocalAddr enables using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer. + // Requires SetMemberLocalAddr featuragate to be enabled. + // owner: @flawedmatrix + // alpha: v3.6 + // main PR: https://github.com/etcd-io/etcd/pull/17661 + SetMemberLocalAddr featuregate.Feature = "SetMemberLocalAddr" ) -var ( - DefaultEtcdServerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - DistributedTracing: {Default: false, PreRelease: featuregate.Alpha}, - StopGRPCServiceOnDefrag: {Default: false, PreRelease: featuregate.Alpha}, - InitialCorruptCheck: {Default: false, PreRelease: featuregate.Alpha}, - CompactHashCheck: {Default: false, PreRelease: featuregate.Alpha}, - TxnModeWriteWithSharedBuffer: {Default: true, PreRelease: featuregate.Beta}, - LeaseCheckpoint: {Default: false, PreRelease: featuregate.Alpha}, - LeaseCheckpointPersist: {Default: false, PreRelease: featuregate.Alpha}, - } - // ExperimentalFlagToFeatureMap is the map from the cmd line flags of experimental features - // to their corresponding feature gates. - // Deprecated: only add existing experimental features here. DO NOT use for new features. - ExperimentalFlagToFeatureMap = map[string]featuregate.Feature{ - "experimental-stop-grpc-service-on-defrag": StopGRPCServiceOnDefrag, - "experimental-initial-corrupt-check": InitialCorruptCheck, - "experimental-compact-hash-check-enabled": CompactHashCheck, - "experimental-txn-mode-write-with-shared-buffer": TxnModeWriteWithSharedBuffer, - "experimental-enable-lease-checkpoint": LeaseCheckpoint, - "experimental-enable-lease-checkpoint-persist": LeaseCheckpointPersist, - } -) +var DefaultEtcdServerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + StopGRPCServiceOnDefrag: {Default: false, PreRelease: featuregate.Alpha}, + InitialCorruptCheck: {Default: false, PreRelease: featuregate.Alpha}, + CompactHashCheck: {Default: false, PreRelease: featuregate.Alpha}, + TxnModeWriteWithSharedBuffer: {Default: true, PreRelease: featuregate.Beta}, + LeaseCheckpoint: {Default: false, PreRelease: featuregate.Alpha}, + LeaseCheckpointPersist: {Default: false, PreRelease: featuregate.Alpha}, + SetMemberLocalAddr: {Default: false, PreRelease: featuregate.Alpha}, +} func NewDefaultServerFeatureGate(name string, lg *zap.Logger) featuregate.FeatureGate { fg := featuregate.New(fmt.Sprintf("%sServerFeatureGate", name), lg) diff --git a/server/go.mod b/server/go.mod index 20d84a62c780..f6a82f95c83b 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,27 +1,27 @@ module go.etcd.io/etcd/server/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/coreos/go-semver v0.3.1 github.com/coreos/go-systemd/v22 v22.5.0 github.com/dustin/go-humanize v1.0.1 github.com/gogo/protobuf v1.3.2 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da + github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 github.com/golang/protobuf v1.5.4 github.com/google/btree v1.1.3 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/jonboulle/clockwork v0.5.0 - github.com/prometheus/client_golang v1.20.5 - github.com/prometheus/client_model v0.6.1 + github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_model v0.6.2 github.com/soheilhy/cmux v0.1.5 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 @@ -32,51 +32,50 @@ require ( go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 go.etcd.io/raft/v3 v3.6.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 + go.opentelemetry.io/otel v1.36.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 + go.opentelemetry.io/otel/sdk v1.36.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 - golang.org/x/net v0.34.0 - golang.org/x/time v0.9.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f - google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.4 + golang.org/x/crypto v0.38.0 + golang.org/x/net v0.40.0 + golang.org/x/time v0.11.0 + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 + google.golang.org/grpc v1.72.2 + google.golang.org/protobuf v1.36.6 gopkg.in/natefinch/lumberjack.v2 v2.2.1 sigs.k8s.io/yaml v1.4.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.6 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect ) replace ( diff --git a/server/go.sum b/server/go.sum index 0c8e9dca903a..82e55f74ce91 100644 --- a/server/go.sum +++ b/server/go.sum @@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -15,10 +15,11 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -36,11 +37,11 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -51,28 +52,28 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -85,28 +86,28 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -128,24 +129,24 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -158,8 +159,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -175,30 +176,30 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -216,19 +217,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -240,7 +241,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/server/lease/lease.go b/server/lease/lease.go index 95f3eb6f7568..8f194c98030c 100644 --- a/server/lease/lease.go +++ b/server/lease/lease.go @@ -97,8 +97,8 @@ func (l *Lease) forever() { // Demoted returns true if the lease's expiry has been reset to forever. func (l *Lease) Demoted() bool { - l.expiryMu.Lock() - defer l.expiryMu.Unlock() + l.expiryMu.RLock() + defer l.expiryMu.RUnlock() return l.expiry == forever } diff --git a/server/lease/leasehttp/http_test.go b/server/lease/leasehttp/http_test.go index 7fb284ff41f9..f606486b5bae 100644 --- a/server/lease/leasehttp/http_test.go +++ b/server/lease/leasehttp/http_test.go @@ -15,7 +15,6 @@ package leasehttp import ( - "context" "net/http" "net/http/httptest" "testing" @@ -42,7 +41,7 @@ func TestRenewHTTP(t *testing.T) { ts := httptest.NewServer(NewHandler(le, waitReady)) defer ts.Close() - ttl, err := RenewHTTP(context.TODO(), l.ID, ts.URL+LeasePrefix, http.DefaultTransport) + ttl, err := RenewHTTP(t.Context(), l.ID, ts.URL+LeasePrefix, http.DefaultTransport) if err != nil { t.Fatal(err) } @@ -66,7 +65,7 @@ func TestTimeToLiveHTTP(t *testing.T) { ts := httptest.NewServer(NewHandler(le, waitReady)) defer ts.Close() - resp, err := TimeToLiveHTTP(context.TODO(), l.ID, true, ts.URL+LeaseInternalPrefix, http.DefaultTransport) + resp, err := TimeToLiveHTTP(t.Context(), l.ID, true, ts.URL+LeaseInternalPrefix, http.DefaultTransport) if err != nil { t.Fatal(err) } @@ -80,14 +79,14 @@ func TestTimeToLiveHTTP(t *testing.T) { func TestRenewHTTPTimeout(t *testing.T) { testApplyTimeout(t, func(l *lease.Lease, serverURL string) error { - _, err := RenewHTTP(context.TODO(), l.ID, serverURL+LeasePrefix, http.DefaultTransport) + _, err := RenewHTTP(t.Context(), l.ID, serverURL+LeasePrefix, http.DefaultTransport) return err }) } func TestTimeToLiveHTTPTimeout(t *testing.T) { testApplyTimeout(t, func(l *lease.Lease, serverURL string) error { - _, err := TimeToLiveHTTP(context.TODO(), l.ID, true, serverURL+LeaseInternalPrefix, http.DefaultTransport) + _, err := TimeToLiveHTTP(t.Context(), l.ID, true, serverURL+LeaseInternalPrefix, http.DefaultTransport) return err }) } diff --git a/server/lease/lessor_bench_test.go b/server/lease/lessor_bench_test.go index 8e6ff791b193..b28ba45c8e87 100644 --- a/server/lease/lessor_bench_test.go +++ b/server/lease/lessor_bench_test.go @@ -60,9 +60,9 @@ func demote(le *lessor) { } // return new lessor and tearDown to release resource -func setUp(t testing.TB) (le *lessor, tearDown func()) { +func setUp(tb testing.TB) (le *lessor, tearDown func()) { lg := zap.NewNop() - be, _ := betesting.NewDefaultTmpBackend(t) + be, _ := betesting.NewDefaultTmpBackend(tb) // MinLeaseTTL is negative, so we can grant expired lease in benchmark. // ExpiredLeasesRetryInterval should small, so benchmark of findExpired will recheck expired lease. le = newLessor(lg, be, nil, LessorConfig{MinLeaseTTL: -1000, ExpiredLeasesRetryInterval: 10 * time.Microsecond}) diff --git a/server/mock/mockstorage/storage_recorder.go b/server/mock/mockstorage/storage_recorder.go index 41d2952e8a12..7867bee494f0 100644 --- a/server/mock/mockstorage/storage_recorder.go +++ b/server/mock/mockstorage/storage_recorder.go @@ -22,42 +22,42 @@ import ( "go.etcd.io/raft/v3/raftpb" ) -type storageRecorder struct { +type StorageRecorder struct { testutil.Recorder dbPath string // must have '/' suffix if set } -func NewStorageRecorder(db string) *storageRecorder { - return &storageRecorder{&testutil.RecorderBuffered{}, db} +func NewStorageRecorder(db string) *StorageRecorder { + return &StorageRecorder{&testutil.RecorderBuffered{}, db} } -func NewStorageRecorderStream(db string) *storageRecorder { - return &storageRecorder{testutil.NewRecorderStream(), db} +func NewStorageRecorderStream(db string) *StorageRecorder { + return &StorageRecorder{testutil.NewRecorderStream(), db} } -func (p *storageRecorder) Save(st raftpb.HardState, ents []raftpb.Entry) error { +func (p *StorageRecorder) Save(st raftpb.HardState, ents []raftpb.Entry) error { p.Record(testutil.Action{Name: "Save"}) return nil } -func (p *storageRecorder) SaveSnap(st raftpb.Snapshot) error { +func (p *StorageRecorder) SaveSnap(st raftpb.Snapshot) error { if !raft.IsEmptySnap(st) { p.Record(testutil.Action{Name: "SaveSnap"}) } return nil } -func (p *storageRecorder) Release(st raftpb.Snapshot) error { +func (p *StorageRecorder) Release(st raftpb.Snapshot) error { if !raft.IsEmptySnap(st) { p.Record(testutil.Action{Name: "Release"}) } return nil } -func (p *storageRecorder) Sync() error { +func (p *StorageRecorder) Sync() error { p.Record(testutil.Action{Name: "Sync"}) return nil } -func (p *storageRecorder) Close() error { return nil } -func (p *storageRecorder) MinimalEtcdVersion() *semver.Version { return nil } +func (p *StorageRecorder) Close() error { return nil } +func (p *StorageRecorder) MinimalEtcdVersion() *semver.Version { return nil } diff --git a/server/proxy/grpcproxy/cluster.go b/server/proxy/grpcproxy/cluster.go index a528e161ef37..35b6ce09a9d2 100644 --- a/server/proxy/grpcproxy/cluster.go +++ b/server/proxy/grpcproxy/cluster.go @@ -107,7 +107,11 @@ func (cp *clusterProxy) monitor(wa endpoints.WatchChannel) { case <-cp.ctx.Done(): cp.lg.Info("watching endpoints interrupted", zap.Error(cp.ctx.Err())) return - case updates := <-wa: + case updates, ok := <-wa: + if !ok { + cp.lg.Info("endpoints watch channel closed") + return + } cp.umu.Lock() for _, up := range updates { switch up.Op { diff --git a/server/storage/backend/batch_tx_test.go b/server/storage/backend/batch_tx_test.go index 279f199a5dbd..25af69f11e02 100644 --- a/server/storage/backend/batch_tx_test.go +++ b/server/storage/backend/batch_tx_test.go @@ -393,9 +393,9 @@ func checkUnsafeForEach(t *testing.T, tx backend.UnsafeReader, expectedKeys, exp // runWriteback is used test the txWriteBuffer.writeback function, which is called inside tx.Unlock(). // The parameters are chosen based on defaultBatchLimit = 10000 -func runWriteback(t testing.TB, kss, vss [][]string, isSeq bool) { - b, _ := betesting.NewTmpBackend(t, time.Hour, 10000) - defer betesting.Close(t, b) +func runWriteback(tb testing.TB, kss, vss [][]string, isSeq bool) { + b, _ := betesting.NewTmpBackend(tb, time.Hour, 10000) + defer betesting.Close(tb, b) tx := b.BatchTx() diff --git a/server/storage/backend/hooks_test.go b/server/storage/backend/hooks_test.go index 9a1d33d8253b..52b3ed8ec33a 100644 --- a/server/storage/backend/hooks_test.go +++ b/server/storage/backend/hooks_test.go @@ -69,7 +69,7 @@ func write(tx backend.BatchTx, k, v []byte) { } func TestBackendAutoCommitBatchIntervalHook(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + ctx, cancel := context.WithTimeout(t.Context(), time.Minute) defer cancel() cfg := backend.DefaultBackendConfig(zaptest.NewLogger(t)) @@ -94,11 +94,11 @@ func TestBackendAutoCommitBatchIntervalHook(t *testing.T) { waitUntil(ctx, t, func() bool { return getCommitsKey(t, be) == ">ccc" }) } -func waitUntil(ctx context.Context, t testing.TB, f func() bool) { +func waitUntil(ctx context.Context, tb testing.TB, f func() bool) { for !f() { select { case <-ctx.Done(): - t.Fatalf("Context cancelled/timedout without condition met: %v", ctx.Err()) + tb.Fatalf("Context cancelled/timedout without condition met: %v", ctx.Err()) default: } time.Sleep(10 * time.Millisecond) @@ -112,28 +112,28 @@ func prepareBuckenAndKey(tx backend.BatchTx) { tx.UnsafePut(bucket, key, []byte(">")) } -func newTestHooksBackend(t testing.TB, baseConfig backend.BackendConfig) backend.Backend { +func newTestHooksBackend(tb testing.TB, baseConfig backend.BackendConfig) backend.Backend { cfg := baseConfig cfg.Hooks = backend.NewHooks(func(tx backend.UnsafeReadWriter) { k, v := tx.UnsafeRange(bucket, key, nil, 1) - t.Logf("OnPreCommit executed: %v %v", string(k[0]), string(v[0])) - assert.Len(t, k, 1) - assert.Len(t, v, 1) + tb.Logf("OnPreCommit executed: %v %v", string(k[0]), string(v[0])) + assert.Len(tb, k, 1) + assert.Len(tb, v, 1) tx.UnsafePut(bucket, key, append(v[0], byte('c'))) }) - be, _ := betesting.NewTmpBackendFromCfg(t, cfg) - t.Cleanup(func() { - betesting.Close(t, be) + be, _ := betesting.NewTmpBackendFromCfg(tb, cfg) + tb.Cleanup(func() { + betesting.Close(tb, be) }) return be } -func getCommitsKey(t testing.TB, be backend.Backend) string { +func getCommitsKey(tb testing.TB, be backend.Backend) string { rtx := be.BatchTx() rtx.Lock() defer rtx.Unlock() _, v := rtx.UnsafeRange(bucket, key, nil, 1) - assert.Len(t, v, 1) + assert.Len(tb, v, 1) return string(v[0]) } diff --git a/server/storage/backend/testing/betesting.go b/server/storage/backend/testing/betesting.go index e42908f9365d..eae54de9af81 100644 --- a/server/storage/backend/testing/betesting.go +++ b/server/storage/backend/testing/betesting.go @@ -26,28 +26,28 @@ import ( "go.etcd.io/etcd/server/v3/storage/backend" ) -func NewTmpBackendFromCfg(t testing.TB, bcfg backend.BackendConfig) (backend.Backend, string) { - dir, err := os.MkdirTemp(t.TempDir(), "etcd_backend_test") +func NewTmpBackendFromCfg(tb testing.TB, bcfg backend.BackendConfig) (backend.Backend, string) { + dir, err := os.MkdirTemp(tb.TempDir(), "etcd_backend_test") if err != nil { panic(err) } tmpPath := filepath.Join(dir, "database") bcfg.Path = tmpPath - bcfg.Logger = zaptest.NewLogger(t) + bcfg.Logger = zaptest.NewLogger(tb) return backend.New(bcfg), tmpPath } // NewTmpBackend creates a backend implementation for testing. -func NewTmpBackend(t testing.TB, batchInterval time.Duration, batchLimit int) (backend.Backend, string) { - bcfg := backend.DefaultBackendConfig(zaptest.NewLogger(t)) +func NewTmpBackend(tb testing.TB, batchInterval time.Duration, batchLimit int) (backend.Backend, string) { + bcfg := backend.DefaultBackendConfig(zaptest.NewLogger(tb)) bcfg.BatchInterval, bcfg.BatchLimit = batchInterval, batchLimit - return NewTmpBackendFromCfg(t, bcfg) + return NewTmpBackendFromCfg(tb, bcfg) } -func NewDefaultTmpBackend(t testing.TB) (backend.Backend, string) { - return NewTmpBackendFromCfg(t, backend.DefaultBackendConfig(zaptest.NewLogger(t))) +func NewDefaultTmpBackend(tb testing.TB) (backend.Backend, string) { + return NewTmpBackendFromCfg(tb, backend.DefaultBackendConfig(zaptest.NewLogger(tb))) } -func Close(t testing.TB, b backend.Backend) { - assert.NoError(t, b.Close()) +func Close(tb testing.TB, b backend.Backend) { + assert.NoError(tb, b.Close()) } diff --git a/server/storage/mvcc/hash_test.go b/server/storage/mvcc/hash_test.go index d08b3ad19821..9555293aaa9d 100644 --- a/server/storage/mvcc/hash_test.go +++ b/server/storage/mvcc/hash_test.go @@ -138,7 +138,7 @@ func TestCompactionHash(t *testing.T) { s := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) defer cleanup(s, b) - testutil.TestCompactionHash(context.Background(), t, hashTestCase{s}, s.cfg.CompactionBatchLimit) + testutil.TestCompactionHash(t.Context(), t, hashTestCase{s}, s.cfg.CompactionBatchLimit) } type hashTestCase struct { diff --git a/server/storage/mvcc/kv_test.go b/server/storage/mvcc/kv_test.go index 790b534395b0..1b2bf6ae567b 100644 --- a/server/storage/mvcc/kv_test.go +++ b/server/storage/mvcc/kv_test.go @@ -273,7 +273,7 @@ func testKVPutMultipleTimes(t *testing.T, f putFunc) { t.Errorf("#%d: rev = %d, want %d", i, rev, base+1) } - r, err := s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{}) + r, err := s.Range(t.Context(), []byte("foo"), nil, RangeOptions{}) if err != nil { t.Fatal(err) } @@ -384,7 +384,7 @@ func testKVPutWithSameLease(t *testing.T, f putFunc) { } // check leaseID - r, err := s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{}) + r, err := s.Range(t.Context(), []byte("foo"), nil, RangeOptions{}) if err != nil { t.Fatal(err) } @@ -412,7 +412,7 @@ func TestKVOperationInSequence(t *testing.T) { t.Errorf("#%d: put rev = %d, want %d", i, rev, base+1) } - r, err := s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) + r, err := s.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) if err != nil { t.Fatal(err) } @@ -432,7 +432,7 @@ func TestKVOperationInSequence(t *testing.T) { t.Errorf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, 1, base+2) } - r, err = s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: base + 2}) + r, err = s.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: base + 2}) if err != nil { t.Fatal(err) } @@ -490,7 +490,7 @@ func TestKVTxnNonBlockRange(t *testing.T) { donec := make(chan struct{}) go func() { defer close(donec) - s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{}) + s.Range(t.Context(), []byte("foo"), nil, RangeOptions{}) }() select { case <-donec: @@ -516,7 +516,7 @@ func TestKVTxnOperationInSequence(t *testing.T) { t.Errorf("#%d: put rev = %d, want %d", i, rev, base+1) } - r, err := txn.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) + r, err := txn.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) if err != nil { t.Fatal(err) } @@ -536,7 +536,7 @@ func TestKVTxnOperationInSequence(t *testing.T) { t.Errorf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, 1, base+1) } - r, err = txn.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) + r, err = txn.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: base + 1}) if err != nil { t.Errorf("#%d: range error (%v)", i, err) } @@ -595,7 +595,7 @@ func TestKVCompactReserveLastValue(t *testing.T) { if err != nil { t.Errorf("#%d: unexpect compact error %v", i, err) } - r, err := s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: tt.rev + 1}) + r, err := s.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: tt.rev + 1}) if err != nil { t.Errorf("#%d: unexpect range error %v", i, err) } @@ -697,7 +697,7 @@ func TestKVRestore(t *testing.T) { tt(s) var kvss [][]mvccpb.KeyValue for k := int64(0); k < 10; k++ { - r, _ := s.Range(context.TODO(), []byte("a"), []byte("z"), RangeOptions{Rev: k}) + r, _ := s.Range(t.Context(), []byte("a"), []byte("z"), RangeOptions{Rev: k}) kvss = append(kvss, r.KVs) } @@ -715,7 +715,7 @@ func TestKVRestore(t *testing.T) { testutil.WaitSchedule() var nkvss [][]mvccpb.KeyValue for k := int64(0); k < 10; k++ { - r, _ := ns.Range(context.TODO(), []byte("a"), []byte("z"), RangeOptions{Rev: k}) + r, _ := ns.Range(t.Context(), []byte("a"), []byte("z"), RangeOptions{Rev: k}) nkvss = append(nkvss, r.KVs) } cleanup(ns, b) @@ -759,7 +759,7 @@ func TestKVSnapshot(t *testing.T) { ns := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) defer ns.Close() - r, err := ns.Range(context.TODO(), []byte("a"), []byte("z"), RangeOptions{}) + r, err := ns.Range(t.Context(), []byte("a"), []byte("z"), RangeOptions{}) if err != nil { t.Errorf("unexpect range error (%v)", err) } diff --git a/server/storage/mvcc/kvstore_bench_test.go b/server/storage/mvcc/kvstore_bench_test.go index f7a9bd7296c3..024fc7dd10d8 100644 --- a/server/storage/mvcc/kvstore_bench_test.go +++ b/server/storage/mvcc/kvstore_bench_test.go @@ -15,7 +15,6 @@ package mvcc import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -70,7 +69,7 @@ func benchmarkStoreRange(b *testing.B, n int) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - s.Range(context.TODO(), begin, end, RangeOptions{}) + s.Range(b.Context(), begin, end, RangeOptions{}) } } diff --git a/server/storage/mvcc/kvstore_compaction.go b/server/storage/mvcc/kvstore_compaction.go index 45409114e392..9c555fce52b5 100644 --- a/server/storage/mvcc/kvstore_compaction.go +++ b/server/storage/mvcc/kvstore_compaction.go @@ -40,8 +40,6 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal binary.BigEndian.PutUint64(end, uint64(compactMainRev+1)) batchNum := s.cfg.CompactionBatchLimit - batchTicker := time.NewTicker(s.cfg.CompactionSleepInterval) - defer batchTicker.Stop() h := newKVHasher(prevCompactRev, compactMainRev, keep) last := make([]byte, 8+1+8) for { @@ -51,6 +49,7 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal tx := s.b.BatchTx() tx.LockOutsideApply() + // gofail: var compactAfterAcquiredBatchTxLock struct{} keys, values := tx.UnsafeRange(schema.Key, last, end, int64(batchNum)) for i := range keys { rev = BytesToRev(keys[i]) @@ -65,6 +64,7 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal // gofail: var compactBeforeSetFinishedCompact struct{} UnsafeSetFinishedCompact(tx, compactMainRev) tx.Unlock() + dbCompactionPauseMs.Observe(float64(time.Since(start) / time.Millisecond)) // gofail: var compactAfterSetFinishedCompact struct{} hash := h.Hash() size, sizeInUse := s.b.Size(), s.b.SizeInUse() @@ -91,7 +91,7 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal dbCompactionPauseMs.Observe(float64(time.Since(start) / time.Millisecond)) select { - case <-batchTicker.C: + case <-time.After(s.cfg.CompactionSleepInterval): case <-s.stopc: return KeyValueHash{}, fmt.Errorf("interrupted due to stop signal") } diff --git a/server/storage/mvcc/kvstore_compaction_test.go b/server/storage/mvcc/kvstore_compaction_test.go index b4a7f41a3677..0b28c931ca7c 100644 --- a/server/storage/mvcc/kvstore_compaction_test.go +++ b/server/storage/mvcc/kvstore_compaction_test.go @@ -15,7 +15,6 @@ package mvcc import ( - "context" "reflect" "testing" "time" @@ -139,7 +138,7 @@ func TestCompactAllAndRestore(t *testing.T) { if s1.Rev() != rev { t.Errorf("rev = %v, want %v", s1.Rev(), rev) } - _, err = s1.Range(context.TODO(), []byte("foo"), nil, RangeOptions{}) + _, err = s1.Range(t.Context(), []byte("foo"), nil, RangeOptions{}) if err != nil { t.Errorf("unexpect range error %v", err) } diff --git a/server/storage/mvcc/kvstore_test.go b/server/storage/mvcc/kvstore_test.go index 65ad4f240ec7..c993b0d8ba00 100644 --- a/server/storage/mvcc/kvstore_test.go +++ b/server/storage/mvcc/kvstore_test.go @@ -16,7 +16,6 @@ package mvcc import ( "bytes" - "context" "crypto/rand" "encoding/binary" "errors" @@ -218,7 +217,7 @@ func TestStoreRange(t *testing.T) { b.tx.rangeRespc <- tt.r fi.indexRangeRespc <- tt.idxr - ret, err := s.Range(context.TODO(), []byte("foo"), []byte("goo"), ro) + ret, err := s.Range(t.Context(), []byte("foo"), []byte("goo"), ro) if err != nil { t.Errorf("#%d: err = %v, want nil", i, err) } @@ -469,7 +468,7 @@ func TestRestoreDelete(t *testing.T) { defer s.Close() for i := 0; i < 20; i++ { ks := fmt.Sprintf("foo-%d", i) - r, err := s.Range(context.TODO(), []byte(ks), nil, RangeOptions{}) + r, err := s.Range(t.Context(), []byte(ks), nil, RangeOptions{}) if err != nil { t.Fatal(err) } @@ -519,7 +518,7 @@ func TestRestoreContinueUnfinishedCompaction(t *testing.T) { // wait for scheduled compaction to be finished time.Sleep(100 * time.Millisecond) - if _, err := s.Range(context.TODO(), []byte("foo"), nil, RangeOptions{Rev: 1}); !errors.Is(err, ErrCompacted) { + if _, err := s.Range(t.Context(), []byte("foo"), nil, RangeOptions{Rev: 1}); !errors.Is(err, ErrCompacted) { t.Errorf("range on compacted rev error = %v, want %v", err, ErrCompacted) } // check the key in backend is deleted @@ -757,7 +756,7 @@ func TestConcurrentReadNotBlockingWrite(t *testing.T) { // readTx2 simulates a short read request readTx2 := s.Read(ConcurrentReadTxMode, traceutil.TODO()) ro := RangeOptions{Limit: 1, Rev: 0, Count: false} - ret, err := readTx2.Range(context.TODO(), []byte("foo"), nil, ro) + ret, err := readTx2.Range(t.Context(), []byte("foo"), nil, ro) if err != nil { t.Fatalf("failed to range: %v", err) } @@ -774,7 +773,7 @@ func TestConcurrentReadNotBlockingWrite(t *testing.T) { } readTx2.End() - ret, err = readTx1.Range(context.TODO(), []byte("foo"), nil, ro) + ret, err = readTx1.Range(t.Context(), []byte("foo"), nil, ro) if err != nil { t.Fatalf("failed to range: %v", err) } @@ -841,7 +840,7 @@ func TestConcurrentReadTxAndWrite(t *testing.T) { tx := s.Read(ConcurrentReadTxMode, traceutil.TODO()) mu.Unlock() // get all keys in backend store, and compare with wKVs - ret, err := tx.Range(context.TODO(), []byte("\x00000000"), []byte("\xffffffff"), RangeOptions{}) + ret, err := tx.Range(t.Context(), []byte("\x00000000"), []byte("\xffffffff"), RangeOptions{}) tx.End() if err != nil { t.Errorf("failed to range keys: %v", err) diff --git a/server/storage/mvcc/watchable_store.go b/server/storage/mvcc/watchable_store.go index 0a7cb02513f4..e309b5cfac2a 100644 --- a/server/storage/mvcc/watchable_store.go +++ b/server/storage/mvcc/watchable_store.go @@ -162,12 +162,12 @@ func (s *watchableStore) cancelWatcher(wa *watcher) { } else if s.synced.delete(wa) { watcherGauge.Dec() break - } else if wa.compacted { - watcherGauge.Dec() - break } else if wa.ch == nil { // already canceled (e.g., cancel/close race) break + } else if wa.compacted { + watcherGauge.Dec() + break } if !wa.victim { diff --git a/server/storage/mvcc/watchable_store_test.go b/server/storage/mvcc/watchable_store_test.go index 50de7466ac7f..e888447e0eb0 100644 --- a/server/storage/mvcc/watchable_store_test.go +++ b/server/storage/mvcc/watchable_store_test.go @@ -17,10 +17,12 @@ package mvcc import ( "fmt" "reflect" + "strings" "sync" "testing" "time" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -73,6 +75,175 @@ func TestNewWatcherCancel(t *testing.T) { } } +func TestNewWatcherCountGauge(t *testing.T) { + expectWatchGauge := func(watchers int) { + expected := fmt.Sprintf(`# HELP etcd_debugging_mvcc_watcher_total Total number of watchers. +# TYPE etcd_debugging_mvcc_watcher_total gauge +etcd_debugging_mvcc_watcher_total %d +`, watchers) + err := testutil.CollectAndCompare(watcherGauge, strings.NewReader(expected), "etcd_debugging_mvcc_watcher_total") + if err != nil { + t.Error(err) + } + } + + t.Run("regular watch", func(t *testing.T) { + b, _ := betesting.NewDefaultTmpBackend(t) + s := New(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) + defer cleanup(s, b) + + // watcherGauge is a package variable and its value may change depending on + // the execution of other tests + initialGaugeState := int(testutil.ToFloat64(watcherGauge)) + + testKey := []byte("foo") + testValue := []byte("bar") + s.Put(testKey, testValue, lease.NoLease) + + // we expect the gauge state to still be in its initial state + expectWatchGauge(initialGaugeState) + + w := s.NewWatchStream() + defer w.Close() + + wt, _ := w.Watch(0, testKey, nil, 0) + + // after creating watch, the gauge state should have increased + expectWatchGauge(initialGaugeState + 1) + + if err := w.Cancel(wt); err != nil { + t.Error(err) + } + + // after cancelling watch, the gauge state should have decreased + expectWatchGauge(initialGaugeState) + + w.Cancel(wt) + + // cancelling the watch twice shouldn't decrement the counter twice + expectWatchGauge(initialGaugeState) + }) + + t.Run("compacted watch", func(t *testing.T) { + b, _ := betesting.NewDefaultTmpBackend(t) + s := New(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) + defer cleanup(s, b) + + // watcherGauge is a package variable and its value may change depending on + // the execution of other tests + initialGaugeState := int(testutil.ToFloat64(watcherGauge)) + + testKey := []byte("foo") + testValue := []byte("bar") + + s.Put(testKey, testValue, lease.NoLease) + rev := s.Put(testKey, testValue, lease.NoLease) + + // compact up to the revision of the key we just put + _, err := s.Compact(traceutil.TODO(), rev) + if err != nil { + t.Error(err) + } + + // we expect the gauge state to still be in its initial state + expectWatchGauge(initialGaugeState) + + w := s.NewWatchStream() + defer w.Close() + + wt, _ := w.Watch(0, testKey, nil, rev-1) + + // wait for the watcher to be marked as compacted + select { + case resp := <-w.Chan(): + if resp.CompactRevision == 0 { + t.Errorf("resp.Compacted = %v, want %v", resp.CompactRevision, rev) + } + case <-time.After(time.Second): + t.Fatalf("failed to receive response (timeout)") + } + + // after creating watch, the gauge state should have increased + expectWatchGauge(initialGaugeState + 1) + + if err := w.Cancel(wt); err != nil { + t.Error(err) + } + + // after cancelling watch, the gauge state should have decreased + expectWatchGauge(initialGaugeState) + + w.Cancel(wt) + + // cancelling the watch twice shouldn't decrement the counter twice + expectWatchGauge(initialGaugeState) + }) + + t.Run("compacted watch, close/cancel race", func(t *testing.T) { + b, _ := betesting.NewDefaultTmpBackend(t) + s := New(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) + defer cleanup(s, b) + + // watcherGauge is a package variable and its value may change depending on + // the execution of other tests + initialGaugeState := int(testutil.ToFloat64(watcherGauge)) + + testKey := []byte("foo") + testValue := []byte("bar") + + s.Put(testKey, testValue, lease.NoLease) + rev := s.Put(testKey, testValue, lease.NoLease) + + // compact up to the revision of the key we just put + _, err := s.Compact(traceutil.TODO(), rev) + if err != nil { + t.Error(err) + } + + // we expect the gauge state to still be in its initial state + expectWatchGauge(initialGaugeState) + + w := s.NewWatchStream() + + wt, _ := w.Watch(0, testKey, nil, rev-1) + + // wait for the watcher to be marked as compacted + select { + case resp := <-w.Chan(): + if resp.CompactRevision == 0 { + t.Errorf("resp.Compacted = %v, want %v", resp.CompactRevision, rev) + } + case <-time.After(time.Second): + t.Fatalf("failed to receive response (timeout)") + } + + // after creating watch, the gauge state should have increased + expectWatchGauge(initialGaugeState + 1) + + // now race cancelling and closing the watcher and watch stream. + // in rare scenarios the watcher cancel function can be invoked + // multiple times, leading to a potentially negative gauge state, + // see: https://github.com/etcd-io/etcd/issues/19577 + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + w.Cancel(wt) + wg.Done() + }() + + go func() { + w.Close() + wg.Done() + }() + + wg.Wait() + + // the gauge should be decremented to its original state + expectWatchGauge(initialGaugeState) + }) +} + // TestCancelUnsynced tests if running CancelFunc removes watchers from unsynced. func TestCancelUnsynced(t *testing.T) { b, _ := betesting.NewDefaultTmpBackend(t) diff --git a/server/storage/schema/alarm.go b/server/storage/schema/alarm.go index 6e81d0f46712..929fbd4cebdb 100644 --- a/server/storage/schema/alarm.go +++ b/server/storage/schema/alarm.go @@ -21,12 +21,20 @@ import ( "go.etcd.io/etcd/server/v3/storage/backend" ) +type AlarmBackend interface { + CreateAlarmBucket() + MustPutAlarm(member *etcdserverpb.AlarmMember) + MustDeleteAlarm(alarm *etcdserverpb.AlarmMember) + GetAllAlarms() ([]*etcdserverpb.AlarmMember, error) + ForceCommit() +} + type alarmBackend struct { lg *zap.Logger be backend.Backend } -func NewAlarmBackend(lg *zap.Logger, be backend.Backend) *alarmBackend { +func NewAlarmBackend(lg *zap.Logger, be backend.Backend) AlarmBackend { return &alarmBackend{ lg: lg, be: be, diff --git a/server/storage/schema/membership.go b/server/storage/schema/membership.go index c79ab8b76a9d..726657ba9a04 100644 --- a/server/storage/schema/membership.go +++ b/server/storage/schema/membership.go @@ -37,6 +37,9 @@ type membershipBackend struct { be backend.Backend } +// NewMembershipBackend returns a new membership backend +// Refer to https://github.com/etcd-io/etcd/pull/19343#discussion_r1958056718 +// revive:disable-next-line:unexported-return func NewMembershipBackend(lg *zap.Logger, be backend.Backend) *membershipBackend { return &membershipBackend{ lg: lg, diff --git a/server/storage/schema/migration_test.go b/server/storage/schema/migration_test.go index 2b1d563864de..0f762c406f28 100644 --- a/server/storage/schema/migration_test.go +++ b/server/storage/schema/migration_test.go @@ -51,11 +51,16 @@ func TestNewPlan(t *testing.T) { target: version.V3_5, }, { - name: "Upgrade v3.6 to v3.7 should fail as v3.7 is unknown", - current: version.V3_6, - target: version.V3_7, + name: "Upgrade v3.6 to v3.7 should work", + current: version.V3_6, + target: version.V3_7, + }, + { + name: "Upgrade v3.7 to v3.8 should fail as v3.8 is unknown", + current: version.V3_7, + target: version.V3_8, expectError: true, - expectErrorMsg: `version "3.7.0" is not supported`, + expectErrorMsg: `version "3.8.0" is not supported`, }, { name: "Upgrade v3.6 to v4.0 as major version changes are unsupported", diff --git a/server/storage/schema/schema.go b/server/storage/schema/schema.go index 00a441a3fc01..72f870a108bc 100644 --- a/server/storage/schema/schema.go +++ b/server/storage/schema/schema.go @@ -22,6 +22,7 @@ import ( "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/server/v3/storage/backend" + "go.etcd.io/etcd/server/v3/storage/wal" ) // Validate checks provided backend to confirm that schema used is supported. @@ -47,21 +48,16 @@ func localBinaryVersion() semver.Version { return semver.Version{Major: v.Major, Minor: v.Minor} } -type WALVersion interface { - // MinimalEtcdVersion returns minimal etcd version able to interpret WAL log. - MinimalEtcdVersion() *semver.Version -} - // Migrate updates storage schema to provided target version. // Downgrading requires that provided WAL doesn't contain unsupported entries. -func Migrate(lg *zap.Logger, tx backend.BatchTx, w WALVersion, target semver.Version) error { +func Migrate(lg *zap.Logger, tx backend.BatchTx, w wal.Version, target semver.Version) error { tx.LockOutsideApply() defer tx.Unlock() return UnsafeMigrate(lg, tx, w, target) } // UnsafeMigrate is non thread-safe version of Migrate. -func UnsafeMigrate(lg *zap.Logger, tx backend.UnsafeReadWriter, w WALVersion, target semver.Version) error { +func UnsafeMigrate(lg *zap.Logger, tx backend.UnsafeReadWriter, w wal.Version, target semver.Version) error { current, err := UnsafeDetectSchemaVersion(lg, tx) if err != nil { return fmt.Errorf("cannot detect storage schema version: %w", err) @@ -139,6 +135,7 @@ var ( version.V3_6: { addNewField(Meta, MetaStorageVersionName, emptyStorageVersion), }, + version.V3_7: {}, } // emptyStorageVersion is used for v3.6 Step for the first time, in all other version StoragetVersion should be set by migrator. // Adding a addNewField for StorageVersion we can reuse logic to remove it when downgrading to v3.5 diff --git a/server/storage/schema/schema_test.go b/server/storage/schema/schema_test.go index 5f062730f293..9e46be0dbfe8 100644 --- a/server/storage/schema/schema_test.go +++ b/server/storage/schema/schema_test.go @@ -73,10 +73,14 @@ func TestValidate(t *testing.T) { version: version.V3_6, }, { - name: `V3.7 schema is unknown and should return error`, - version: version.V3_7, + name: `V3.7 is correct`, + version: version.V3_7, + }, + { + name: `V3.8 schema is unknown and should return error`, + version: version.V3_8, expectError: true, - expectErrorMsg: `version "3.7.0" is not supported`, + expectErrorMsg: `version "3.8.0" is not supported`, }, } for _, tc := range tcs { @@ -157,20 +161,32 @@ func TestMigrate(t *testing.T) { expectVersion: &version.V3_7, }, { - name: "Upgrading 3.6 to v3.7 is not supported", - version: version.V3_6, - targetVersion: version.V3_7, - expectVersion: &version.V3_6, - expectError: true, - expectErrorMsg: `cannot create migration plan: version "3.7.0" is not supported`, + name: "Upgrading 3.6 to v3.7 should work", + version: version.V3_6, + targetVersion: version.V3_7, + expectVersion: &version.V3_7, }, { - name: "Downgrading v3.7 to v3.6 is not supported", + name: "Upgrading 3.7 to v3.8 is not supported", version: version.V3_7, - targetVersion: version.V3_6, + targetVersion: version.V3_8, expectVersion: &version.V3_7, expectError: true, - expectErrorMsg: `cannot create migration plan: version "3.7.0" is not supported`, + expectErrorMsg: `cannot create migration plan: version "3.8.0" is not supported`, + }, + { + name: "Downgrading v3.7 to v3.6 should work", + version: version.V3_7, + targetVersion: version.V3_6, + expectVersion: &version.V3_6, + }, + { + name: "Downgrading v3.8 to v3.7 is not supported", + version: version.V3_8, + targetVersion: version.V3_7, + expectVersion: &version.V3_8, + expectError: true, + expectErrorMsg: `cannot create migration plan: version "3.8.0" is not supported`, }, { name: "Downgrading v3.6 to v3.5 works as there are no v3.6 wal entries", @@ -319,6 +335,11 @@ func setupBackendData(t *testing.T, ver semver.Version, overrideKeys func(tx bac UnsafeUpdateConsistentIndex(tx, 1, 1) UnsafeSetStorageVersion(tx, &version.V3_7) tx.UnsafePut(Meta, []byte("future-key"), []byte("")) + case version.V3_8: + MustUnsafeSaveConfStateToBackend(zap.NewNop(), tx, &raftpb.ConfState{}) + UnsafeUpdateConsistentIndex(tx, 1, 1) + UnsafeSetStorageVersion(tx, &version.V3_8) + tx.UnsafePut(Meta, []byte("future-key"), []byte("")) default: t.Fatalf("Unsupported storage version") } diff --git a/server/storage/util.go b/server/storage/util.go index 0dc7f1c6d305..8ea2b9b9ba35 100644 --- a/server/storage/util.go +++ b/server/storage/util.go @@ -109,16 +109,6 @@ func CreateConfigChangeEnts(lg *zap.Logger, ids []uint64, self uint64, term, ind return ents } -// GetEffectiveNodeIdsFromWalEntries returns an ordered set of IDs included in the given snapshot and -// the entries. -// -// Deprecated: use GetEffectiveNodeIDsFromWALEntries instead. -// -//revive:disable-next-line:var-naming -func GetEffectiveNodeIdsFromWalEntries(lg *zap.Logger, snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 { - return GetEffectiveNodeIDsFromWALEntries(lg, snap, ents) -} - // GetEffectiveNodeIDsFromWALEntries returns an ordered set of IDs included in the given snapshot and // the entries. The given snapshot/entries can contain three kinds of // ID-related entry: diff --git a/server/storage/wal/repair.go b/server/storage/wal/repair.go index 16277540f34d..f8f814f04497 100644 --- a/server/storage/wal/repair.go +++ b/server/storage/wal/repair.go @@ -49,8 +49,7 @@ func Repair(lg *zap.Logger, dirpath string) bool { switch { case err == nil: // update crc of the decoder when necessary - switch rec.Type { - case CrcType: + if rec.Type == CrcType { crc := decoder.LastCRC() // current crc of decoder must match the crc of the record. // do no need to match 0 crc, since the decoder is a new one at this case. diff --git a/server/storage/wal/testing/waltesting.go b/server/storage/wal/testing/waltesting.go index bd1dbaade636..7613a3f7107f 100644 --- a/server/storage/wal/testing/waltesting.go +++ b/server/storage/wal/testing/waltesting.go @@ -28,32 +28,32 @@ import ( "go.etcd.io/raft/v3/raftpb" ) -func NewTmpWAL(t testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL, string) { - t.Helper() - dir, err := os.MkdirTemp(t.TempDir(), "etcd_wal_test") +func NewTmpWAL(tb testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL, string) { + tb.Helper() + dir, err := os.MkdirTemp(tb.TempDir(), "etcd_wal_test") if err != nil { panic(err) } tmpPath := filepath.Join(dir, "wal") - lg := zaptest.NewLogger(t) + lg := zaptest.NewLogger(tb) w, err := wal.Create(lg, tmpPath, nil) if err != nil { - t.Fatalf("Failed to create WAL: %v", err) + tb.Fatalf("Failed to create WAL: %v", err) } err = w.Close() if err != nil { - t.Fatalf("Failed to close WAL: %v", err) + tb.Fatalf("Failed to close WAL: %v", err) } if len(reqs) != 0 { w, err = wal.Open(lg, tmpPath, walpb.Snapshot{}) if err != nil { - t.Fatalf("Failed to open WAL: %v", err) + tb.Fatalf("Failed to open WAL: %v", err) } var state raftpb.HardState _, state, _, err = w.ReadAll() if err != nil { - t.Fatalf("Failed to read WAL: %v", err) + tb.Fatalf("Failed to read WAL: %v", err) } var entries []raftpb.Entry for _, req := range reqs { @@ -66,27 +66,27 @@ func NewTmpWAL(t testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL, } err = w.Save(state, entries) if err != nil { - t.Fatalf("Failed to save WAL: %v", err) + tb.Fatalf("Failed to save WAL: %v", err) } err = w.Close() if err != nil { - t.Fatalf("Failed to close WAL: %v", err) + tb.Fatalf("Failed to close WAL: %v", err) } } w, err = wal.OpenForRead(lg, tmpPath, walpb.Snapshot{}) if err != nil { - t.Fatalf("Failed to open WAL: %v", err) + tb.Fatalf("Failed to open WAL: %v", err) } return w, tmpPath } -func Reopen(t testing.TB, walPath string) *wal.WAL { - t.Helper() - lg := zaptest.NewLogger(t) +func Reopen(tb testing.TB, walPath string) *wal.WAL { + tb.Helper() + lg := zaptest.NewLogger(tb) w, err := wal.OpenForRead(lg, walPath, walpb.Snapshot{}) if err != nil { - t.Fatalf("Failed to open WAL: %v", err) + tb.Fatalf("Failed to open WAL: %v", err) } return w } diff --git a/server/storage/wal/version.go b/server/storage/wal/version.go index 1c0775bde55c..b592fd2ee86c 100644 --- a/server/storage/wal/version.go +++ b/server/storage/wal/version.go @@ -29,9 +29,15 @@ import ( "go.etcd.io/raft/v3/raftpb" ) +// Version defines the wal version interface. +type Version interface { + // MinimalEtcdVersion returns minimal etcd version able to interpret WAL log. + MinimalEtcdVersion() *semver.Version +} + // ReadWALVersion reads remaining entries from opened WAL and returns struct // that implements schema.WAL interface. -func ReadWALVersion(w *WAL) (*walVersion, error) { +func ReadWALVersion(w *WAL) (Version, error) { _, _, ents, err := w.ReadAll() if err != nil { return nil, err diff --git a/tests/antithesis/Dockerfile.config b/tests/antithesis/Dockerfile.config new file mode 100644 index 000000000000..e34c18321e06 --- /dev/null +++ b/tests/antithesis/Dockerfile.config @@ -0,0 +1,3 @@ +from scratch + +COPY docker-compose.yml /docker-compose.yml diff --git a/tests/antithesis/Makefile b/tests/antithesis/Makefile new file mode 100644 index 000000000000..0efaa84a1120 --- /dev/null +++ b/tests/antithesis/Makefile @@ -0,0 +1,98 @@ +REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) +USER_ID := $(shell id -u) +GROUP_ID := $(shell id -g) +ARCH ?= $(shell go env GOARCH) +REF = HEAD +DOCKERFILE = ./Dockerfile +REMOTE = origin + +.PHONY: antithesis-build-client-docker-image +antithesis-build-client-docker-image: + docker build --build-arg GO_VERSION=$(shell cat $(REPOSITORY_ROOT)/.go-version) -f $(REPOSITORY_ROOT)/tests/antithesis/test-template/Dockerfile $(REPOSITORY_ROOT) -t etcd-client:latest + +.PHONY: antithesis-build-etcd-create-worktree +antithesis-build-etcd-create-worktree: + git fetch $(REMOTE) $(REF) + git worktree remove -f /tmp/etcd$(REF) || true + git worktree add /tmp/etcd$(REF) $(REMOTE)/$(REF) + $(eval BUILDDIR=/tmp/etcd$(REF)) + +.PHONY: antithesis-build-etcd +antithesis-build-etcd: + cd $(BUILDDIR) && make build + cd $(BUILDDIR) && cp $(DOCKERFILE) ./bin/Dockerfile + cd $(BUILDDIR) && docker build \ + --tag etcd-server:latest \ + ./bin + rm $(BUILDDIR)/bin/Dockerfile + +.PHONY: antithesis-build-etcd-remove-worktree +antithesis-build-etcd-remove-worktree: + git worktree remove -f $(BUILDDIR) + +.PHONY: antithesis-build-etcd-image +antithesis-build-etcd-image: antithesis-build-etcd-create-worktree antithesis-build-etcd antithesis-build-etcd-remove-worktree + +.PHONY: antithesis-build-etcd-image-release-3.4 +antithesis-build-etcd-image-release-3.4: set-version-3.4 antithesis-build-etcd-image + +.PHONY: antithesis-build-etcd-image-release-3.5 +antithesis-build-etcd-image-release-3.5: set-version-3.5 antithesis-build-etcd-image + +.PHONY: antithesis-build-etcd-image-release-3.6 +antithesis-build-etcd-image-release-3.6: set-version-3.6 antithesis-build-etcd-image + +.PHONY: antithesis-build-etcd-image-main +antithesis-build-etcd-image-main: set-version-main antithesis-build-etcd-image + +.PHONY: antithesis-build-etcd-image-local +antithesis-build-etcd-image-local: set-local antithesis-build-etcd + +.PHONY: antithesis-docker-compose-up +antithesis-docker-compose-up: + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose up + +.PHONY: antithesis-run-container-traffic +antithesis-run-container-traffic: + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose exec client /opt/antithesis/test/v1/robustness/singleton_driver_traffic + +.PHONY: antithesis-run-container-validation +antithesis-run-container-validation: + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose exec client /opt/antithesis/test/v1/robustness/finally_validation + +.PHONY: antithesis-run-local-traffic +antithesis-run-local-traffic: + go run --race ./test-template/robustness/traffic/main.go --local + +.PHONY: antithesis-run-local-validation +antithesis-run-local-validation: + go run --race ./test-template/robustness/finally/main.go --local + +.PHONY: antithesis-clean +antithesis-clean: + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose down + rm -rf /tmp/etcddata0 /tmp/etcddata1 /tmp/etcddata2 /tmp/etcdreport + +.PHONY: set-version-3.4 +set-version-3.4: + $(eval REF=release-3.4) + $(eval DOCKERFILE=./Dockerfile-release) + +.PHONY: set-version-3.5 +set-version-3.5: + $(eval REF=release-3.5) + $(eval DOCKERFILE=./Dockerfile-release.amd64) + +.PHONY: set-version-3.6 +set-version-3.6: + $(eval REF=release-3.6) + $(eval DOCKERFILE=./Dockerfile) + +.PHONY: set-version-main +set-version-main: + $(eval REF=main) + $(eval DOCKERFILE=./Dockerfile) + +.PHONY: set-local +set-local: + $(eval BUILDDIR=$(REPOSITORY_ROOT)) diff --git a/tests/antithesis/README.md b/tests/antithesis/README.md new file mode 100644 index 000000000000..fb69b576ab1b --- /dev/null +++ b/tests/antithesis/README.md @@ -0,0 +1,108 @@ +This directory enables integration of Antithesis with etcd. There are 4 containers running in this system: 3 that make up an etcd cluster (etcd0, etcd1, etcd2) and one that "[makes the system go](https://antithesis.com/docs/getting_started/basic_test_hookup/)" (client). + +## Quickstart + +### 1. Build and Tag the Docker Image + +Run this command from the `antithesis/test-template` directory: + +```bash +make antithesis-build-client-docker-image +make antithesis-build-etcd-image +``` + +Both commands build etcd-server and etcd-client from the current branch. To build a different version of etcd you can use: + +```bash +make antithesis-build-etcd-image REF=${GIT_REF} +``` + +### 2. (Optional) Check the Image Locally + +You can verify your new image is built: + +```bash +docker images | grep etcd-client +``` + +It should show something like: + +``` +etcd-client latest +``` + +### 3. Use in Docker Compose + +Run the following command from the root directory for Antithesis tests (`tests/antithesis`): + +```bash +make antithesis-docker-compose-up +``` + +The command uses the etcd client and server images built from step 1. + +The client will continuously check the health of the etcd nodes and print logs similar to: + +``` +[+] Running 4/4 + ✔ Container etcd0 Created 0.0s + ✔ Container etcd2 Created 0.0s + ✔ Container etcd1 Created 0.0s + ✔ Container client Recreated 0.1s +Attaching to client, etcd0, etcd1, etcd2 +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.134294Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_ADVERTISE_CLIENT_URLS","variable-value":"http://etcd2.etcd:2379"} +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.138501Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_INITIAL_ADVERTISE_PEER_URLS","variable-value":"http://etcd2:2380"} +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.138646Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_INITIAL_CLUSTER","variable-value":"etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380"} +etcd0 | {"level":"info","ts":"2025-04-14T07:23:25.138434Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_ADVERTISE_CLIENT_URLS","variable-value":"http://etcd0.etcd:2379"} +etcd0 | {"level":"info","ts":"2025-04-14T07:23:25.138582Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_INITIAL_ADVERTISE_PEER_URLS","variable-value":"http://etcd0:2380"} +etcd0 | {"level":"info","ts":"2025-04-14T07:23:25.138592Z","caller":"flags/flag.go:113","msg":"recognized and used environment variable","variable-name":"ETCD_INITIAL_CLUSTER","variable-value":"etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380"} + +... +... +(skipping some repeated logs for brevity) +... +... + +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.484698Z","caller":"etcdmain/main.go:50","msg":"successfully notified init daemon"} +etcd1 | {"level":"info","ts":"2025-04-14T07:23:25.484092Z","caller":"embed/serve.go:210","msg":"serving client traffic insecurely; this is strongly discouraged!","traffic":"grpc+http","address":"[::]:2379"} +etcd0 | {"level":"info","ts":"2025-04-14T07:23:25.484563Z","caller":"etcdmain/main.go:50","msg":"successfully notified init daemon"} +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.485101Z","caller":"v3rpc/health.go:61","msg":"grpc service status changed","service":"","status":"SERVING"} +etcd1 | {"level":"info","ts":"2025-04-14T07:23:25.484130Z","caller":"etcdmain/main.go:44","msg":"notifying init daemon"} +etcd2 | {"level":"info","ts":"2025-04-14T07:23:25.485782Z","caller":"embed/serve.go:210","msg":"serving client traffic insecurely; this is strongly discouraged!","traffic":"grpc+http","address":"[::]:2379"} +etcd1 | {"level":"info","ts":"2025-04-14T07:23:25.484198Z","caller":"etcdmain/main.go:50","msg":"successfully notified init daemon"} +client | Client [entrypoint]: starting... +client | Client [entrypoint]: checking cluster health... +client | Client [entrypoint]: connection successful with etcd0 +client | Client [entrypoint]: connection successful with etcd1 +client | Client [entrypoint]: connection successful with etcd2 +client | Client [entrypoint]: cluster is healthy! +``` + +And it will stay running indefinitely. + +### 4. Running the tests + +```bash +make antithesis-run-container-traffic +make antithesis-run-container-validation +``` + +Alternatively, with the etcd cluster from step 3, to run the tests locally without rebuilding the client image: + +```bash +make antithesis-run-local-traffic +make antithesis-run-local-validation +``` + +### 5. Prepare for next run + +Unfortunatelly robustness tests don't support running on non empty database. +So for now you need to cleanup the storage before repeating the run or you will get "non empty database at start, required by model used for linearizability validation" error. + +```bash +make antithesis-clean +``` + +## Troubleshooting + +- **Image Pull Errors**: If Docker can’t pull `etcd-client:latest`, make sure you built it locally (see the “Build and Tag” step) or push it to a registry that Compose can access. diff --git a/tests/antithesis/docker-compose.yml b/tests/antithesis/docker-compose.yml new file mode 100644 index 000000000000..ec03689e7533 --- /dev/null +++ b/tests/antithesis/docker-compose.yml @@ -0,0 +1,108 @@ +--- +services: + # This is needed for creating non-root data folders on host. + # By default, if the folders don't exist when mounting, compose creates them with root as owner. + # With root owner, accessing the WAL files from local tests will fail due to an unauthorized access error. + init: + image: 'docker.io/library/ubuntu:latest' + user: root + group_add: + - '${GROUP_ID:-root}' + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcddata1 + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcddata2 + - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report + command: + - /bin/sh + - -c + - | + rm -rf /var/etcddata0/* /var/etcddata1/* /var/etcddata2/* /var/report/* + chown -R ${USER_ID:-root}:${GROUP_ID:-root} /var/etcddata0 /var/etcddata1 /var/etcddata2 /var/report + + etcd0: + image: 'etcd-server:latest' + container_name: etcd0 + hostname: etcd0 + environment: + ETCD_NAME: "etcd0" + ETCD_INITIAL_ADVERTISE_PEER_URLS: "http://etcd0:2380" + ETCD_LISTEN_PEER_URLS: "http://0.0.0.0:2380" + ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" + ETCD_ADVERTISE_CLIENT_URLS: "http://etcd0.etcd:2379" + ETCD_INITIAL_CLUSTER_TOKEN: "etcd-cluster-1" + ETCD_INITIAL_CLUSTER: "etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380" + ETCD_INITIAL_CLUSTER_STATE: "new" + ETCD_DATA_DIR: "/var/etcd/data" + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + init: + condition: service_completed_successfully + ports: + - 12379:2379 + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcd/data + + etcd1: + image: 'etcd-server:latest' + container_name: etcd1 + hostname: etcd1 + environment: + ETCD_NAME: "etcd1" + ETCD_INITIAL_ADVERTISE_PEER_URLS: "http://etcd1:2380" + ETCD_LISTEN_PEER_URLS: "http://0.0.0.0:2380" + ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" + ETCD_ADVERTISE_CLIENT_URLS: "http://etcd1.etcd:2379" + ETCD_INITIAL_CLUSTER_TOKEN: "etcd-cluster-1" + ETCD_INITIAL_CLUSTER: "etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380" + ETCD_INITIAL_CLUSTER_STATE: "new" + ETCD_DATA_DIR: "/var/etcd/data" + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + init: + condition: service_completed_successfully + ports: + - 22379:2379 + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcd/data + + etcd2: + image: 'etcd-server:latest' + container_name: etcd2 + hostname: etcd2 + environment: + ETCD_NAME: "etcd2" + ETCD_INITIAL_ADVERTISE_PEER_URLS: "http://etcd2:2380" + ETCD_LISTEN_PEER_URLS: "http://0.0.0.0:2380" + ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" + ETCD_ADVERTISE_CLIENT_URLS: "http://etcd2.etcd:2379" + ETCD_INITIAL_CLUSTER_TOKEN: "etcd-cluster-1" + ETCD_INITIAL_CLUSTER: "etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380" + ETCD_INITIAL_CLUSTER_STATE: "new" + ETCD_DATA_DIR: "/var/etcd/data" + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + init: + condition: service_completed_successfully + ports: + - 32379:2379 + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcd/data + + client: + image: 'etcd-client:latest' + container_name: client + entrypoint: ["/opt/antithesis/entrypoint/entrypoint"] + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + etcd0: + condition: service_started + etcd1: + condition: service_started + etcd2: + condition: service_started + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcddata1 + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcddata2 + - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report diff --git a/tests/antithesis/test-template/Dockerfile b/tests/antithesis/test-template/Dockerfile new file mode 100644 index 000000000000..850857a7925b --- /dev/null +++ b/tests/antithesis/test-template/Dockerfile @@ -0,0 +1,11 @@ +ARG GO_VERSION=1.24.3 +ARG ARCH=amd64 + +FROM golang:$GO_VERSION +WORKDIR /build +COPY . . + +WORKDIR /build/tests +RUN go build -o /opt/antithesis/entrypoint/entrypoint -race ./antithesis/test-template/entrypoint/main.go +RUN go build -o /opt/antithesis/test/v1/robustness/singleton_driver_traffic -race ./antithesis/test-template/robustness/traffic/main.go +RUN go build -o /opt/antithesis/test/v1/robustness/finally_validation -race ./antithesis/test-template/robustness/finally/main.go diff --git a/tests/antithesis/test-template/entrypoint/main.go b/tests/antithesis/test-template/entrypoint/main.go new file mode 100644 index 000000000000..cf2f46f75f28 --- /dev/null +++ b/tests/antithesis/test-template/entrypoint/main.go @@ -0,0 +1,91 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +//go:build cgo && amd64 + +package main + +import ( + "context" + "fmt" + "time" + + "github.com/antithesishq/antithesis-sdk-go/lifecycle" + + clientv3 "go.etcd.io/etcd/client/v3" +) + +// Sleep duration +const SLEEP = 10 + +// CheckHealth checks health of all etcd nodes +func CheckHealth() bool { + nodeOptions := []string{"etcd0", "etcd1", "etcd2"} + + // iterate over each node and check health + for _, node := range nodeOptions { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: []string{fmt.Sprintf("http://%s:2379", node)}, + DialTimeout: 5 * time.Second, + }) + if err != nil { + fmt.Printf("Client [entrypoint]: connection failed with %s\n", node) + fmt.Printf("Client [entrypoint]: error: %v\n", err) + return false + } + + defer func() { + cErr := cli.Close() + if cErr != nil { + fmt.Printf("Client [entrypoint]: error closing connection: %v\n", cErr) + } + }() + + // fetch the key setting-up to confirm that the node is available + _, err = cli.Get(context.Background(), "setting-up") + if err != nil { + fmt.Printf("Client [entrypoint]: connection failed with %s\n", node) + fmt.Printf("Client [entrypoint]: error: %v\n", err) + return false + } + + fmt.Printf("Client [entrypoint]: connection successful with %s\n", node) + } + + return true +} + +func main() { + fmt.Println("Client [entrypoint]: starting...") + + // run loop until all nodes are healthy + for { + fmt.Println("Client [entrypoint]: checking cluster health...") + if CheckHealth() { + fmt.Println("Client [entrypoint]: cluster is healthy!") + break + } + fmt.Printf("Client [entrypoint]: cluster is not healthy. retrying in %d seconds...\n", SLEEP) + time.Sleep(SLEEP * time.Second) + } + + // signal that the setup looks complete + lifecycle.SetupComplete( + map[string]string{ + "Message": "ETCD cluster is healthy", + }, + ) + + select {} +} diff --git a/tests/antithesis/test-template/robustness/common/path.go b/tests/antithesis/test-template/robustness/common/path.go new file mode 100644 index 000000000000..7c0e9e02a80b --- /dev/null +++ b/tests/antithesis/test-template/robustness/common/path.go @@ -0,0 +1,66 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +//go:build cgo && amd64 + +package common + +import ( + "fmt" + "os" +) + +const ( + defaultetcd0 = "etcd0:2379" + defaultetcd1 = "etcd1:2379" + defaultetcd2 = "etcd2:2379" + // mounted by the client in docker compose + defaultetcdDataPath = "/var/etcddata%d" + defaultReportPath = "/var/report/" + + localetcd0 = "127.0.0.1:12379" + localetcd1 = "127.0.0.1:22379" + localetcd2 = "127.0.0.1:32379" + // used by default when running the client locally + defaultetcdLocalDataPath = "/tmp/etcddata%d" + localetcdDataPathEnv = "ETCD_ROBUSTNESS_DATA_PATH" + localReportPath = "report" +) + +func DefaultPaths() (string, []string, map[string]string) { + hosts := []string{defaultetcd0, defaultetcd1, defaultetcd2} + reportPath := defaultReportPath + dataPaths := etcdDataPaths(defaultetcdDataPath, len(hosts)) + return reportPath, hosts, dataPaths +} + +func LocalPaths() (string, []string, map[string]string) { + hosts := []string{localetcd0, localetcd1, localetcd2} + reportPath := localReportPath + etcdDataPath := defaultetcdLocalDataPath + envPath := os.Getenv(localetcdDataPathEnv) + if envPath != "" { + etcdDataPath = envPath + "%d" + } + dataPaths := etcdDataPaths(etcdDataPath, len(hosts)) + return reportPath, hosts, dataPaths +} + +func etcdDataPaths(dir string, amount int) map[string]string { + dataPaths := make(map[string]string) + for i := range amount { + dataPaths[fmt.Sprintf("etcd%d", i)] = fmt.Sprintf(dir, i) + } + return dataPaths +} diff --git a/tests/antithesis/test-template/robustness/finally/main.go b/tests/antithesis/test-template/robustness/finally/main.go new file mode 100644 index 000000000000..852820bfbdde --- /dev/null +++ b/tests/antithesis/test-template/robustness/finally/main.go @@ -0,0 +1,77 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +//go:build cgo && amd64 + +package main + +import ( + "flag" + "maps" + "path/filepath" + "slices" + "time" + + "github.com/anishathalye/porcupine" + "github.com/antithesishq/antithesis-sdk-go/assert" + "go.uber.org/zap" + + "go.etcd.io/etcd/tests/v3/antithesis/test-template/robustness/common" + "go.etcd.io/etcd/tests/v3/robustness/report" + "go.etcd.io/etcd/tests/v3/robustness/traffic" + "go.etcd.io/etcd/tests/v3/robustness/validate" +) + +const ( + reportFileName = "history.html" +) + +func main() { + local := flag.Bool("local", false, "run finally locally and connect to etcd instances via localhost") + flag.Parse() + + reportPath, _, dirs := common.DefaultPaths() + if *local { + reportPath, _, dirs = common.LocalPaths() + } + + lg, err := zap.NewProduction() + if err != nil { + panic(err) + } + reports, err := report.LoadClientReports(reportPath) + assert.Always(err == nil, "Loaded client reports", map[string]any{"error": err}) + result := validateReports(lg, dirs, reports) + if err := result.Linearization.Visualize(lg, filepath.Join(reportPath, reportFileName)); err != nil { + panic(err) + } +} + +func validateReports(lg *zap.Logger, serversDataPath map[string]string, reports []report.ClientReport) validate.Result { + persistedRequests, err := report.PersistedRequests(lg, slices.Collect(maps.Values(serversDataPath))) + assert.Always(err == nil, "Loaded persisted requests", map[string]any{"error": err}) + + validateConfig := validate.Config{ExpectRevisionUnique: traffic.EtcdAntithesis.ExpectUniqueRevision()} + result := validate.ValidateAndReturnVisualize(lg, validateConfig, reports, persistedRequests, 5*time.Minute) + assert.Always(result.Assumptions == nil, "Validation assumptions fulfilled", map[string]any{"error": result.Assumptions}) + if result.Linearization.Linearizable == porcupine.Unknown { + assert.Unreachable("Linearization timeout", nil) + } else { + assert.Always(result.Linearization.Linearizable == porcupine.Ok, "Linearization validation passes", nil) + } + assert.Always(result.WatchError == nil, "Watch validation passes", map[string]any{"error": result.WatchError}) + assert.Always(result.SerializableError == nil, "Serializable validation passes", map[string]any{"error": result.SerializableError}) + lg.Info("Completed robustness validation") + return result +} diff --git a/tests/antithesis/test-template/robustness/traffic/main.go b/tests/antithesis/test-template/robustness/traffic/main.go new file mode 100644 index 000000000000..9e51afe9b0be --- /dev/null +++ b/tests/antithesis/test-template/robustness/traffic/main.go @@ -0,0 +1,176 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +//go:build cgo && amd64 + +package main + +import ( + "context" + "flag" + "os" + "slices" + "sync" + "time" + + "github.com/antithesishq/antithesis-sdk-go/assert" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "golang.org/x/time/rate" + + "go.etcd.io/etcd/tests/v3/antithesis/test-template/robustness/common" + "go.etcd.io/etcd/tests/v3/robustness/client" + "go.etcd.io/etcd/tests/v3/robustness/identity" + robustnessrand "go.etcd.io/etcd/tests/v3/robustness/random" + "go.etcd.io/etcd/tests/v3/robustness/report" + "go.etcd.io/etcd/tests/v3/robustness/traffic" +) + +var profile = traffic.Profile{ + MinimalQPS: 100, + MaximalQPS: 1000, + BurstableQPS: 1000, + ClientCount: 3, + MaxNonUniqueRequestConcurrency: 3, +} + +func main() { + local := flag.Bool("local", false, "run tests locally and connect to etcd instances via localhost") + flag.Parse() + + reportPath, hosts, etcdetcdDataPaths := common.DefaultPaths() + if *local { + reportPath, hosts, etcdetcdDataPaths = common.LocalPaths() + } + + ctx := context.Background() + baseTime := time.Now() + duration := time.Duration(robustnessrand.RandRange(5, 15) * int64(time.Second)) + + lg, err := zap.NewProduction() + if err != nil { + panic(err) + } + r := report.TestReport{Logger: lg, ServersDataPath: etcdetcdDataPaths} + defer func() { + if err = r.Report(reportPath); err != nil { + lg.Error("Failed to save traffic generation report", zap.Error(err)) + } + }() + + lg.Info("Start traffic generation", zap.Duration("duration", duration)) + r.Client, err = runTraffic(ctx, lg, hosts, baseTime, duration) + if err != nil { + lg.Error("Failed to generate traffic") + panic(err) + } +} + +func runTraffic(ctx context.Context, lg *zap.Logger, hosts []string, baseTime time.Time, duration time.Duration) ([]report.ClientReport, error) { + ids := identity.NewIDProvider() + r, err := traffic.CheckEmptyDatabaseAtStart(ctx, lg, hosts, ids, baseTime) + if err != nil { + lg.Fatal("Failed empty database at start check", zap.Error(err)) + } + trafficReports := []report.ClientReport{r} + watchReport := []report.ClientReport{} + maxRevisionChan := make(chan int64, 1) + watchConfig := client.WatchConfig{ + RequestProgress: true, + } + g := errgroup.Group{} + startTime := time.Since(baseTime) + g.Go(func() error { + defer close(maxRevisionChan) + trafficReports = slices.Concat(trafficReports, simulateTraffic(ctx, hosts, ids, baseTime, duration)) + maxRevision := report.OperationsMaxRevision(trafficReports) + maxRevisionChan <- maxRevision + lg.Info("Finished simulating Traffic", zap.Int64("max-revision", maxRevision)) + return nil + }) + g.Go(func() error { + var err error + watchReport, err = client.CollectClusterWatchEvents(ctx, lg, hosts, maxRevisionChan, watchConfig, baseTime, ids) + return err + }) + if err := g.Wait(); err != nil { + return nil, err + } + endTime := time.Since(baseTime) + reports := slices.Concat(trafficReports, watchReport) + totalStats := traffic.CalculateStats(reports, startTime, endTime) + lg.Info("Completed traffic generation", + zap.Int("successes", totalStats.Successes), + zap.Int("failures", totalStats.Failures), + zap.Float64("successRate", totalStats.SuccessRate()), + zap.Duration("period", totalStats.Period), + zap.Float64("qps", totalStats.QPS()), + ) + return reports, nil +} + +func simulateTraffic(ctx context.Context, hosts []string, ids identity.Provider, baseTime time.Time, duration time.Duration) []report.ClientReport { + var mux sync.Mutex + var wg sync.WaitGroup + storage := identity.NewLeaseIDStorage() + limiter := rate.NewLimiter(rate.Limit(profile.MaximalQPS), profile.BurstableQPS) + concurrencyLimiter := traffic.NewConcurrencyLimiter(profile.MaxNonUniqueRequestConcurrency) + finish := closeAfter(ctx, duration) + reports := []report.ClientReport{} + keyStore := traffic.NewKeyStore(10, "key") + for i := 0; i < profile.ClientCount; i++ { + c := connect([]string{hosts[i%len(hosts)]}, ids, baseTime) + defer c.Close() + wg.Add(1) + go func(c *client.RecordingClient) { + defer wg.Done() + defer c.Close() + + traffic.EtcdAntithesis.RunTrafficLoop(ctx, c, limiter, + ids, + storage, + concurrencyLimiter, + keyStore, + finish, + ) + mux.Lock() + reports = append(reports, c.Report()) + mux.Unlock() + }(c) + } + wg.Wait() + return reports +} + +func connect(endpoints []string, ids identity.Provider, baseTime time.Time) *client.RecordingClient { + cli, err := client.NewRecordingClient(endpoints, ids, baseTime) + if err != nil { + // Antithesis Assertion: client should always be able to connect to an etcd host + assert.Unreachable("Client failed to connect to an etcd host", map[string]any{"endpoints": endpoints, "error": err}) + os.Exit(1) + } + return cli +} + +func closeAfter(ctx context.Context, t time.Duration) <-chan struct{} { + out := make(chan struct{}) + go func() { + select { + case <-time.After(t): + case <-ctx.Done(): + } + close(out) + }() + return out +} diff --git a/tests/common/alarm_test.go b/tests/common/alarm_test.go index 67d83ddf5be5..af47316b6e2b 100644 --- a/tests/common/alarm_test.go +++ b/tests/common/alarm_test.go @@ -30,7 +30,7 @@ import ( func TestAlarm(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1), @@ -97,7 +97,7 @@ func TestAlarm(t *testing.T) { func TestAlarmlistOnMemberRestart(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1), diff --git a/tests/common/auth_test.go b/tests/common/auth_test.go index 7031bbfe062b..fb995fa3c3f3 100644 --- a/tests/common/auth_test.go +++ b/tests/common/auth_test.go @@ -33,6 +33,9 @@ var ( tokenTTL = time.Second * 3 defaultAuthToken = fmt.Sprintf("jwt,pub-key=%s,priv-key=%s,sign-method=RS256,ttl=%s", mustAbsPath("../fixtures/server.crt"), mustAbsPath("../fixtures/server.key.insecure"), tokenTTL) + defaultKeyPath = mustAbsPath("../fixtures/server.key.insecure") + verifyJWTOnlyAuth = fmt.Sprintf("jwt,pub-key=%s,sign-method=RS256,ttl=%s", + mustAbsPath("../fixtures/server.crt"), tokenTTL) ) const ( @@ -45,7 +48,7 @@ const ( func TestAuthEnable(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -57,7 +60,7 @@ func TestAuthEnable(t *testing.T) { func TestAuthDisable(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -87,7 +90,7 @@ func TestAuthDisable(t *testing.T) { func TestAuthGracefulDisable(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -137,7 +140,7 @@ func TestAuthGracefulDisable(t *testing.T) { func TestAuthStatus(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -157,7 +160,7 @@ func TestAuthStatus(t *testing.T) { func TestAuthRoleUpdate(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -197,7 +200,7 @@ func TestAuthRoleUpdate(t *testing.T) { func TestAuthUserDeleteDuringOps(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -228,7 +231,7 @@ func TestAuthUserDeleteDuringOps(t *testing.T) { func TestAuthRoleRevokeDuringOps(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -285,7 +288,7 @@ func TestAuthRoleRevokeDuringOps(t *testing.T) { func TestAuthWriteKey(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -375,7 +378,7 @@ func TestAuthTxn(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.cfg)) defer clus.Close() @@ -421,7 +424,7 @@ func TestAuthTxn(t *testing.T) { func TestAuthPrefixPerm(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -455,7 +458,7 @@ func TestAuthPrefixPerm(t *testing.T) { func TestAuthLeaseKeepAlive(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -481,7 +484,7 @@ func TestAuthLeaseKeepAlive(t *testing.T) { func TestAuthRevokeWithDelete(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -512,7 +515,7 @@ func TestAuthRevokeWithDelete(t *testing.T) { func TestAuthLeaseTimeToLiveExpired(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -550,7 +553,7 @@ func TestAuthLeaseGrantLeases(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -575,7 +578,7 @@ func TestAuthLeaseGrantLeases(t *testing.T) { func TestAuthMemberAdd(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -593,7 +596,7 @@ func TestAuthMemberAdd(t *testing.T) { func TestAuthMemberRemove(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clusterSize := 3 clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: clusterSize})) @@ -633,7 +636,7 @@ func TestAuthMemberRemove(t *testing.T) { func TestAuthTestInvalidMgmt(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -650,7 +653,7 @@ func TestAuthTestInvalidMgmt(t *testing.T) { func TestAuthLeaseRevoke(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -674,7 +677,7 @@ func TestAuthLeaseRevoke(t *testing.T) { func TestAuthRoleGet(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -700,7 +703,7 @@ func TestAuthRoleGet(t *testing.T) { func TestAuthUserGet(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -726,7 +729,7 @@ func TestAuthUserGet(t *testing.T) { func TestAuthRoleList(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) defer clus.Close() @@ -743,7 +746,7 @@ func TestAuthRoleList(t *testing.T) { func TestAuthJWTExpire(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: defaultAuthToken})) defer clus.Close() @@ -762,10 +765,29 @@ func TestAuthJWTExpire(t *testing.T) { }) } +func TestAuthJWTOnly(t *testing.T) { + testRunner.BeforeTest(t) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: verifyJWTOnlyAuth})) + defer clus.Close() + cc := testutils.MustClient(clus.Client()) + testutils.ExecuteUntil(ctx, t, func() { + authRev, err := setupAuthAndGetRevision(cc, []authRole{testRole}, []authUser{rootUser, testUser}) + require.NoErrorf(t, err, "failed to enable auth") + + token, err := createSignedJWT(defaultKeyPath, "RS256", testUserName, authRev) + require.NoErrorf(t, err, "failed to create test user JWT") + + testUserAuthClient := testutils.MustClient(clus.Client(WithAuthToken(token))) + require.NoError(t, testUserAuthClient.Put(ctx, "foo", "bar", config.PutOptions{})) + }) +} + // TestAuthRevisionConsistency ensures auth revision is the same after member restarts func TestAuthRevisionConsistency(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: defaultAuthToken})) defer clus.Close() @@ -801,7 +823,7 @@ func TestAuthRevisionConsistency(t *testing.T) { // TestAuthTestCacheReload ensures permissions are persisted and will be reloaded after member restarts func TestAuthTestCacheReload(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: defaultAuthToken})) defer clus.Close() @@ -826,7 +848,7 @@ func TestAuthTestCacheReload(t *testing.T) { // TestAuthLeaseTimeToLive gated lease time to live with RBAC control func TestAuthLeaseTimeToLive(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: defaultAuthToken})) defer clus.Close() diff --git a/tests/common/auth_util.go b/tests/common/auth_util.go index b157ef4fc83b..6b347f9799de 100644 --- a/tests/common/auth_util.go +++ b/tests/common/auth_util.go @@ -17,8 +17,11 @@ package common import ( "context" "fmt" + "os" "testing" + "time" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" "go.etcd.io/etcd/api/v3/authpb" @@ -93,6 +96,29 @@ func createUsers(c interfaces.Client, users []authUser) error { return nil } +func createSignedJWT(keyPath, alg, username string, authRevision uint64) (string, error) { + signMethod := jwt.GetSigningMethod(alg) + + keyBytes, err := os.ReadFile(keyPath) + if err != nil { + return "", err + } + + key, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes) + if err != nil { + return "", err + } + + tk := jwt.NewWithClaims(signMethod, + jwt.MapClaims{ + "username": username, + "revision": authRevision, + "exp": time.Now().Add(time.Minute).Unix(), + }) + + return tk.SignedString(key) +} + func setupAuth(c interfaces.Client, roles []authRole, users []authUser) error { // create roles if err := createRoles(c, roles); err != nil { @@ -107,6 +133,29 @@ func setupAuth(c interfaces.Client, roles []authRole, users []authUser) error { return c.AuthEnable(context.TODO()) } +func setupAuthAndGetRevision(c interfaces.Client, roles []authRole, users []authUser) (uint64, error) { + // create roles + if err := createRoles(c, roles); err != nil { + return 0, err + } + + if err := createUsers(c, users); err != nil { + return 0, err + } + + // This needs to happen before enabling auth for the TestAuthJWTOnly + // test case because once auth is enabled we can no longer mint a valid + // auth token without the revision, which we won't be able to obtain + // without a valid auth token. + authrev, err := c.AuthStatus(context.TODO()) + if err != nil { + return 0, err + } + + // enable auth + return authrev.AuthRevision, c.AuthEnable(context.TODO()) +} + func requireRolePermissionEqual(t *testing.T, expectRole authRole, actual []*authpb.Permission) { require.Len(t, actual, 1) require.Equal(t, expectRole.permission, clientv3.PermissionType(actual[0].PermType)) diff --git a/tests/common/compact_test.go b/tests/common/compact_test.go index 412c46215507..ff83cbe225ff 100644 --- a/tests/common/compact_test.go +++ b/tests/common/compact_test.go @@ -43,7 +43,7 @@ func TestCompact(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) defer clus.Close() diff --git a/tests/common/defrag_test.go b/tests/common/defrag_test.go index d0af0e68c4a2..6ad5bba796f0 100644 --- a/tests/common/defrag_test.go +++ b/tests/common/defrag_test.go @@ -27,7 +27,7 @@ import ( func TestDefragOnline(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() options := config.DefragOption{Timeout: 10 * time.Second} clus := testRunner.NewCluster(ctx, t) diff --git a/tests/common/e2e_test.go b/tests/common/e2e_test.go index 11c4f94a335b..e9e2f19215bb 100644 --- a/tests/common/e2e_test.go +++ b/tests/common/e2e_test.go @@ -78,6 +78,10 @@ func WithAuth(userName, password string) config.ClientOption { return e2e.WithAuth(userName, password) } +func WithAuthToken(token string) config.ClientOption { + return e2e.WithAuthToken(token) +} + func WithEndpoints(endpoints []string) config.ClientOption { return e2e.WithEndpoints(endpoints) } diff --git a/tests/common/endpoint_test.go b/tests/common/endpoint_test.go index 570b59d7f171..5681e54aa121 100644 --- a/tests/common/endpoint_test.go +++ b/tests/common/endpoint_test.go @@ -28,7 +28,7 @@ import ( func TestEndpointStatus(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) defer clus.Close() @@ -41,7 +41,7 @@ func TestEndpointStatus(t *testing.T) { func TestEndpointHashKV(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) defer clus.Close() @@ -72,7 +72,7 @@ func TestEndpointHashKV(t *testing.T) { func TestEndpointHealth(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) defer clus.Close() diff --git a/tests/common/integration_test.go b/tests/common/integration_test.go index c4cabeeb1f98..f41055c44287 100644 --- a/tests/common/integration_test.go +++ b/tests/common/integration_test.go @@ -56,6 +56,10 @@ func WithAuth(userName, password string) config.ClientOption { return integration.WithAuth(userName, password) } +func WithAuthToken(token string) config.ClientOption { + return integration.WithAuthToken(token) +} + func WithEndpoints(endpoints []string) config.ClientOption { return integration.WithEndpoints(endpoints) } diff --git a/tests/common/kv_test.go b/tests/common/kv_test.go index 593ea1e19a0b..323d263db7e4 100644 --- a/tests/common/kv_test.go +++ b/tests/common/kv_test.go @@ -31,7 +31,7 @@ func TestKVPut(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -55,7 +55,7 @@ func TestKVGet(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -111,7 +111,7 @@ func TestKVDelete(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -201,7 +201,7 @@ func TestKVGetNoQuorum(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) defer clus.Close() diff --git a/tests/common/lease_test.go b/tests/common/lease_test.go index afb27c330f91..4d6c136f2fc4 100644 --- a/tests/common/lease_test.go +++ b/tests/common/lease_test.go @@ -31,7 +31,7 @@ func TestLeaseGrantTimeToLive(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -74,7 +74,7 @@ func TestLeaseGrantAndList(t *testing.T) { for _, nc := range nestedCases { t.Run(tc.name+"/"+nc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() t.Logf("Creating cluster...") clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) @@ -122,7 +122,7 @@ func TestLeaseGrantTimeToLiveExpired(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 15*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -139,7 +139,23 @@ func TestLeaseGrantTimeToLiveExpired(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(1), getResp.Count) - time.Sleep(3 * time.Second) + // FIXME: When leader changes, old leader steps + // back to follower and ignores the lease revoking. + // The new leader will restart TTL counting. If so, + // we should call time.Sleep again and wait for revoking. + // It can't avoid flakey but reduce flakey possiblility. + for i := 0; i < 3; i++ { + currentLeader := clus.WaitLeader(t) + t.Logf("[%d] current leader index %d", i, currentLeader) + + time.Sleep(3 * time.Second) + + newLeader := clus.WaitLeader(t) + if newLeader == currentLeader { + break + } + t.Logf("[%d] leader changed, new leader index %d", i, newLeader) + } ttlResp, err := cc.TimeToLive(ctx, leaseResp.ID, config.LeaseOption{}) require.NoError(t, err) @@ -159,7 +175,7 @@ func TestLeaseGrantKeepAliveOnce(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 15*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -172,7 +188,23 @@ func TestLeaseGrantKeepAliveOnce(t *testing.T) { _, err = cc.KeepAliveOnce(ctx, leaseResp.ID) require.NoError(t, err) - time.Sleep(2 * time.Second) // Wait for the original lease to expire + // FIXME: When leader changes, old leader steps + // back to follower and ignores the lease revoking. + // The new leader will restart TTL counting. If so, + // we should call time.Sleep again and wait for revoking. + // It can't avoid flakey but reduce flakey possiblility. + for i := 0; i < 3; i++ { + currentLeader := clus.WaitLeader(t) + t.Logf("[%d] current leader index %d", i, currentLeader) + + time.Sleep(2 * time.Second) + + newLeader := clus.WaitLeader(t) + if newLeader == currentLeader { + break + } + t.Logf("[%d] leader changed, new leader index %d", i, newLeader) + } ttlResp, err := cc.TimeToLive(ctx, leaseResp.ID, config.LeaseOption{}) require.NoError(t, err) @@ -188,7 +220,7 @@ func TestLeaseGrantRevoke(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() diff --git a/tests/common/maintenance_auth_test.go b/tests/common/maintenance_auth_test.go index 21a72580ede4..1a0e7b22b39a 100644 --- a/tests/common/maintenance_auth_test.go +++ b/tests/common/maintenance_auth_test.go @@ -203,7 +203,7 @@ func setupAuthForMaintenanceTest(c intf.Client) error { func testMaintenanceOperationWithAuth(t *testing.T, expectConnectError, expectOperationError bool, f func(context.Context, intf.Client) error, opts ...config.ClientOption) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t) diff --git a/tests/common/member_test.go b/tests/common/member_test.go index a3e0251403e7..10037a42e157 100644 --- a/tests/common/member_test.go +++ b/tests/common/member_test.go @@ -34,7 +34,7 @@ func TestMemberList(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -114,7 +114,7 @@ func TestMemberAdd(t *testing.T) { if quorumTc.waitForQuorum { ctxTimeout += etcdserver.HealthInterval } - ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) + ctx, cancel := context.WithTimeout(t.Context(), ctxTimeout) defer cancel() c := clusterTc.config c.StrictReconfigCheck = quorumTc.strictReconfigCheck @@ -189,7 +189,7 @@ func TestMemberRemove(t *testing.T) { continue } t.Run(quorumTc.name+"/"+clusterTc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 14*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 14*time.Second) defer cancel() c := clusterTc.config c.StrictReconfigCheck = quorumTc.strictReconfigCheck diff --git a/tests/common/role_test.go b/tests/common/role_test.go index 7dda25d303b2..2eb1af1e6b87 100644 --- a/tests/common/role_test.go +++ b/tests/common/role_test.go @@ -31,7 +31,7 @@ func TestRoleAdd_Simple(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -47,7 +47,7 @@ func TestRoleAdd_Simple(t *testing.T) { func TestRoleAdd_Error(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1)) defer clus.Close() @@ -64,7 +64,7 @@ func TestRoleAdd_Error(t *testing.T) { func TestRootRole(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1)) defer clus.Close() @@ -86,7 +86,7 @@ func TestRootRole(t *testing.T) { func TestRoleGrantRevokePermission(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1)) defer clus.Close() @@ -109,7 +109,7 @@ func TestRoleGrantRevokePermission(t *testing.T) { func TestRoleDelete(t *testing.T) { testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(1)) defer clus.Close() diff --git a/tests/common/status_test.go b/tests/common/status_test.go index 519bb07e4355..ec6e1c219ca2 100644 --- a/tests/common/status_test.go +++ b/tests/common/status_test.go @@ -30,7 +30,7 @@ func TestStatus(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() diff --git a/tests/common/txn_test.go b/tests/common/txn_test.go index b9c1b688c2f5..771155584cc2 100644 --- a/tests/common/txn_test.go +++ b/tests/common/txn_test.go @@ -59,7 +59,7 @@ func TestTxnSucc(t *testing.T) { } for _, cfg := range clusterTestCases() { t.Run(cfg.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(cfg.config)) defer clus.Close() @@ -99,7 +99,7 @@ func TestTxnFail(t *testing.T) { } for _, cfg := range clusterTestCases() { t.Run(cfg.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(cfg.config)) defer clus.Close() diff --git a/tests/common/unit_test.go b/tests/common/unit_test.go index 4b172e7a3cb4..48e3a51cda17 100644 --- a/tests/common/unit_test.go +++ b/tests/common/unit_test.go @@ -37,6 +37,10 @@ func WithAuth(userName, password string) config.ClientOption { return func(any) {} } +func WithAuthToken(token string) config.ClientOption { + return func(any) {} +} + func WithEndpoints(endpoints []string) config.ClientOption { return func(any) {} } diff --git a/tests/common/user_test.go b/tests/common/user_test.go index 7f189b000b7e..dc8e64871a94 100644 --- a/tests/common/user_test.go +++ b/tests/common/user_test.go @@ -66,7 +66,7 @@ func TestUserAdd_Simple(t *testing.T) { for _, tc := range clusterTestCases() { for _, nc := range tcs { t.Run(tc.name+"/"+nc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -90,7 +90,7 @@ func TestUserAdd_DuplicateUserNotAllowed(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -114,7 +114,7 @@ func TestUserList(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -145,7 +145,7 @@ func TestUserDelete(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -182,7 +182,7 @@ func TestUserChangePassword(t *testing.T) { testRunner.BeforeTest(t) for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() diff --git a/tests/common/wait_leader_test.go b/tests/common/wait_leader_test.go index 5f9a8ba0572c..fd15a8ae5773 100644 --- a/tests/common/wait_leader_test.go +++ b/tests/common/wait_leader_test.go @@ -29,7 +29,7 @@ func TestWaitLeader(t *testing.T) { for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() @@ -56,7 +56,7 @@ func TestWaitLeader_MemberStop(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) defer clus.Close() diff --git a/tests/common/watch_test.go b/tests/common/watch_test.go index fe6d6b0c8f25..4e853df921c3 100644 --- a/tests/common/watch_test.go +++ b/tests/common/watch_test.go @@ -31,7 +31,7 @@ func TestWatch(t *testing.T) { watchTimeout := 1 * time.Second for _, tc := range clusterTestCases() { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(tc.config)) diff --git a/tests/e2e/cluster_downgrade_test.go b/tests/e2e/cluster_downgrade_test.go index 8b6b967fe6b1..511a9fe1f925 100644 --- a/tests/e2e/cluster_downgrade_test.go +++ b/tests/e2e/cluster_downgrade_test.go @@ -24,8 +24,8 @@ import ( "github.com/coreos/go-semver/semver" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap" + pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/client/pkg/v3/types" @@ -52,6 +52,10 @@ func TestDowngradeUpgradeClusterOf1(t *testing.T) { testDowngradeUpgrade(t, 1, 1, false, noCancellation) } +func TestDowngradeUpgrade2InClusterOf3(t *testing.T) { + testDowngradeUpgrade(t, 2, 3, false, noCancellation) +} + func TestDowngradeUpgradeClusterOf3(t *testing.T) { testDowngradeUpgrade(t, 3, 3, false, noCancellation) } @@ -129,8 +133,11 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS time.Sleep(etcdserver.HealthInterval) } + t.Log("Downgrade should be disabled") + e2e.ValidateDowngradeInfo(t, epc, &pb.DowngradeInfo{Enabled: false}) + t.Log("Adding member to test membership, but a learner avoid breaking quorum") - resp, err := cc.MemberAddAsLearner(context.Background(), "fake1", []string{"http://127.0.0.1:1001"}) + resp, err := cc.MemberAddAsLearner(t.Context(), "fake1", []string{"http://127.0.0.1:1001"}) require.NoError(t, err) if triggerSnapshot { t.Logf("Generating snapshot") @@ -138,7 +145,7 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS verifySnapshot(t, epc) } t.Log("Removing learner to test membership") - _, err = cc.MemberRemove(context.Background(), resp.Member.ID) + _, err = cc.MemberRemove(t.Context(), resp.Member.ID) require.NoError(t, err) beforeMembers, beforeKV := getMembersAndKeys(t, cc) @@ -151,6 +158,10 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS return // No need to perform downgrading, end the test here } e2e.DowngradeEnable(t, epc, lastVersion) + + t.Log("Downgrade should be enabled") + e2e.ValidateDowngradeInfo(t, epc, &pb.DowngradeInfo{Enabled: true, TargetVersion: lastClusterVersion.String()}) + if triggerCancellation == cancelRightAfterEnable { t.Logf("Cancelling downgrade right after enabling (no node is downgraded yet)") e2e.DowngradeCancel(t, epc) @@ -160,13 +171,13 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS } membersToChange := rand.Perm(len(epc.Procs))[:numberOfMembersToDowngrade] - t.Logf(fmt.Sprintln("Elect members for operations"), zap.Any("members", membersToChange)) + t.Logf("Elect members for operations on members: %v", membersToChange) t.Logf("Starting downgrade process to %q", lastVersionStr) - err = e2e.DowngradeUpgradeMembersByID(t, nil, epc, membersToChange, currentVersion, lastClusterVersion) + err = e2e.DowngradeUpgradeMembersByID(t, nil, epc, membersToChange, true, currentVersion, lastClusterVersion) require.NoError(t, err) if len(membersToChange) == len(epc.Procs) { - e2e.AssertProcessLogs(t, leader(t, epc), "the cluster has been downgraded") + e2e.AssertProcessLogs(t, epc.Procs[epc.WaitLeader(t)], "the cluster has been downgraded") } t.Log("Downgrade complete") @@ -186,7 +197,7 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS } t.Log("Adding learner to test membership, but avoid breaking quorum") - resp, err = cc.MemberAddAsLearner(context.Background(), "fake2", []string{"http://127.0.0.1:1002"}) + resp, err = cc.MemberAddAsLearner(t.Context(), "fake2", []string{"http://127.0.0.1:1002"}) require.NoError(t, err) if triggerSnapshot { t.Logf("Generating snapshot") @@ -194,22 +205,31 @@ func testDowngradeUpgrade(t *testing.T, numberOfMembersToDowngrade int, clusterS verifySnapshot(t, epc) } t.Log("Removing learner to test membership") - _, err = cc.MemberRemove(context.Background(), resp.Member.ID) + _, err = cc.MemberRemove(t.Context(), resp.Member.ID) require.NoError(t, err) beforeMembers, beforeKV = getMembersAndKeys(t, cc) t.Logf("Starting upgrade process to %q", currentVersionStr) - err = e2e.DowngradeUpgradeMembersByID(t, nil, epc, membersToChange, lastClusterVersion, currentVersion) + downgradeEnabled := triggerCancellation == noCancellation && numberOfMembersToDowngrade < clusterSize + err = e2e.DowngradeUpgradeMembersByID(t, nil, epc, membersToChange, downgradeEnabled, lastClusterVersion, currentVersion) require.NoError(t, err) t.Log("Upgrade complete") + if downgradeEnabled { + t.Log("Downgrade should be still enabled") + e2e.ValidateDowngradeInfo(t, epc, &pb.DowngradeInfo{Enabled: true, TargetVersion: lastClusterVersion.String()}) + } else { + t.Log("Downgrade should be disabled") + e2e.ValidateDowngradeInfo(t, epc, &pb.DowngradeInfo{Enabled: false}) + } + afterMembers, afterKV = getMembersAndKeys(t, cc) assert.Equal(t, beforeKV.Kvs, afterKV.Kvs) assert.Equal(t, beforeMembers.Members, afterMembers.Members) } func newCluster(t *testing.T, clusterSize int, snapshotCount uint64) *e2e.EtcdProcessCluster { - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(clusterSize), e2e.WithSnapshotCount(snapshotCount), e2e.WithKeepDataDir(true), @@ -225,29 +245,8 @@ func newCluster(t *testing.T, clusterSize int, snapshotCount uint64) *e2e.EtcdPr return epc } -func leader(t *testing.T, epc *e2e.EtcdProcessCluster) e2e.EtcdProcess { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - for i := 0; i < len(epc.Procs); i++ { - endpoints := epc.Procs[i].EndpointsGRPC() - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: 3 * time.Second, - }) - require.NoError(t, err) - defer cli.Close() - resp, err := cli.Status(ctx, endpoints[0]) - require.NoError(t, err) - if resp.Header.GetMemberId() == resp.Leader { - return epc.Procs[i] - } - } - t.Fatal("Leader not found") - return nil -} - func generateSnapshot(t *testing.T, snapshotCount uint64, cc *e2e.EtcdctlV3) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() var i uint64 @@ -287,7 +286,7 @@ func verifySnapshotMembers(t *testing.T, epc *e2e.EtcdProcessCluster, expectedMe } func getMembersAndKeys(t *testing.T, cc *e2e.EtcdctlV3) (*clientv3.MemberListResponse, *clientv3.GetResponse) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() kvs, err := cc.Get(ctx, "", config.GetOptions{Prefix: true}) diff --git a/tests/e2e/cmux_test.go b/tests/e2e/cmux_test.go index 9281705ee049..7d9596dbe112 100644 --- a/tests/e2e/cmux_test.go +++ b/tests/e2e/cmux_test.go @@ -69,7 +69,7 @@ func TestConnectionMultiplexing(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() + ctx := t.Context() cfg := e2e.NewConfig(e2e.WithClusterSize(1)) cfg.Client.ConnectionType = tc.serverTLS cfg.ClientHTTPSeparate = tc.separateHTTPPort diff --git a/tests/e2e/corrupt_test.go b/tests/e2e/corrupt_test.go index 86535345e997..f18f094cdb47 100644 --- a/tests/e2e/corrupt_test.go +++ b/tests/e2e/corrupt_test.go @@ -99,7 +99,7 @@ func corruptTest(cx ctlCtx) { func TestInPlaceRecovery(t *testing.T) { basePort := 20000 e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() // Initialize the cluster. @@ -188,24 +188,13 @@ func TestInPlaceRecovery(t *testing.T) { } func TestPeriodicCheckDetectsCorruption(t *testing.T) { - testPeriodicCheckDetectsCorruption(t, false) -} - -func TestPeriodicCheckDetectsCorruptionWithExperimentalFlag(t *testing.T) { - testPeriodicCheckDetectsCorruption(t, true) -} - -func testPeriodicCheckDetectsCorruption(t *testing.T, useExperimentalFlag bool) { checkTime := time.Second e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() - var corruptCheckTime e2e.EPClusterOption - if useExperimentalFlag { - corruptCheckTime = e2e.WithExperimentalCorruptCheckTime(time.Second) - } else { - corruptCheckTime = e2e.WithCorruptCheckTime(time.Second) - } + + corruptCheckTime := e2e.WithCorruptCheckTime(time.Second) + epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithKeepDataDir(true), corruptCheckTime, @@ -233,7 +222,7 @@ func testPeriodicCheckDetectsCorruption(t *testing.T, useExperimentalFlag bool) err = testutil.CorruptBBolt(datadir.ToBackendFileName(epc.Procs[0].Config().DataDirPath)) require.NoError(t, err) - err = epc.Procs[0].Restart(context.TODO()) + err = epc.Procs[0].Restart(t.Context()) require.NoError(t, err) time.Sleep(checkTime * 11 / 10) alarmResponse, err := cc.AlarmList(ctx) @@ -252,7 +241,7 @@ func TestCompactHashCheckDetectCorruptionWithFeatureGate(t *testing.T) { func testCompactHashCheckDetectCorruption(t *testing.T, useFeatureGate bool) { checkTime := time.Second e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() opts := []e2e.EPClusterOption{e2e.WithKeepDataDir(true), e2e.WithCompactHashCheckTime(checkTime)} if useFeatureGate { @@ -294,21 +283,17 @@ func testCompactHashCheckDetectCorruption(t *testing.T, useFeatureGate bool) { } func TestCompactHashCheckDetectCorruptionInterrupt(t *testing.T) { - testCompactHashCheckDetectCorruptionInterrupt(t, false, false) + testCompactHashCheckDetectCorruptionInterrupt(t, false) } func TestCompactHashCheckDetectCorruptionInterruptWithFeatureGate(t *testing.T) { - testCompactHashCheckDetectCorruptionInterrupt(t, true, false) -} - -func TestCompactHashCheckDetectCorruptionInterruptWithExperimentalFlag(t *testing.T) { - testCompactHashCheckDetectCorruptionInterrupt(t, true, true) + testCompactHashCheckDetectCorruptionInterrupt(t, true) } -func testCompactHashCheckDetectCorruptionInterrupt(t *testing.T, useFeatureGate bool, useExperimentalFlag bool) { +func testCompactHashCheckDetectCorruptionInterrupt(t *testing.T, useFeatureGate bool) { checkTime := time.Second e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second) defer cancel() slowCompactionNodeIndex := 1 @@ -329,12 +314,8 @@ func testCompactHashCheckDetectCorruptionInterrupt(t *testing.T, useFeatureGate } else { opts = append(opts, e2e.WithCompactHashCheckEnabled(true)) } - var compactionBatchLimit e2e.EPClusterOption - if useExperimentalFlag { - compactionBatchLimit = e2e.WithExperimentalCompactionBatchLimit(1) - } else { - compactionBatchLimit = e2e.WithCompactionBatchLimit(1) - } + + compactionBatchLimit := e2e.WithCompactionBatchLimit(1) cfg := e2e.NewConfig(opts...) epc, err := e2e.InitEtcdProcessCluster(t, cfg) @@ -407,7 +388,7 @@ func TestCtlV3LinearizableRead(t *testing.T) { func testCtlV3ReadAfterWrite(t *testing.T, ops ...clientv3.OpOption) { e2e.BeforeTest(t) - ctx := context.Background() + ctx := t.Context() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(1), diff --git a/tests/e2e/ctl_v3_auth_cluster_test.go b/tests/e2e/ctl_v3_auth_cluster_test.go index 35b7cd289bed..e758d6e9b5b4 100644 --- a/tests/e2e/ctl_v3_auth_cluster_test.go +++ b/tests/e2e/ctl_v3_auth_cluster_test.go @@ -30,7 +30,7 @@ import ( func TestAuthCluster(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, diff --git a/tests/e2e/ctl_v3_auth_no_proxy_test.go b/tests/e2e/ctl_v3_auth_no_proxy_test.go index 8529ff38dc98..8b79cc0dde63 100644 --- a/tests/e2e/ctl_v3_auth_no_proxy_test.go +++ b/tests/e2e/ctl_v3_auth_no_proxy_test.go @@ -43,7 +43,7 @@ func TestCtlV3AuthCertCNAndUsernameNoPassword(t *testing.T) { func TestCtlV3AuthCertCNWithWithConcurrentOperation(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() // apply the certificate which has `root` CommonName, diff --git a/tests/e2e/ctl_v3_grpc_test.go b/tests/e2e/ctl_v3_grpc_test.go index 78881f3ed056..58f2791e2b7f 100644 --- a/tests/e2e/ctl_v3_grpc_test.go +++ b/tests/e2e/ctl_v3_grpc_test.go @@ -110,7 +110,7 @@ func TestAuthority(t *testing.T) { for _, clusterSize := range []int{1, 3} { t.Run(fmt.Sprintf("Size: %d, Scenario: %q", clusterSize, tc.name), func(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() cfg := e2e.NewConfigNoTLS() @@ -125,7 +125,7 @@ func TestAuthority(t *testing.T) { cfg.BaseClientScheme = "unix" } - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) if err != nil { t.Fatalf("could not start etcd process cluster (%v)", err) } @@ -159,7 +159,7 @@ func templateEndpoints(t *testing.T, pattern string, clus *e2e.EtcdProcessCluste func assertAuthority(t *testing.T, expectAuthorityPattern string, clus *e2e.EtcdProcessCluster) { for i := range clus.Procs { - line, _ := clus.Procs[i].Logs().ExpectWithContext(context.TODO(), expect.ExpectedResponse{Value: `http2: decoded hpack field header field ":authority"`}) + line, _ := clus.Procs[i].Logs().ExpectWithContext(t.Context(), expect.ExpectedResponse{Value: `http2: decoded hpack field header field ":authority"`}) line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\r") diff --git a/tests/e2e/ctl_v3_member_no_proxy_test.go b/tests/e2e/ctl_v3_member_no_proxy_test.go index be492f6a4c8c..983d4b356d9f 100644 --- a/tests/e2e/ctl_v3_member_no_proxy_test.go +++ b/tests/e2e/ctl_v3_member_no_proxy_test.go @@ -33,7 +33,7 @@ import ( func TestMemberReplace(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t) @@ -78,7 +78,7 @@ func TestMemberReplace(t *testing.T) { removedMemberPeerURL := member.Config().PeerURL.String() _, err = cc.MemberAdd(ctx, memberName, []string{removedMemberPeerURL}) require.NoError(t, err) - err = patchArgs(member.Config().Args, "initial-cluster-state", "existing") + err = e2e.PatchArgs(member.Config().Args, "initial-cluster-state", "existing") require.NoError(t, err) // Sleep 100ms to bypass the known issue https://github.com/etcd-io/etcd/issues/16687. @@ -100,7 +100,7 @@ func TestMemberReplace(t *testing.T) { func TestMemberReplaceWithLearner(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t) @@ -146,7 +146,7 @@ func TestMemberReplaceWithLearner(t *testing.T) { _, err = cc.MemberAddAsLearner(ctx, memberName, []string{removedMemberPeerURL}) require.NoError(t, err) - err = patchArgs(member.Config().Args, "initial-cluster-state", "existing") + err = e2e.PatchArgs(member.Config().Args, "initial-cluster-state", "existing") require.NoError(t, err) // Sleep 100ms to bypass the known issue https://github.com/etcd-io/etcd/issues/16687. diff --git a/tests/e2e/ctl_v3_member_test.go b/tests/e2e/ctl_v3_member_test.go index 7eb2a046be00..e9d302716d56 100644 --- a/tests/e2e/ctl_v3_member_test.go +++ b/tests/e2e/ctl_v3_member_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "encoding/json" "errors" "fmt" @@ -69,7 +68,7 @@ func TestCtlV3MemberUpdatePeerTLS(t *testing.T) { func TestCtlV3ConsistentMemberList(t *testing.T) { e2e.BeforeTest(t) - ctx := context.Background() + ctx := t.Context() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(1), @@ -290,7 +289,7 @@ func ctlV3MemberUpdate(cx ctlCtx, memberID, peerURL string) error { func TestRemoveNonExistingMember(t *testing.T) { e2e.BeforeTest(t) - ctx := context.Background() + ctx := t.Context() cfg := e2e.ConfigStandalone(*e2e.NewConfig()) epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithConfig(cfg)) diff --git a/tests/e2e/ctl_v3_move_leader_test.go b/tests/e2e/ctl_v3_move_leader_test.go index cc3e80774434..c77beb2b8b8d 100644 --- a/tests/e2e/ctl_v3_move_leader_test.go +++ b/tests/e2e/ctl_v3_move_leader_test.go @@ -84,7 +84,7 @@ func testCtlV3MoveLeader(t *testing.T, cfg e2e.EtcdProcessClusterConfig, envVars TLS: tcfg, }) require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) resp, err := cli.Status(ctx, ep) if err != nil { t.Fatalf("failed to get status from endpoint %s: %v", ep, err) @@ -145,7 +145,7 @@ func setupEtcdctlTest(t *testing.T, cfg *e2e.EtcdProcessClusterConfig, quorum bo if !quorum { cfg = e2e.ConfigStandalone(*cfg) } - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) if err != nil { t.Fatalf("could not start etcd process cluster (%v)", err) } diff --git a/tests/e2e/ctl_v3_snapshot_test.go b/tests/e2e/ctl_v3_snapshot_test.go index c9dc03235d17..c5346fc595ca 100644 --- a/tests/e2e/ctl_v3_snapshot_test.go +++ b/tests/e2e/ctl_v3_snapshot_test.go @@ -163,17 +163,13 @@ func TestIssue6361(t *testing.T) { testIssue6361(t) } // TestIssue6361 ensures new member that starts with snapshot correctly // syncs up with other members and serve correct data. func testIssue6361(t *testing.T) { - { - // This tests is pretty flaky on semaphoreci as of 2021-01-10. - // TODO: Remove when the flakiness source is identified. - oldenv := os.Getenv("EXPECT_DEBUG") - defer os.Setenv("EXPECT_DEBUG", oldenv) - os.Setenv("EXPECT_DEBUG", "1") - } + // This tests is pretty flaky on semaphoreci as of 2021-01-10. + // TODO: Remove when the flakiness source is identified. + t.Setenv("EXPECT_DEBUG", "1") e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(1), e2e.WithKeepDataDir(true), ) @@ -226,7 +222,7 @@ func testIssue6361(t *testing.T) { epc.Procs[0].Config().Args[i+1] = newDataDir } } - require.NoError(t, epc.Procs[0].Restart(context.TODO())) + require.NoError(t, epc.Procs[0].Restart(t.Context())) t.Log("Ensuring the restored member has the correct data...") for i := range kvs { @@ -290,15 +286,15 @@ func snapshotVersionTest(cx ctlCtx) { if err != nil { cx.t.Fatalf("snapshotVersionTest getSnapshotStatus error (%v)", err) } - if st.Version != "3.6.0" { - cx.t.Fatalf("expected %q, got %q", "3.6.0", st.Version) + if st.Version != "3.7.0" { + cx.t.Fatalf("expected %q, got %q", "3.7.0", st.Version) } } func TestRestoreCompactionRevBump(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(1), e2e.WithKeepDataDir(true), ) @@ -313,13 +309,13 @@ func TestRestoreCompactionRevBump(t *testing.T) { ctl := epc.Etcdctl() - watchCh := ctl.Watch(context.Background(), "foo", config.WatchOptions{Prefix: true}) + watchCh := ctl.Watch(t.Context(), "foo", config.WatchOptions{Prefix: true}) // flake-fix: the watch can sometimes miss the first put below causing test failure time.Sleep(100 * time.Millisecond) kvs := []testutils.KV{{Key: "foo1", Val: "val1"}, {Key: "foo2", Val: "val2"}, {Key: "foo3", Val: "val3"}} for i := range kvs { - require.NoError(t, ctl.Put(context.Background(), kvs[i].Key, kvs[i].Val, config.PutOptions{})) + require.NoError(t, ctl.Put(t.Context(), kvs[i].Key, kvs[i].Val, config.PutOptions{})) } watchTimeout := 1 * time.Second @@ -341,10 +337,10 @@ func TestRestoreCompactionRevBump(t *testing.T) { // add some more kvs that are not in the snapshot that will be lost after restore unsnappedKVs := []testutils.KV{{Key: "unsnapped1", Val: "one"}, {Key: "unsnapped2", Val: "two"}, {Key: "unsnapped3", Val: "three"}} for i := range unsnappedKVs { - require.NoError(t, ctl.Put(context.Background(), unsnappedKVs[i].Key, unsnappedKVs[i].Val, config.PutOptions{})) + require.NoError(t, ctl.Put(t.Context(), unsnappedKVs[i].Key, unsnappedKVs[i].Val, config.PutOptions{})) } - membersBefore, err := ctl.MemberList(context.Background(), false) + membersBefore, err := ctl.MemberList(t.Context(), false) require.NoError(t, err) t.Log("Stopping the original server...") @@ -369,21 +365,18 @@ func TestRestoreCompactionRevBump(t *testing.T) { t.Log("(Re)starting the etcd member using the restored snapshot...") epc.Procs[0].Config().DataDirPath = newDataDir - for i := range epc.Procs[0].Config().Args { - if epc.Procs[0].Config().Args[i] == "--data-dir" { - epc.Procs[0].Config().Args[i+1] = newDataDir - } - } + err = e2e.PatchArgs(epc.Procs[0].Config().Args, "data-dir", newDataDir) + require.NoError(t, err) // Verify that initial snapshot is created by the restore operation verifySnapshotMembers(t, epc, membersBefore) - require.NoError(t, epc.Restart(context.Background())) + require.NoError(t, epc.Restart(t.Context())) t.Log("Ensuring the restored member has the correct data...") hasKVs(t, ctl, kvs, currentRev, baseRev) for i := range unsnappedKVs { - v, gerr := ctl.Get(context.Background(), unsnappedKVs[i].Key, config.GetOptions{}) + v, gerr := ctl.Get(t.Context(), unsnappedKVs[i].Key, config.GetOptions{}) require.NoError(t, gerr) require.Equal(t, int64(0), v.Count) } @@ -399,7 +392,7 @@ func TestRestoreCompactionRevBump(t *testing.T) { // clients might restart the watch at the old base revision, that should not yield any new data // everything up until bumpAmount+currentRev should return "already compacted" for i := bumpAmount - 2; i < bumpAmount+currentRev; i++ { - watchCh = ctl.Watch(context.Background(), "foo", config.WatchOptions{Prefix: true, Revision: int64(i)}) + watchCh = ctl.Watch(t.Context(), "foo", config.WatchOptions{Prefix: true, Revision: int64(i)}) cancelResult := <-watchCh require.Equal(t, v3rpc.ErrCompacted, cancelResult.Err()) require.Truef(t, cancelResult.Canceled, "expected ongoing watch to be cancelled after restoring with --mark-compacted") @@ -407,10 +400,10 @@ func TestRestoreCompactionRevBump(t *testing.T) { } // a watch after that revision should yield successful results when a new put arrives - ctx, cancel := context.WithTimeout(context.Background(), watchTimeout*5) + ctx, cancel := context.WithTimeout(t.Context(), watchTimeout*5) defer cancel() watchCh = ctl.Watch(ctx, "foo", config.WatchOptions{Prefix: true, Revision: int64(bumpAmount + currentRev + 1)}) - require.NoError(t, ctl.Put(context.Background(), "foo4", "val4", config.PutOptions{})) + require.NoError(t, ctl.Put(t.Context(), "foo4", "val4", config.PutOptions{})) watchRes, err = testutils.KeyValuesFromWatchChan(watchCh, 1, watchTimeout) require.NoErrorf(t, err, "failed to get key-values from watch channel %s", err) require.Equal(t, []testutils.KV{{Key: "foo4", Val: "val4"}}, watchRes) @@ -418,7 +411,7 @@ func TestRestoreCompactionRevBump(t *testing.T) { func hasKVs(t *testing.T, ctl *e2e.EtcdctlV3, kvs []testutils.KV, currentRev int, baseRev int) { for i := range kvs { - v, err := ctl.Get(context.Background(), kvs[i].Key, config.GetOptions{}) + v, err := ctl.Get(t.Context(), kvs[i].Key, config.GetOptions{}) require.NoError(t, err) require.Equal(t, int64(1), v.Count) require.Equal(t, kvs[i].Val, string(v.Kvs[0].Value)) @@ -431,7 +424,7 @@ func hasKVs(t *testing.T, ctl *e2e.EtcdctlV3, kvs []testutils.KV, currentRev int func TestBreakConsistentIndexNewerThanSnapshot(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() var snapshotCount uint64 = 50 diff --git a/tests/e2e/ctl_v3_test.go b/tests/e2e/ctl_v3_test.go index 2cd112dc6045..51334915071d 100644 --- a/tests/e2e/ctl_v3_test.go +++ b/tests/e2e/ctl_v3_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "fmt" "os" "strings" @@ -28,6 +27,7 @@ import ( "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/pkg/v3/expect" + "go.etcd.io/etcd/pkg/v3/featuregate" "go.etcd.io/etcd/pkg/v3/flags" "go.etcd.io/etcd/tests/v3/framework/e2e" ) @@ -60,7 +60,7 @@ func TestClusterVersion(t *testing.T) { e2e.WithRollingStart(tt.rollingStart), ) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) if err != nil { t.Fatalf("could not start etcd process cluster (%v)", err) } @@ -226,13 +226,13 @@ func testCtlWithOffline(t *testing.T, testFunc func(ctlCtx), testOfflineFunc fun } ret.cfg.ServerConfig.StrictReconfigCheck = !ret.disableStrictReconfigCheck if ret.initialCorruptCheck { - ret.cfg.ServerConfig.ExperimentalInitialCorruptCheck = ret.initialCorruptCheck + ret.cfg.ServerConfig.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("InitialCorruptCheck=%t", ret.initialCorruptCheck)) } if testOfflineFunc != nil { ret.cfg.KeepDataDir = true } - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(&ret.cfg)) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(&ret.cfg)) if err != nil { t.Fatalf("could not start etcd process cluster (%v)", err) } diff --git a/tests/e2e/defrag_no_space_test.go b/tests/e2e/defrag_no_space_test.go index f6ceabe667b3..f3c70c744478 100644 --- a/tests/e2e/defrag_no_space_test.go +++ b/tests/e2e/defrag_no_space_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "fmt" "testing" "time" @@ -48,7 +47,7 @@ func TestDefragNoSpace(t *testing.T) { t.Run(tc.name, func(t *testing.T) { e2e.BeforeTest(t) - clus, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(1), e2e.WithGoFailEnabled(true), ) @@ -57,12 +56,12 @@ func TestDefragNoSpace(t *testing.T) { member := clus.Procs[0] - require.NoError(t, member.Failpoints().SetupHTTP(context.Background(), tc.failpoint, fmt.Sprintf(`return("%s")`, tc.err))) - require.ErrorContains(t, member.Etcdctl().Defragment(context.Background(), config.DefragOption{Timeout: time.Minute}), tc.err) + require.NoError(t, member.Failpoints().SetupHTTP(t.Context(), tc.failpoint, fmt.Sprintf(`return("%s")`, tc.err))) + require.ErrorContains(t, member.Etcdctl().Defragment(t.Context(), config.DefragOption{Timeout: time.Minute}), tc.err) // Make sure etcd continues to run even after the failed defrag attempt - require.NoError(t, member.Etcdctl().Put(context.Background(), "foo", "bar", config.PutOptions{})) - value, err := member.Etcdctl().Get(context.Background(), "foo", config.GetOptions{}) + require.NoError(t, member.Etcdctl().Put(t.Context(), "foo", "bar", config.PutOptions{})) + value, err := member.Etcdctl().Get(t.Context(), "foo", config.GetOptions{}) require.NoError(t, err) require.Len(t, value.Kvs, 1) require.Equal(t, "bar", string(value.Kvs[0].Value)) diff --git a/tests/e2e/discovery_test.go b/tests/e2e/discovery_test.go deleted file mode 100644 index 15f51a9572d6..000000000000 --- a/tests/e2e/discovery_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 The etcd Authors -// -// Licensed 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. - -//go:build !cluster_proxy - -package e2e - -import ( - "context" - "fmt" - "net/http" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "go.etcd.io/etcd/client/pkg/v3/fileutil" - "go.etcd.io/etcd/client/pkg/v3/testutil" - "go.etcd.io/etcd/client/pkg/v3/transport" - "go.etcd.io/etcd/client/v2" - "go.etcd.io/etcd/pkg/v3/expect" - "go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp" - "go.etcd.io/etcd/tests/v3/framework/e2e" - "go.etcd.io/etcd/tests/v3/framework/integration" -) - -func TestClusterOf1UsingDiscovery(t *testing.T) { testClusterUsingDiscovery(t, 1, false) } -func TestClusterOf3UsingDiscovery(t *testing.T) { testClusterUsingDiscovery(t, 3, false) } -func TestTLSClusterOf3UsingDiscovery(t *testing.T) { testClusterUsingDiscovery(t, 3, true) } - -func testClusterUsingDiscovery(t *testing.T, size int, peerTLS bool) { - e2e.BeforeTest(t) - - if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { - t.Skipf("%q does not exist", e2e.BinPath.EtcdLastRelease) - } - - dc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, - e2e.WithBasePort(2000), - e2e.WithVersion(e2e.LastVersion), - e2e.WithClusterSize(1), - e2e.WithEnableV2(true), - ) - if err != nil { - t.Fatalf("could not start etcd process cluster (%v)", err) - } - defer dc.Close() - - dcc := MustNewHTTPClient(t, dc.EndpointsHTTP(), nil) - dkapi := client.NewKeysAPI(dcc) - ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) - _, err = dkapi.Create(ctx, "/_config/size", fmt.Sprintf("%d", size)) - require.NoError(t, err) - cancel() - - c, err := e2e.NewEtcdProcessCluster(context.TODO(), t, - e2e.WithBasePort(3000), - e2e.WithClusterSize(size), - e2e.WithIsPeerTLS(peerTLS), - e2e.WithDiscovery(dc.EndpointsHTTP()[0]+"/v2/keys"), - ) - if err != nil { - t.Fatalf("could not start etcd process cluster (%v)", err) - } - defer c.Close() - - kubectl := []string{e2e.BinPath.Etcdctl, "--endpoints", strings.Join(c.EndpointsGRPC(), ",")} - require.NoError(t, e2e.SpawnWithExpect(append(kubectl, "put", "key", "value"), expect.ExpectedResponse{Value: "OK"})) - require.NoError(t, e2e.SpawnWithExpect(append(kubectl, "get", "key"), expect.ExpectedResponse{Value: "value"})) -} - -func MustNewHTTPClient(t testutil.TB, eps []string, tls *transport.TLSInfo) client.Client { - cfgtls := transport.TLSInfo{} - if tls != nil { - cfgtls = *tls - } - cfg := client.Config{Transport: mustNewTransport(t, cfgtls), Endpoints: eps} - c, err := client.New(cfg) - require.NoError(t, err) - return c -} - -func mustNewTransport(t testutil.TB, tlsInfo transport.TLSInfo) *http.Transport { - // tick in integration test is short, so 1s dial timeout could play well. - tr, err := transport.NewTimeoutTransport(tlsInfo, time.Second, rafthttp.ConnReadTimeout, rafthttp.ConnWriteTimeout) - require.NoError(t, err) - return tr -} diff --git a/tests/e2e/discovery_v3_test.go b/tests/e2e/discovery_v3_test.go index f3c47dd34e59..384f294cbfe9 100644 --- a/tests/e2e/discovery_v3_test.go +++ b/tests/e2e/discovery_v3_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "fmt" "strconv" "strings" @@ -55,7 +54,7 @@ func testClusterUsingV3Discovery(t *testing.T, discoveryClusterSize, targetClust e2e.BeforeTest(t) // step 1: start the discovery service - ds, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + ds, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithBasePort(2000), e2e.WithClusterSize(discoveryClusterSize), e2e.WithClientConnType(clientTLSType), @@ -122,5 +121,5 @@ func bootstrapEtcdClusterUsingV3Discovery(t *testing.T, discoveryEndpoints []str } // start the cluster - return e2e.StartEtcdProcessCluster(context.TODO(), t, epc, cfg) + return e2e.StartEtcdProcessCluster(t.Context(), t, epc, cfg) } diff --git a/tests/e2e/etcd_config_test.go b/tests/e2e/etcd_config_test.go index 21d587623daf..50e1aa50ab8c 100644 --- a/tests/e2e/etcd_config_test.go +++ b/tests/e2e/etcd_config_test.go @@ -40,7 +40,7 @@ func TestEtcdExampleConfig(t *testing.T) { proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--config-file", exampleConfigFile}, nil) require.NoError(t, err) - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, e2e.EtcdServerReadyLines)) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, e2e.EtcdServerReadyLines)) require.NoError(t, proc.Stop()) } @@ -80,7 +80,7 @@ func TestEtcdMultiPeer(t *testing.T) { } for _, p := range procs { - err := e2e.WaitReadyExpectProc(context.TODO(), p, e2e.EtcdServerReadyLines) + err := e2e.WaitReadyExpectProc(t.Context(), p, e2e.EtcdServerReadyLines) require.NoError(t, err) } } @@ -102,7 +102,7 @@ func TestEtcdUnixPeers(t *testing.T) { ) defer os.Remove("etcd.unix:1") require.NoError(t, err) - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, e2e.EtcdServerReadyLines)) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, e2e.EtcdServerReadyLines)) require.NoError(t, proc.Stop()) } @@ -150,7 +150,7 @@ func TestEtcdListenMetricsURLsWithMissingClientTLSInfo(t *testing.T) { _ = proc.Close() }() - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, []string{embed.ErrMissingClientTLSInfoForMetricsURL.Error()})) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, []string{embed.ErrMissingClientTLSInfoForMetricsURL.Error()})) } // TestEtcdPeerCNAuth checks that the inter peer auth based on CN of cert is working correctly. @@ -224,7 +224,7 @@ func TestEtcdPeerCNAuth(t *testing.T) { } else { expect = []string{"remote error: tls: bad certificate"} } - err := e2e.WaitReadyExpectProc(context.TODO(), p, expect) + err := e2e.WaitReadyExpectProc(t.Context(), p, expect) require.NoError(t, err) } } @@ -311,7 +311,7 @@ func TestEtcdPeerMultiCNAuth(t *testing.T) { } else { expect = []string{"remote error: tls: bad certificate"} } - err := e2e.WaitReadyExpectProc(context.TODO(), p, expect) + err := e2e.WaitReadyExpectProc(t.Context(), p, expect) require.NoError(t, err) } } @@ -384,7 +384,7 @@ func TestEtcdPeerNameAuth(t *testing.T) { } else { expect = []string{"client certificate authentication failed"} } - err := e2e.WaitReadyExpectProc(context.TODO(), p, expect) + err := e2e.WaitReadyExpectProc(t.Context(), p, expect) require.NoError(t, err) } } @@ -427,7 +427,7 @@ func TestEtcdPeerLocalAddr(t *testing.T) { os.RemoveAll(tempDir) }() - // node 0 (127.0.0.1) does not set `--experimental-set-member-localaddr`, + // node 0 (127.0.0.1) does not set `--feature-gates=SetMemberLocalAddr=true`, // while nodes 1 and nodes 2 do. // // node 0's peer certificate is signed for 127.0.0.1, but it uses the host @@ -437,7 +437,7 @@ func TestEtcdPeerLocalAddr(t *testing.T) { // Both node 1 and node 2's peer certificates are signed for the host IP, // and they also communicate with peers using the host IP (explicitly set // with --initial-advertise-peer-urls and - // --experimental-set-member-localaddr), so node 0 has no issue connecting + // --feature-gates=SetMemberLocalAddr=true), so node 0 has no issue connecting // to them. // // Refer to https://github.com/etcd-io/etcd/issues/17068. @@ -472,7 +472,7 @@ func TestEtcdPeerLocalAddr(t *testing.T) { "--peer-key-file", keyFiles[1], "--peer-trusted-ca-file", caFile, "--peer-client-cert-auth", - "--experimental-set-member-localaddr", + "--feature-gates=SetMemberLocalAddr=true", } } @@ -490,7 +490,7 @@ func TestEtcdPeerLocalAddr(t *testing.T) { } else { expect = []string{"x509: certificate is valid for 127.0.0.1, not "} } - err := e2e.WaitReadyExpectProc(context.TODO(), p, expect) + err := e2e.WaitReadyExpectProc(t.Context(), p, expect) require.NoError(t, err) } } @@ -568,9 +568,9 @@ func TestGrpcproxyAndListenCipherSuite(t *testing.T) { func TestBootstrapDefragFlag(t *testing.T) { e2e.SkipInShortMode(t) - proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--experimental-bootstrap-defrag-threshold-megabytes", "1000"}, nil) + proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--bootstrap-defrag-threshold-megabytes", "1000"}, nil) require.NoError(t, err) - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, []string{"Skipping defragmentation"})) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, []string{"Skipping defragmentation"})) require.NoError(t, proc.Stop()) // wait for the process to exit, otherwise test will have leaked goroutine @@ -582,10 +582,10 @@ func TestBootstrapDefragFlag(t *testing.T) { func TestSnapshotCatchupEntriesFlag(t *testing.T) { e2e.SkipInShortMode(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() - proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--experimental-snapshot-catchup-entries", "1000"}, nil) + proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--snapshot-catchup-entries", "1000"}, nil) require.NoError(t, err) require.NoError(t, e2e.WaitReadyExpectProc(ctx, proc, []string{"\"snapshot-catchup-entries\":1000"})) require.NoError(t, e2e.WaitReadyExpectProc(ctx, proc, []string{"serving client traffic"})) @@ -600,7 +600,7 @@ func TestSnapshotCatchupEntriesFlag(t *testing.T) { // TestEtcdHealthyWithTinySnapshotCatchupEntries ensures multi-node etcd cluster remains healthy with 1 snapshot catch up entry func TestEtcdHealthyWithTinySnapshotCatchupEntries(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(3), e2e.WithSnapshotCount(1), e2e.WithSnapshotCatchUpEntries(1), @@ -613,7 +613,7 @@ func TestEtcdHealthyWithTinySnapshotCatchupEntries(t *testing.T) { }) // simulate 10 clients keep writing to etcd in parallel with no error - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() g, ctx := errgroup.WithContext(ctx) for i := 0; i < 10; i++ { @@ -655,7 +655,7 @@ func TestEtcdTLSVersion(t *testing.T) { }, nil, ) assert.NoError(t, err) - assert.NoErrorf(t, e2e.WaitReadyExpectProc(context.TODO(), proc, e2e.EtcdServerReadyLines), "did not receive expected output from etcd process") + assert.NoErrorf(t, e2e.WaitReadyExpectProc(t.Context(), proc, e2e.EtcdServerReadyLines), "did not receive expected output from etcd process") assert.NoError(t, proc.Stop()) proc.Wait() // ensure the port has been released @@ -699,7 +699,7 @@ func TestEtcdDeprecatedFlags(t *testing.T) { tc.args, nil, ) require.NoError(t, err) - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, []string{tc.expectedMsg})) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, []string{tc.expectedMsg})) require.NoError(t, proc.Stop()) proc.Wait() // ensure the port has been released @@ -727,7 +727,7 @@ func TestV2DeprecationEnforceDefaultValue(t *testing.T) { append(commonArgs, "--v2-deprecation", optionLevel), nil, ) require.NoError(t, err) - require.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, []string{expectedDeprecationLevelMsg})) + require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, []string{expectedDeprecationLevelMsg})) require.NoError(t, proc.Stop()) proc.Wait() // ensure the port has been released diff --git a/tests/e2e/etcd_grpcproxy_test.go b/tests/e2e/etcd_grpcproxy_test.go index 02174e89f626..4f7a54d5ffa7 100644 --- a/tests/e2e/etcd_grpcproxy_test.go +++ b/tests/e2e/etcd_grpcproxy_test.go @@ -33,7 +33,7 @@ import ( func TestGrpcProxyAutoSync(t *testing.T) { e2e.SkipInShortMode(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(1)) @@ -93,7 +93,7 @@ func TestGrpcProxyAutoSync(t *testing.T) { func TestGrpcProxyTLSVersions(t *testing.T) { e2e.SkipInShortMode(t) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(1)) diff --git a/tests/e2e/etcd_mix_versions_test.go b/tests/e2e/etcd_mix_versions_test.go index 46dab60db71b..205a76168419 100644 --- a/tests/e2e/etcd_mix_versions_test.go +++ b/tests/e2e/etcd_mix_versions_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "fmt" "testing" "time" @@ -89,7 +88,7 @@ func mixVersionsSnapshotTestByAddingMember(t *testing.T, cfg *e2e.EtcdProcessClu } t.Logf("Create an etcd cluster with %d member", cfg.ClusterSize) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg), e2e.WithSnapshotCount(10), ) @@ -107,9 +106,9 @@ func mixVersionsSnapshotTestByAddingMember(t *testing.T, cfg *e2e.EtcdProcessClu newCfg.Version = newInstanceVersion newCfg.ServerConfig.SnapshotCatchUpEntries = 10 t.Log("Starting a new etcd instance") - _, err = epc.StartNewProc(context.TODO(), &newCfg, t, false /* addAsLearner */) + _, err = epc.StartNewProc(t.Context(), &newCfg, t, false /* addAsLearner */) require.NoErrorf(t, err, "failed to start the new etcd instance") - defer epc.CloseProc(context.TODO(), nil) + defer epc.CloseProc(t.Context(), nil) assertKVHash(t, epc) } @@ -136,7 +135,7 @@ func mixVersionsSnapshotTestByMockPartition(t *testing.T, cfg *e2e.EtcdProcessCl e2e.WithSnapshotCatchUpEntries(10), } t.Logf("Create an etcd cluster with %d member", cfg.ClusterSize) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, clusterOptions...) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, clusterOptions...) require.NoErrorf(t, err, "failed to start etcd cluster") defer func() { derr := epc.Close() @@ -156,7 +155,7 @@ func mixVersionsSnapshotTestByMockPartition(t *testing.T, cfg *e2e.EtcdProcessCl e2e.AssertProcessLogs(t, leaderEPC, "saved snapshot") t.Log("Restart the partitioned member") - err = toPartitionedMember.Restart(context.TODO()) + err = toPartitionedMember.Restart(t.Context()) require.NoError(t, err) assertKVHash(t, epc) @@ -170,7 +169,7 @@ func writeKVs(t *testing.T, etcdctl *e2e.EtcdctlV3, startIdx, endIdx int) { for i := startIdx; i < endIdx; i++ { key := fmt.Sprintf("key-%d", i) value := fmt.Sprintf("value-%d", i) - err := etcdctl.Put(context.TODO(), key, value, config.PutOptions{}) + err := etcdctl.Put(t.Context(), key, value, config.PutOptions{}) require.NoErrorf(t, err, "failed to put %q", key) } } @@ -182,7 +181,7 @@ func assertKVHash(t *testing.T, epc *e2e.EtcdProcessCluster) { } t.Log("Verify all nodes have exact same revision and hash") assert.Eventually(t, func() bool { - hashKvs, err := epc.Etcdctl().HashKV(context.TODO(), 0) + hashKvs, err := epc.Etcdctl().HashKV(t.Context(), 0) if err != nil { t.Logf("failed to get HashKV: %v", err) return false diff --git a/tests/e2e/etcd_release_upgrade_test.go b/tests/e2e/etcd_release_upgrade_test.go index 2722cacdb65f..18e08f7d3a03 100644 --- a/tests/e2e/etcd_release_upgrade_test.go +++ b/tests/e2e/etcd_release_upgrade_test.go @@ -17,6 +17,7 @@ package e2e import ( "context" "fmt" + "strings" "sync" "testing" "time" @@ -24,10 +25,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/pkg/v3/expect" + "go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/e2e" + "go.etcd.io/etcd/tests/v3/framework/testutils" ) // TestReleaseUpgrade ensures that changes to master branch does not affect @@ -39,7 +43,7 @@ func TestReleaseUpgrade(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithVersion(e2e.LastVersion), e2e.WithSnapshotCount(3), e2e.WithBasePeerScheme("unix"), // to avoid port conflict @@ -82,7 +86,7 @@ func TestReleaseUpgrade(t *testing.T) { epc.Procs[i].Config().KeepDataDir = true t.Logf("Restarting node in the new version: %v", i) - if err = epc.Procs[i].Restart(context.TODO()); err != nil { + if err = epc.Procs[i].Restart(t.Context()); err != nil { t.Fatalf("error restarting etcd process (%v)", err) } @@ -119,7 +123,7 @@ func TestReleaseUpgradeWithRestart(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithVersion(e2e.LastVersion), e2e.WithSnapshotCount(10), e2e.WithBasePeerScheme("unix"), @@ -158,7 +162,7 @@ func TestReleaseUpgradeWithRestart(t *testing.T) { go func(i int) { epc.Procs[i].Config().ExecPath = e2e.BinPath.Etcd epc.Procs[i].Config().KeepDataDir = true - assert.NoErrorf(t, epc.Procs[i].Restart(context.TODO()), "error restarting etcd process") + assert.NoErrorf(t, epc.Procs[i].Restart(t.Context()), "error restarting etcd process") wg.Done() }(i) } @@ -166,3 +170,127 @@ func TestReleaseUpgradeWithRestart(t *testing.T) { require.NoError(t, ctlV3Get(cx, []string{kvs[0].key}, []kv{kvs[0]}...)) } + +func TestClusterUpgradeAfterPromotingMembers(t *testing.T) { + if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { + t.Skipf("%q does not exist", e2e.BinPath.EtcdLastRelease) + } + + e2e.BeforeTest(t) + + currentVersion, err := e2e.GetVersionFromBinary(e2e.BinPath.Etcd) + require.NoErrorf(t, err, "failed to get version from binary") + + lastClusterVersion, err := e2e.GetVersionFromBinary(e2e.BinPath.EtcdLastRelease) + require.NoErrorf(t, err, "failed to get version from last release binary") + + clusterSize := 3 + + for _, tc := range []struct { + name string + snapshot int + }{ + { + name: "create snapshot after promoted", + snapshot: 10, + }, + { + name: "no snapshot after promoted", + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + epc, _ := mustCreateNewClusterByPromotingMembers(t, e2e.LastVersion, clusterSize, + e2e.WithSnapshotCount(uint64(tc.snapshot))) + defer func() { + require.NoError(t, epc.Close()) + }() + + for i := 0; i < tc.snapshot; i++ { + err = epc.Etcdctl().Put(ctx, "foo", "bar", config.PutOptions{}) + require.NoError(t, err) + } + + err = e2e.DowngradeUpgradeMembers(t, nil, epc, clusterSize, false, lastClusterVersion, currentVersion) + require.NoError(t, err) + + t.Logf("Checking all members' status after upgrading") + ensureAllMembersAreVotingMembers(t, epc) + + t.Logf("Checking all members are ready to serve client requests") + for i := 0; i < clusterSize; i++ { + err = epc.Procs[i].Etcdctl().Put(context.Background(), "foo", "bar", config.PutOptions{}) + require.NoError(t, err) + } + }) + } +} + +func mustCreateNewClusterByPromotingMembers(t *testing.T, clusterVersion e2e.ClusterVersion, clusterSize int, opts ...e2e.EPClusterOption) (*e2e.EtcdProcessCluster, []*etcdserverpb.Member) { + require.GreaterOrEqualf(t, clusterSize, 1, "clusterSize must be at least 1") + + ctx := context.Background() + + t.Logf("Creating new etcd cluster - version: %s, clusterSize: %v", clusterVersion, clusterSize) + opts = append(opts, e2e.WithVersion(clusterVersion), e2e.WithClusterSize(1)) + epc, err := e2e.NewEtcdProcessCluster(ctx, t, opts...) + require.NoErrorf(t, err, "failed to start first etcd process") + defer func() { + if t.Failed() { + epc.Close() + } + }() + + var promotedMembers []*etcdserverpb.Member + for i := 1; i < clusterSize; i++ { + var ( + memberID uint64 + aerr error + ) + + // NOTE: New promoted member needs time to get connected. + t.Logf("[%d] Adding new member as learner", i) + testutils.ExecuteWithTimeout(t, 1*time.Minute, func() { + for { + memberID, aerr = epc.StartNewProc(ctx, nil, t, true) + if aerr != nil { + if strings.Contains(aerr.Error(), "etcdserver: unhealthy cluster") { + time.Sleep(1 * time.Second) + continue + } + } + break + } + }) + require.NoError(t, aerr) + + t.Logf("[%d] Promoting member (%x)", i, memberID) + etcdctl := epc.Procs[0].Etcdctl() + resp, merr := etcdctl.MemberPromote(ctx, memberID) + require.NoError(t, merr) + + for _, m := range resp.Members { + if m.ID == memberID { + promotedMembers = append(promotedMembers, m) + } + } + } + + t.Log("Ensure all members are voting members from user perspective") + ensureAllMembersAreVotingMembers(t, epc) + + return epc, promotedMembers +} + +func ensureAllMembersAreVotingMembers(t *testing.T, epc *e2e.EtcdProcessCluster) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + resp, err := epc.Etcdctl().MemberList(ctx, false) + require.NoError(t, err) + require.Len(t, resp.Members, len(epc.Procs)) + for _, m := range resp.Members { + require.Falsef(t, m.IsLearner, "node(%x)", m.ID) + } +} diff --git a/tests/e2e/failover_test.go b/tests/e2e/failover_test.go index 878603673485..22411f5c8a8c 100644 --- a/tests/e2e/failover_test.go +++ b/tests/e2e/failover_test.go @@ -59,7 +59,7 @@ func TestFailoverOnDefrag(t *testing.T) { name: "defrag failover happy case", clusterOptions: []e2e.EPClusterOption{ e2e.WithClusterSize(3), - e2e.WithExperimentalStopGRPCServiceOnDefrag(true), + e2e.WithServerFeatureGate("StopGRPCServiceOnDefrag", true), e2e.WithGoFailEnabled(true), }, gRPCDialOptions: []grpc.DialOption{ @@ -73,7 +73,7 @@ func TestFailoverOnDefrag(t *testing.T) { name: "defrag blocks one-third of requests with stopGRPCServiceOnDefrag set to false", clusterOptions: []e2e.EPClusterOption{ e2e.WithClusterSize(3), - e2e.WithExperimentalStopGRPCServiceOnDefrag(false), + e2e.WithServerFeatureGate("StopGRPCServiceOnDefrag", false), e2e.WithGoFailEnabled(true), }, gRPCDialOptions: []grpc.DialOption{ @@ -87,7 +87,7 @@ func TestFailoverOnDefrag(t *testing.T) { name: "defrag blocks one-third of requests with stopGRPCServiceOnDefrag set to true and client health check disabled", clusterOptions: []e2e.EPClusterOption{ e2e.WithClusterSize(3), - e2e.WithExperimentalStopGRPCServiceOnDefrag(true), + e2e.WithServerFeatureGate("StopGRPCServiceOnDefrag", true), e2e.WithGoFailEnabled(true), }, expectedMinQPS: 20, @@ -136,7 +136,7 @@ func TestFailoverOnDefrag(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { e2e.BeforeTest(t) - clus, cerr := e2e.NewEtcdProcessCluster(context.TODO(), t, tc.clusterOptions...) + clus, cerr := e2e.NewEtcdProcessCluster(t.Context(), t, tc.clusterOptions...) require.NoError(t, cerr) t.Cleanup(func() { clus.Stop() }) @@ -165,7 +165,7 @@ func TestFailoverOnDefrag(t *testing.T) { return lastErr default: } - getContext, cancel := context.WithTimeout(context.Background(), requestTimeout) + getContext, cancel := context.WithTimeout(t.Context(), requestTimeout) _, err := clusterClient.Get(getContext, "health") cancel() requestVolume++ @@ -199,6 +199,6 @@ func TestFailoverOnDefrag(t *testing.T) { } func triggerDefrag(t *testing.T, member e2e.EtcdProcess) { - require.NoError(t, member.Failpoints().SetupHTTP(context.Background(), "defragBeforeCopy", `sleep("10s")`)) - require.NoError(t, member.Etcdctl().Defragment(context.Background(), config.DefragOption{Timeout: time.Minute})) + require.NoError(t, member.Failpoints().SetupHTTP(t.Context(), "defragBeforeCopy", `sleep("10s")`)) + require.NoError(t, member.Etcdctl().Defragment(t.Context(), config.DefragOption{Timeout: time.Minute})) } diff --git a/tests/e2e/gateway_test.go b/tests/e2e/gateway_test.go index 600207610884..8d4f44221824 100644 --- a/tests/e2e/gateway_test.go +++ b/tests/e2e/gateway_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "strings" "testing" @@ -28,7 +27,7 @@ import ( var defaultGatewayEndpoint = "127.0.0.1:23790" func TestGateway(t *testing.T) { - ec, err := e2e.NewEtcdProcessCluster(context.TODO(), t) + ec, err := e2e.NewEtcdProcessCluster(t.Context(), t) require.NoError(t, err) defer ec.Stop() diff --git a/tests/e2e/graceful_shutdown_test.go b/tests/e2e/graceful_shutdown_test.go index b612a5c1b6a0..d889bfa4ca76 100644 --- a/tests/e2e/graceful_shutdown_test.go +++ b/tests/e2e/graceful_shutdown_test.go @@ -47,7 +47,7 @@ func TestGracefulShutdown(t *testing.T) { t.Run(tc.name, func(t *testing.T) { testRunner := e2e.NewE2eRunner() testRunner.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clus := testRunner.NewCluster(ctx, t, config.WithClusterSize(tc.clusterSize)) // clean up orphaned resources like closing member client. diff --git a/tests/e2e/hashkv_test.go b/tests/e2e/hashkv_test.go index 6c81e1cc7dd9..e17ec3d6a787 100644 --- a/tests/e2e/hashkv_test.go +++ b/tests/e2e/hashkv_test.go @@ -17,7 +17,6 @@ package e2e import ( - "context" "fmt" "testing" @@ -63,7 +62,7 @@ func TestVerifyHashKVAfterCompact(t *testing.T) { } } - ctx := context.Background() + ctx := t.Context() cfg := e2e.NewConfigClientTLS() clus, err := e2e.NewEtcdProcessCluster(ctx, t, @@ -107,7 +106,7 @@ func TestVerifyHashKVAfterTwoCompactionsOnTombstone_MixVersions(t *testing.T) { t.Skipf("%q does not exist", e2e.BinPath.EtcdLastRelease) } - ctx := context.Background() + ctx := t.Context() cfg := e2e.NewConfigClientTLS() clus, err := e2e.NewEtcdProcessCluster(ctx, t, @@ -149,7 +148,7 @@ func TestVerifyHashKVAfterCompactionOnLastTombstone_MixVersions(t *testing.T) { {"key0", "key1"}, } { t.Run(fmt.Sprintf("#%v", keys), func(t *testing.T) { - ctx := context.Background() + ctx := t.Context() cfg := e2e.NewConfigClientTLS() clus, err := e2e.NewEtcdProcessCluster(ctx, t, @@ -182,7 +181,7 @@ func populateDataForHashKV(t *testing.T, clus *e2e.EtcdProcessCluster, clientCfg c := newClient(t, clus.EndpointsGRPC(), clientCfg) defer c.Close() - ctx := context.Background() + ctx := t.Context() totalOperations := 40 var ( @@ -218,7 +217,7 @@ func populateDataForHashKV(t *testing.T, clus *e2e.EtcdProcessCluster, clientCfg } func verifyConsistentHashKVAcrossAllMembers(t *testing.T, cli *e2e.EtcdctlV3, hashKVOnRev int64) { - ctx := context.Background() + ctx := t.Context() t.Logf("HashKV on rev=%d", hashKVOnRev) resp, err := cli.HashKV(ctx, hashKVOnRev) diff --git a/tests/e2e/http_health_check_test.go b/tests/e2e/http_health_check_test.go index 86b41bfd6544..883a115af0c6 100644 --- a/tests/e2e/http_health_check_test.go +++ b/tests/e2e/http_health_check_test.go @@ -150,7 +150,7 @@ func TestHTTPHealthHandler(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second) defer cancel() clus, err := e2e.NewEtcdProcessCluster(ctx, t, tc.clusterOptions...) require.NoError(t, err) @@ -313,7 +313,7 @@ func TestHTTPLivezReadyzHandler(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second) defer cancel() clus, err := e2e.NewEtcdProcessCluster(ctx, t, tc.clusterOptions...) require.NoError(t, err) @@ -336,7 +336,7 @@ func TestHTTPLivezReadyzHandler(t *testing.T) { } func doHealthCheckAndVerify(t *testing.T, client *http.Client, url string, expectTimeoutError bool, expectStatusCode int, expectRespSubStrings []string) { - ctx, cancel := context.WithTimeout(context.Background(), healthCheckTimeout) + ctx, cancel := context.WithTimeout(t.Context(), healthCheckTimeout) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) require.NoErrorf(t, err, "failed to creat request %+v", err) resp, herr := client.Do(req) diff --git a/tests/e2e/leader_snapshot_no_proxy_test.go b/tests/e2e/leader_snapshot_no_proxy_test.go index 7b3c39270f39..b58d294cf1ab 100644 --- a/tests/e2e/leader_snapshot_no_proxy_test.go +++ b/tests/e2e/leader_snapshot_no_proxy_test.go @@ -35,7 +35,7 @@ import ( func TestRecoverSnapshotBackend(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, diff --git a/tests/e2e/logging_test.go b/tests/e2e/logging_test.go index d391e2f1b5d8..8e6b064ad354 100644 --- a/tests/e2e/logging_test.go +++ b/tests/e2e/logging_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "encoding/json" "testing" "time" @@ -109,7 +108,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { e2e.BeforeTest(t) - ctx := context.TODO() + ctx := t.Context() epc, err := e2e.NewEtcdProcessCluster(ctx, t, tc.options...) require.NoError(t, err) diff --git a/tests/e2e/metrics_test.go b/tests/e2e/metrics_test.go index 184a430d6d2a..9d730ed3aa3a 100644 --- a/tests/e2e/metrics_test.go +++ b/tests/e2e/metrics_test.go @@ -15,17 +15,12 @@ package e2e import ( - "bytes" "context" "fmt" - "io" - "net/http" "net/url" "testing" "time" - dto "github.com/prometheus/client_model/go" - "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/require" "go.etcd.io/etcd/api/v3/version" @@ -167,7 +162,6 @@ func TestNoMetricsMissing(t *testing.T) { "etcd_debugging_snap_save_marshalling_duration_seconds", "etcd_debugging_snap_save_total_duration_seconds", "etcd_debugging_store_expires_total", - "etcd_debugging_store_reads_total", "etcd_debugging_store_watch_requests_total", "etcd_debugging_store_watchers", "etcd_debugging_store_writes_total", @@ -307,7 +301,7 @@ func TestNoMetricsMissing(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { e2e.BeforeTest(t) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, tc.options...) @@ -325,7 +319,7 @@ func TestNoMetricsMissing(t *testing.T) { metricsURL, err := url.JoinPath(epc.Procs[0].Config().ClientURL, "metrics") require.NoError(t, err) - mfs, err := getMetrics(metricsURL) + mfs, err := e2e.GetMetrics(metricsURL) require.NoError(t, err) var missingMetrics []string @@ -342,23 +336,6 @@ func TestNoMetricsMissing(t *testing.T) { } } -func getMetrics(metricsURL string) (map[string]*dto.MetricFamily, error) { - httpClient := http.Client{Transport: &http.Transport{}} - resp, err := httpClient.Get(metricsURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var parser expfmt.TextParser - return parser.TextToMetricFamilies(bytes.NewReader(data)) -} - // formatMetrics is only for test purpose /*func formatMetrics(metrics []string) string { quoted := make([]string, len(metrics)) diff --git a/tests/e2e/promote_experimental_flag_test.go b/tests/e2e/promote_experimental_flag_test.go index 13a8fcba4fb1..76992f811f29 100644 --- a/tests/e2e/promote_experimental_flag_test.go +++ b/tests/e2e/promote_experimental_flag_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "testing" "time" @@ -28,7 +27,7 @@ import ( func TestWarningApplyDuration(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(1), e2e.WithWarningUnaryRequestDuration(time.Microsecond), ) @@ -42,49 +41,9 @@ func TestWarningApplyDuration(t *testing.T) { }) cc := epc.Etcdctl() - err = cc.Put(context.TODO(), "foo", "bar", config.PutOptions{}) + err = cc.Put(t.Context(), "foo", "bar", config.PutOptions{}) require.NoErrorf(t, err, "error on put") // verify warning e2e.AssertProcessLogs(t, epc.Procs[0], "request stats") } - -// TestExperimentalWarningApplyDuration tests the experimental warning apply duration -// TODO: this test is a duplicate of TestWarningApplyDuration except it uses --experimental-warning-unary-request-duration -// Remove this test after --experimental-warning-unary-request-duration flag is removed. -func TestExperimentalWarningApplyDuration(t *testing.T) { - e2e.BeforeTest(t) - - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, - e2e.WithClusterSize(1), - e2e.WithExperimentalWarningUnaryRequestDuration(time.Microsecond), - ) - if err != nil { - t.Fatalf("could not start etcd process cluster (%v)", err) - } - t.Cleanup(func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }) - - cc := epc.Etcdctl() - err = cc.Put(context.TODO(), "foo", "bar", config.PutOptions{}) - require.NoErrorf(t, err, "error on put") - - // verify warning - e2e.AssertProcessLogs(t, epc.Procs[0], "request stats") -} - -func TestBothWarningApplyDurationFlagsFail(t *testing.T) { - e2e.BeforeTest(t) - - _, err := e2e.NewEtcdProcessCluster(context.TODO(), t, - e2e.WithClusterSize(1), - e2e.WithWarningUnaryRequestDuration(time.Second), - e2e.WithExperimentalWarningUnaryRequestDuration(time.Second), - ) - if err == nil { - t.Fatal("Expected process to fail") - } -} diff --git a/tests/e2e/reproduce_17780_test.go b/tests/e2e/reproduce_17780_test.go index f5ef97b91a4b..fc54b8e8cc9a 100644 --- a/tests/e2e/reproduce_17780_test.go +++ b/tests/e2e/reproduce_17780_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "fmt" "testing" "time" @@ -34,7 +33,7 @@ func TestReproduce17780(t *testing.T) { compactionBatchLimit := 10 - ctx := context.TODO() + ctx := t.Context() clus, cerr := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(3), e2e.WithGoFailEnabled(true), diff --git a/tests/e2e/reproduce_19406_test.go b/tests/e2e/reproduce_19406_test.go new file mode 100644 index 000000000000..be942aaba719 --- /dev/null +++ b/tests/e2e/reproduce_19406_test.go @@ -0,0 +1,113 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package e2e + +import ( + "context" + "fmt" + "net/url" + "testing" + "time" + + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/pkg/v3/stringutil" + "go.etcd.io/etcd/tests/v3/framework/e2e" + + "github.com/stretchr/testify/require" +) + +// TestReproduce19406 reproduces the issue: https://github.com/etcd-io/etcd/issues/19406 +func TestReproduce19406(t *testing.T) { + e2e.BeforeTest(t) + + compactionSleepInterval := 100 * time.Millisecond + ctx := context.TODO() + + clus, cerr := e2e.NewEtcdProcessCluster(ctx, t, + e2e.WithClusterSize(1), + e2e.WithGoFailEnabled(true), + e2e.WithCompactionBatchLimit(1), + e2e.WithCompactionSleepInterval(compactionSleepInterval), + ) + require.NoError(t, cerr) + t.Cleanup(func() { require.NoError(t, clus.Stop()) }) + + // Produce some data + cli := newClient(t, clus.EndpointsGRPC(), e2e.ClientConfig{}) + valueSize := 10 + var latestRevision int64 + + produceKeyNum := 20 + for i := 0; i <= produceKeyNum; i++ { + resp, err := cli.Put(ctx, fmt.Sprintf("%d", i), stringutil.RandString(uint(valueSize))) + require.NoError(t, err) + latestRevision = resp.Header.Revision + } + + // Sleep for PerCompactionInterationInterval to simulate a single iteration of compaction lasting at least this duration. + PerCompactionInterationInterval := compactionSleepInterval + require.NoError(t, clus.Procs[0].Failpoints().SetupHTTP(ctx, "compactAfterAcquiredBatchTxLock", + fmt.Sprintf(`sleep("%s")`, PerCompactionInterationInterval))) + + // start compaction + t.Log("start compaction...") + _, err := cli.Compact(ctx, latestRevision, clientv3.WithCompactPhysical()) + require.NoError(t, err) + t.Log("finished compaction...") + + // Validate that total compaction sleep interval + // Compaction runs in batches. During each batch, it acquires a lock, releases it at the end, + // and then waits for a compactionSleepInterval before starting the next batch. This pause + // allows PUT requests to be processed. + // Therefore, the total compaction sleep interval larger or equal to + // (compaction iteration number - 1) * compactionSleepInterval + httpEndpoint := clus.EndpointsHTTP()[0] + totalKeys := produceKeyNum + 1 + pauseDuration, totalDuration := getEtcdCompactionMetrics(t, httpEndpoint) + require.NoError(t, err) + actualSleepInterval := time.Duration(totalDuration-pauseDuration) * time.Millisecond + expectSleepInterval := compactionSleepInterval * time.Duration(totalKeys) + t.Logf("db_compaction_pause_duration: %.2f db_compaction_total_duration: %.2f, totalKeys: %d", + pauseDuration, totalDuration, totalKeys) + require.GreaterOrEqualf(t, actualSleepInterval, expectSleepInterval, + "expect total compact sleep interval larger than (%v) but got (%v)", + expectSleepInterval, actualSleepInterval) +} + +func getEtcdCompactionMetrics(t *testing.T, httpEndpoint string) (pauseDuration, totalDuration float64) { + metricsURL, err := url.JoinPath(httpEndpoint, "metrics") + require.NoError(t, err) + + // Fetch metrics from the endpoint + metricFamilies, err := e2e.GetMetrics(metricsURL) + require.NoError(t, err) + + // Extract sum from histogram metric + getHistogramSum := func(name string) float64 { + mf, ok := metricFamilies[name] + require.Truef(t, ok, "metric %q not found", name) + require.NotEmptyf(t, mf.Metric, "metric %q has no data", name) + + hist := mf.Metric[0].GetHistogram() + require.NotEmptyf(t, hist, "metric %q is not a histogram", name) + + return hist.GetSampleSum() + } + + pauseDuration = getHistogramSum("etcd_debugging_mvcc_db_compaction_pause_duration_milliseconds") + totalDuration = getHistogramSum("etcd_debugging_mvcc_db_compaction_total_duration_milliseconds") + + return pauseDuration, totalDuration +} diff --git a/tests/e2e/runtime_reconfiguration_test.go b/tests/e2e/runtime_reconfiguration_test.go index 1736126e6a6e..650e9358d2ba 100644 --- a/tests/e2e/runtime_reconfiguration_test.go +++ b/tests/e2e/runtime_reconfiguration_test.go @@ -60,7 +60,7 @@ func TestRuntimeReconfigGrowClusterSize(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(tc.clusterSize)) @@ -102,7 +102,7 @@ func TestRuntimeReconfigDecreaseClusterSize(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(tc.clusterSize)) @@ -140,7 +140,7 @@ func TestRuntimeReconfigRollingUpgrade(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(3)) diff --git a/tests/e2e/utils.go b/tests/e2e/utils.go index 8917bd8072ad..9eb7e0ec2c30 100644 --- a/tests/e2e/utils.go +++ b/tests/e2e/utils.go @@ -69,13 +69,13 @@ func newClient(t *testing.T, entpoints []string, cfg e2e.ClientConfig) *clientv3 } // tlsInfo follows the Client-to-server communication in https://etcd.io/docs/v3.6/op-guide/security/#basic-setup -func tlsInfo(t testing.TB, cfg e2e.ClientConfig) (*transport.TLSInfo, error) { +func tlsInfo(tb testing.TB, cfg e2e.ClientConfig) (*transport.TLSInfo, error) { switch cfg.ConnectionType { case e2e.ClientNonTLS, e2e.ClientTLSAndNonTLS: return nil, nil case e2e.ClientTLS: if cfg.AutoTLS { - tls, err := transport.SelfCert(zap.NewNop(), t.TempDir(), []string{"localhost"}, 1) + tls, err := transport.SelfCert(zap.NewNop(), tb.TempDir(), []string{"localhost"}, 1) if err != nil { return nil, fmt.Errorf("failed to generate cert: %w", err) } @@ -145,16 +145,6 @@ func getMemberIDByName(ctx context.Context, c *e2e.EtcdctlV3, name string) (id u return 0, false, nil } -func patchArgs(args []string, flag, newValue string) error { - for i, arg := range args { - if strings.Contains(arg, flag) { - args[i] = fmt.Sprintf("--%s=%s", flag, newValue) - return nil - } - } - return fmt.Errorf("--%s flag not found", flag) -} - func generateCertsForIPs(tempDir string, ips []net.IP) (caFile string, certFiles []string, keyFiles []string, err error) { ca := &x509.Certificate{ SerialNumber: big.NewInt(1001), diff --git a/tests/e2e/utl_migrate_test.go b/tests/e2e/utl_migrate_test.go index 5ee933f0ef28..b6f887319c02 100644 --- a/tests/e2e/utl_migrate_test.go +++ b/tests/e2e/utl_migrate_test.go @@ -17,7 +17,6 @@ package e2e import ( - "context" "fmt" "path/filepath" "strings" @@ -55,78 +54,79 @@ func TestEtctlutlMigrate(t *testing.T) { targetVersion: "abc", clusterSize: 1, expectLogsSubString: `Error: wrong target version format, expected "X.Y", got "abc"`, - expectStorageVersion: &version.V3_6, + expectStorageVersion: &version.V3_7, }, { name: "Invalid target version", targetVersion: "3.a", clusterSize: 1, expectLogsSubString: `Error: failed to parse target version: strconv.ParseInt: parsing "a": invalid syntax`, - expectStorageVersion: &version.V3_6, + expectStorageVersion: &version.V3_7, }, { name: "Target with only major version is invalid", targetVersion: "3", clusterSize: 1, expectLogsSubString: `Error: wrong target version format, expected "X.Y", got "3"`, - expectStorageVersion: &version.V3_6, + expectStorageVersion: &version.V3_7, }, { name: "Target with patch version is invalid", targetVersion: "3.6.0", clusterSize: 1, expectLogsSubString: `Error: wrong target version format, expected "X.Y", got "3.6.0"`, - expectStorageVersion: &version.V3_6, + expectStorageVersion: &version.V3_7, }, { - name: "Migrate v3.5 to v3.5 is no-op", - clusterVersion: e2e.LastVersion, - clusterSize: 1, - targetVersion: "3.5", - expectLogsSubString: "storage version up-to-date\t" + `{"storage-version": "3.5"}`, + name: "Migrate v3.6 to v3.6 is no-op", + clusterVersion: e2e.LastVersion, + clusterSize: 1, + targetVersion: "3.6", + expectStorageVersion: &version.V3_6, + expectLogsSubString: "storage version up-to-date\t" + `{"storage-version": "3.6"}`, }, { name: "Upgrade 1 member cluster from v3.5 to v3.6 should work", clusterVersion: e2e.LastVersion, clusterSize: 1, - targetVersion: "3.6", - expectStorageVersion: &version.V3_6, + targetVersion: "3.7", + expectStorageVersion: &version.V3_7, }, { - name: "Upgrade 3 member cluster from v3.5 to v3.6 should work", + name: "Upgrade 3 member cluster from v3.6 to v3.7 should work", clusterVersion: e2e.LastVersion, clusterSize: 3, - targetVersion: "3.6", - expectStorageVersion: &version.V3_6, + targetVersion: "3.7", + expectStorageVersion: &version.V3_7, }, { - name: "Migrate v3.6 to v3.6 is no-op", - targetVersion: "3.6", + name: "Migrate v3.7 to v3.7 is no-op", + targetVersion: "3.7", clusterSize: 1, - expectLogsSubString: "storage version up-to-date\t" + `{"storage-version": "3.6"}`, - expectStorageVersion: &version.V3_6, + expectLogsSubString: "storage version up-to-date\t" + `{"storage-version": "3.7"}`, + expectStorageVersion: &version.V3_7, }, { - name: "Downgrade 1 member cluster from v3.6 to v3.5 should work", - targetVersion: "3.5", + name: "Downgrade 1 member cluster from v3.7 to v3.6 should work", + targetVersion: "3.6", clusterSize: 1, expectLogsSubString: "updated storage version", - expectStorageVersion: nil, // 3.5 doesn't have the field `storageVersion`, so it returns nil. + expectStorageVersion: &version.V3_6, }, { - name: "Downgrade 3 member cluster from v3.6 to v3.5 should work", - targetVersion: "3.5", + name: "Downgrade 3 member cluster from v3.7 to v3.6 should work", + targetVersion: "3.6", clusterSize: 3, expectLogsSubString: "updated storage version", - expectStorageVersion: nil, // 3.5 doesn't have the field `storageVersion`, so it returns nil. + expectStorageVersion: &version.V3_6, }, { - name: "Upgrade v3.6 to v3.7 with force should work", - targetVersion: "3.7", + name: "Upgrade v3.7 to v3.8 with force should work", + targetVersion: "3.8", clusterSize: 1, force: true, - expectLogsSubString: "forcefully set storage version\t" + `{"storage-version": "3.7"}`, - expectStorageVersion: &semver.Version{Major: 3, Minor: 7}, + expectLogsSubString: "forcefully set storage version\t" + `{"storage-version": "3.8"}`, + expectStorageVersion: &semver.Version{Major: 3, Minor: 8}, }, } for _, tc := range tcs { @@ -138,7 +138,7 @@ func TestEtctlutlMigrate(t *testing.T) { } dataDirPath := t.TempDir() - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithVersion(tc.clusterVersion), e2e.WithDataDirPath(dataDirPath), e2e.WithClusterSize(1), diff --git a/tests/e2e/v2store_deprecation_test.go b/tests/e2e/v2store_deprecation_test.go index b267ae240b8a..77a59a365889 100644 --- a/tests/e2e/v2store_deprecation_test.go +++ b/tests/e2e/v2store_deprecation_test.go @@ -30,26 +30,16 @@ import ( "go.uber.org/zap/zaptest" "go.etcd.io/etcd/client/pkg/v3/fileutil" - "go.etcd.io/etcd/pkg/v3/expect" "go.etcd.io/etcd/server/v3/etcdserver" "go.etcd.io/etcd/server/v3/etcdserver/api/membership" "go.etcd.io/etcd/server/v3/etcdserver/api/snap" "go.etcd.io/etcd/server/v3/etcdserver/api/v2store" + betesting "go.etcd.io/etcd/server/v3/storage/backend/testing" + "go.etcd.io/etcd/server/v3/storage/schema" "go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/e2e" ) -func writeCustomV2Data(t testing.TB, epc *e2e.EtcdProcessCluster, count int) { - for i := 0; i < count; i++ { - if err := e2e.CURLPut(epc, e2e.CURLReq{ - Endpoint: "/v2/keys/foo", Value: "bar" + fmt.Sprint(i), - Expected: expect.ExpectedResponse{Value: `{"action":"set","node":{"key":"/foo","value":"bar` + fmt.Sprint(i)}, - }); err != nil { - t.Fatalf("failed put with curl (%v)", err) - } - } -} - func TestV2DeprecationNotYet(t *testing.T) { e2e.BeforeTest(t) t.Log("Verify its infeasible to start etcd with --v2-deprecation=not-yet mode") @@ -60,70 +50,11 @@ func TestV2DeprecationNotYet(t *testing.T) { assert.NoError(t, err) } -func TestV2DeprecationWriteOnlyWAL(t *testing.T) { - e2e.BeforeTest(t) - dataDirPath := t.TempDir() - - if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { - t.Skipf("%q does not exist", e2e.BinPath.EtcdLastRelease) - } - cfg := e2e.ConfigStandalone(*e2e.NewConfig( - e2e.WithVersion(e2e.LastVersion), - e2e.WithEnableV2(true), - e2e.WithDataDirPath(dataDirPath), - )) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) - require.NoError(t, err) - memberDataDir := epc.Procs[0].Config().DataDirPath - - writeCustomV2Data(t, epc, 1) - - require.NoError(t, epc.Stop()) - - t.Log("Verify its infeasible to start etcd with --v2-deprecation=write-only mode") - proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--v2-deprecation=write-only", "--data-dir=" + memberDataDir}, nil) - require.NoError(t, err) - - _, err = proc.Expect("detected disallowed v2 WAL for stage --v2-deprecation=write-only") - assert.NoError(t, err) -} - -func TestV2DeprecationWriteOnlySnapshot(t *testing.T) { - e2e.BeforeTest(t) - dataDirPath := t.TempDir() - - if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { - t.Skipf("%q does not exist", e2e.BinPath.EtcdLastRelease) - } - cfg := e2e.ConfigStandalone(*e2e.NewConfig( - e2e.WithVersion(e2e.LastVersion), - e2e.WithEnableV2(true), - e2e.WithDataDirPath(dataDirPath), - e2e.WithSnapshotCount(10), - )) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) - require.NoError(t, err) - memberDataDir := epc.Procs[0].Config().DataDirPath - - // We need to exceed 'SnapshotCount' such that v2 snapshot is dumped. - writeCustomV2Data(t, epc, 10) - - require.NoError(t, epc.Stop()) - - t.Log("Verify its infeasible to start etcd with --v2-deprecation=write-only mode") - proc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "--v2-deprecation=write-only", "--data-dir=" + memberDataDir}, nil) - require.NoError(t, err) - defer proc.Close() - - _, err = proc.Expect("detected disallowed custom content in v2store for stage --v2-deprecation=write-only") - assert.NoError(t, err) -} - func TestV2DeprecationSnapshotMatches(t *testing.T) { e2e.BeforeTest(t) lastReleaseData := t.TempDir() currentReleaseData := t.TempDir() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { @@ -156,7 +87,7 @@ func TestV2DeprecationSnapshotMatches(t *testing.T) { func TestV2DeprecationSnapshotRecover(t *testing.T) { e2e.BeforeTest(t) dataDir := t.TempDir() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() if !fileutil.Exist(e2e.BinPath.EtcdLastRelease) { @@ -176,7 +107,7 @@ func TestV2DeprecationSnapshotRecover(t *testing.T) { e2e.WithVersion(e2e.CurrentVersion), e2e.WithDataDirPath(dataDir), )) - epc, err = e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) + epc, err = e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) require.NoError(t, err) cc = epc.Etcdctl() @@ -191,47 +122,47 @@ func TestV2DeprecationSnapshotRecover(t *testing.T) { assert.NoError(t, epc.Close()) } -func runEtcdAndCreateSnapshot(t testing.TB, serverVersion e2e.ClusterVersion, dataDir string, snapshotCount uint64) *e2e.EtcdProcessCluster { +func runEtcdAndCreateSnapshot(tb testing.TB, serverVersion e2e.ClusterVersion, dataDir string, snapshotCount uint64) *e2e.EtcdProcessCluster { cfg := e2e.ConfigStandalone(*e2e.NewConfig( e2e.WithVersion(serverVersion), e2e.WithDataDirPath(dataDir), e2e.WithSnapshotCount(snapshotCount), e2e.WithKeepDataDir(true), )) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(cfg)) - assert.NoError(t, err) + epc, err := e2e.NewEtcdProcessCluster(tb.Context(), tb, e2e.WithConfig(cfg)) + assert.NoError(tb, err) return epc } -func addAndRemoveKeysAndMembers(ctx context.Context, t testing.TB, cc *e2e.EtcdctlV3, snapshotCount uint64) (members []uint64) { +func addAndRemoveKeysAndMembers(ctx context.Context, tb testing.TB, cc *e2e.EtcdctlV3, snapshotCount uint64) (members []uint64) { // Execute some non-trivial key&member operation var i uint64 for i = 0; i < snapshotCount*3; i++ { err := cc.Put(ctx, fmt.Sprintf("%d", i), "1", config.PutOptions{}) - require.NoError(t, err) + require.NoError(tb, err) } member1, err := cc.MemberAddAsLearner(ctx, "member1", []string{"http://127.0.0.1:2000"}) - require.NoError(t, err) + require.NoError(tb, err) members = append(members, member1.Member.ID) for i = 0; i < snapshotCount*2; i++ { _, err = cc.Delete(ctx, fmt.Sprintf("%d", i), config.DeleteOptions{}) - require.NoError(t, err) + require.NoError(tb, err) } _, err = cc.MemberRemove(ctx, member1.Member.ID) - require.NoError(t, err) + require.NoError(tb, err) for i = 0; i < snapshotCount; i++ { err = cc.Put(ctx, fmt.Sprintf("%d", i), "2", config.PutOptions{}) - require.NoError(t, err) + require.NoError(tb, err) } member2, err := cc.MemberAddAsLearner(ctx, "member2", []string{"http://127.0.0.1:2001"}) - require.NoError(t, err) + require.NoError(tb, err) members = append(members, member2.Member.ID) for i = 0; i < snapshotCount/2; i++ { err = cc.Put(ctx, fmt.Sprintf("%d", i), "3", config.PutOptions{}) - assert.NoError(t, err) + assert.NoError(tb, err) } return members } @@ -240,39 +171,45 @@ func filterSnapshotFiles(path string) bool { return strings.HasSuffix(path, ".snap") } -func assertSnapshotsMatch(t testing.TB, firstDataDir, secondDataDir string, patch func([]byte) []byte) { - lg := zaptest.NewLogger(t) +func assertSnapshotsMatch(tb testing.TB, firstDataDir, secondDataDir string, patch func([]byte) []byte) { + lg := zaptest.NewLogger(tb) firstFiles, err := fileutil.ListFiles(firstDataDir, filterSnapshotFiles) - require.NoError(t, err) + require.NoError(tb, err) secondFiles, err := fileutil.ListFiles(secondDataDir, filterSnapshotFiles) - require.NoError(t, err) - assert.NotEmpty(t, firstFiles) - assert.NotEmpty(t, secondFiles) - assert.Equal(t, len(firstFiles), len(secondFiles)) + require.NoError(tb, err) + assert.NotEmpty(tb, firstFiles) + assert.NotEmpty(tb, secondFiles) + assert.Len(tb, secondFiles, len(firstFiles)) sort.Strings(firstFiles) sort.Strings(secondFiles) for i := 0; i < len(firstFiles); i++ { firstSnapshot, err := snap.Read(lg, firstFiles[i]) - require.NoError(t, err) + require.NoError(tb, err) secondSnapshot, err := snap.Read(lg, secondFiles[i]) - require.NoError(t, err) - assertMembershipEqual(t, openSnap(patch(firstSnapshot.Data)), openSnap(patch(secondSnapshot.Data))) + require.NoError(tb, err) + assertMembershipEqual(tb, lg, openSnap(patch(firstSnapshot.Data)), openSnap(patch(secondSnapshot.Data))) } } -func assertMembershipEqual(t testing.TB, firstStore v2store.Store, secondStore v2store.Store) { - rc1 := membership.NewCluster(zaptest.NewLogger(t)) +func assertMembershipEqual(tb testing.TB, lg *zap.Logger, firstStore v2store.Store, secondStore v2store.Store) { + rc1 := membership.NewCluster(zaptest.NewLogger(tb)) rc1.SetStore(firstStore) + be1, _ := betesting.NewDefaultTmpBackend(tb) + defer betesting.Close(tb, be1) + rc1.SetBackend(schema.NewMembershipBackend(lg, be1)) rc1.Recover(func(lg *zap.Logger, v *semver.Version) {}) - rc2 := membership.NewCluster(zaptest.NewLogger(t)) + rc2 := membership.NewCluster(zaptest.NewLogger(tb)) + be2, _ := betesting.NewDefaultTmpBackend(tb) + defer betesting.Close(tb, be2) + rc2.SetBackend(schema.NewMembershipBackend(lg, be2)) rc2.SetStore(secondStore) rc2.Recover(func(lg *zap.Logger, v *semver.Version) {}) // membership should match if !reflect.DeepEqual(rc1.Members(), rc2.Members()) { - t.Logf("memberids_from_last_version = %+v, member_ids_from_current_version = %+v", rc1.MemberIDs(), rc2.MemberIDs()) - t.Errorf("members_from_last_version_snapshot = %+v, members_from_current_version_snapshot %+v", rc1.Members(), rc2.Members()) + tb.Logf("memberids_from_last_version = %+v, member_ids_from_current_version = %+v", rc1.MemberIDs(), rc2.MemberIDs()) + tb.Errorf("members_from_last_version_snapshot = %+v, members_from_current_version_snapshot %+v", rc1.Members(), rc2.Members()) } } diff --git a/tests/e2e/v3_curl_maxstream_test.go b/tests/e2e/v3_curl_maxstream_test.go index 027a3b538e8c..0833fdbd36ed 100644 --- a/tests/e2e/v3_curl_maxstream_test.go +++ b/tests/e2e/v3_curl_maxstream_test.go @@ -88,7 +88,7 @@ func testCurlV3MaxStream(t *testing.T, reachLimit bool, opts ...ctlOption) { // Step 2: create the cluster t.Log("Creating an etcd cluster") - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, e2e.WithConfig(&cx.cfg)) + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(&cx.cfg)) require.NoErrorf(t, err, "Failed to start etcd cluster") cx.epc = epc cx.dataDir = epc.Procs[0].Config().DataDirPath diff --git a/tests/e2e/v3_curl_watch_test.go b/tests/e2e/v3_curl_watch_test.go index 5e7bc8de9bce..f0c20b8575e4 100644 --- a/tests/e2e/v3_curl_watch_test.go +++ b/tests/e2e/v3_curl_watch_test.go @@ -15,8 +15,11 @@ package e2e import ( + "context" "encoding/json" + "syscall" "testing" + "time" "github.com/stretchr/testify/require" @@ -46,3 +49,26 @@ func testCurlV3Watch(cx ctlCtx) { // expects "bar", timeout after 2 seconds since stream waits forever require.ErrorContains(cx.t, e2e.CURLPost(cx.epc, e2e.CURLReq{Endpoint: "/v3/watch", Value: wstr, Expected: expect.ExpectedResponse{Value: `"YmFy"`}, Timeout: 2}), "unexpected exit code") } + +// TestCurlWatchIssue19509 tries to reproduce https://github.com/etcd-io/etcd/issues/19509 +func TestCurlWatchIssue19509(t *testing.T) { + e2e.BeforeTest(t) + + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithConfig(e2e.NewConfigClientTLS()), e2e.WithClusterSize(1)) + require.NoError(t, err) + defer epc.Close() + + curlCmdAndArgs := e2e.CURLPrefixArgsCluster(epc.Cfg, epc.Procs[0], "POST", e2e.CURLReq{Endpoint: "/v3/watch", Timeout: 3}) + for i := 0; i < 10; i++ { + curlProc, err := e2e.SpawnCmd(curlCmdAndArgs, nil) + require.NoError(t, err) + time.Sleep(100 * time.Millisecond) + + _ = curlProc.Signal(syscall.SIGKILL) + _ = curlProc.Close() + + require.Truef(t, epc.Procs[0].IsRunning(), "etcdserver already exited after %d curl watch requests", i+1) + } +} diff --git a/tests/e2e/v3_lease_no_proxy_test.go b/tests/e2e/v3_lease_no_proxy_test.go index c6418c8691a6..9897005878ce 100644 --- a/tests/e2e/v3_lease_no_proxy_test.go +++ b/tests/e2e/v3_lease_no_proxy_test.go @@ -56,7 +56,7 @@ func TestLeaseRevoke_ClientSwitchToOtherMember(t *testing.T) { func testLeaseRevokeIssue(t *testing.T, clusterSize int, connectToOneFollower bool) { e2e.BeforeTest(t) - ctx := context.Background() + ctx := t.Context() t.Log("Starting a new etcd cluster") epc, err := e2e.NewEtcdProcessCluster(ctx, t, @@ -126,7 +126,7 @@ func testLeaseRevokeIssue(t *testing.T, clusterSize int, connectToOneFollower bo err = epc.Procs[leaderIdx].Failpoints().SetupHTTP(ctx, "raftBeforeSave", `sleep("30s")`) require.NoError(t, err) - cctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + cctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) t.Logf("Waiting for a new leader to be elected, old leader index: %d, old leader ID: %d", leaderIdx, oldLeaderID) testutils.ExecuteUntil(cctx, t, func() { for { diff --git a/tests/e2e/watch_test.go b/tests/e2e/watch_test.go index 758177c64898..c628a0cc888c 100644 --- a/tests/e2e/watch_test.go +++ b/tests/e2e/watch_test.go @@ -95,13 +95,13 @@ func TestWatchDelayForPeriodicProgressNotification(t *testing.T) { cfg.Client = tc.client cfg.ClientHTTPSeparate = tc.clientHTTPSeparate t.Run(tc.name, func(t *testing.T) { - clus, err := e2e.NewEtcdProcessCluster(context.Background(), t, e2e.WithConfig(cfg)) + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) require.NoError(t, err) defer clus.Close() c := newClient(t, clus.EndpointsGRPC(), tc.client) - require.NoError(t, fillEtcdWithData(context.Background(), c, tc.dbSizeBytes)) + require.NoError(t, fillEtcdWithData(t.Context(), c, tc.dbSizeBytes)) - ctx, cancel := context.WithTimeout(context.Background(), watchTestDuration) + ctx, cancel := context.WithTimeout(t.Context(), watchTestDuration) defer cancel() g := errgroup.Group{} continuouslyExecuteGetAll(ctx, t, &g, c) @@ -120,13 +120,13 @@ func TestWatchDelayForManualProgressNotification(t *testing.T) { cfg.Client = tc.client cfg.ClientHTTPSeparate = tc.clientHTTPSeparate t.Run(tc.name, func(t *testing.T) { - clus, err := e2e.NewEtcdProcessCluster(context.Background(), t, e2e.WithConfig(cfg)) + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) require.NoError(t, err) defer clus.Close() c := newClient(t, clus.EndpointsGRPC(), tc.client) - require.NoError(t, fillEtcdWithData(context.Background(), c, tc.dbSizeBytes)) + require.NoError(t, fillEtcdWithData(t.Context(), c, tc.dbSizeBytes)) - ctx, cancel := context.WithTimeout(context.Background(), watchTestDuration) + ctx, cancel := context.WithTimeout(t.Context(), watchTestDuration) defer cancel() g := errgroup.Group{} continuouslyExecuteGetAll(ctx, t, &g, c) @@ -157,13 +157,13 @@ func TestWatchDelayForEvent(t *testing.T) { cfg.Client = tc.client cfg.ClientHTTPSeparate = tc.clientHTTPSeparate t.Run(tc.name, func(t *testing.T) { - clus, err := e2e.NewEtcdProcessCluster(context.Background(), t, e2e.WithConfig(cfg)) + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) require.NoError(t, err) defer clus.Close() c := newClient(t, clus.EndpointsGRPC(), tc.client) - require.NoError(t, fillEtcdWithData(context.Background(), c, tc.dbSizeBytes)) + require.NoError(t, fillEtcdWithData(t.Context(), c, tc.dbSizeBytes)) - ctx, cancel := context.WithTimeout(context.Background(), watchTestDuration) + ctx, cancel := context.WithTimeout(t.Context(), watchTestDuration) defer cancel() g := errgroup.Group{} g.Go(func() error { @@ -270,14 +270,14 @@ func TestDeleteEventDrop_Issue18089(t *testing.T) { cfg := e2e.DefaultConfig() cfg.ClusterSize = 1 cfg.Client = e2e.ClientConfig{ConnectionType: e2e.ClientTLS} - clus, err := e2e.NewEtcdProcessCluster(context.Background(), t, e2e.WithConfig(cfg)) + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg)) require.NoError(t, err) defer clus.Close() c := newClient(t, clus.EndpointsGRPC(), cfg.Client) defer c.Close() - ctx := context.Background() + ctx := t.Context() const ( key = "k" v2 = "v2" @@ -345,14 +345,14 @@ func testStartWatcherFromCompactedRevision(t *testing.T, performCompactOnTombsto e2e.BeforeTest(t) cfg := e2e.DefaultConfig() cfg.Client = e2e.ClientConfig{ConnectionType: e2e.ClientTLS} - clus, err := e2e.NewEtcdProcessCluster(context.Background(), t, e2e.WithConfig(cfg), e2e.WithClusterSize(1)) + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg), e2e.WithClusterSize(1)) require.NoError(t, err) defer clus.Close() c := newClient(t, clus.EndpointsGRPC(), cfg.Client) defer c.Close() - ctx := context.Background() + ctx := t.Context() key := "foo" totalRev := 100 @@ -494,11 +494,11 @@ func testStartWatcherFromCompactedRevision(t *testing.T, performCompactOnTombsto func TestResumeCompactionOnTombstone(t *testing.T) { e2e.BeforeTest(t) - ctx := context.Background() + ctx := t.Context() compactBatchLimit := 5 cfg := e2e.DefaultConfig() - clus, err := e2e.NewEtcdProcessCluster(context.Background(), + clus, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg), e2e.WithClusterSize(1), diff --git a/tests/e2e/zap_logging_test.go b/tests/e2e/zap_logging_test.go index 6752a23cc2f5..0d7b7ffed501 100644 --- a/tests/e2e/zap_logging_test.go +++ b/tests/e2e/zap_logging_test.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "encoding/json" "testing" "time" @@ -29,7 +28,7 @@ import ( func TestServerJsonLogging(t *testing.T) { e2e.BeforeTest(t) - epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, + epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithClusterSize(1), e2e.WithLogLevel("debug"), ) @@ -115,7 +114,7 @@ func TestConnectionRejectMessage(t *testing.T) { t.Log("Starting an etcd process and wait for it to get ready.") p, err := e2e.SpawnCmd(commonArgs, nil) require.NoError(t, err) - err = e2e.WaitReadyExpectProc(context.TODO(), p, e2e.EtcdServerReadyLines) + err = e2e.WaitReadyExpectProc(t.Context(), p, e2e.EtcdServerReadyLines) require.NoError(t, err) defer func() { p.Stop() @@ -127,7 +126,7 @@ func TestConnectionRejectMessage(t *testing.T) { doneCh := make(chan struct{}, 1) go func() { startedCh <- struct{}{} - verr := e2e.WaitReadyExpectProc(context.TODO(), p, []string{tc.expectedErrMsg}) + verr := e2e.WaitReadyExpectProc(t.Context(), p, []string{tc.expectedErrMsg}) assert.NoError(t, verr) doneCh <- struct{}{} }() diff --git a/tests/framework/e2e/cluster.go b/tests/framework/e2e/cluster.go index 282e96149018..cddb23758b67 100644 --- a/tests/framework/e2e/cluster.go +++ b/tests/framework/e2e/cluster.go @@ -28,6 +28,7 @@ import ( "testing" "time" + "github.com/coreos/go-semver/semver" "go.uber.org/zap" "go.uber.org/zap/zaptest" @@ -177,10 +178,9 @@ type EtcdProcessClusterConfig struct { IsPeerAutoTLS bool CN bool - // Removed in v3.6 - + // Discovery is for v2discovery + // Note: remove this field when we remove the v2discovery Discovery string // v2 discovery - EnableV2 bool } func DefaultConfig() *EtcdProcessClusterConfig { @@ -229,7 +229,9 @@ func WithSnapshotCount(count uint64) EPClusterOption { } func WithSnapshotCatchUpEntries(count uint64) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.SnapshotCatchUpEntries = count } + return func(c *EtcdProcessClusterConfig) { + c.ServerConfig.SnapshotCatchUpEntries = count + } } func WithClusterSize(size int) EPClusterOption { @@ -284,10 +286,6 @@ func WithStrictReconfigCheck(strict bool) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.StrictReconfigCheck = strict } } -func WithEnableV2(enable bool) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.EnableV2 = enable } -} - func WithAuthTokenOpts(token string) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.AuthToken = token } } @@ -316,20 +314,20 @@ func WithCorruptCheckTime(time time.Duration) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.CorruptCheckTime = time } } -func WithExperimentalCorruptCheckTime(time time.Duration) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalCorruptCheckTime = time } -} - func WithInitialClusterToken(token string) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.InitialClusterToken = token } } func WithInitialCorruptCheck(enabled bool) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalInitialCorruptCheck = enabled } + return func(c *EtcdProcessClusterConfig) { + c.ServerConfig.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("InitialCorruptCheck=%t", enabled)) + } } func WithCompactHashCheckEnabled(enabled bool) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalCompactHashCheckEnabled = enabled } + return func(c *EtcdProcessClusterConfig) { + c.ServerConfig.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("CompactHashCheck=%t", enabled)) + } } func WithCompactHashCheckTime(time time.Duration) EPClusterOption { @@ -352,18 +350,6 @@ func WithWarningUnaryRequestDuration(time time.Duration) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.WarningUnaryRequestDuration = time } } -// WithExperimentalWarningUnaryRequestDuration sets a value for `-experimental-warning-unary-request-duration`. -// TODO(ahrtr): remove this function when the corresponding experimental flag is decommissioned. -func WithExperimentalWarningUnaryRequestDuration(time time.Duration) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalWarningUnaryRequestDuration = time } -} - -func WithExperimentalStopGRPCServiceOnDefrag(stopGRPCServiceOnDefrag bool) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { - c.ServerConfig.ExperimentalStopGRPCServiceOnDefrag = stopGRPCServiceOnDefrag - } -} - func WithServerFeatureGate(featureName string, val bool) EPClusterOption { return func(c *EtcdProcessClusterConfig) { if err := c.ServerConfig.ServerFeatureGate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", featureName, val)); err != nil { @@ -376,22 +362,14 @@ func WithCompactionBatchLimit(limit int) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.CompactionBatchLimit = limit } } -func WithExperimentalCompactionBatchLimit(limit int) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalCompactionBatchLimit = limit } -} - func WithCompactionSleepInterval(time time.Duration) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalCompactionSleepInterval = time } + return func(c *EtcdProcessClusterConfig) { c.ServerConfig.CompactionSleepInterval = time } } func WithWatchProcessNotifyInterval(interval time.Duration) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.ServerConfig.WatchProgressNotifyInterval = interval } } -func WithExperimentalWatchProcessNotifyInterval(interval time.Duration) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalWatchProgressNotifyInterval = interval } -} - func WithEnvVars(ev map[string]string) EPClusterOption { return func(c *EtcdProcessClusterConfig) { c.EnvVars = ev } } @@ -422,23 +400,23 @@ func WithExtensiveMetrics() EPClusterOption { // NewEtcdProcessCluster launches a new cluster from etcd processes, returning // a new EtcdProcessCluster once all nodes are ready to accept client requests. -func NewEtcdProcessCluster(ctx context.Context, t testing.TB, opts ...EPClusterOption) (*EtcdProcessCluster, error) { +func NewEtcdProcessCluster(ctx context.Context, tb testing.TB, opts ...EPClusterOption) (*EtcdProcessCluster, error) { cfg := NewConfig(opts...) - epc, err := InitEtcdProcessCluster(t, cfg) + epc, err := InitEtcdProcessCluster(tb, cfg) if err != nil { return nil, err } - return StartEtcdProcessCluster(ctx, t, epc, cfg) + return StartEtcdProcessCluster(ctx, tb, epc, cfg) } // InitEtcdProcessCluster initializes a new cluster based on the given config. // It doesn't start the cluster. -func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) { - SkipInShortMode(t) +func InitEtcdProcessCluster(tb testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) { + SkipInShortMode(tb) if cfg.Logger == nil { - cfg.Logger = zaptest.NewLogger(t) + cfg.Logger = zaptest.NewLogger(tb) } if cfg.BasePort == 0 { cfg.BasePort = EtcdProcessBasePort @@ -457,17 +435,17 @@ func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdP } } - etcdCfgs := cfg.EtcdAllServerProcessConfigs(t) + etcdCfgs := cfg.EtcdAllServerProcessConfigs(tb) epc := &EtcdProcessCluster{ Cfg: cfg, - lg: zaptest.NewLogger(t), + lg: zaptest.NewLogger(tb), Procs: make([]EtcdProcess, cfg.ClusterSize), nextSeq: cfg.ClusterSize, } // launch etcd processes for i := range etcdCfgs { - proc, err := NewEtcdProcess(t, etcdCfgs[i]) + proc, err := NewEtcdProcess(tb, etcdCfgs[i]) if err != nil { epc.Close() return nil, fmt.Errorf("cannot configure: %w", err) @@ -479,7 +457,7 @@ func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdP } // StartEtcdProcessCluster launches a new cluster from etcd processes. -func StartEtcdProcessCluster(ctx context.Context, t testing.TB, epc *EtcdProcessCluster, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) { +func StartEtcdProcessCluster(ctx context.Context, tb testing.TB, epc *EtcdProcessCluster, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) { if cfg.RollingStart { if err := epc.RollingStart(ctx); err != nil { return nil, fmt.Errorf("cannot rolling-start: %w", err) @@ -493,11 +471,11 @@ func StartEtcdProcessCluster(ctx context.Context, t testing.TB, epc *EtcdProcess for _, proc := range epc.Procs { if cfg.GoFailEnabled && !proc.Failpoints().Enabled() { epc.Close() - t.Skip("please run 'make gofail-enable && make build' before running the test") + tb.Skip("please run 'make gofail-enable && make build' before running the test") } } if cfg.InitialLeaderIndex >= 0 { - if err := epc.MoveLeader(ctx, t, cfg.InitialLeaderIndex); err != nil { + if err := epc.MoveLeader(ctx, tb, cfg.InitialLeaderIndex); err != nil { return nil, fmt.Errorf("failed to move leader: %w", err) } } @@ -595,7 +573,7 @@ func (cfg *EtcdProcessClusterConfig) EtcdServerProcessConfig(tb testing.TB, i in "--listen-peer-urls=" + peerListenURL.String(), "--initial-advertise-peer-urls=" + peerAdvertiseURL.String(), "--initial-cluster-token=" + cfg.ServerConfig.InitialClusterToken, - "--data-dir", dataDirPath, + "--data-dir=" + dataDirPath, "--snapshot-count=" + fmt.Sprintf("%d", cfg.ServerConfig.SnapshotCount), } var clientHTTPURL string @@ -615,9 +593,7 @@ func (cfg *EtcdProcessClusterConfig) EtcdServerProcessConfig(tb testing.TB, i in if !cfg.ServerConfig.StrictReconfigCheck { args = append(args, "--strict-reconfig-check=false") } - if cfg.EnableV2 { - args = append(args, "--enable-v2=true") - } + var murl string if cfg.MetricsURLScheme != "" { murl = (&url.URL{ @@ -633,38 +609,34 @@ func (cfg *EtcdProcessClusterConfig) EtcdServerProcessConfig(tb testing.TB, i in args = append(args, "--discovery="+cfg.Discovery) } - var execPath string - switch cfg.Version { - case CurrentVersion: - execPath = BinPath.Etcd - case MinorityLastVersion: - if i <= cfg.ClusterSize/2 { - execPath = BinPath.Etcd - } else { - execPath = BinPath.EtcdLastRelease - } - case QuorumLastVersion: - if i <= cfg.ClusterSize/2 { - execPath = BinPath.EtcdLastRelease - } else { - execPath = BinPath.Etcd + execPath := cfg.binaryPath(i) + + if cfg.ServerConfig.SnapshotCatchUpEntries != etcdserver.DefaultSnapshotCatchUpEntries { + if !IsSnapshotCatchupEntriesFlagAvailable(execPath) { + cfg.ServerConfig.SnapshotCatchUpEntries = etcdserver.DefaultSnapshotCatchUpEntries } - case LastVersion: - execPath = BinPath.EtcdLastRelease - default: - panic(fmt.Sprintf("Unknown cluster version %v", cfg.Version)) } + var ( + binaryVersion *semver.Version + err error + ) + if execPath != "" { + binaryVersion, err = GetVersionFromBinary(execPath) + if err != nil { + tb.Logf("Failed to get binary version from %s: %v", execPath, err) + } + } defaultValues := values(*embed.NewConfig()) overrideValues := values(cfg.ServerConfig) for flag, value := range overrideValues { if defaultValue := defaultValues[flag]; value == "" || value == defaultValue { continue } - if flag == "experimental-snapshot-catchup-entries" && !CouldSetSnapshotCatchupEntries(execPath) { + if strings.HasSuffix(flag, "snapshot-catchup-entries") && !CouldSetSnapshotCatchupEntries(execPath) { continue } - args = append(args, fmt.Sprintf("--%s=%s", flag, value)) + args = append(args, convertFlag(flag, value, binaryVersion)) } envVars := map[string]string{} maps.Copy(envVars, cfg.EnvVars) @@ -696,6 +668,47 @@ func (cfg *EtcdProcessClusterConfig) EtcdServerProcessConfig(tb testing.TB, i in } } +func (cfg *EtcdProcessClusterConfig) binaryPath(i int) string { + var execPath string + switch cfg.Version { + case CurrentVersion: + execPath = BinPath.Etcd + case MinorityLastVersion: + if i <= cfg.ClusterSize/2 { + execPath = BinPath.Etcd + } else { + execPath = BinPath.EtcdLastRelease + } + case QuorumLastVersion: + if i <= cfg.ClusterSize/2 { + execPath = BinPath.EtcdLastRelease + } else { + execPath = BinPath.Etcd + } + case LastVersion: + execPath = BinPath.EtcdLastRelease + default: + panic(fmt.Sprintf("Unknown cluster version %v", cfg.Version)) + } + + return execPath +} + +func (epc *EtcdProcessCluster) MinServerVersion() (*semver.Version, error) { + var minVersion *semver.Version + for _, member := range epc.Procs { + ver, err := GetVersionFromBinary(member.Config().ExecPath) + if err != nil { + return nil, fmt.Errorf("failed to get version from member %s binary: %w", member.Config().Name, err) + } + + if minVersion == nil || ver.LessThan(*minVersion) { + minVersion = ver + } + } + return minVersion, nil +} + func values(cfg embed.Config) map[string]string { fs := flag.NewFlagSet("etcd", flag.ContinueOnError) cfg.AddFlags(fs) @@ -929,6 +942,16 @@ func (epc *EtcdProcessCluster) UpdateProcOptions(i int, tb testing.TB, opts ...E return nil } +func PatchArgs(args []string, flag, newValue string) error { + for i, arg := range args { + if strings.Contains(arg, flag) { + args[i] = fmt.Sprintf("--%s=%s", flag, newValue) + return nil + } + } + return fmt.Errorf("--%s flag not found", flag) +} + func (epc *EtcdProcessCluster) Start(ctx context.Context) error { return epc.start(func(ep EtcdProcess) error { return ep.Start(ctx) }) } @@ -1047,29 +1070,29 @@ func findMemberIDByEndpoint(members []*etcdserverpb.Member, endpoint string) (ui // WaitLeader returns index of the member in c.Members() that is leader // or fails the test (if not established in 30s). -func (epc *EtcdProcessCluster) WaitLeader(t testing.TB) int { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +func (epc *EtcdProcessCluster) WaitLeader(tb testing.TB) int { + ctx, cancel := context.WithTimeout(tb.Context(), 30*time.Second) defer cancel() - return epc.WaitMembersForLeader(ctx, t, epc.Procs) + return epc.WaitMembersForLeader(ctx, tb, epc.Procs) } // WaitMembersForLeader waits until given members agree on the same leader, // and returns its 'index' in the 'membs' list -func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testing.TB, membs []EtcdProcess) int { +func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, tb testing.TB, membs []EtcdProcess) int { cc := epc.Etcdctl() // ensure leader is up via linearizable get for { select { case <-ctx.Done(): - t.Fatal("WaitMembersForLeader timeout") + tb.Fatal("WaitMembersForLeader timeout") default: } _, err := cc.Get(ctx, "0", config.GetOptions{Timeout: 10*config.TickDuration + time.Second}) if err == nil || strings.Contains(err.Error(), "Key not found") { break } - t.Logf("WaitMembersForLeader Get err: %v", err) + tb.Logf("WaitMembersForLeader Get err: %v", err) } leaders := make(map[uint64]struct{}) @@ -1077,7 +1100,7 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi for { select { case <-ctx.Done(): - t.Fatal("WaitMembersForLeader timeout") + tb.Fatal("WaitMembersForLeader timeout") default: } for i := range membs { @@ -1087,7 +1110,7 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi // if member[i] has stopped continue } - t.Fatal(err) + tb.Fatal(err) } members[resp[0].Header.MemberId] = i leaders[resp[0].Leader] = struct{}{} @@ -1102,24 +1125,24 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi } for l := range leaders { if index, ok := members[l]; ok { - t.Logf("members agree on a leader, members:%v , leader:%v", members, l) + tb.Logf("members agree on a leader, members:%v , leader:%v", members, l) return index } - t.Fatalf("members agree on a leader which is not one of members, members:%v , leader:%v", members, l) + tb.Fatalf("members agree on a leader which is not one of members, members:%v , leader:%v", members, l) } - t.Fatal("impossible path of execution") + tb.Fatal("impossible path of execution") return -1 } // MoveLeader moves the leader to the ith process. -func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, t testing.TB, i int) error { +func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, tb testing.TB, i int) error { if i < 0 || i >= len(epc.Procs) { return fmt.Errorf("invalid index: %d, must between 0 and %d", i, len(epc.Procs)-1) } - t.Logf("moving leader to Procs[%d]", i) - oldLeader := epc.WaitMembersForLeader(ctx, t, epc.Procs) + tb.Logf("moving leader to Procs[%d]", i) + oldLeader := epc.WaitMembersForLeader(ctx, tb, epc.Procs) if oldLeader == i { - t.Logf("Procs[%d] is already the leader", i) + tb.Logf("Procs[%d] is already the leader", i) return nil } resp, err := epc.Procs[i].Etcdctl().Status(ctx) @@ -1131,10 +1154,10 @@ func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, t testing.TB, i i if err != nil { return err } - newLeader := epc.WaitMembersForLeader(ctx, t, epc.Procs) + newLeader := epc.WaitMembersForLeader(ctx, tb, epc.Procs) if newLeader != i { - t.Fatalf("expect new leader to be Procs[%d] but got Procs[%d]", i, newLeader) + tb.Fatalf("expect new leader to be Procs[%d] but got Procs[%d]", i, newLeader) } - t.Logf("moved leader from Procs[%d] to Procs[%d]", oldLeader, i) + tb.Logf("moved leader from Procs[%d] to Procs[%d]", oldLeader, i) return nil } diff --git a/tests/framework/e2e/cluster_direct.go b/tests/framework/e2e/cluster_direct.go index 70c60dbf4c0a..ea46ea31d010 100644 --- a/tests/framework/e2e/cluster_direct.go +++ b/tests/framework/e2e/cluster_direct.go @@ -18,6 +18,6 @@ package e2e import "testing" -func NewEtcdProcess(t testing.TB, cfg *EtcdServerProcessConfig) (EtcdProcess, error) { - return NewEtcdServerProcess(t, cfg) +func NewEtcdProcess(tb testing.TB, cfg *EtcdServerProcessConfig) (EtcdProcess, error) { + return NewEtcdServerProcess(tb, cfg) } diff --git a/tests/framework/e2e/cluster_test.go b/tests/framework/e2e/cluster_test.go index fc9787addaa7..1b45c0b06c16 100644 --- a/tests/framework/e2e/cluster_test.go +++ b/tests/framework/e2e/cluster_test.go @@ -23,8 +23,8 @@ import ( ) func TestEtcdServerProcessConfig(t *testing.T) { - v3_5_12 := semver.Version{Major: 3, Minor: 5, Patch: 12} - v3_5_14 := semver.Version{Major: 3, Minor: 5, Patch: 14} + v3_6_0 := semver.Version{Major: 3, Minor: 6, Patch: 0} + v3_7_0 := semver.Version{Major: 3, Minor: 7, Patch: 0} tcs := []struct { name string config *EtcdProcessClusterConfig @@ -43,8 +43,7 @@ func TestEtcdServerProcessConfig(t *testing.T) { "--listen-peer-urls=http://localhost:1", "--initial-advertise-peer-urls=http://localhost:1", "--initial-cluster-token=new", - "--data-dir", - "/tmp/a/member-0", + "--data-dir=/tmp/a/member-0", "--snapshot-count=10000", "--initial-cluster-token=new", }, @@ -67,7 +66,7 @@ func TestEtcdServerProcessConfig(t *testing.T) { name: "CorruptCheck", config: NewConfig(WithInitialCorruptCheck(true)), expectArgsContain: []string{ - "--experimental-initial-corrupt-check=true", + "--feature-gates=InitialCorruptCheck=true", }, }, { @@ -81,24 +80,24 @@ func TestEtcdServerProcessConfig(t *testing.T) { name: "CatchUpEntries", config: NewConfig(WithSnapshotCatchUpEntries(100)), expectArgsContain: []string{ - "--experimental-snapshot-catchup-entries=100", + "--snapshot-catchup-entries=100", }, - mockBinaryVersion: &v3_5_14, + mockBinaryVersion: &v3_7_0, }, { name: "CatchUpEntriesNoVersion", config: NewConfig(WithSnapshotCatchUpEntries(100), WithVersion(LastVersion)), expectArgsNotContain: []string{ - "--experimental-snapshot-catchup-entries=100", + "--snapshot-catchup-entries=100", }, }, { name: "CatchUpEntriesOldVersion", config: NewConfig(WithSnapshotCatchUpEntries(100), WithVersion(LastVersion)), - expectArgsNotContain: []string{ - "--experimental-snapshot-catchup-entries=100", + expectArgsContain: []string{ + "--snapshot-catchup-entries=100", }, - mockBinaryVersion: &v3_5_12, + mockBinaryVersion: &v3_6_0, }, { name: "ClientHTTPSeparate", @@ -114,13 +113,6 @@ func TestEtcdServerProcessConfig(t *testing.T) { "--force-new-cluster=true", }, }, - { - name: "EnableV2", - config: NewConfig(WithEnableV2(true)), - expectArgsContain: []string{ - "--enable-v2=true", - }, - }, { name: "MetricsURL", config: NewConfig(WithMetricsURLScheme("http")), diff --git a/tests/framework/e2e/config.go b/tests/framework/e2e/config.go index acc1d82e0484..3cdc3f692b73 100644 --- a/tests/framework/e2e/config.go +++ b/tests/framework/e2e/config.go @@ -14,6 +14,15 @@ package e2e +import ( + "fmt" + "strings" + + "github.com/coreos/go-semver/semver" + + "go.etcd.io/etcd/api/v3/version" +) + type ClusterVersion string const ( @@ -23,6 +32,70 @@ const ( LastVersion ClusterVersion = "last-version" ) +func (cv ClusterVersion) String() string { + if cv == CurrentVersion { + return "current-version" + } + return string(cv) +} + type ClusterContext struct { Version ClusterVersion } + +var experimentalFlags = map[string]struct{}{ + "compact-hash-check-time": {}, + "corrupt-check-time": {}, + "compaction-batch-limit": {}, + "watch-progress-notify-interval": {}, + "warning-apply-duration": {}, + "bootstrap-defrag-threshold-megabytes": {}, + "memory-mlock": {}, + "snapshot-catchup-entries": {}, + "compaction-sleep-interval": {}, + "downgrade-check-time": {}, + "peer-skip-client-san-verification": {}, + "enable-distributed-tracing": {}, + "distributed-tracing-address": {}, + "distributed-tracing-service-name": {}, + "distributed-tracing-instance-id": {}, + "distributed-tracing-sampling-rate": {}, +} + +// convertFlag converts between experimental and non-experimental flags. +// All experimental flags have been removed in version 3.7, but versions prior +// to 3.6 only support experimental flags. The robustness tests use the same +// code from the main branch to test all previously supported releases, so we +// need to convert flags accordingly based on the binary version. +func convertFlag(name, value string, ver *semver.Version) string { + // For versions >= 3.6, use the normal (non-experimental) flag. + if ver == nil || !ver.LessThan(version.V3_6) { + return fmt.Sprintf("--%s=%s", name, value) + } + + // For versions < 3.6, use the experimental flag if it exists in `experimentalFlags` + if _, ok := experimentalFlags[name]; ok { + return fmt.Sprintf("--experimental-%s=%s", name, value) + } + + return fmt.Sprintf("--%s=%s", name, value) +} + +func convertFlags(args []string, ver *semver.Version) []string { + var retArgs []string + + for _, arg := range args { + kv := strings.Split(arg, "=") + if len(kv) != 2 { + retArgs = append(retArgs, arg) + continue + } + + name := strings.TrimPrefix(kv[0], "--") + name = strings.TrimPrefix(name, "experimental-") + + retArgs = append(retArgs, convertFlag(name, kv[1], ver)) + } + + return retArgs +} diff --git a/tests/framework/e2e/downgrade.go b/tests/framework/e2e/downgrade.go index 673b705e9285..ecc22d591847 100644 --- a/tests/framework/e2e/downgrade.go +++ b/tests/framework/e2e/downgrade.go @@ -15,7 +15,6 @@ package e2e import ( - "context" "encoding/json" "fmt" "math/rand" @@ -26,8 +25,11 @@ import ( "github.com/coreos/go-semver/semver" "github.com/stretchr/testify/require" "go.uber.org/zap" + "golang.org/x/sync/errgroup" + pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/version" + "go.etcd.io/etcd/server/v3/etcdserver" "go.etcd.io/etcd/tests/v3/framework/testutils" ) @@ -35,7 +37,7 @@ func DowngradeEnable(t *testing.T, epc *EtcdProcessCluster, ver *semver.Version) t.Logf("etcdctl downgrade enable %s", ver.String()) c := epc.Etcdctl() testutils.ExecuteWithTimeout(t, 20*time.Second, func() { - err := c.DowngradeEnable(context.TODO(), ver.String()) + err := c.DowngradeEnable(t.Context(), ver.String()) require.NoError(t, err) }) @@ -46,7 +48,6 @@ func DowngradeEnable(t *testing.T, epc *EtcdProcessCluster, ver *semver.Version) Server: OffsetMinor(ver, 1).String(), Storage: ver.String(), }) - AssertProcessLogs(t, epc.Procs[i], "The server is ready to downgrade") } t.Log("Cluster is ready for downgrade") @@ -59,7 +60,7 @@ func DowngradeCancel(t *testing.T, epc *EtcdProcessCluster) { testutils.ExecuteWithTimeout(t, 1*time.Minute, func() { for { t.Logf("etcdctl downgrade cancel") - err = c.DowngradeCancel(context.TODO()) + err = c.DowngradeCancel(t.Context()) if err != nil { if strings.Contains(err.Error(), "no inflight downgrade job") { // cancellation has been performed successfully @@ -82,14 +83,59 @@ func DowngradeCancel(t *testing.T, epc *EtcdProcessCluster) { t.Log("Cluster downgrade cancellation is completed") } -func DowngradeUpgradeMembers(t *testing.T, lg *zap.Logger, clus *EtcdProcessCluster, numberOfMembersToChange int, currentVersion, targetVersion *semver.Version) error { +func ValidateDowngradeInfo(t *testing.T, clus *EtcdProcessCluster, expected *pb.DowngradeInfo) { + cfg := clus.Cfg + + for i := 0; i < len(clus.Procs); i++ { + member := clus.Procs[i] + mc := member.Etcdctl() + mName := member.Config().Name + + testutils.ExecuteWithTimeout(t, 1*time.Minute, func() { + for { + statuses, err := mc.Status(t.Context()) + if err != nil { + cfg.Logger.Warn("failed to get member status and retrying", + zap.Error(err), + zap.String("member", mName)) + + time.Sleep(time.Second) + continue + } + + require.Lenf(t, statuses, 1, "member %s", mName) + got := (*pb.StatusResponse)(statuses[0]).GetDowngradeInfo() + + if got.GetEnabled() == expected.GetEnabled() && got.GetTargetVersion() == expected.GetTargetVersion() { + cfg.Logger.Info("DowngradeInfo match", zap.String("member", mName)) + break + } + + cfg.Logger.Warn("DowngradeInfo didn't match retrying", + zap.String("member", mName), + zap.Dict("expected", + zap.Bool("Enabled", expected.GetEnabled()), + zap.String("TargetVersion", expected.GetTargetVersion()), + ), + zap.Dict("got", + zap.Bool("Enabled", got.GetEnabled()), + zap.String("TargetVersion", got.GetTargetVersion()), + ), + ) + time.Sleep(time.Second) + } + }) + } +} + +func DowngradeUpgradeMembers(t *testing.T, lg *zap.Logger, clus *EtcdProcessCluster, numberOfMembersToChange int, downgradeEnabled bool, currentVersion, targetVersion *semver.Version) error { membersToChange := rand.Perm(len(clus.Procs))[:numberOfMembersToChange] - t.Logf(fmt.Sprintln("Elect members for operations"), zap.Any("members", membersToChange)) + t.Logf("Elect members for operations on members: %v", membersToChange) - return DowngradeUpgradeMembersByID(t, lg, clus, membersToChange, currentVersion, targetVersion) + return DowngradeUpgradeMembersByID(t, lg, clus, membersToChange, downgradeEnabled, currentVersion, targetVersion) } -func DowngradeUpgradeMembersByID(t *testing.T, lg *zap.Logger, clus *EtcdProcessCluster, membersToChange []int, currentVersion, targetVersion *semver.Version) error { +func DowngradeUpgradeMembersByID(t *testing.T, lg *zap.Logger, clus *EtcdProcessCluster, membersToChange []int, downgradeEnabled bool, currentVersion, targetVersion *semver.Version) error { if lg == nil { lg = clus.lg } @@ -101,6 +147,12 @@ func DowngradeUpgradeMembersByID(t *testing.T, lg *zap.Logger, clus *EtcdProcess newExecPath = BinPath.EtcdLastRelease } + binaryVersion, err := GetVersionFromBinary(newExecPath) + if err != nil { + return fmt.Errorf("failed to get binary version from %s: %w", newExecPath, err) + } + + g := new(errgroup.Group) for _, memberID := range membersToChange { member := clus.Procs[memberID] if member.Config().ExecPath == newExecPath { @@ -110,28 +162,53 @@ func DowngradeUpgradeMembersByID(t *testing.T, lg *zap.Logger, clus *EtcdProcess if err := member.Stop(); err != nil { return err } + + // When we downgrade or upgrade a member, we need to re-generate the flags, to convert some non-experimental + // flags to experimental flags, or vice verse. + member.Config().Args = convertFlags(member.Config().Args, binaryVersion) + member.Config().ExecPath = newExecPath lg.Info("Restarting member", zap.String("member", member.Config().Name)) - err := member.Start(context.TODO()) - if err != nil { - return err - } + // We shouldn't block on waiting for the member to be ready, + // otherwise it will be blocked forever if other members are + // not started yet. + g.Go(func() error { + return member.Start(t.Context()) + }) + } + if err := g.Wait(); err != nil { + return err } + + t.Log("Waiting health interval to make sure the leader propagates version to new processes") + time.Sleep(etcdserver.HealthInterval) + lg.Info("Validating versions") - for _, memberID := range membersToChange { - member := clus.Procs[memberID] - if isDowngrade || len(membersToChange) == len(clus.Procs) { - ValidateVersion(t, clus.Cfg, member, version.Versions{ - Cluster: targetVersion.String(), - Server: targetVersion.String(), - }) + clusterVersion := targetVersion + if !isDowngrade { + if downgradeEnabled { + // If the downgrade isn't cancelled yet, then the cluster + // version will always stay at the lower version, no matter + // what's the binary version of each member. + clusterVersion = currentVersion } else { - ValidateVersion(t, clus.Cfg, member, version.Versions{ - Cluster: currentVersion.String(), - Server: targetVersion.String(), - }) + // If the downgrade has already been cancelled, then the + // cluster version is the minimal server version. + minVer, err := clus.MinServerVersion() + if err != nil { + return fmt.Errorf("failed to get min server version: %w", err) + } + clusterVersion = minVer } } + + for _, memberID := range membersToChange { + member := clus.Procs[memberID] + ValidateVersion(t, clus.Cfg, member, version.Versions{ + Cluster: clusterVersion.String(), + Server: targetVersion.String(), + }) + } return nil } diff --git a/tests/framework/e2e/e2e.go b/tests/framework/e2e/e2e.go index f78df57926ea..6f3954d5da7d 100644 --- a/tests/framework/e2e/e2e.go +++ b/tests/framework/e2e/e2e.go @@ -39,11 +39,11 @@ func (e e2eRunner) TestMain(m *testing.M) { os.Exit(v) } -func (e e2eRunner) BeforeTest(t testing.TB) { - BeforeTest(t) +func (e e2eRunner) BeforeTest(tb testing.TB) { + BeforeTest(tb) } -func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster { +func (e e2eRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster { cfg := config.NewClusterConfig(opts...) e2eConfig := NewConfig( WithClusterSize(cfg.ClusterSize), @@ -68,7 +68,7 @@ func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config. e2eConfig.Client.AutoTLS = false e2eConfig.Client.ConnectionType = ClientTLS default: - t.Fatalf("ClientTLS config %q not supported", cfg.ClientTLS) + tb.Fatalf("ClientTLS config %q not supported", cfg.ClientTLS) } switch cfg.PeerTLS { case config.NoTLS: @@ -81,13 +81,13 @@ func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config. e2eConfig.IsPeerTLS = true e2eConfig.IsPeerAutoTLS = false default: - t.Fatalf("PeerTLS config %q not supported", cfg.PeerTLS) + tb.Fatalf("PeerTLS config %q not supported", cfg.PeerTLS) } - epc, err := NewEtcdProcessCluster(ctx, t, WithConfig(e2eConfig)) + epc, err := NewEtcdProcessCluster(ctx, tb, WithConfig(e2eConfig)) if err != nil { - t.Fatalf("could not start etcd integrationCluster: %s", err) + tb.Fatalf("could not start etcd integrationCluster: %s", err) } - return &e2eCluster{t, *epc} + return &e2eCluster{tb, *epc} } type e2eCluster struct { diff --git a/tests/framework/e2e/etcd_process.go b/tests/framework/e2e/etcd_process.go index 445ea26e94cc..6d8dd3036538 100644 --- a/tests/framework/e2e/etcd_process.go +++ b/tests/framework/e2e/etcd_process.go @@ -31,6 +31,7 @@ import ( "github.com/coreos/go-semver/semver" "go.uber.org/zap" + "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/pkg/v3/expect" "go.etcd.io/etcd/pkg/v3/proxy" @@ -102,7 +103,7 @@ type EtcdServerProcessConfig struct { Proxy *proxy.ServerConfig } -func NewEtcdServerProcess(t testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServerProcess, error) { +func NewEtcdServerProcess(tb testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServerProcess, error) { if !fileutil.Exist(cfg.ExecPath) { return nil, fmt.Errorf("could not find etcd binary: %s", cfg.ExecPath) } @@ -122,7 +123,7 @@ func NewEtcdServerProcess(t testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServ } } if cfg.LazyFSEnabled { - ep.lazyfs = newLazyFS(cfg.lg, cfg.DataDirPath, t) + ep.lazyfs = newLazyFS(cfg.lg, cfg.DataDirPath, tb) } return ep, nil } @@ -320,7 +321,7 @@ func (ep *EtcdServerProcess) IsRunning() bool { func AssertProcessLogs(t *testing.T, ep EtcdProcess, expectLog string) { t.Helper() var err error - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() _, err = ep.Logs().ExpectWithContext(ctx, expect.ExpectedResponse{Value: expectLog}) if err != nil { @@ -531,3 +532,11 @@ func CouldSetSnapshotCatchupEntries(execPath string) bool { v3_5_14 := semver.Version{Major: 3, Minor: 5, Patch: 14} return v.Compare(v3_5_14) >= 0 } + +func IsSnapshotCatchupEntriesFlagAvailable(execPath string) bool { + v, err := GetVersionFromBinary(execPath) + if err != nil { + return false + } + return !v.LessThan(version.V3_6) +} diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index 9235ab28d854..e212e447cf73 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -56,6 +56,7 @@ func NewEtcdctl(cfg ClientConfig, endpoints []string, opts ...config.ClientOptio DialOptions: []grpc.DialOption{grpc.WithBlock()}, Username: ctl.authConfig.Username, Password: ctl.authConfig.Password, + Token: ctl.authConfig.Token, }) if err != nil { return nil, err @@ -74,6 +75,13 @@ func WithAuth(userName, password string) config.ClientOption { } } +func WithAuthToken(token string) config.ClientOption { + return func(c any) { + ctl := c.(*EtcdctlV3) + ctl.authConfig.Token = token + } +} + func WithEndpoints(endpoints []string) config.ClientOption { return func(c any) { ctl := c.(*EtcdctlV3) @@ -350,7 +358,9 @@ func (ctl *EtcdctlV3) flags() map[string]string { } } fmap["endpoints"] = strings.Join(ctl.endpoints, ",") - if !ctl.authConfig.Empty() { + if ctl.authConfig.Token != "" { + fmap["auth-jwt-token"] = ctl.authConfig.Token + } else if !ctl.authConfig.Empty() { fmap["user"] = ctl.authConfig.Username + ":" + ctl.authConfig.Password } return fmap diff --git a/tests/framework/e2e/metrics.go b/tests/framework/e2e/metrics.go new file mode 100644 index 000000000000..e28661206049 --- /dev/null +++ b/tests/framework/e2e/metrics.go @@ -0,0 +1,41 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package e2e + +import ( + "bytes" + "io" + "net/http" + + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" +) + +func GetMetrics(metricsURL string) (map[string]*dto.MetricFamily, error) { + httpClient := http.Client{Transport: &http.Transport{}} + resp, err := httpClient.Get(metricsURL) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var parser expfmt.TextParser + return parser.TextToMetricFamilies(bytes.NewReader(data)) +} diff --git a/tests/framework/e2e/testing.go b/tests/framework/e2e/testing.go index 7d7de27fdddf..99aa8dac6d65 100644 --- a/tests/framework/e2e/testing.go +++ b/tests/framework/e2e/testing.go @@ -20,7 +20,7 @@ import ( "go.etcd.io/etcd/client/pkg/v3/testutil" ) -func BeforeTest(t testing.TB) { - SkipInShortMode(t) - testutil.BeforeTest(t) +func BeforeTest(tb testing.TB) { + SkipInShortMode(tb) + testutil.BeforeTest(tb) } diff --git a/tests/framework/e2e/util.go b/tests/framework/e2e/util.go index d72f2d4939d0..94e1cd6a84ab 100644 --- a/tests/framework/e2e/util.go +++ b/tests/framework/e2e/util.go @@ -150,8 +150,8 @@ func ToTLS(s string) string { return s } -func SkipInShortMode(t testing.TB) { - testutil.SkipTestIfShortMode(t, "e2e tests are not running in --short mode") +func SkipInShortMode(tb testing.TB) { + testutil.SkipTestIfShortMode(tb, "e2e tests are not running in --short mode") } func mergeEnvVariables(envVars map[string]string) []string { diff --git a/tests/framework/integration/cluster.go b/tests/framework/integration/cluster.go index 8b0f2b549528..e4d7620ac4a5 100644 --- a/tests/framework/integration/cluster.go +++ b/tests/framework/integration/cluster.go @@ -412,23 +412,23 @@ func (c *Cluster) WaitMembersMatch(t testutil.TB, membs []*pb.Member) { // WaitLeader returns index of the member in c.Members that is leader // or fails the test (if not established in 30s). -func (c *Cluster) WaitLeader(t testing.TB) int { - return c.WaitMembersForLeader(t, c.Members) +func (c *Cluster) WaitLeader(tb testing.TB) int { + return c.WaitMembersForLeader(tb, c.Members) } // WaitMembersForLeader waits until given members agree on the same leader, // and returns its 'index' in the 'membs' list -func (c *Cluster) WaitMembersForLeader(t testing.TB, membs []*Member) int { - t.Logf("WaitMembersForLeader") - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +func (c *Cluster) WaitMembersForLeader(tb testing.TB, membs []*Member) int { + tb.Logf("WaitMembersForLeader") + ctx, cancel := context.WithTimeout(tb.Context(), 30*time.Second) defer cancel() l := 0 - for l = c.waitMembersForLeader(ctx, t, membs); l < 0; { + for l = c.waitMembersForLeader(ctx, tb, membs); l < 0; { if ctx.Err() != nil { - t.Fatalf("WaitLeader FAILED: %v", ctx.Err()) + tb.Fatalf("WaitLeader FAILED: %v", ctx.Err()) } } - t.Logf("WaitMembersForLeader succeeded. Cluster leader index: %v", l) + tb.Logf("WaitMembersForLeader succeeded. Cluster leader index: %v", l) // TODO: Consider second pass check as sometimes leadership is lost // soon after election: @@ -444,15 +444,15 @@ func (c *Cluster) WaitMembersForLeader(t testing.TB, membs []*Member) int { // WaitMembersForLeader waits until given members agree on the same leader, // and returns its 'index' in the 'membs' list -func (c *Cluster) waitMembersForLeader(ctx context.Context, t testing.TB, membs []*Member) int { +func (c *Cluster) waitMembersForLeader(ctx context.Context, tb testing.TB, membs []*Member) int { possibleLead := make(map[uint64]bool) var lead uint64 for _, m := range membs { possibleLead[uint64(m.Server.MemberID())] = true } - cc, err := c.ClusterClient(t) + cc, err := c.ClusterClient(tb) if err != nil { - t.Fatal(err) + tb.Fatal(err) } // ensure leader is up via linearizable get for { @@ -483,12 +483,12 @@ func (c *Cluster) waitMembersForLeader(ctx context.Context, t testing.TB, membs for i, m := range membs { if uint64(m.Server.MemberID()) == lead { - t.Logf("waitMembersForLeader found leader. Member: %v lead: %x", i, lead) + tb.Logf("waitMembersForLeader found leader. Member: %v lead: %x", i, lead) return i } } - t.Logf("waitMembersForLeader failed (-1)") + tb.Logf("waitMembersForLeader failed (-1)") return -1 } @@ -971,7 +971,6 @@ func (m *Member) Launch() error { if m.Server, err = etcdserver.NewServer(m.ServerConfig); err != nil { return fmt.Errorf("failed to initialize the etcd server: %w", err) } - m.Server.SyncTicker = time.NewTicker(500 * time.Millisecond) m.Server.Start() var peerTLScfg *tls.Config @@ -1457,7 +1456,7 @@ func (c *Cluster) Endpoints() []string { return endpoints } -func (c *Cluster) ClusterClient(t testing.TB, opts ...framecfg.ClientOption) (client *clientv3.Client, err error) { +func (c *Cluster) ClusterClient(tb testing.TB, opts ...framecfg.ClientOption) (client *clientv3.Client, err error) { cfg, err := c.newClientCfg() if err != nil { return nil, err @@ -1469,7 +1468,7 @@ func (c *Cluster) ClusterClient(t testing.TB, opts ...framecfg.ClientOption) (cl if err != nil { return nil, err } - t.Cleanup(func() { + tb.Cleanup(func() { client.Close() }) return client, nil @@ -1483,6 +1482,13 @@ func WithAuth(userName, password string) framecfg.ClientOption { } } +func WithAuthToken(token string) framecfg.ClientOption { + return func(c any) { + cfg := c.(*clientv3.Config) + cfg.Token = token + } +} + func WithEndpoints(endpoints []string) framecfg.ClientOption { return func(c any) { cfg := c.(*clientv3.Config) diff --git a/tests/framework/integration/integration.go b/tests/framework/integration/integration.go index 6e5de0cd5281..6152ca705435 100644 --- a/tests/framework/integration/integration.go +++ b/tests/framework/integration/integration.go @@ -41,11 +41,11 @@ func (e integrationRunner) TestMain(m *testing.M) { testutil.MustTestMainWithLeakDetection(m) } -func (e integrationRunner) BeforeTest(t testing.TB) { - BeforeTest(t) +func (e integrationRunner) BeforeTest(tb testing.TB) { + BeforeTest(tb) } -func (e integrationRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster { +func (e integrationRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster { var err error cfg := config.NewClusterConfig(opts...) integrationCfg := ClusterConfig{ @@ -55,27 +55,27 @@ func (e integrationRunner) NewCluster(ctx context.Context, t testing.TB, opts .. AuthToken: cfg.AuthToken, SnapshotCount: cfg.SnapshotCount, } - integrationCfg.ClientTLS, err = tlsInfo(t, cfg.ClientTLS) + integrationCfg.ClientTLS, err = tlsInfo(tb, cfg.ClientTLS) if err != nil { - t.Fatalf("ClientTLS: %s", err) + tb.Fatalf("ClientTLS: %s", err) } - integrationCfg.PeerTLS, err = tlsInfo(t, cfg.PeerTLS) + integrationCfg.PeerTLS, err = tlsInfo(tb, cfg.PeerTLS) if err != nil { - t.Fatalf("PeerTLS: %s", err) + tb.Fatalf("PeerTLS: %s", err) } return &integrationCluster{ - Cluster: NewCluster(t, &integrationCfg), - t: t, + Cluster: NewCluster(tb, &integrationCfg), + t: tb, ctx: ctx, } } -func tlsInfo(t testing.TB, cfg config.TLSConfig) (*transport.TLSInfo, error) { +func tlsInfo(tb testing.TB, cfg config.TLSConfig) (*transport.TLSInfo, error) { switch cfg { case config.NoTLS: return nil, nil case config.AutoTLS: - tls, err := transport.SelfCert(zap.NewNop(), t.TempDir(), []string{"localhost"}, 1) + tls, err := transport.SelfCert(zap.NewNop(), tb.TempDir(), []string{"localhost"}, 1) if err != nil { return nil, fmt.Errorf("failed to generate cert: %w", err) } diff --git a/tests/framework/integration/testing.go b/tests/framework/integration/testing.go index a4d03c532123..7e0dd38d4df2 100644 --- a/tests/framework/integration/testing.go +++ b/tests/framework/integration/testing.go @@ -138,18 +138,18 @@ func assertInTestContext(t testutil.TB) { } } -func NewEmbedConfig(t testing.TB, name string) *embed.Config { +func NewEmbedConfig(tb testing.TB, name string) *embed.Config { cfg := embed.NewConfig() cfg.Name = name - lg := zaptest.NewLogger(t, zaptest.Level(zapcore.InfoLevel)).Named(cfg.Name) + lg := zaptest.NewLogger(tb, zaptest.Level(zapcore.InfoLevel)).Named(cfg.Name) cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(lg) - cfg.Dir = t.TempDir() + cfg.Dir = tb.TempDir() return cfg } -func NewClient(t testing.TB, cfg clientv3.Config) (*clientv3.Client, error) { +func NewClient(tb testing.TB, cfg clientv3.Config) (*clientv3.Client, error) { if cfg.Logger == nil { - cfg.Logger = zaptest.NewLogger(t).Named("client") + cfg.Logger = zaptest.NewLogger(tb).Named("client") } return clientv3.New(cfg) } diff --git a/tests/framework/testutils/execute.go b/tests/framework/testutils/execute.go index d9c3d3358796..9ea7e1222243 100644 --- a/tests/framework/testutils/execute.go +++ b/tests/framework/testutils/execute.go @@ -24,7 +24,7 @@ import ( ) func ExecuteWithTimeout(t *testing.T, timeout time.Duration, f func()) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := context.WithTimeout(t.Context(), timeout) defer cancel() ExecuteUntil(ctx, t, f) } diff --git a/tests/framework/testutils/log_observer_test.go b/tests/framework/testutils/log_observer_test.go index 695caf97261d..a11ee175c7a2 100644 --- a/tests/framework/testutils/log_observer_test.go +++ b/tests/framework/testutils/log_observer_test.go @@ -32,7 +32,7 @@ func TestLogObserver_Timeout(t *testing.T) { logger := zap.New(logCore) logger.Info(t.Name()) - ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) + ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) _, err := logOb.Expect(ctx, "unknown", 1) cancel() require.ErrorIs(t, err, context.DeadlineExceeded) @@ -45,7 +45,7 @@ func TestLogObserver_Expect(t *testing.T) { logger := zap.New(logCore) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() resCh := make(chan []string, 1) diff --git a/tests/framework/unit/unit.go b/tests/framework/unit/unit.go index f822b7dd1f92..d09ef0bdd2be 100644 --- a/tests/framework/unit/unit.go +++ b/tests/framework/unit/unit.go @@ -42,10 +42,10 @@ func (e unitRunner) TestMain(m *testing.M) { } } -func (e unitRunner) BeforeTest(t testing.TB) { +func (e unitRunner) BeforeTest(tb testing.TB) { } -func (e unitRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster { - testutil.SkipTestIfShortMode(t, "Cannot create clusters in --short tests") +func (e unitRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster { + testutil.SkipTestIfShortMode(tb, "Cannot create clusters in --short tests") return nil } diff --git a/tests/go.mod b/tests/go.mod index 41285fac31e1..d0806abfe97d 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,8 +1,8 @@ module go.etcd.io/etcd/tests/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 replace ( go.etcd.io/etcd/api/v3 => ../api @@ -16,21 +16,22 @@ replace ( ) require ( - github.com/anishathalye/porcupine v0.1.4 + github.com/anishathalye/porcupine v1.0.2 + github.com/antithesishq/antithesis-sdk-go v0.4.3 github.com/coreos/go-semver v0.3.1 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang/protobuf v1.5.4 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 - github.com/prometheus/client_golang v1.20.5 - github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.62.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 + github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_model v0.6.2 + github.com/prometheus/common v0.64.0 github.com/soheilhy/cmux v0.1.5 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 - go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 @@ -38,69 +39,70 @@ require ( go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 go.etcd.io/gofail v0.2.0 go.etcd.io/raft/v3 v3.6.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/trace v1.34.0 - go.opentelemetry.io/proto/otlp v1.5.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 + go.opentelemetry.io/otel v1.36.0 + go.opentelemetry.io/otel/sdk v1.36.0 + go.opentelemetry.io/otel/trace v1.36.0 + go.opentelemetry.io/proto/otlp v1.6.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 - golang.org/x/sync v0.10.0 - golang.org/x/time v0.9.0 - google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.4 + golang.org/x/crypto v0.38.0 + golang.org/x/sync v0.14.0 + golang.org/x/time v0.11.0 + google.golang.org/grpc v1.72.2 + google.golang.org/protobuf v1.36.6 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cheggaaa/pb/v3 v3.1.6 // indirect + github.com/cheggaaa/pb/v3 v3.1.7 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/creack/pty v1.1.18 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect + github.com/olekukonko/tablewriter v1.0.6 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.4.0 // indirect + go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 1c38cabd539f..d527ab4232a5 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -2,19 +2,21 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/anishathalye/porcupine v0.1.4 h1:rRekB2jH1mbtLPEzuqyMHp4scU52Bcc1jgkPi1kWFQA= -github.com/anishathalye/porcupine v0.1.4/go.mod h1:/X9OQYnVb7DzfKCQVO4tI1Aq+o56UJW+RvN/5U4EuZA= +github.com/anishathalye/porcupine v1.0.2 h1:cXMWjnN95KYsbZVTi9VmXj0ePs1w3ZJ82zWoXDy6WPE= +github.com/anishathalye/porcupine v1.0.2/go.mod h1:WM0SsFjWNl2Y4BqHr/E/ll2yY1GY1jqn+W7Z/84Zoog= +github.com/antithesishq/antithesis-sdk-go v0.4.3 h1:a2hGdDogClzHzFu20r1z0tzD6zwSWUipiaerAjZVP90= +github.com/antithesishq/antithesis-sdk-go v0.4.3/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.1.6 h1:h0x+vd7EiUohAJ29DJtJy+SNAc55t/elW3jCD086EXk= -github.com/cheggaaa/pb/v3 v3.1.6/go.mod h1:urxmfVtaxT+9aWk92DbsvXFZtNSWQSO5TRAp+MJ3l1s= +github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= +github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= @@ -23,12 +25,13 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -48,11 +51,11 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -63,28 +66,28 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -97,40 +100,43 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= +github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -154,24 +160,24 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -184,8 +190,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -201,16 +207,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -218,14 +224,14 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -243,19 +249,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -267,7 +273,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/tests/integration/clientv3/concurrency/election_test.go b/tests/integration/clientv3/concurrency/election_test.go index 951c6a91fbf1..b597e9130aed 100644 --- a/tests/integration/clientv3/concurrency/election_test.go +++ b/tests/integration/clientv3/concurrency/election_test.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/concurrency" integration2 "go.etcd.io/etcd/tests/v3/framework/integration" @@ -49,16 +51,12 @@ func TestResumeElection(t *testing.T) { defer cancel() // become leader - if err = e.Campaign(ctx, "candidate1"); err != nil { - t.Fatalf("Campaign() returned non nil err: %s", err) - } + require.NoErrorf(t, e.Campaign(ctx, "candidate1"), "Campaign() returned non nil err") // get the leadership details of the current election var leader *clientv3.GetResponse leader, err = e.Leader(ctx) - if err != nil { - t.Fatalf("Leader() returned non nil err: %s", err) - } + require.NoErrorf(t, err, "Leader() returned non nil err") // Recreate the election e = concurrency.ResumeElection(s, prefix, @@ -86,19 +84,13 @@ func TestResumeElection(t *testing.T) { // put some random data to generate a change event, this put should be // ignored by Observe() because it is not under the election prefix. _, err = cli.Put(ctx, "foo", "bar") - if err != nil { - t.Fatalf("Put('foo') returned non nil err: %s", err) - } + require.NoErrorf(t, err, "Put('foo') returned non nil err") // resign as leader - if err := e.Resign(ctx); err != nil { - t.Fatalf("Resign() returned non nil err: %s", err) - } + require.NoErrorf(t, e.Resign(ctx), "Resign() returned non nil err") // elect a different candidate - if err := e.Campaign(ctx, "candidate2"); err != nil { - t.Fatalf("Campaign() returned non nil err: %s", err) - } + require.NoErrorf(t, e.Campaign(ctx, "candidate2"), "Campaign() returned non nil err") // wait for observed leader change resp := <-respChan diff --git a/tests/integration/clientv3/connectivity/black_hole_test.go b/tests/integration/clientv3/connectivity/black_hole_test.go index 00b7849ea565..a484447149c8 100644 --- a/tests/integration/clientv3/connectivity/black_hole_test.go +++ b/tests/integration/clientv3/connectivity/black_hole_test.go @@ -67,9 +67,8 @@ func TestBalancerUnderBlackholeKeepAliveWatch(t *testing.T) { defer cli.Close() wch := cli.Watch(context.Background(), "foo", clientv3.WithCreatedNotify()) - if _, ok := <-wch; !ok { - t.Fatalf("watch failed on creation") - } + _, ok := <-wch + require.Truef(t, ok, "watch failed on creation") // endpoint can switch to eps[1] when it detects the failure of eps[0] cli.SetEndpoints(eps...) diff --git a/tests/integration/clientv3/connectivity/dial_test.go b/tests/integration/clientv3/connectivity/dial_test.go index 54556d0f8ddd..80a3cb90f7ab 100644 --- a/tests/integration/clientv3/connectivity/dial_test.go +++ b/tests/integration/clientv3/connectivity/dial_test.go @@ -63,9 +63,7 @@ func TestDialTLSExpired(t *testing.T) { DialOptions: []grpc.DialOption{grpc.WithBlock()}, TLS: tls, }) - if !clientv3test.IsClientTimeout(err) { - t.Fatalf("expected dial timeout error, got %v", err) - } + require.Truef(t, clientv3test.IsClientTimeout(err), "expected dial timeout error") } // TestDialTLSNoConfig ensures the client fails to dial / times out @@ -85,9 +83,7 @@ func TestDialTLSNoConfig(t *testing.T) { c.Close() } }() - if !clientv3test.IsClientTimeout(err) { - t.Fatalf("expected dial timeout error, got %v", err) - } + require.Truef(t, clientv3test.IsClientTimeout(err), "expected dial timeout error") } // TestDialSetEndpointsBeforeFail ensures SetEndpoints can replace unavailable diff --git a/tests/integration/clientv3/connectivity/network_partition_test.go b/tests/integration/clientv3/connectivity/network_partition_test.go index 557cdb0b31e8..eb14534e92da 100644 --- a/tests/integration/clientv3/connectivity/network_partition_test.go +++ b/tests/integration/clientv3/connectivity/network_partition_test.go @@ -260,9 +260,7 @@ func testBalancerUnderNetworkPartitionWatch(t *testing.T, isolateLeader bool) { if len(ev.Events) != 0 { t.Fatal("expected no event") } - if err = ev.Err(); !errors.Is(err, rpctypes.ErrNoLeader) { - t.Fatalf("expected %v, got %v", rpctypes.ErrNoLeader, err) - } + require.ErrorIs(t, ev.Err(), rpctypes.ErrNoLeader) case <-time.After(integration2.RequestWaitTimeout): // enough time to detect leader lost t.Fatal("took too long to detect leader lost") } @@ -302,9 +300,7 @@ func TestDropReadUnderNetworkPartition(t *testing.T) { ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) _, err = kvc.Get(ctx, "a") cancel() - if !errors.Is(err, rpctypes.ErrLeaderChanged) { - t.Fatalf("expected %v, got %v", rpctypes.ErrLeaderChanged, err) - } + require.ErrorIsf(t, err, rpctypes.ErrLeaderChanged, "expected %v, got %v", rpctypes.ErrLeaderChanged, err) for i := 0; i < 5; i++ { ctx, cancel = context.WithTimeout(context.TODO(), 10*time.Second) diff --git a/tests/integration/clientv3/examples/example_cluster_test.go b/tests/integration/clientv3/examples/example_cluster_test.go index 1d2da78c777e..3a83e8a997fc 100644 --- a/tests/integration/clientv3/examples/example_cluster_test.go +++ b/tests/integration/clientv3/examples/example_cluster_test.go @@ -22,12 +22,12 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockCluster_memberList() { +func mockClusterMemberList() { fmt.Println("members: 3") } func ExampleCluster_memberList() { - forUnitTestsRunInMockedContext(mockCluster_memberList, func() { + forUnitTestsRunInMockedContext(mockClusterMemberList, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -46,13 +46,13 @@ func ExampleCluster_memberList() { // Output: members: 3 } -func mockCluster_memberAdd() { +func mockClusterMemberAdd() { fmt.Println("added member.PeerURLs: [http://localhost:32380]") fmt.Println("members count: 4") } func ExampleCluster_memberAdd() { - forUnitTestsRunInMockedContext(mockCluster_memberAdd, func() { + forUnitTestsRunInMockedContext(mockClusterMemberAdd, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -81,13 +81,13 @@ func ExampleCluster_memberAdd() { // members count: 4 } -func mockCluster_memberAddAsLearner() { +func mockClusterMemberAddAsLearner() { fmt.Println("members count: 4") fmt.Println("added member.IsLearner: true") } func ExampleCluster_memberAddAsLearner() { - forUnitTestsRunInMockedContext(mockCluster_memberAddAsLearner, func() { + forUnitTestsRunInMockedContext(mockClusterMemberAddAsLearner, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -116,10 +116,10 @@ func ExampleCluster_memberAddAsLearner() { // added member.IsLearner: true } -func mockCluster_memberRemove() {} +func mockClusterMemberRemove() {} func ExampleCluster_memberRemove() { - forUnitTestsRunInMockedContext(mockCluster_memberRemove, func() { + forUnitTestsRunInMockedContext(mockClusterMemberRemove, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -147,10 +147,10 @@ func ExampleCluster_memberRemove() { }) } -func mockCluster_memberUpdate() {} +func mockClusterMemberUpdate() {} func ExampleCluster_memberUpdate() { - forUnitTestsRunInMockedContext(mockCluster_memberUpdate, func() { + forUnitTestsRunInMockedContext(mockClusterMemberUpdate, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, diff --git a/tests/integration/clientv3/examples/example_kv_test.go b/tests/integration/clientv3/examples/example_kv_test.go index e4fa4bf5f441..235404b6753f 100644 --- a/tests/integration/clientv3/examples/example_kv_test.go +++ b/tests/integration/clientv3/examples/example_kv_test.go @@ -16,6 +16,7 @@ package clientv3_test import ( "context" + "errors" "fmt" "log" @@ -23,10 +24,10 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockKV_put() {} +func mockKVPut() {} func ExampleKV_put() { - forUnitTestsRunInMockedContext(mockKV_put, func() { + forUnitTestsRunInMockedContext(mockKVPut, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -46,12 +47,12 @@ func ExampleKV_put() { // Output: } -func mockKV_putErrorHandling() { +func mockKVPutErrorHandling() { fmt.Println("client-side error: etcdserver: key is not provided") } func ExampleKV_putErrorHandling() { - forUnitTestsRunInMockedContext(mockKV_putErrorHandling, func() { + forUnitTestsRunInMockedContext(mockKVPutErrorHandling, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -65,14 +66,13 @@ func ExampleKV_putErrorHandling() { _, err = cli.Put(ctx, "", "sample_value") cancel() if err != nil { - switch err { - case context.Canceled: + if errors.Is(err, context.Canceled) { fmt.Printf("ctx is canceled by another routine: %v\n", err) - case context.DeadlineExceeded: + } else if errors.Is(err, context.DeadlineExceeded) { fmt.Printf("ctx is attached with a deadline is exceeded: %v\n", err) - case rpctypes.ErrEmptyKey: + } else if errors.Is(err, rpctypes.ErrEmptyKey) { fmt.Printf("client-side error: %v\n", err) - default: + } else { fmt.Printf("bad cluster endpoints, which are not etcd servers: %v\n", err) } } @@ -80,12 +80,12 @@ func ExampleKV_putErrorHandling() { // Output: client-side error: etcdserver: key is not provided } -func mockKV_get() { +func mockKVGet() { fmt.Println("foo : bar") } func ExampleKV_get() { - forUnitTestsRunInMockedContext(mockKV_get, func() { + forUnitTestsRunInMockedContext(mockKVGet, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -113,12 +113,12 @@ func ExampleKV_get() { // Output: foo : bar } -func mockKV_getWithRev() { +func mockKVGetWithRev() { fmt.Println("foo : bar1") } func ExampleKV_getWithRev() { - forUnitTestsRunInMockedContext(mockKV_getWithRev, func() { + forUnitTestsRunInMockedContext(mockKVGetWithRev, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -150,14 +150,14 @@ func ExampleKV_getWithRev() { // Output: foo : bar1 } -func mockKV_getSortedPrefix() { +func mockKVGetSortedPrefix() { fmt.Println(`key_2 : value`) fmt.Println(`key_1 : value`) fmt.Println(`key_0 : value`) } func ExampleKV_getSortedPrefix() { - forUnitTestsRunInMockedContext(mockKV_getSortedPrefix, func() { + forUnitTestsRunInMockedContext(mockKVGetSortedPrefix, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -192,12 +192,12 @@ func ExampleKV_getSortedPrefix() { // key_0 : value } -func mockKV_delete() { +func mockKVDelete() { fmt.Println("Deleted all keys: true") } func ExampleKV_delete() { - forUnitTestsRunInMockedContext(mockKV_delete, func() { + forUnitTestsRunInMockedContext(mockKVDelete, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -228,10 +228,10 @@ func ExampleKV_delete() { // Deleted all keys: true } -func mockKV_compact() {} +func mockKVCompact() {} func ExampleKV_compact() { - forUnitTestsRunInMockedContext(mockKV_compact, func() { + forUnitTestsRunInMockedContext(mockKVCompact, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -259,12 +259,12 @@ func ExampleKV_compact() { // Output: } -func mockKV_txn() { +func mockKVTxn() { fmt.Println("key : XYZ") } func ExampleKV_txn() { - forUnitTestsRunInMockedContext(mockKV_txn, func() { + forUnitTestsRunInMockedContext(mockKVTxn, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -306,10 +306,10 @@ func ExampleKV_txn() { // Output: key : XYZ } -func mockKV_do() {} +func mockKVDo() {} func ExampleKV_do() { - forUnitTestsRunInMockedContext(mockKV_do, func() { + forUnitTestsRunInMockedContext(mockKVDo, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, diff --git a/tests/integration/clientv3/examples/example_lease_test.go b/tests/integration/clientv3/examples/example_lease_test.go index b0e6c5ef3668..f55e8314b2d3 100644 --- a/tests/integration/clientv3/examples/example_lease_test.go +++ b/tests/integration/clientv3/examples/example_lease_test.go @@ -22,11 +22,11 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockLease_grant() { +func mockLeaseGrant() { } func ExampleLease_grant() { - forUnitTestsRunInMockedContext(mockLease_grant, func() { + forUnitTestsRunInMockedContext(mockLeaseGrant, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -51,12 +51,12 @@ func ExampleLease_grant() { // Output: } -func mockLease_revoke() { +func mockLeaseRevoke() { fmt.Println("number of keys: 0") } func ExampleLease_revoke() { - forUnitTestsRunInMockedContext(mockLease_revoke, func() { + forUnitTestsRunInMockedContext(mockLeaseRevoke, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -91,12 +91,12 @@ func ExampleLease_revoke() { // Output: number of keys: 0 } -func mockLease_keepAlive() { +func mockLeaseKeepAlive() { fmt.Println("ttl: 5") } func ExampleLease_keepAlive() { - forUnitTestsRunInMockedContext(mockLease_keepAlive, func() { + forUnitTestsRunInMockedContext(mockLeaseKeepAlive, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -132,12 +132,12 @@ func ExampleLease_keepAlive() { // Output: ttl: 5 } -func mockLease_keepAliveOnce() { +func mockLeaseKeepAliveOnce() { fmt.Println("ttl: 5") } func ExampleLease_keepAliveOnce() { - forUnitTestsRunInMockedContext(mockLease_keepAliveOnce, func() { + forUnitTestsRunInMockedContext(mockLeaseKeepAliveOnce, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, diff --git a/tests/integration/clientv3/examples/example_maintenance_test.go b/tests/integration/clientv3/examples/example_maintenance_test.go index ff545e8de7d5..2c901475a7c0 100644 --- a/tests/integration/clientv3/examples/example_maintenance_test.go +++ b/tests/integration/clientv3/examples/example_maintenance_test.go @@ -21,10 +21,10 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockMaintenance_status() {} +func mockMaintenanceStatus() {} func ExampleMaintenance_status() { - forUnitTestsRunInMockedContext(mockMaintenance_status, func() { + forUnitTestsRunInMockedContext(mockMaintenanceStatus, func() { for _, ep := range exampleEndpoints() { cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{ep}, @@ -44,10 +44,10 @@ func ExampleMaintenance_status() { // Output: } -func mockMaintenance_defragment() {} +func mockMaintenanceDefragment() {} func ExampleMaintenance_defragment() { - forUnitTestsRunInMockedContext(mockMaintenance_defragment, func() { + forUnitTestsRunInMockedContext(mockMaintenanceDefragment, func() { for _, ep := range exampleEndpoints() { cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{ep}, diff --git a/tests/integration/clientv3/examples/example_metrics_test.go b/tests/integration/clientv3/examples/example_metrics_test.go index 75b47e53d23e..d467c8281333 100644 --- a/tests/integration/clientv3/examples/example_metrics_test.go +++ b/tests/integration/clientv3/examples/example_metrics_test.go @@ -31,12 +31,12 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockClient_metrics() { +func mockClientMetrics() { fmt.Println(`grpc_client_started_total{grpc_method="Range",grpc_service="etcdserverpb.KV",grpc_type="unary"} 1`) } func ExampleClient_metrics() { - forUnitTestsRunInMockedContext(mockClient_metrics, func() { + forUnitTestsRunInMockedContext(mockClientMetrics, func() { clientMetrics := grpcprom.NewClientMetrics() prometheus.Register(clientMetrics) cli, err := clientv3.New(clientv3.Config{ diff --git a/tests/integration/clientv3/examples/example_test.go b/tests/integration/clientv3/examples/example_test.go index b9b8be461e7c..166f4fbd2aa6 100644 --- a/tests/integration/clientv3/examples/example_test.go +++ b/tests/integration/clientv3/examples/example_test.go @@ -22,10 +22,10 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockConfig_insecure() {} +func mockConfigInsecure() {} func ExampleConfig_insecure() { - forUnitTestsRunInMockedContext(mockConfig_insecure, func() { + forUnitTestsRunInMockedContext(mockConfigInsecure, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -46,10 +46,10 @@ func ExampleConfig_insecure() { // Output: } -func mockConfig_withTLS() {} +func mockConfigWithTLS() {} func ExampleConfig_withTLS() { - forUnitTestsRunInMockedContext(mockConfig_withTLS, func() { + forUnitTestsRunInMockedContext(mockConfigWithTLS, func() { tlsInfo := transport.TLSInfo{ CertFile: "/tmp/test-certs/test-name-1.pem", KeyFile: "/tmp/test-certs/test-name-1-key.pem", diff --git a/tests/integration/clientv3/examples/example_watch_test.go b/tests/integration/clientv3/examples/example_watch_test.go index ac44f8ca38dd..c78d1e7f84e4 100644 --- a/tests/integration/clientv3/examples/example_watch_test.go +++ b/tests/integration/clientv3/examples/example_watch_test.go @@ -23,12 +23,12 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" ) -func mockWatcher_watch() { +func mockWatcherWatch() { fmt.Println(`PUT "foo" : "bar"`) } func ExampleWatcher_watch() { - forUnitTestsRunInMockedContext(mockWatcher_watch, func() { + forUnitTestsRunInMockedContext(mockWatcherWatch, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -48,12 +48,12 @@ func ExampleWatcher_watch() { // PUT "foo" : "bar" } -func mockWatcher_watchWithPrefix() { +func mockWatcherWatchWithPrefix() { fmt.Println(`PUT "foo1" : "bar"`) } func ExampleWatcher_watchWithPrefix() { - forUnitTestsRunInMockedContext(mockWatcher_watchWithPrefix, func() { + forUnitTestsRunInMockedContext(mockWatcherWatchWithPrefix, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -73,14 +73,14 @@ func ExampleWatcher_watchWithPrefix() { // PUT "foo1" : "bar" } -func mockWatcher_watchWithRange() { +func mockWatcherWatchWithRange() { fmt.Println(`PUT "foo1" : "bar1"`) fmt.Println(`PUT "foo2" : "bar2"`) fmt.Println(`PUT "foo3" : "bar3"`) } func ExampleWatcher_watchWithRange() { - forUnitTestsRunInMockedContext(mockWatcher_watchWithRange, func() { + forUnitTestsRunInMockedContext(mockWatcherWatchWithRange, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, @@ -121,12 +121,12 @@ func ExampleWatcher_watchWithRange() { // PUT "foo3" : "bar3" } -func mockWatcher_watchWithProgressNotify() { +func mockWatcherWatchWithProgressNotify() { fmt.Println(`wresp.IsProgressNotify: true`) } func ExampleWatcher_watchWithProgressNotify() { - forUnitTestsRunInMockedContext(mockWatcher_watchWithProgressNotify, func() { + forUnitTestsRunInMockedContext(mockWatcherWatchWithProgressNotify, func() { cli, err := clientv3.New(clientv3.Config{ Endpoints: exampleEndpoints(), DialTimeout: dialTimeout, diff --git a/tests/integration/clientv3/experimental/recipes/v3_barrier_test.go b/tests/integration/clientv3/experimental/recipes/v3_barrier_test.go index b6dfef385a7a..1fefd906d526 100644 --- a/tests/integration/clientv3/experimental/recipes/v3_barrier_test.go +++ b/tests/integration/clientv3/experimental/recipes/v3_barrier_test.go @@ -19,6 +19,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + clientv3 "go.etcd.io/etcd/client/v3" recipe "go.etcd.io/etcd/client/v3/experimental/recipes" integration2 "go.etcd.io/etcd/tests/v3/framework/integration" @@ -40,12 +42,8 @@ func TestBarrierMultiNode(t *testing.T) { func testBarrier(t *testing.T, waiters int, chooseClient func() *clientv3.Client) { b := recipe.NewBarrier(chooseClient(), "test-barrier") - if err := b.Hold(); err != nil { - t.Fatalf("could not hold barrier (%v)", err) - } - if err := b.Hold(); err == nil { - t.Fatalf("able to double-hold barrier") - } + require.NoErrorf(t, b.Hold(), "could not hold barrier") + require.Errorf(t, b.Hold(), "able to double-hold barrier") // put a random key to move the revision forward if _, err := chooseClient().Put(context.Background(), "x", ""); err != nil { @@ -75,9 +73,7 @@ func testBarrier(t *testing.T, waiters int, chooseClient func() *clientv3.Client default: } - if err := b.Release(); err != nil { - t.Fatalf("could not release barrier (%v)", err) - } + require.NoErrorf(t, b.Release(), "could not release barrier") timerC := time.After(time.Duration(waiters*100) * time.Millisecond) for i := 0; i < waiters; i++ { diff --git a/tests/integration/clientv3/experimental/recipes/v3_double_barrier_test.go b/tests/integration/clientv3/experimental/recipes/v3_double_barrier_test.go index d8f610e66c50..9dcc5530cf0b 100644 --- a/tests/integration/clientv3/experimental/recipes/v3_double_barrier_test.go +++ b/tests/integration/clientv3/experimental/recipes/v3_double_barrier_test.go @@ -73,9 +73,7 @@ func TestDoubleBarrier(t *testing.T) { default: } - if err := b.Enter(); err != nil { - t.Fatalf("could not enter last barrier (%v)", err) - } + require.NoErrorf(t, b.Enter(), "could not enter last barrier") timerC := time.After(time.Duration(waiters*100) * time.Millisecond) for i := 0; i < waiters-1; i++ { diff --git a/tests/integration/clientv3/experimental/recipes/v3_lock_test.go b/tests/integration/clientv3/experimental/recipes/v3_lock_test.go index 4a802f72cb47..50b571b15bcc 100644 --- a/tests/integration/clientv3/experimental/recipes/v3_lock_test.go +++ b/tests/integration/clientv3/experimental/recipes/v3_lock_test.go @@ -93,9 +93,7 @@ func testMutexLock(t *testing.T, waiters int, chooseClient func() *clientv3.Clie t.Fatalf("lock %d followers did not wait", i) default: } - if err := m.Unlock(context.TODO()); err != nil { - t.Fatalf("could not release lock (%v)", err) - } + require.NoErrorf(t, m.Unlock(context.TODO()), "could not release lock") } } wg.Wait() @@ -233,9 +231,7 @@ func TestMutexWaitsOnCurrentHolder(t *testing.T) { t.Fatal("failed to receive watch response") } } - if putCounts != 2 { - t.Fatalf("expect 2 put events, but got %v", putCounts) - } + require.Equalf(t, 2, putCounts, "expect 2 put events, but got %v", putCounts) newOwnerSession, err := concurrency.NewSession(cli) if err != nil { @@ -250,12 +246,9 @@ func TestMutexWaitsOnCurrentHolder(t *testing.T) { select { case wrp := <-wch: - if len(wrp.Events) != 1 { - t.Fatalf("expect a event, but got %v events", len(wrp.Events)) - } - if e := wrp.Events[0]; e.Type != mvccpb.PUT { - t.Fatalf("expect a put event on prefix test-mutex, but got event type %v", e.Type) - } + require.Lenf(t, wrp.Events, 1, "expect a event, but got %v events", len(wrp.Events)) + e := wrp.Events[0] + require.Equalf(t, mvccpb.PUT, e.Type, "expect a put event on prefix test-mutex, but got event type %v", e.Type) case <-time.After(time.Second): t.Fatalf("failed to receive a watch response") } @@ -266,12 +259,9 @@ func TestMutexWaitsOnCurrentHolder(t *testing.T) { // ensures the deletion of victim waiter from server side. select { case wrp := <-wch: - if len(wrp.Events) != 1 { - t.Fatalf("expect a event, but got %v events", len(wrp.Events)) - } - if e := wrp.Events[0]; e.Type != mvccpb.DELETE { - t.Fatalf("expect a delete event on prefix test-mutex, but got event type %v", e.Type) - } + require.Lenf(t, wrp.Events, 1, "expect a event, but got %v events", len(wrp.Events)) + e := wrp.Events[0] + require.Equalf(t, mvccpb.DELETE, e.Type, "expect a delete event on prefix test-mutex, but got event type %v", e.Type) case <-time.After(time.Second): t.Fatal("failed to receive a watch response") } @@ -357,18 +347,14 @@ func testRWMutex(t *testing.T, waiters int, chooseClient func() *clientv3.Client t.Fatalf("rlock %d readers did not wait", i) default: } - if err := wl.Unlock(); err != nil { - t.Fatalf("could not release lock (%v)", err) - } + require.NoErrorf(t, wl.Unlock(), "could not release lock") case rl := <-rlockedC: select { case <-wlockedC: t.Fatalf("rlock %d writers did not wait", i) default: } - if err := rl.RUnlock(); err != nil { - t.Fatalf("could not release rlock (%v)", err) - } + require.NoErrorf(t, rl.RUnlock(), "could not release rlock") } } } diff --git a/tests/integration/clientv3/experimental/recipes/v3_queue_test.go b/tests/integration/clientv3/experimental/recipes/v3_queue_test.go index 73ed5552fe2a..89172dfec20b 100644 --- a/tests/integration/clientv3/experimental/recipes/v3_queue_test.go +++ b/tests/integration/clientv3/experimental/recipes/v3_queue_test.go @@ -20,6 +20,8 @@ import ( "sync/atomic" "testing" + "github.com/stretchr/testify/require" + recipe "go.etcd.io/etcd/client/v3/experimental/recipes" integration2 "go.etcd.io/etcd/tests/v3/framework/integration" ) @@ -57,12 +59,8 @@ func TestQueueOneReaderOneWriter(t *testing.T) { q := recipe.NewQueue(etcdc, "testq") for i := 0; i < 5; i++ { s, err := q.Dequeue() - if err != nil { - t.Fatalf("error dequeueing (%v)", err) - } - if s != fmt.Sprintf("%d", i) { - t.Fatalf("expected dequeue value %v, got %v", s, i) - } + require.NoErrorf(t, err, "error dequeueing (%v)", err) + require.Equalf(t, s, fmt.Sprintf("%d", i), "expected dequeue value %v, got %v", s, i) } } @@ -103,25 +101,18 @@ func TestPrQueueOneReaderOneWriter(t *testing.T) { for i := 0; i < 5; i++ { // [0, 2] priority for priority collision to test seq keys pr := uint16(rand.Intn(3)) - if err := q.Enqueue(fmt.Sprintf("%d", pr), pr); err != nil { - t.Fatalf("error enqueuing (%v)", err) - } + require.NoErrorf(t, q.Enqueue(fmt.Sprintf("%d", pr), pr), "error enqueuing") } // read back items; confirm priority order is respected lastPr := -1 for i := 0; i < 5; i++ { s, err := q.Dequeue() - if err != nil { - t.Fatalf("error dequeueing (%v)", err) - } + require.NoErrorf(t, err, "error dequeueing (%v)", err) curPr := 0 - if _, err := fmt.Sscanf(s, "%d", &curPr); err != nil { - t.Fatalf(`error parsing item "%s" (%v)`, s, err) - } - if lastPr > curPr { - t.Fatalf("expected priority %v > %v", curPr, lastPr) - } + _, err = fmt.Sscanf(s, "%d", &curPr) + require.NoErrorf(t, err, `error parsing item "%s" (%v)`, s, err) + require.LessOrEqualf(t, lastPr, curPr, "expected priority %v > %v", curPr, lastPr) } } diff --git a/tests/integration/clientv3/lease/lease_test.go b/tests/integration/clientv3/lease/lease_test.go index 943a169e35db..083d45a3cd99 100644 --- a/tests/integration/clientv3/lease/lease_test.go +++ b/tests/integration/clientv3/lease/lease_test.go @@ -41,9 +41,7 @@ func TestLeaseNotFoundError(t *testing.T) { kv := clus.RandClient() _, err := kv.Put(context.TODO(), "foo", "bar", clientv3.WithLease(clientv3.LeaseID(500))) - if !errors.Is(err, rpctypes.ErrLeaseNotFound) { - t.Fatalf("expected %v, got %v", rpctypes.ErrLeaseNotFound, err) - } + require.ErrorIsf(t, err, rpctypes.ErrLeaseNotFound, "expected %v, got %v", rpctypes.ErrLeaseNotFound, err) } func TestLeaseGrant(t *testing.T) { @@ -57,9 +55,7 @@ func TestLeaseGrant(t *testing.T) { kv := clus.RandClient() _, merr := lapi.Grant(context.Background(), clientv3.MaxLeaseTTL+1) - if !errors.Is(merr, rpctypes.ErrLeaseTTLTooLarge) { - t.Fatalf("err = %v, want %v", merr, rpctypes.ErrLeaseTTLTooLarge) - } + require.ErrorIsf(t, merr, rpctypes.ErrLeaseTTLTooLarge, "err = %v, want %v", merr, rpctypes.ErrLeaseTTLTooLarge) resp, err := lapi.Grant(context.Background(), 10) if err != nil { @@ -67,9 +63,7 @@ func TestLeaseGrant(t *testing.T) { } _, err = kv.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if err != nil { - t.Fatalf("failed to create key with lease %v", err) - } + require.NoErrorf(t, err, "failed to create key with lease %v", err) } func TestLeaseRevoke(t *testing.T) { @@ -93,9 +87,7 @@ func TestLeaseRevoke(t *testing.T) { } _, err = kv.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if !errors.Is(err, rpctypes.ErrLeaseNotFound) { - t.Fatalf("err = %v, want %v", err, rpctypes.ErrLeaseNotFound) - } + require.ErrorIsf(t, err, rpctypes.ErrLeaseNotFound, "err = %v, want %v", err, rpctypes.ErrLeaseNotFound) } func TestLeaseKeepAliveOnce(t *testing.T) { @@ -153,9 +145,7 @@ func TestLeaseKeepAlive(t *testing.T) { t.Errorf("chan is closed, want not closed") } - if kresp == nil { - t.Fatalf("unexpected null response") - } + require.NotNilf(t, kresp, "unexpected null response") if kresp.ID != resp.ID { t.Errorf("ID = %x, want %x", kresp.ID, resp.ID) @@ -189,7 +179,7 @@ func TestLeaseKeepAlive(t *testing.T) { } } -func TestLeaseKeepAliveOneSecond(t *testing.T) { +func TestLeaseKeepAliveSeconds(t *testing.T) { integration2.BeforeTest(t) clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1}) @@ -197,7 +187,7 @@ func TestLeaseKeepAliveOneSecond(t *testing.T) { cli := clus.Client(0) - resp, err := cli.Grant(context.Background(), 1) + resp, err := cli.Grant(context.Background(), 3) if err != nil { t.Errorf("failed to create lease %v", err) } @@ -208,7 +198,7 @@ func TestLeaseKeepAliveOneSecond(t *testing.T) { for i := 0; i < 3; i++ { if _, ok := <-rc; !ok { - t.Errorf("chan is closed, want not closed") + t.Errorf("[%d] chan is closed, want not closed", i) } } } @@ -341,9 +331,7 @@ func TestLeaseKeepAliveFullResponseQueue(t *testing.T) { // expect lease keepalive every 10-second lresp, err := lapi.Grant(context.Background(), 30) - if err != nil { - t.Fatalf("failed to create lease %v", err) - } + require.NoErrorf(t, err, "failed to create lease %v", err) id := lresp.ID old := clientv3.LeaseResponseChSize @@ -354,18 +342,14 @@ func TestLeaseKeepAliveFullResponseQueue(t *testing.T) { // never fetch from response queue, and let it become full _, err = lapi.KeepAlive(context.Background(), id) - if err != nil { - t.Fatalf("failed to keepalive lease %v", err) - } + require.NoErrorf(t, err, "failed to keepalive lease %v", err) // TTL should not be refreshed after 3 seconds // expect keepalive to be triggered after TTL/3 time.Sleep(3 * time.Second) tr, terr := lapi.TimeToLive(context.Background(), id) - if terr != nil { - t.Fatalf("failed to get lease information %v", terr) - } + require.NoErrorf(t, terr, "failed to get lease information %v", terr) if tr.TTL >= 29 { t.Errorf("unexpected kept-alive lease TTL %d", tr.TTL) } @@ -423,9 +407,7 @@ func TestLeaseRevokeNewAfterClose(t *testing.T) { case <-time.After(integration2.RequestWaitTimeout): t.Fatal("le.Revoke took too long") case errMsg := <-errMsgCh: - if errMsg != "" { - t.Fatalf("%v", errMsg) - } + require.Empty(t, errMsg) } } @@ -445,9 +427,7 @@ func TestLeaseKeepAliveCloseAfterDisconnectRevoke(t *testing.T) { rc, kerr := cli.KeepAlive(context.Background(), resp.ID) require.NoError(t, kerr) kresp := <-rc - if kresp.ID != resp.ID { - t.Fatalf("ID = %x, want %x", kresp.ID, resp.ID) - } + require.Equalf(t, kresp.ID, resp.ID, "ID = %x, want %x", kresp.ID, resp.ID) // keep client disconnected clus.Members[0].Stop(t) @@ -489,9 +469,7 @@ func TestLeaseKeepAliveInitTimeout(t *testing.T) { require.NoError(t, kerr) select { case ka, ok := <-rc: - if ok { - t.Fatalf("unexpected keepalive %v, expected closed channel", ka) - } + require.Falsef(t, ok, "unexpected keepalive %v, expected closed channel", ka) case <-time.After(10 * time.Second): t.Fatalf("keepalive channel did not close") } @@ -514,17 +492,14 @@ func TestLeaseKeepAliveTTLTimeout(t *testing.T) { require.NoError(t, err) rc, kerr := cli.KeepAlive(context.Background(), resp.ID) require.NoError(t, kerr) - if kresp := <-rc; kresp.ID != resp.ID { - t.Fatalf("ID = %x, want %x", kresp.ID, resp.ID) - } + kresp := <-rc + require.Equalf(t, kresp.ID, resp.ID, "ID = %x, want %x", kresp.ID, resp.ID) // keep client disconnected clus.Members[0].Stop(t) select { case ka, ok := <-rc: - if ok { - t.Fatalf("unexpected keepalive %v, expected closed channel", ka) - } + require.Falsef(t, ok, "unexpected keepalive %v, expected closed channel", ka) case <-time.After(10 * time.Second): t.Fatalf("keepalive channel did not close") } @@ -559,12 +534,8 @@ func TestLeaseTimeToLive(t *testing.T) { lresp, lerr := lapi.TimeToLive(context.Background(), resp.ID, clientv3.WithAttachedKeys()) require.NoError(t, lerr) - if lresp.ID != resp.ID { - t.Fatalf("leaseID expected %d, got %d", resp.ID, lresp.ID) - } - if lresp.GrantedTTL != int64(10) { - t.Fatalf("GrantedTTL expected %d, got %d", 10, lresp.GrantedTTL) - } + require.Equalf(t, lresp.ID, resp.ID, "leaseID expected %d, got %d", resp.ID, lresp.ID) + require.Equalf(t, int64(10), lresp.GrantedTTL, "GrantedTTL expected %d, got %d", 10, lresp.GrantedTTL) if lresp.TTL == 0 || lresp.TTL > lresp.GrantedTTL { t.Fatalf("unexpected TTL %d (granted %d)", lresp.TTL, lresp.GrantedTTL) } @@ -573,15 +544,11 @@ func TestLeaseTimeToLive(t *testing.T) { ks[i] = string(lresp.Keys[i]) } sort.Strings(ks) - if !reflect.DeepEqual(ks, keys) { - t.Fatalf("keys expected %v, got %v", keys, ks) - } + require.Truef(t, reflect.DeepEqual(ks, keys), "keys expected %v, got %v", keys, ks) lresp, lerr = lapi.TimeToLive(context.Background(), resp.ID) require.NoError(t, lerr) - if len(lresp.Keys) != 0 { - t.Fatalf("unexpected keys %+v", lresp.Keys) - } + require.Emptyf(t, lresp.Keys, "unexpected keys %+v", lresp.Keys) } func TestLeaseTimeToLiveLeaseNotFound(t *testing.T) { @@ -602,21 +569,11 @@ func TestLeaseTimeToLiveLeaseNotFound(t *testing.T) { lresp, err := cli.TimeToLive(context.Background(), resp.ID) // TimeToLive() should return a response with TTL=-1. - if err != nil { - t.Fatalf("expected err to be nil") - } - if lresp == nil { - t.Fatalf("expected lresp not to be nil") - } - if lresp.ResponseHeader == nil { - t.Fatalf("expected ResponseHeader not to be nil") - } - if lresp.ID != resp.ID { - t.Fatalf("expected Lease ID %v, but got %v", resp.ID, lresp.ID) - } - if lresp.TTL != -1 { - t.Fatalf("expected TTL %v, but got %v", lresp.TTL, lresp.TTL) - } + require.NoErrorf(t, err, "expected err to be nil") + require.NotNilf(t, lresp, "expected lresp not to be nil") + require.NotNilf(t, lresp.ResponseHeader, "expected ResponseHeader not to be nil") + require.Equalf(t, lresp.ID, resp.ID, "expected Lease ID %v, but got %v", resp.ID, lresp.ID) + require.Equalf(t, lresp.TTL, int64(-1), "expected TTL %v, but got %v", lresp.TTL, lresp.TTL) } func TestLeaseLeases(t *testing.T) { @@ -638,13 +595,9 @@ func TestLeaseLeases(t *testing.T) { resp, err := cli.Leases(context.Background()) require.NoError(t, err) - if len(resp.Leases) != 5 { - t.Fatalf("len(resp.Leases) expected 5, got %d", len(resp.Leases)) - } + require.Lenf(t, resp.Leases, 5, "len(resp.Leases) expected 5, got %d", len(resp.Leases)) for i := range resp.Leases { - if ids[i] != resp.Leases[i].ID { - t.Fatalf("#%d: lease ID expected %d, got %d", i, ids[i], resp.Leases[i].ID) - } + require.Equalf(t, ids[i], resp.Leases[i].ID, "#%d: lease ID expected %d, got %d", i, ids[i], resp.Leases[i].ID) } } @@ -686,9 +639,7 @@ func TestLeaseRenewLostQuorum(t *testing.T) { select { case _, ok := <-ka: - if !ok { - t.Fatalf("keepalive closed") - } + require.Truef(t, ok, "keepalive closed") case <-time.After(time.Duration(r.TTL) * time.Second): t.Fatalf("timed out waiting for keepalive") } @@ -710,9 +661,7 @@ func TestLeaseKeepAliveLoopExit(t *testing.T) { _, err = cli.KeepAlive(ctx, resp.ID) var keepAliveHaltedErr clientv3.ErrKeepAliveHalted - if !errors.As(err, &keepAliveHaltedErr) { - t.Fatalf("expected %T, got %v(%T)", clientv3.ErrKeepAliveHalted{}, err, err) - } + require.ErrorAsf(t, err, &keepAliveHaltedErr, "expected %T, got %v(%T)", clientv3.ErrKeepAliveHalted{}, err, err) } // TestV3LeaseFailureOverlap issues Grant and KeepAlive requests to a cluster @@ -813,17 +762,13 @@ func TestLeaseWithRequireLeader(t *testing.T) { select { case resp, ok := <-kaReqLeader: - if ok { - t.Fatalf("expected closed require leader, got response %+v", resp) - } + require.Falsef(t, ok, "expected closed require leader, got response %+v", resp) case <-time.After(5 * time.Second): t.Fatal("keepalive with require leader took too long to close") } select { case _, ok := <-kaWait: - if !ok { - t.Fatalf("got closed channel with no require leader, expected non-closed") - } + require.Truef(t, ok, "got closed channel with no require leader, expected non-closed") case <-time.After(10 * time.Millisecond): // wait some to detect any closes happening soon after kaReqLeader closing } diff --git a/tests/integration/clientv3/lease/leasing_test.go b/tests/integration/clientv3/lease/leasing_test.go index 9d6d77b353f5..1751fdeb771e 100644 --- a/tests/integration/clientv3/lease/leasing_test.go +++ b/tests/integration/clientv3/lease/leasing_test.go @@ -102,9 +102,7 @@ func TestLeasingInterval(t *testing.T) { resp, err := lkv.Get(context.TODO(), "abc/", clientv3.WithPrefix()) require.NoError(t, err) - if len(resp.Kvs) != 3 { - t.Fatalf("expected keys %+v, got response keys %+v", keys, resp.Kvs) - } + require.Lenf(t, resp.Kvs, 3, "expected keys %+v, got response keys %+v", keys, resp.Kvs) // load into cache _, err = lkv.Get(context.TODO(), "abc/a") @@ -113,9 +111,7 @@ func TestLeasingInterval(t *testing.T) { // get when prefix is also a cached key resp, err = lkv.Get(context.TODO(), "abc/a", clientv3.WithPrefix()) require.NoError(t, err) - if len(resp.Kvs) != 2 { - t.Fatalf("expected keys %+v, got response keys %+v", keys, resp.Kvs) - } + require.Lenf(t, resp.Kvs, 2, "expected keys %+v, got response keys %+v", keys, resp.Kvs) } // TestLeasingPutInvalidateNew checks the leasing KV updates its cache on a Put to a new key. @@ -137,9 +133,7 @@ func TestLeasingPutInvalidateNew(t *testing.T) { require.NoError(t, err) cResp, cerr := clus.Client(0).Get(context.TODO(), "k") require.NoError(t, cerr) - if !reflect.DeepEqual(lkvResp, cResp) { - t.Fatalf(`expected %+v, got response %+v`, cResp, lkvResp) - } + require.Truef(t, reflect.DeepEqual(lkvResp, cResp), `expected %+v, got response %+v`, cResp, lkvResp) } // TestLeasingPutInvalidateExisting checks the leasing KV updates its cache on a Put to an existing key. @@ -164,9 +158,7 @@ func TestLeasingPutInvalidateExisting(t *testing.T) { require.NoError(t, err) cResp, cerr := clus.Client(0).Get(context.TODO(), "k") require.NoError(t, cerr) - if !reflect.DeepEqual(lkvResp, cResp) { - t.Fatalf(`expected %+v, got response %+v`, cResp, lkvResp) - } + require.Truef(t, reflect.DeepEqual(lkvResp, cResp), `expected %+v, got response %+v`, cResp, lkvResp) } // TestLeasingGetNoLeaseTTL checks a key with a TTL is not leased. @@ -218,9 +210,7 @@ func TestLeasingGetSerializable(t *testing.T) { // don't necessarily try to acquire leasing key ownership for new key resp, err := lkv.Get(context.TODO(), "uncached", clientv3.WithSerializable()) require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf(`expected no keys, got response %+v`, resp) - } + require.Emptyf(t, resp.Kvs, `expected no keys, got response %+v`, resp) clus.Members[0].Stop(t) @@ -414,9 +404,7 @@ func TestLeasingDeleteOwner(t *testing.T) { resp, err := lkv.Get(context.TODO(), "k") require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf(`expected "k" to be deleted, got response %+v`, resp) - } + require.Emptyf(t, resp.Kvs, `expected "k" to be deleted, got response %+v`, resp) // try to double delete _, err = lkv.Delete(context.TODO(), "k") require.NoError(t, err) @@ -447,9 +435,7 @@ func TestLeasingDeleteNonOwner(t *testing.T) { // key should be removed from lkv1 resp, err := lkv1.Get(context.TODO(), "k") require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf(`expected "k" to be deleted, got response %+v`, resp) - } + require.Emptyf(t, resp.Kvs, `expected "k" to be deleted, got response %+v`, resp) } func TestLeasingOverwriteResponse(t *testing.T) { @@ -532,9 +518,8 @@ func TestLeasingTxnOwnerGetRange(t *testing.T) { tresp, terr := lkv.Txn(context.TODO()).Then(clientv3.OpGet("k-", clientv3.WithPrefix())).Commit() require.NoError(t, terr) - if resp := tresp.Responses[0].GetResponseRange(); len(resp.Kvs) != keyCount { - t.Fatalf("expected %d keys, got response %+v", keyCount, resp.Kvs) - } + resp := tresp.Responses[0].GetResponseRange() + require.Equalf(t, len(resp.Kvs), keyCount, "expected %d keys, got response %+v", keyCount, resp.Kvs) } func TestLeasingTxnOwnerGet(t *testing.T) { @@ -596,22 +581,14 @@ func TestLeasingTxnOwnerGet(t *testing.T) { Else(elseOps...).Commit() require.NoError(t, terr) - if tresp.Succeeded != useThen { - t.Fatalf("expected succeeded=%v, got tresp=%+v", useThen, tresp) - } - if len(tresp.Responses) != len(ops) { - t.Fatalf("expected %d responses, got %d", len(ops), len(tresp.Responses)) - } + require.Equalf(t, tresp.Succeeded, useThen, "expected succeeded=%v, got tresp=%+v", useThen, tresp) + require.Lenf(t, ops, len(tresp.Responses), "expected %d responses, got %d", len(ops), len(tresp.Responses)) wrev := presps[len(presps)-1].Header.Revision - if tresp.Header.Revision < wrev { - t.Fatalf("expected header revision >= %d, got %d", wrev, tresp.Header.Revision) - } + require.GreaterOrEqualf(t, tresp.Header.Revision, wrev, "expected header revision >= %d, got %d", wrev, tresp.Header.Revision) for i := range ops { k := fmt.Sprintf("k-%d", i) rr := tresp.Responses[i].GetResponseRange() - if rr == nil { - t.Fatalf("expected get response, got %+v", tresp.Responses[i]) - } + require.NotNilf(t, rr, "expected get response, got %+v", tresp.Responses[i]) if string(rr.Kvs[0].Key) != k || string(rr.Kvs[0].Value) != k+k { t.Errorf(`expected key for %q, got %+v`, k, rr.Kvs) } @@ -637,18 +614,14 @@ func TestLeasingTxnOwnerDeleteRange(t *testing.T) { // cache in lkv resp, err := lkv.Get(context.TODO(), "k-", clientv3.WithPrefix()) require.NoError(t, err) - if len(resp.Kvs) != keyCount { - t.Fatalf("expected %d keys, got %d", keyCount, len(resp.Kvs)) - } + require.Equalf(t, len(resp.Kvs), keyCount, "expected %d keys, got %d", keyCount, len(resp.Kvs)) _, terr := lkv.Txn(context.TODO()).Then(clientv3.OpDelete("k-", clientv3.WithPrefix())).Commit() require.NoError(t, terr) resp, err = lkv.Get(context.TODO(), "k-", clientv3.WithPrefix()) require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf("expected no keys, got %d", len(resp.Kvs)) - } + require.Emptyf(t, resp.Kvs, "expected no keys, got %d", len(resp.Kvs)) } func TestLeasingTxnOwnerDelete(t *testing.T) { @@ -672,9 +645,7 @@ func TestLeasingTxnOwnerDelete(t *testing.T) { resp, err := lkv.Get(context.TODO(), "k") require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf("expected no keys, got %d", len(resp.Kvs)) - } + require.Emptyf(t, resp.Kvs, "expected no keys, got %d", len(resp.Kvs)) } func TestLeasingTxnOwnerIf(t *testing.T) { @@ -794,9 +765,8 @@ func TestLeasingTxnCancel(t *testing.T) { time.Sleep(100 * time.Millisecond) cancel() }() - if _, err := lkv2.Txn(ctx).Then(clientv3.OpPut("k", "v")).Commit(); !errors.Is(err, context.Canceled) { - t.Fatalf("expected %v, got %v", context.Canceled, err) - } + _, err = lkv2.Txn(ctx).Then(clientv3.OpPut("k", "v")).Commit() + require.ErrorIsf(t, err, context.Canceled, "expected %v, got %v", context.Canceled, err) } func TestLeasingTxnNonOwnerPut(t *testing.T) { @@ -860,9 +830,7 @@ func TestLeasingTxnNonOwnerPut(t *testing.T) { c++ } } - if c != 3 { - t.Fatalf("expected 3 put events, got %+v", evs) - } + require.Equalf(t, 3, c, "expected 3 put events, got %+v", evs) } // TestLeasingTxnRandIfThenOrElse randomly leases keys two separate clients, then @@ -946,9 +914,7 @@ func TestLeasingTxnRandIfThenOrElse(t *testing.T) { tresp, terr := lkv1.Txn(context.TODO()).If(cmps...).Then(thenOps...).Else(elseOps...).Commit() require.NoError(t, terr) // cmps always succeed - if tresp.Succeeded != useThen { - t.Fatalf("expected succeeded=%v, got tresp=%+v", useThen, tresp) - } + require.Equalf(t, tresp.Succeeded, useThen, "expected succeeded=%v, got tresp=%+v", useThen, tresp) // get should match what was put checkPuts := func(s string, kv clientv3.KV) { for _, op := range ops { @@ -982,9 +948,8 @@ func TestLeasingOwnerPutError(t *testing.T) { clus.Members[0].Stop(t) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() - if resp, err := lkv.Put(ctx, "k", "v"); err == nil { - t.Fatalf("expected error, got response %+v", resp) - } + resp, err := lkv.Put(ctx, "k", "v") + require.Errorf(t, err, "expected error, got response %+v", resp) } func TestLeasingOwnerDeleteError(t *testing.T) { @@ -1002,9 +967,8 @@ func TestLeasingOwnerDeleteError(t *testing.T) { clus.Members[0].Stop(t) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() - if resp, err := lkv.Delete(ctx, "k"); err == nil { - t.Fatalf("expected error, got response %+v", resp) - } + resp, err := lkv.Delete(ctx, "k") + require.Errorf(t, err, "expected error, got response %+v", resp) } func TestLeasingNonOwnerPutError(t *testing.T) { @@ -1019,9 +983,8 @@ func TestLeasingNonOwnerPutError(t *testing.T) { clus.Members[0].Stop(t) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() - if resp, err := lkv.Put(ctx, "k", "v"); err == nil { - t.Fatalf("expected error, got response %+v", resp) - } + resp, err := lkv.Put(ctx, "k", "v") + require.Errorf(t, err, "expected error, got response %+v", resp) } func TestLeasingOwnerDeletePrefix(t *testing.T) { @@ -1057,9 +1020,7 @@ func testLeasingOwnerDelete(t *testing.T, del clientv3.Op) { for i := 0; i < 8; i++ { resp, err := lkv.Get(context.TODO(), fmt.Sprintf("key/%d", i)) require.NoError(t, err) - if len(resp.Kvs) != 0 { - t.Fatalf("expected no keys on key/%d, got %+v", i, resp) - } + require.Emptyf(t, resp.Kvs, "expected no keys on key/%d, got %+v", i, resp) } // confirm keys were deleted atomically @@ -1070,9 +1031,8 @@ func testLeasingOwnerDelete(t *testing.T, del clientv3.Op) { clientv3.WithRev(delResp.Header.Revision), clientv3.WithPrefix()) - if wresp := <-w; len(wresp.Events) != 8 { - t.Fatalf("expected %d delete events,got %d", 8, len(wresp.Events)) - } + wresp := <-w + require.Lenf(t, wresp.Events, 8, "expected %d delete events,got %d", 8, len(wresp.Events)) } func TestLeasingDeleteRangeBounds(t *testing.T) { @@ -1102,9 +1062,7 @@ func TestLeasingDeleteRangeBounds(t *testing.T) { for _, k := range []string{"j", "m"} { resp, geterr := clus.Client(0).Get(context.TODO(), "0/"+k, clientv3.WithPrefix()) require.NoError(t, geterr) - if len(resp.Kvs) != 1 { - t.Fatalf("expected leasing key, got %+v", resp) - } + require.Lenf(t, resp.Kvs, 1, "expected leasing key, got %+v", resp) } // j and m should still have leases registered since not under k* @@ -1220,15 +1178,11 @@ func TestLeasingPutGetDeleteConcurrent(t *testing.T) { resp, err := lkvs[0].Get(context.TODO(), "k") require.NoError(t, err) - if len(resp.Kvs) > 0 { - t.Fatalf("expected no kvs, got %+v", resp.Kvs) - } + require.Emptyf(t, resp.Kvs, "expected no kvs, got %+v", resp.Kvs) resp, err = clus.Client(0).Get(context.TODO(), "k") require.NoError(t, err) - if len(resp.Kvs) > 0 { - t.Fatalf("expected no kvs, got %+v", resp.Kvs) - } + require.Emptyf(t, resp.Kvs, "expected no kvs, got %+v", resp.Kvs) } // TestLeasingReconnectOwnerRevoke checks that revocation works if @@ -1330,9 +1284,7 @@ func TestLeasingReconnectOwnerRevokeCompact(t *testing.T) { require.NoError(t, err) resp, err := lkv1.Get(cctx, "k") require.NoError(t, err) - if string(resp.Kvs[0].Value) != "v" { - t.Fatalf(`expected "v" value, got %+v`, resp) - } + require.Equalf(t, "v", string(resp.Kvs[0].Value), `expected "v" value, got %+v`, resp) } // TestLeasingReconnectOwnerConsistency checks a write error on an owner will @@ -1399,9 +1351,7 @@ func TestLeasingReconnectOwnerConsistency(t *testing.T) { require.NoError(t, lerr) cresp, cerr := clus.Client(0).Get(context.TODO(), "k") require.NoError(t, cerr) - if !reflect.DeepEqual(lresp.Kvs, cresp.Kvs) { - t.Fatalf("expected %+v, got %+v", cresp, lresp) - } + require.Truef(t, reflect.DeepEqual(lresp.Kvs, cresp.Kvs), "expected %+v, got %+v", cresp, lresp) } func TestLeasingTxnAtomicCache(t *testing.T) { @@ -1561,9 +1511,7 @@ func TestLeasingReconnectNonOwnerGet(t *testing.T) { require.NoError(t, lerr) cresp, cerr := clus.Client(0).Get(context.TODO(), k) require.NoError(t, cerr) - if !reflect.DeepEqual(lresp.Kvs, cresp.Kvs) { - t.Fatalf("expected %+v, got %+v", cresp, lresp) - } + require.Truef(t, reflect.DeepEqual(lresp.Kvs, cresp.Kvs), "expected %+v, got %+v", cresp, lresp) } } @@ -1591,9 +1539,7 @@ func TestLeasingTxnRangeCmp(t *testing.T) { cmp := clientv3.Compare(clientv3.Version("k").WithPrefix(), "=", 1) tresp, terr := lkv.Txn(context.TODO()).If(cmp).Commit() require.NoError(t, terr) - if tresp.Succeeded { - t.Fatalf("expected Succeeded=false, got %+v", tresp) - } + require.Falsef(t, tresp.Succeeded, "expected Succeeded=false, got %+v", tresp) } func TestLeasingDo(t *testing.T) { @@ -1631,9 +1577,7 @@ func TestLeasingDo(t *testing.T) { gresp, err := clus.Client(0).Get(context.TODO(), "a", clientv3.WithPrefix()) require.NoError(t, err) - if len(gresp.Kvs) != 0 { - t.Fatalf("expected no keys, got %+v", gresp.Kvs) - } + require.Emptyf(t, gresp.Kvs, "expected no keys, got %+v", gresp.Kvs) } func TestLeasingTxnOwnerPutBranch(t *testing.T) { @@ -1667,9 +1611,7 @@ func TestLeasingTxnOwnerPutBranch(t *testing.T) { require.NoError(t, err) clusResp, err := clus.Client(1).Get(context.TODO(), k) require.NoError(t, err) - if !reflect.DeepEqual(clusResp.Kvs, lkvResp.Kvs) { - t.Fatalf("expected %+v, got %+v", clusResp.Kvs, lkvResp.Kvs) - } + require.Truef(t, reflect.DeepEqual(clusResp.Kvs, lkvResp.Kvs), "expected %+v, got %+v", clusResp.Kvs, lkvResp.Kvs) } } @@ -1751,9 +1693,7 @@ func TestLeasingSessionExpire(t *testing.T) { resp, err := lkv.Get(context.TODO(), "abc") require.NoError(t, err) - if v := string(resp.Kvs[0].Value); v != "def" { - t.Fatalf("expected %q, got %q", "v", v) - } + require.Equal(t, "def", string(resp.Kvs[0].Value)) } func TestLeasingSessionExpireCancel(t *testing.T) { diff --git a/tests/integration/clientv3/maintenance_test.go b/tests/integration/clientv3/maintenance_test.go index 7283006d26c9..dc46bf1823a1 100644 --- a/tests/integration/clientv3/maintenance_test.go +++ b/tests/integration/clientv3/maintenance_test.go @@ -31,6 +31,8 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" "go.etcd.io/etcd/api/v3/version" @@ -230,9 +232,22 @@ func testMaintenanceSnapshotTimeout(t *testing.T, snapshot func(context.Context, time.Sleep(2 * time.Second) _, err = io.Copy(io.Discard, rc2) - if err != nil && !IsClientTimeout(err) { - t.Errorf("expected client timeout, got %v", err) + if IsClientTimeout(err) { + return } + // Assumes the client receives a single message header and then + // waits for the payload body. If the context is canceled before + // the payload arrives, the client will read io.EOF. However, the + // grpc-go client converts this into io.ErrUnexpectedEOF with an + // internal error code. Ideally, grpc-go might return context.Canceled + // instead, but it's unclear if that's feasible. Let's explicitly + // check for this error in the test code. + // + // REF: https://github.com/grpc/grpc-go/blob/6821606f351799b026fda1e6ba143315e6c1e620/rpc_util.go#L644 + // + // Once https://github.com/grpc/grpc-go/issues/8281 is fixed, we should + // revert this change. See more discussion in https://github.com/etcd-io/etcd/pull/19833. + assert.ErrorIs(t, status.Error(codes.Internal, io.ErrUnexpectedEOF.Error()), err) } // TestMaintenanceSnapshotWithVersionErrorInflight ensures that ReaderCloser returned by SnapshotWithVersion function @@ -327,7 +342,7 @@ func TestMaintenanceSnapshotWithVersionVersion(t *testing.T) { resp, err := clus.RandClient().SnapshotWithVersion(context.Background()) require.NoError(t, err) defer resp.Snapshot.Close() - if resp.Version != "3.6.0" { + if resp.Version != "3.7.0" { t.Errorf("unexpected version, expected %q, got %q", version.Version, resp.Version) } } diff --git a/tests/integration/clientv3/naming/endpoints_test.go b/tests/integration/clientv3/naming/endpoints_test.go index 3c93c6c7d191..649c0581348d 100644 --- a/tests/integration/clientv3/naming/endpoints_test.go +++ b/tests/integration/clientv3/naming/endpoints_test.go @@ -61,14 +61,10 @@ func TestEndpointManager(t *testing.T) { Endpoint: e1, } - if !reflect.DeepEqual(us[0], wu) { - t.Fatalf("up = %#v, want %#v", us[0], wu) - } + require.Truef(t, reflect.DeepEqual(us[0], wu), "up = %#v, want %#v", us[0], wu) err = em.DeleteEndpoint(context.TODO(), "foo/a1") - if err != nil { - t.Fatalf("failed to udpate %v", err) - } + require.NoErrorf(t, err, "failed to udpate %v", err) us = <-w if us == nil { @@ -80,9 +76,7 @@ func TestEndpointManager(t *testing.T) { Key: "foo/a1", } - if !reflect.DeepEqual(us[0], wu) { - t.Fatalf("up = %#v, want %#v", us[1], wu) - } + require.Truef(t, reflect.DeepEqual(us[0], wu), "up = %#v, want %#v", us[0], wu) } // TestEndpointManagerAtomicity ensures the resolver will initialize @@ -112,9 +106,7 @@ func TestEndpointManagerAtomicity(t *testing.T) { require.NoError(t, err) updates := <-w - if len(updates) != 2 { - t.Fatalf("expected two updates, got %+v", updates) - } + require.Lenf(t, updates, 2, "expected two updates, got %+v", updates) _, err = c.Txn(context.TODO()).Then(etcd.OpDelete("foo/host"), etcd.OpDelete("foo/host2")).Commit() require.NoError(t, err) @@ -155,15 +147,9 @@ func TestEndpointManagerCRUD(t *testing.T) { if err != nil { t.Fatal("failed to list foo") } - if len(eps) != 2 { - t.Fatalf("unexpected the number of endpoints: %d", len(eps)) - } - if !reflect.DeepEqual(eps[k1], e1) { - t.Fatalf("unexpected endpoints: %s", k1) - } - if !reflect.DeepEqual(eps[k2], e2) { - t.Fatalf("unexpected endpoints: %s", k2) - } + require.Lenf(t, eps, 2, "unexpected the number of endpoints: %d", len(eps)) + require.Truef(t, reflect.DeepEqual(eps[k1], e1), "unexpected endpoints: %s", k1) + require.Truef(t, reflect.DeepEqual(eps[k2], e2), "unexpected endpoints: %s", k2) // Delete err = em.DeleteEndpoint(context.TODO(), k1) @@ -175,12 +161,8 @@ func TestEndpointManagerCRUD(t *testing.T) { if err != nil { t.Fatal("failed to list foo") } - if len(eps) != 1 { - t.Fatalf("unexpected the number of endpoints: %d", len(eps)) - } - if !reflect.DeepEqual(eps[k2], e2) { - t.Fatalf("unexpected endpoints: %s", k2) - } + require.Lenf(t, eps, 1, "unexpected the number of endpoints: %d", len(eps)) + require.Truef(t, reflect.DeepEqual(eps[k2], e2), "unexpected endpoints: %s", k2) // Update k3 := "foo/a3" @@ -198,10 +180,6 @@ func TestEndpointManagerCRUD(t *testing.T) { if err != nil { t.Fatal("failed to list foo") } - if len(eps) != 1 { - t.Fatalf("unexpected the number of endpoints: %d", len(eps)) - } - if !reflect.DeepEqual(eps[k3], e3) { - t.Fatalf("unexpected endpoints: %s", k3) - } + require.Lenf(t, eps, 1, "unexpected the number of endpoints: %d", len(eps)) + require.Truef(t, reflect.DeepEqual(eps[k3], e3), "unexpected endpoints: %s", k3) } diff --git a/tests/integration/clientv3/naming/resolver_test.go b/tests/integration/clientv3/naming/resolver_test.go index f39dbb2c2341..40dc177214be 100644 --- a/tests/integration/clientv3/naming/resolver_test.go +++ b/tests/integration/clientv3/naming/resolver_test.go @@ -104,26 +104,20 @@ func testEtcdGRPCResolver(t *testing.T, lbPolicy string) { t.Logf("Response: %v", string(resp.GetPayload().GetBody())) - if resp.GetPayload() == nil { - t.Fatalf("unexpected response from foo: %s", resp.GetPayload().GetBody()) - } + require.NotNilf(t, resp.GetPayload(), "unexpected response from foo: %s", resp.GetPayload().GetBody()) lastResponse = resp.GetPayload().GetBody() } // If the load balancing policy is pick first then return payload should equal number of requests t.Logf("Last response: %v", string(lastResponse)) if lbPolicy == "pick_first" { - if string(lastResponse) != "3500" { - t.Fatalf("unexpected total responses from foo: %s", lastResponse) - } + require.Equalf(t, "3500", string(lastResponse), "unexpected total responses from foo: %s", lastResponse) } // If the load balancing policy is round robin we should see roughly half total requests served by each server if lbPolicy == "round_robin" { responses, err := strconv.Atoi(string(lastResponse)) - if err != nil { - t.Fatalf("couldn't convert to int: %s", lastResponse) - } + require.NoErrorf(t, err, "couldn't convert to int: %s", lastResponse) // Allow 25% tolerance as round robin is not perfect and we don't want the test to flake expected := float64(totalRequests) * 0.5 diff --git a/tests/integration/clientv3/ordering_util_test.go b/tests/integration/clientv3/ordering_util_test.go index bb46989e4798..f7513190d345 100644 --- a/tests/integration/clientv3/ordering_util_test.go +++ b/tests/integration/clientv3/ordering_util_test.go @@ -138,7 +138,5 @@ func TestUnresolvableOrderViolation(t *testing.T) { time.Sleep(1 * time.Second) // give enough time for operation _, err = OrderingKv.Get(ctx, "foo", clientv3.WithSerializable()) - if !errors.Is(err, ordering.ErrNoGreaterRev) { - t.Fatalf("expected %v, got %v", ordering.ErrNoGreaterRev, err) - } + require.ErrorIsf(t, err, ordering.ErrNoGreaterRev, "expected %v, got %v", ordering.ErrNoGreaterRev, err) } diff --git a/tests/integration/clientv3/snapshot/v3_snapshot_test.go b/tests/integration/clientv3/snapshot/v3_snapshot_test.go index c39b092e1cf6..a306718839ea 100644 --- a/tests/integration/clientv3/snapshot/v3_snapshot_test.go +++ b/tests/integration/clientv3/snapshot/v3_snapshot_test.go @@ -44,14 +44,10 @@ func TestSaveSnapshotFilePermissions(t *testing.T) { defer os.RemoveAll(dbPath) dbInfo, err := os.Stat(dbPath) - if err != nil { - t.Fatalf("failed to get test snapshot file status: %v", err) - } + require.NoErrorf(t, err, "failed to get test snapshot file status: %v", err) actualFileMode := dbInfo.Mode() - if expectedFileMode != actualFileMode { - t.Fatalf("expected test snapshot file mode %s, got %s:", expectedFileMode, actualFileMode) - } + require.Equalf(t, expectedFileMode, actualFileMode, "expected test snapshot file mode %s, got %s:", expectedFileMode, actualFileMode) } // TestSaveSnapshotVersion ensures that the snapshot returns proper storage version. @@ -67,9 +63,7 @@ func TestSaveSnapshotVersion(t *testing.T) { ver, dbPath := createSnapshotFile(t, cfg, kvs) defer os.RemoveAll(dbPath) - if ver != "3.6.0" { - t.Fatalf("expected snapshot version %s, got %s:", "3.6.0", ver) - } + require.Equalf(t, "3.7.0", ver, "expected snapshot version %s, got %s:", "3.7.0", ver) } type kv struct { diff --git a/tests/integration/clientv3/user_test.go b/tests/integration/clientv3/user_test.go index 201740fefcfa..8f3ef838065a 100644 --- a/tests/integration/clientv3/user_test.go +++ b/tests/integration/clientv3/user_test.go @@ -40,19 +40,13 @@ func TestUserError(t *testing.T) { require.NoError(t, err) _, err = authapi.UserAdd(context.TODO(), "foo", "bar") - if !errors.Is(err, rpctypes.ErrUserAlreadyExist) { - t.Fatalf("expected %v, got %v", rpctypes.ErrUserAlreadyExist, err) - } + require.ErrorIsf(t, err, rpctypes.ErrUserAlreadyExist, "expected %v, got %v", rpctypes.ErrUserAlreadyExist, err) _, err = authapi.UserDelete(context.TODO(), "not-exist-user") - if !errors.Is(err, rpctypes.ErrUserNotFound) { - t.Fatalf("expected %v, got %v", rpctypes.ErrUserNotFound, err) - } + require.ErrorIsf(t, err, rpctypes.ErrUserNotFound, "expected %v, got %v", rpctypes.ErrUserNotFound, err) _, err = authapi.UserGrantRole(context.TODO(), "foo", "test-role-does-not-exist") - if !errors.Is(err, rpctypes.ErrRoleNotFound) { - t.Fatalf("expected %v, got %v", rpctypes.ErrRoleNotFound, err) - } + require.ErrorIsf(t, err, rpctypes.ErrRoleNotFound, "expected %v, got %v", rpctypes.ErrRoleNotFound, err) } func TestAddUserAfterDelete(t *testing.T) { @@ -115,9 +109,8 @@ func TestUserErrorAuth(t *testing.T) { authSetupRoot(t, authapi.Auth) // unauthenticated client - if _, err := authapi.UserAdd(context.TODO(), "foo", "bar"); !errors.Is(err, rpctypes.ErrUserEmpty) { - t.Fatalf("expected %v, got %v", rpctypes.ErrUserEmpty, err) - } + _, err := authapi.UserAdd(context.TODO(), "foo", "bar") + require.ErrorIsf(t, err, rpctypes.ErrUserEmpty, "expected %v, got %v", rpctypes.ErrUserEmpty, err) // wrong id or password cfg := clientv3.Config{ @@ -126,13 +119,11 @@ func TestUserErrorAuth(t *testing.T) { DialOptions: []grpc.DialOption{grpc.WithBlock()}, } cfg.Username, cfg.Password = "wrong-id", "123" - if _, err := integration2.NewClient(t, cfg); !errors.Is(err, rpctypes.ErrAuthFailed) { - t.Fatalf("expected %v, got %v", rpctypes.ErrAuthFailed, err) - } + _, err = integration2.NewClient(t, cfg) + require.ErrorIsf(t, err, rpctypes.ErrAuthFailed, "expected %v, got %v", rpctypes.ErrAuthFailed, err) cfg.Username, cfg.Password = "root", "wrong-pass" - if _, err := integration2.NewClient(t, cfg); !errors.Is(err, rpctypes.ErrAuthFailed) { - t.Fatalf("expected %v, got %v", rpctypes.ErrAuthFailed, err) - } + _, err = integration2.NewClient(t, cfg) + require.ErrorIsf(t, err, rpctypes.ErrAuthFailed, "expected %v, got %v", rpctypes.ErrAuthFailed, err) cfg.Username, cfg.Password = "root", "123" authed, err := integration2.NewClient(t, cfg) diff --git a/tests/integration/clientv3/watch_fragment_test.go b/tests/integration/clientv3/watch_fragment_test.go index 81450f5f9aa8..fd58fed8180e 100644 --- a/tests/integration/clientv3/watch_fragment_test.go +++ b/tests/integration/clientv3/watch_fragment_test.go @@ -23,6 +23,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "go.etcd.io/etcd/client/pkg/v3/testutil" clientv3 "go.etcd.io/etcd/client/v3" integration2 "go.etcd.io/etcd/tests/v3/framework/integration" @@ -87,9 +89,8 @@ func testWatchFragment(t *testing.T, fragment, exceedRecvLimit bool) { }(i) } for i := 0; i < 10; i++ { - if err := <-errc; err != nil { - t.Fatalf("failed to put: %v", err) - } + err := <-errc + require.NoErrorf(t, err, "failed to put") } opts := []clientv3.OpOption{clientv3.WithPrefix(), clientv3.WithRev(1)} @@ -103,23 +104,15 @@ func testWatchFragment(t *testing.T, fragment, exceedRecvLimit bool) { case ws := <-wch: // without fragment, should exceed gRPC client receive limit if !fragment && exceedRecvLimit { - if len(ws.Events) != 0 { - t.Fatalf("expected 0 events with watch fragmentation, got %d", len(ws.Events)) - } + require.Emptyf(t, ws.Events, "expected 0 events with watch fragmentation") exp := "code = ResourceExhausted desc = grpc: received message larger than max (" - if !strings.Contains(ws.Err().Error(), exp) { - t.Fatalf("expected 'ResourceExhausted' error, got %v", ws.Err()) - } + require.Containsf(t, ws.Err().Error(), exp, "expected 'ResourceExhausted' error") return } // still expect merged watch events - if len(ws.Events) != 10 { - t.Fatalf("expected 10 events with watch fragmentation, got %d", len(ws.Events)) - } - if ws.Err() != nil { - t.Fatalf("unexpected error %v", ws.Err()) - } + require.Lenf(t, ws.Events, 10, "expected 10 events with watch fragmentation") + require.NoErrorf(t, ws.Err(), "unexpected error") case <-time.After(testutil.RequestTimeout): t.Fatalf("took too long to receive events") diff --git a/tests/integration/clientv3/watch_test.go b/tests/integration/clientv3/watch_test.go index f3f5c548d76f..bdb913d3261d 100644 --- a/tests/integration/clientv3/watch_test.go +++ b/tests/integration/clientv3/watch_test.go @@ -172,9 +172,8 @@ func TestWatchRange(t *testing.T) { } func testWatchRange(t *testing.T, wctx *watchctx) { - if wctx.ch = wctx.w.Watch(context.TODO(), "a", clientv3.WithRange("c")); wctx.ch == nil { - t.Fatalf("expected non-nil channel") - } + wctx.ch = wctx.w.Watch(context.TODO(), "a", clientv3.WithRange("c")) + require.NotNilf(t, wctx.ch, "expected non-nil channel") putAndWatch(t, wctx, "a", "a") putAndWatch(t, wctx, "b", "b") putAndWatch(t, wctx, "bar", "bar") @@ -204,9 +203,8 @@ func testWatchReconnRequest(t *testing.T, wctx *watchctx) { } }() // should reconnect when requesting watch - if wctx.ch = wctx.w.Watch(context.TODO(), "a"); wctx.ch == nil { - t.Fatalf("expected non-nil channel") - } + wctx.ch = wctx.w.Watch(context.TODO(), "a") + require.NotNilf(t, wctx.ch, "expected non-nil channel") // wait for disconnections to stop stopc <- struct{}{} @@ -230,9 +228,8 @@ func TestWatchReconnInit(t *testing.T) { } func testWatchReconnInit(t *testing.T, wctx *watchctx) { - if wctx.ch = wctx.w.Watch(context.TODO(), "a"); wctx.ch == nil { - t.Fatalf("expected non-nil channel") - } + wctx.ch = wctx.w.Watch(context.TODO(), "a") + require.NotNilf(t, wctx.ch, "expected non-nil channel") wctx.clus.Members[wctx.wclientMember].Bridge().DropConnections() // watcher should recover putAndWatch(t, wctx, "a", "a") @@ -245,9 +242,8 @@ func TestWatchReconnRunning(t *testing.T) { } func testWatchReconnRunning(t *testing.T, wctx *watchctx) { - if wctx.ch = wctx.w.Watch(context.TODO(), "a"); wctx.ch == nil { - t.Fatalf("expected non-nil channel") - } + wctx.ch = wctx.w.Watch(context.TODO(), "a") + require.NotNilf(t, wctx.ch, "expected non-nil channel") putAndWatch(t, wctx, "a", "a") // take down watcher connection wctx.clus.Members[wctx.wclientMember].Bridge().DropConnections() @@ -267,9 +263,7 @@ func testWatchCancelImmediate(t *testing.T, wctx *watchctx) { wch := wctx.w.Watch(ctx, "a") select { case wresp, ok := <-wch: - if ok { - t.Fatalf("read wch got %v; expected closed channel", wresp) - } + require.Falsef(t, ok, "read wch got %v; expected closed channel", wresp) default: t.Fatalf("closed watcher channel should not block") } @@ -282,17 +276,14 @@ func TestWatchCancelInit(t *testing.T) { func testWatchCancelInit(t *testing.T, wctx *watchctx) { ctx, cancel := context.WithCancel(context.Background()) - if wctx.ch = wctx.w.Watch(ctx, "a"); wctx.ch == nil { - t.Fatalf("expected non-nil watcher channel") - } + wctx.ch = wctx.w.Watch(ctx, "a") + require.NotNilf(t, wctx.ch, "expected non-nil watcher channel") cancel() select { case <-time.After(time.Second): t.Fatalf("took too long to cancel") case _, ok := <-wctx.ch: - if ok { - t.Fatalf("expected watcher channel to close") - } + require.Falsef(t, ok, "expected watcher channel to close") } } @@ -303,9 +294,8 @@ func TestWatchCancelRunning(t *testing.T) { func testWatchCancelRunning(t *testing.T, wctx *watchctx) { ctx, cancel := context.WithCancel(context.Background()) - if wctx.ch = wctx.w.Watch(ctx, "a"); wctx.ch == nil { - t.Fatalf("expected non-nil watcher channel") - } + wctx.ch = wctx.w.Watch(ctx, "a") + require.NotNilf(t, wctx.ch, "expected non-nil watcher channel") _, err := wctx.kv.Put(ctx, "a", "a") require.NoError(t, err) cancel() @@ -322,9 +312,7 @@ func testWatchCancelRunning(t *testing.T, wctx *watchctx) { case <-time.After(time.Second): t.Fatalf("took too long to close") case v, ok2 := <-wctx.ch: - if ok2 { - t.Fatalf("expected watcher channel to close, got %v", v) - } + require.Falsef(t, ok2, "expected watcher channel to close, got %v", v) } } } @@ -336,15 +324,10 @@ func putAndWatch(t *testing.T, wctx *watchctx, key, val string) { case <-time.After(5 * time.Second): t.Fatalf("watch timed out") case v, ok := <-wctx.ch: - if !ok { - t.Fatalf("unexpected watch close") - } - if err := v.Err(); err != nil { - t.Fatalf("unexpected watch response error: %v", err) - } - if string(v.Events[0].Kv.Value) != val { - t.Fatalf("bad value got %v, wanted %v", v.Events[0].Kv.Value, val) - } + require.Truef(t, ok, "unexpected watch close") + err := v.Err() + require.NoErrorf(t, err, "unexpected watch response error") + require.Equalf(t, string(v.Events[0].Kv.Value), val, "bad value got %v, wanted %v", v.Events[0].Kv.Value, val) } } @@ -393,12 +376,8 @@ func TestWatchResumeAfterDisconnect(t *testing.T) { if len(resp.Events) != 2 { t.Fatal("expected two events on watch") } - if string(resp.Events[0].Kv.Value) != "3" { - t.Fatalf("expected value=3, got event %+v", resp.Events[0]) - } - if string(resp.Events[1].Kv.Value) != "4" { - t.Fatalf("expected value=4, got event %+v", resp.Events[1]) - } + require.Equalf(t, "3", string(resp.Events[0].Kv.Value), "expected value=3, got event %+v", resp.Events[0]) + require.Equalf(t, "4", string(resp.Events[1].Kv.Value), "expected value=4, got event %+v", resp.Events[1]) case <-time.After(5 * time.Second): t.Fatal("watch timed out") } @@ -449,16 +428,12 @@ func TestWatchResumeCompacted(t *testing.T) { var ok bool select { case wresp, ok = <-wch: - if !ok { - t.Fatalf("expected wresp, but got closed channel") - } + require.Truef(t, ok, "expected wresp, but got closed channel") case <-time.After(5 * time.Second): t.Fatalf("compacted watch timed out") } for _, ev := range wresp.Events { - if ev.Kv.ModRevision != wRev { - t.Fatalf("expected modRev %v, got %+v", wRev, ev) - } + require.Equalf(t, ev.Kv.ModRevision, wRev, "expected modRev %v, got %+v", wRev, ev) wRev++ } if wresp.Err() == nil { @@ -643,9 +618,7 @@ func TestWatchRequestProgress(t *testing.T) { for _, rch := range watchChans { select { case resp := <-rch: // wait for notification - if len(resp.Events) != 1 { - t.Fatalf("resp.Events expected 1, got %d", len(resp.Events)) - } + require.Lenf(t, resp.Events, 1, "resp.Events expected 1, got %d", len(resp.Events)) case <-time.After(watchTimeout): t.Fatalf("watch response expected in %v, but timed out", watchTimeout) } @@ -661,12 +634,8 @@ func TestWatchRequestProgress(t *testing.T) { for _, rch := range watchChans { select { case resp := <-rch: - if !resp.IsProgressNotify() { - t.Fatalf("expected resp.IsProgressNotify() == true") - } - if resp.Header.Revision != 3 { - t.Fatalf("resp.Header.Revision expected 3, got %d", resp.Header.Revision) - } + require.Truef(t, resp.IsProgressNotify(), "expected resp.IsProgressNotify() == true") + require.Equalf(t, int64(3), resp.Header.Revision, "resp.Header.Revision expected 3, got %d", resp.Header.Revision) case <-time.After(watchTimeout): t.Fatalf("progress response expected in %v, but timed out", watchTimeout) } @@ -685,22 +654,16 @@ func TestWatchEventType(t *testing.T) { ctx := context.Background() watchChan := client.Watch(ctx, "/", clientv3.WithPrefix()) - if _, err := client.Put(ctx, "/toDelete", "foo"); err != nil { - t.Fatalf("Put failed: %v", err) - } - if _, err := client.Put(ctx, "/toDelete", "bar"); err != nil { - t.Fatalf("Put failed: %v", err) - } - if _, err := client.Delete(ctx, "/toDelete"); err != nil { - t.Fatalf("Delete failed: %v", err) - } + _, err := client.Put(ctx, "/toDelete", "foo") + require.NoErrorf(t, err, "Put failed: %v", err) + _, err = client.Put(ctx, "/toDelete", "bar") + require.NoErrorf(t, err, "Put failed: %v", err) + _, err = client.Delete(ctx, "/toDelete") + require.NoErrorf(t, err, "Delete failed: %v", err) lcr, err := client.Lease.Grant(ctx, 1) - if err != nil { - t.Fatalf("lease create failed: %v", err) - } - if _, err := client.Put(ctx, "/toExpire", "foo", clientv3.WithLease(lcr.ID)); err != nil { - t.Fatalf("Put failed: %v", err) - } + require.NoErrorf(t, err, "lease create failed: %v", err) + _, err = client.Put(ctx, "/toExpire", "foo", clientv3.WithLease(lcr.ID)) + require.NoErrorf(t, err, "Put failed: %v", err) tests := []struct { et mvccpb.Event_EventType @@ -835,28 +798,21 @@ func TestWatchWithRequireLeader(t *testing.T) { select { case resp, ok := <-chLeader: - if !ok { - t.Fatalf("expected %v watch channel, got closed channel", rpctypes.ErrNoLeader) - } - if !errors.Is(resp.Err(), rpctypes.ErrNoLeader) { - t.Fatalf("expected %v watch response error, got %+v", rpctypes.ErrNoLeader, resp) - } + require.Truef(t, ok, "expected %v watch channel, got closed channel", rpctypes.ErrNoLeader) + require.ErrorIsf(t, resp.Err(), rpctypes.ErrNoLeader, "expected %v watch response error, got %+v", rpctypes.ErrNoLeader, resp) case <-time.After(integration2.RequestWaitTimeout): t.Fatal("watch without leader took too long to close") } select { case resp, ok := <-chLeader: - if ok { - t.Fatalf("expected closed channel, got response %v", resp) - } + require.Falsef(t, ok, "expected closed channel, got response %v", resp) case <-time.After(integration2.RequestWaitTimeout): t.Fatal("waited too long for channel to close") } - if _, ok := <-chNoLeader; !ok { - t.Fatalf("expected response, got closed channel") - } + _, ok := <-chNoLeader + require.Truef(t, ok, "expected response, got closed channel") cnt, err := clus.Members[0].Metric( "etcd_server_client_requests_total", @@ -866,9 +822,7 @@ func TestWatchWithRequireLeader(t *testing.T) { require.NoError(t, err) cv, err := strconv.ParseInt(cnt, 10, 32) require.NoError(t, err) - if cv < 2 { // >2 when retried - t.Fatalf("expected at least 2, got %q", cnt) - } + require.GreaterOrEqualf(t, cv, int64(2), "expected at least 2, got %q", cnt) } // TestWatchWithFilter checks that watch filtering works. @@ -923,9 +877,7 @@ func TestWatchWithCreatedNotification(t *testing.T) { resp := <-createC - if !resp.Created { - t.Fatalf("expected created event, got %v", resp) - } + require.Truef(t, resp.Created, "expected created event, got %v", resp) } // TestWatchWithCreatedNotificationDropConn ensures that @@ -943,9 +895,7 @@ func TestWatchWithCreatedNotificationDropConn(t *testing.T) { resp := <-wch - if !resp.Created { - t.Fatalf("expected created event, got %v", resp) - } + require.Truef(t, resp.Created, "expected created event, got %v", resp) cluster.Members[0].Bridge().DropConnections() @@ -1012,9 +962,7 @@ func TestWatchCancelOnServer(t *testing.T) { t.Fatalf("expected n=2 and err=nil, got n=%d and err=%v", n, serr) } - if maxWatchV-minWatchV < numWatches { - t.Fatalf("expected %d canceled watchers, got %d", numWatches, maxWatchV-minWatchV) - } + require.GreaterOrEqualf(t, maxWatchV-minWatchV, numWatches, "expected %d canceled watchers, got %d", numWatches, maxWatchV-minWatchV) } // TestWatchOverlapContextCancel stresses the watcher stream teardown path by @@ -1180,14 +1128,8 @@ func testWatchClose(t *testing.T, wctx *watchctx) { ctx, cancel := context.WithCancel(context.Background()) wch := wctx.w.Watch(ctx, "a") cancel() - if wch == nil { - t.Fatalf("expected watcher channel, got nil") - } - if wctx.w.Close() != nil { - t.Fatalf("watch did not close successfully") - } + require.NotNilf(t, wch, "expected watcher channel, got nil") + require.NoErrorf(t, wctx.w.Close(), "watch did not close successfully") wresp, ok := <-wch - if ok { - t.Fatalf("read wch got %v; expected closed channel", wresp) - } + require.Falsef(t, ok, "read wch got %v; expected closed channel", wresp) } diff --git a/tests/integration/cluster_test.go b/tests/integration/cluster_test.go index 732f5e2e19dc..9ba372bb8a4f 100644 --- a/tests/integration/cluster_test.go +++ b/tests/integration/cluster_test.go @@ -132,9 +132,7 @@ func TestForceNewCluster(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) resp, err := c.Members[0].Client.Put(ctx, "/foo", "bar") - if err != nil { - t.Fatalf("unexpected create error: %v", err) - } + require.NoErrorf(t, err, "unexpected create error") cancel() // ensure create has been applied in this machine ctx, cancel = context.WithTimeout(context.Background(), integration.RequestTimeout) @@ -143,12 +141,8 @@ func TestForceNewCluster(t *testing.T) { if len(resp.Events) != 0 { break } - if resp.Err() != nil { - t.Fatalf("unexpected watch error: %q", resp.Err()) - } - if resp.Canceled { - t.Fatalf("watch cancelled") - } + require.NoErrorf(t, resp.Err(), "unexpected watch error") + require.Falsef(t, resp.Canceled, "watch cancelled") } cancel() @@ -157,9 +151,7 @@ func TestForceNewCluster(t *testing.T) { c.Members[2].Terminate(t) c.Members[0].ForceNewCluster = true err = c.Members[0].Restart(t) - if err != nil { - t.Fatalf("unexpected ForceRestart error: %v", err) - } + require.NoErrorf(t, err, "unexpected ForceRestart error") c.WaitMembersForLeader(t, c.Members[:1]) // use new http client to init new connection @@ -170,12 +162,8 @@ func TestForceNewCluster(t *testing.T) { if len(resp.Events) != 0 { break } - if resp.Err() != nil { - t.Fatalf("unexpected watch error: %q", resp.Err()) - } - if resp.Canceled { - t.Fatalf("watch cancelled") - } + require.NoErrorf(t, resp.Err(), "unexpected watch error") + require.Falsef(t, resp.Canceled, "watch cancelled") } cancel() clusterMustProgress(t, c.Members[:1]) @@ -325,9 +313,8 @@ func TestIssue3699(t *testing.T) { t.Logf("Expecting successful put...") // try to participate in Cluster ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) - if _, err := c.Members[0].Client.Put(ctx, "/foo", "bar"); err != nil { - t.Fatalf("unexpected error on Put (%v)", err) - } + _, err := c.Members[0].Client.Put(ctx, "/foo", "bar") + require.NoErrorf(t, err, "unexpected error on Put") cancel() } @@ -344,9 +331,7 @@ func TestRejectUnhealthyAdd(t *testing.T) { // all attempts to add member should fail for i := 1; i < len(c.Members); i++ { err := c.AddMemberByURL(t, c.Members[i].Client, "unix://foo:12345") - if err == nil { - t.Fatalf("should have failed adding peer") - } + require.Errorf(t, err, "should have failed adding peer") // TODO: client should return descriptive error codes for internal errors if !strings.Contains(err.Error(), "unhealthy cluster") { t.Errorf("unexpected error (%v)", err) @@ -365,9 +350,7 @@ func TestRejectUnhealthyAdd(t *testing.T) { break } } - if err != nil { - t.Fatalf("should have added peer to healthy Cluster (%v)", err) - } + require.NoErrorf(t, err, "should have added peer to healthy Cluster (%v)", err) } // TestRejectUnhealthyRemove ensures an unhealthy cluster rejects removing members @@ -384,9 +367,7 @@ func TestRejectUnhealthyRemove(t *testing.T) { // reject remove active member since (3,2)-(1,0) => (2,2) lacks quorum err := c.RemoveMember(t, c.Members[leader].Client, uint64(c.Members[2].Server.MemberID())) - if err == nil { - t.Fatalf("should reject quorum breaking remove: %s", err) - } + require.Errorf(t, err, "should reject quorum breaking remove: %s", err) // TODO: client should return more descriptive error codes for internal errors if !strings.Contains(err.Error(), "unhealthy cluster") { t.Errorf("unexpected error (%v)", err) @@ -396,9 +377,8 @@ func TestRejectUnhealthyRemove(t *testing.T) { time.Sleep(time.Duration(integration.ElectionTicks * int(config.TickDuration))) // permit remove dead member since (3,2) - (0,1) => (3,1) has quorum - if err = c.RemoveMember(t, c.Members[2].Client, uint64(c.Members[0].Server.MemberID())); err != nil { - t.Fatalf("should accept removing down member: %s", err) - } + err = c.RemoveMember(t, c.Members[2].Client, uint64(c.Members[0].Server.MemberID())) + require.NoErrorf(t, err, "should accept removing down member") // bring cluster to (4,1) c.Members[0].Restart(t) @@ -407,9 +387,8 @@ func TestRejectUnhealthyRemove(t *testing.T) { time.Sleep((3 * etcdserver.HealthInterval) / 2) // accept remove member since (4,1)-(1,0) => (3,1) has quorum - if err = c.RemoveMember(t, c.Members[1].Client, uint64(c.Members[0].Server.MemberID())); err != nil { - t.Fatalf("expected to remove member, got error %v", err) - } + err = c.RemoveMember(t, c.Members[1].Client, uint64(c.Members[0].Server.MemberID())) + require.NoErrorf(t, err, "expected to remove member, got error") } // TestRestartRemoved ensures that restarting removed member must exit @@ -431,17 +410,15 @@ func TestRestartRemoved(t *testing.T) { firstMember.KeepDataDirTerminate = true // 3. remove first member, shut down without deleting data - if err := c.RemoveMember(t, c.Members[1].Client, uint64(firstMember.Server.MemberID())); err != nil { - t.Fatalf("expected to remove member, got error %v", err) - } + err := c.RemoveMember(t, c.Members[1].Client, uint64(firstMember.Server.MemberID())) + require.NoErrorf(t, err, "expected to remove member, got error") c.WaitLeader(t) // 4. restart first member with 'initial-cluster-state=new' // wrong config, expects exit within ReqTimeout firstMember.ServerConfig.NewCluster = false - if err := firstMember.Restart(t); err != nil { - t.Fatalf("unexpected ForceRestart error: %v", err) - } + err = firstMember.Restart(t) + require.NoErrorf(t, err, "unexpected ForceRestart error") defer func() { firstMember.Close() os.RemoveAll(firstMember.ServerConfig.DataDir) @@ -472,9 +449,7 @@ func clusterMustProgress(t *testing.T, members []*integration.Member) { } t.Logf("failed to create key on #0 (%v)", err) } - if err != nil { - t.Fatalf("create on #0 error: %v", err) - } + require.NoErrorf(t, err, "create on #0 error") for i, m := range members { mctx, mcancel := context.WithTimeout(context.Background(), integration.RequestTimeout) @@ -483,12 +458,8 @@ func clusterMustProgress(t *testing.T, members []*integration.Member) { if len(resp.Events) != 0 { break } - if resp.Err() != nil { - t.Fatalf("#%d: watch error: %q", i, resp.Err()) - } - if resp.Canceled { - t.Fatalf("#%d: watch: cancelled", i) - } + require.NoErrorf(t, resp.Err(), "#%d: watch error", i) + require.Falsef(t, resp.Canceled, "#%d: watch: cancelled", i) } mcancel() } diff --git a/tests/integration/hashkv_test.go b/tests/integration/hashkv_test.go index 26d5dfaf0409..2aa8cc69e058 100644 --- a/tests/integration/hashkv_test.go +++ b/tests/integration/hashkv_test.go @@ -19,7 +19,6 @@ import ( "net" "net/http" "testing" - "time" "github.com/stretchr/testify/require" @@ -78,8 +77,6 @@ func (tc hashTestCase) Defrag(ctx context.Context) error { } func (tc hashTestCase) Compact(ctx context.Context, rev int64) error { - _, err := tc.Client.Compact(ctx, rev) - // Wait for compaction to be compacted - time.Sleep(50 * time.Millisecond) + _, err := tc.Client.Compact(ctx, rev, clientv3.WithCompactPhysical()) return err } diff --git a/tests/integration/member_test.go b/tests/integration/member_test.go index b012370ab2c4..cc7a0545c04b 100644 --- a/tests/integration/member_test.go +++ b/tests/integration/member_test.go @@ -94,9 +94,7 @@ func TestSnapshotAndRestartMember(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) key := fmt.Sprintf("foo%d", i) _, err = m.Client.Put(ctx, "/"+key, "bar") - if err != nil { - t.Fatalf("#%d: create on %s error: %v", i, m.URL(), err) - } + require.NoErrorf(t, err, "#%d: create on %s error", i, m.URL()) cancel() } m.Stop(t) @@ -107,9 +105,7 @@ func TestSnapshotAndRestartMember(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) key := fmt.Sprintf("foo%d", i) resp, err := m.Client.Get(ctx, "/"+key) - if err != nil { - t.Fatalf("#%d: get on %s error: %v", i, m.URL(), err) - } + require.NoErrorf(t, err, "#%d: get on %s error", i, m.URL()) cancel() if len(resp.Kvs) != 1 || string(resp.Kvs[0].Value) != "bar" { @@ -132,6 +128,46 @@ func TestRemoveMember(t *testing.T) { checkMemberCount(t, c.Members[1], 2) } +// TestRemoveMemberAndWALReplay ensures that etcd can properly handle +// member removal followed by restart with WAL replay, ensuring no panics +// occur when replaying already-applied removal operations. +func TestRemoveMemberAndWALReplay(t *testing.T) { + integration.BeforeTest(t) + + // Create a cluster with 3 member and a low snapshot count + c := integration.NewCluster(t, &integration.ClusterConfig{ + Size: 3, + SnapshotCount: 10, + UseBridge: true, + DisableStrictReconfigCheck: true, + }) + defer c.Terminate(t) + + // Add some k/v to trigger snapshot + for i := 0; i < 15; i++ { + ctx, cancel := context.WithTimeout(context.Background(), integration.RequestTimeout) + _, err := c.Members[0].Client.Put(ctx, fmt.Sprintf("k%d", i), fmt.Sprintf("v%d", i)) + cancel() + require.NoErrorf(t, err, "failed to put key-value") + } + + // Record the ID of the member we'll remove + memberToRemoveID := uint64(c.Members[2].Server.MemberID()) + + // Remove one member from the cluster + err := c.RemoveMember(t, c.Members[0].Client, memberToRemoveID) + require.NoErrorf(t, err, "failed to remove member") + + // Stop the remaining members + c.Members[0].Stop(t) + c.Members[1].Stop(t) + + // Restart one member - this would previously panic when loading + // WAL entries that try to remove an already removed member + err = c.Members[0].Restart(t) + require.NoErrorf(t, err, "failed to restart member after removal") +} + func checkMemberCount(t *testing.T, m *integration.Member, expectedMemberCount int) { be := schema.NewMembershipBackend(m.Logger, m.Server.Backend()) membersFromBackend, _ := be.MustReadMembersFromBackend() diff --git a/tests/integration/metrics_test.go b/tests/integration/metrics_test.go index 8212bb773eb1..1fc1117c52e5 100644 --- a/tests/integration/metrics_test.go +++ b/tests/integration/metrics_test.go @@ -40,9 +40,7 @@ func TestMetricDbSizeBoot(t *testing.T) { v, err := clus.Members[0].Metric("etcd_debugging_mvcc_db_total_size_in_bytes") require.NoError(t, err) - if v == "0" { - t.Fatalf("expected non-zero, got %q", v) - } + require.NotEqualf(t, "0", v, "expected non-zero, got %q", v) } func TestMetricDbSizeDefrag(t *testing.T) { @@ -75,16 +73,12 @@ func testMetricDbSizeDefrag(t *testing.T, name string) { require.NoError(t, err) bv, err := strconv.Atoi(beforeDefrag) require.NoError(t, err) - if bv < expected { - t.Fatalf("expected db size greater than %d, got %d", expected, bv) - } + require.GreaterOrEqualf(t, bv, expected, "expected db size greater than %d, got %d", expected, bv) beforeDefragInUse, err := clus.Members[0].Metric("etcd_mvcc_db_total_size_in_use_in_bytes") require.NoError(t, err) biu, err := strconv.Atoi(beforeDefragInUse) require.NoError(t, err) - if biu < expected { - t.Fatalf("expected db size in use is greater than %d, got %d", expected, biu) - } + require.GreaterOrEqualf(t, biu, expected, "expected db size in use is greater than %d, got %d", expected, biu) // clear out historical keys, in use bytes should free pages creq := &pb.CompactionRequest{Revision: int64(numPuts), Physical: true} @@ -116,9 +110,7 @@ func testMetricDbSizeDefrag(t *testing.T, name string) { break } retry++ - if retry >= maxRetry { - t.Fatalf("%v", err.Error()) - } + require.Lessf(t, retry, maxRetry, "%v", err.Error()) } // defrag should give freed space back to fs @@ -128,17 +120,13 @@ func testMetricDbSizeDefrag(t *testing.T, name string) { require.NoError(t, err) av, err := strconv.Atoi(afterDefrag) require.NoError(t, err) - if bv <= av { - t.Fatalf("expected less than %d, got %d after defrag", bv, av) - } + require.Greaterf(t, bv, av, "expected less than %d, got %d after defrag", bv, av) afterDefragInUse, err := clus.Members[0].Metric("etcd_mvcc_db_total_size_in_use_in_bytes") require.NoError(t, err) adiu, err := strconv.Atoi(afterDefragInUse) require.NoError(t, err) - if adiu > av { - t.Fatalf("db size in use (%d) is expected less than db size (%d) after defrag", adiu, av) - } + require.LessOrEqualf(t, adiu, av, "db size in use (%d) is expected less than db size (%d) after defrag", adiu, av) } func TestMetricQuotaBackendBytes(t *testing.T) { @@ -150,9 +138,7 @@ func TestMetricQuotaBackendBytes(t *testing.T) { require.NoError(t, err) qv, err := strconv.ParseFloat(qs, 64) require.NoError(t, err) - if int64(qv) != storage.DefaultQuotaBytes { - t.Fatalf("expected %d, got %f", storage.DefaultQuotaBytes, qv) - } + require.Equalf(t, storage.DefaultQuotaBytes, int64(qv), "expected %d, got %f", storage.DefaultQuotaBytes, qv) } func TestMetricsHealth(t *testing.T) { @@ -174,9 +160,7 @@ func TestMetricsHealth(t *testing.T) { hv, err := clus.Members[0].Metric("etcd_server_health_failures") require.NoError(t, err) - if hv != "0" { - t.Fatalf("expected '0' from etcd_server_health_failures, got %q", hv) - } + require.Equalf(t, "0", hv, "expected '0' from etcd_server_health_failures, got %q", hv) } func TestMetricsRangeDurationSeconds(t *testing.T) { diff --git a/tests/integration/network_partition_test.go b/tests/integration/network_partition_test.go index 059e93762667..8c24fce2f27a 100644 --- a/tests/integration/network_partition_test.go +++ b/tests/integration/network_partition_test.go @@ -19,6 +19,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "go.etcd.io/etcd/tests/v3/framework/integration" ) @@ -65,9 +67,7 @@ func TestNetworkPartition5MembersLeaderInMajority(t *testing.T) { } t.Logf("[%d] got %v", i, err) } - if err != nil { - t.Fatalf("failed after 3 tries (%v)", err) - } + require.NoErrorf(t, err, "failed after 3 tries (%v)", err) } func testNetworkPartition5MembersLeaderInMajority(t *testing.T) error { diff --git a/tests/integration/proxy/grpcproxy/cluster_test.go b/tests/integration/proxy/grpcproxy/cluster_test.go index ca2fcb506b37..da0c58624f09 100644 --- a/tests/integration/proxy/grpcproxy/cluster_test.go +++ b/tests/integration/proxy/grpcproxy/cluster_test.go @@ -52,9 +52,7 @@ func TestClusterProxyMemberList(t *testing.T) { DialTimeout: 5 * time.Second, } client, err := integration2.NewClient(t, cfg) - if err != nil { - t.Fatalf("err %v, want nil", err) - } + require.NoErrorf(t, err, "err %v, want nil", err) defer client.Close() // wait some time for register-loop to write keys @@ -62,16 +60,10 @@ func TestClusterProxyMemberList(t *testing.T) { var mresp *clientv3.MemberListResponse mresp, err = client.Cluster.MemberList(context.Background()) - if err != nil { - t.Fatalf("err %v, want nil", err) - } + require.NoErrorf(t, err, "err %v, want nil", err) - if len(mresp.Members) != 1 { - t.Fatalf("len(mresp.Members) expected 1, got %d (%+v)", len(mresp.Members), mresp.Members) - } - if len(mresp.Members[0].ClientURLs) != 1 { - t.Fatalf("len(mresp.Members[0].ClientURLs) expected 1, got %d (%+v)", len(mresp.Members[0].ClientURLs), mresp.Members[0].ClientURLs[0]) - } + require.Lenf(t, mresp.Members, 1, "len(mresp.Members) expected 1, got %d (%+v)", len(mresp.Members), mresp.Members) + require.Lenf(t, mresp.Members[0].ClientURLs, 1, "len(mresp.Members[0].ClientURLs) expected 1, got %d (%+v)", len(mresp.Members[0].ClientURLs), mresp.Members[0].ClientURLs[0]) assert.Contains(t, mresp.Members, &pb.Member{Name: hostname, ClientURLs: []string{cts.caddr}}) // test proxy member add @@ -82,12 +74,8 @@ func TestClusterProxyMemberList(t *testing.T) { // check add member succ mresp, err = client.Cluster.MemberList(context.Background()) - if err != nil { - t.Fatalf("err %v, want nil", err) - } - if len(mresp.Members) != 2 { - t.Fatalf("len(mresp.Members) expected 2, got %d (%+v)", len(mresp.Members), mresp.Members) - } + require.NoErrorf(t, err, "err %v, want nil", err) + require.Lenf(t, mresp.Members, 2, "len(mresp.Members) expected 2, got %d (%+v)", len(mresp.Members), mresp.Members) assert.Contains(t, mresp.Members, &pb.Member{Name: hostname, ClientURLs: []string{newMemberAddr}}) // test proxy member delete @@ -97,12 +85,8 @@ func TestClusterProxyMemberList(t *testing.T) { // check delete member succ mresp, err = client.Cluster.MemberList(context.Background()) - if err != nil { - t.Fatalf("err %v, want nil", err) - } - if len(mresp.Members) != 1 { - t.Fatalf("len(mresp.Members) expected 1, got %d (%+v)", len(mresp.Members), mresp.Members) - } + require.NoErrorf(t, err, "err %v, want nil", err) + require.Lenf(t, mresp.Members, 1, "len(mresp.Members) expected 1, got %d (%+v)", len(mresp.Members), mresp.Members) assert.Contains(t, mresp.Members, &pb.Member{Name: hostname, ClientURLs: []string{cts.caddr}}) } @@ -162,10 +146,7 @@ func newClusterProxyServer(lg *zap.Logger, endpoints []string, prefix string, t func deregisterMember(c *clientv3.Client, prefix, addr string, t *testing.T) { em, err := endpoints.NewManager(c, prefix) - if err != nil { - t.Fatalf("new endpoint manager failed, err %v", err) - } - if err = em.DeleteEndpoint(c.Ctx(), prefix+"/"+addr); err != nil { - t.Fatalf("delete endpoint failed, err %v", err) - } + require.NoErrorf(t, err, "new endpoint manager failed, err") + err = em.DeleteEndpoint(c.Ctx(), prefix+"/"+addr) + require.NoErrorf(t, err, "delete endpoint failed, err") } diff --git a/tests/integration/proxy/grpcproxy/kv_test.go b/tests/integration/proxy/grpcproxy/kv_test.go index b871eca95ea8..00bb326ffb2a 100644 --- a/tests/integration/proxy/grpcproxy/kv_test.go +++ b/tests/integration/proxy/grpcproxy/kv_test.go @@ -44,13 +44,9 @@ func TestKVProxyRange(t *testing.T) { DialTimeout: 5 * time.Second, } client, err := integration2.NewClient(t, cfg) - if err != nil { - t.Fatalf("err = %v, want nil", err) - } + require.NoErrorf(t, err, "err = %v, want nil", err) _, err = client.Get(context.Background(), "foo") - if err != nil { - t.Fatalf("err = %v, want nil", err) - } + require.NoErrorf(t, err, "err = %v, want nil", err) client.Close() } diff --git a/tests/integration/proxy/grpcproxy/register_test.go b/tests/integration/proxy/grpcproxy/register_test.go index a0fb5272c52d..855975360802 100644 --- a/tests/integration/proxy/grpcproxy/register_test.go +++ b/tests/integration/proxy/grpcproxy/register_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" clientv3 "go.etcd.io/etcd/client/v3" @@ -40,12 +41,8 @@ func TestRegister(t *testing.T) { donec := grpcproxy.Register(zaptest.NewLogger(t), cli, testPrefix, paddr, 5) ups := <-wa - if len(ups) != 1 { - t.Fatalf("len(ups) expected 1, got %d (%v)", len(ups), ups) - } - if ups[0].Endpoint.Addr != paddr { - t.Fatalf("ups[0].Addr expected %q, got %q", paddr, ups[0].Endpoint.Addr) - } + require.Lenf(t, ups, 1, "len(ups) expected 1, got %d (%v)", len(ups), ups) + require.Equalf(t, ups[0].Endpoint.Addr, paddr, "ups[0].Addr expected %q, got %q", paddr, ups[0].Endpoint.Addr) cli.Close() clus.TakeClient(0) @@ -58,12 +55,8 @@ func TestRegister(t *testing.T) { func mustCreateWatcher(t *testing.T, c *clientv3.Client, prefix string) endpoints.WatchChannel { em, err := endpoints.NewManager(c, prefix) - if err != nil { - t.Fatalf("failed to create endpoints.Manager: %v", err) - } + require.NoErrorf(t, err, "failed to create endpoints.Manager") wc, err := em.NewWatchChannel(c.Ctx()) - if err != nil { - t.Fatalf("failed to resolve %q (%v)", prefix, err) - } + require.NoErrorf(t, err, "failed to resolve %q", prefix) return wc } diff --git a/tests/integration/revision_test.go b/tests/integration/revision_test.go index fd53cfb37b41..49ce4d21468f 100644 --- a/tests/integration/revision_test.go +++ b/tests/integration/revision_test.go @@ -132,9 +132,7 @@ func getWorker(ctx context.Context, t *testing.T, clus *integration.Cluster) { if resp == nil { continue } - if prevRev > resp.Header.Revision { - t.Fatalf("rev is less than previously observed revision, rev: %d, prevRev: %d", resp.Header.Revision, prevRev) - } + require.LessOrEqualf(t, prevRev, resp.Header.Revision, "rev is less than previously observed revision, rev: %d, prevRev: %d", resp.Header.Revision, prevRev) prevRev = resp.Header.Revision } } diff --git a/tests/integration/snapshot/member_test.go b/tests/integration/snapshot/member_test.go index be419efdc27c..1c889df75436 100644 --- a/tests/integration/snapshot/member_test.go +++ b/tests/integration/snapshot/member_test.go @@ -93,9 +93,7 @@ func TestSnapshotV3RestoreMultiMemberAdd(t *testing.T) { mresp, err := cli2.MemberList(ctx) cancel() require.NoError(t, err) - if len(mresp.Members) != 4 { - t.Fatalf("expected 4 members, got %+v", mresp) - } + require.Lenf(t, mresp.Members, 4, "expected 4 members, got %+v", mresp) // make sure restored cluster has kept all data on recovery var gresp *clientv3.GetResponse @@ -104,11 +102,7 @@ func TestSnapshotV3RestoreMultiMemberAdd(t *testing.T) { cancel() require.NoError(t, err) for i := range gresp.Kvs { - if string(gresp.Kvs[i].Key) != kvs[i].k { - t.Fatalf("#%d: key expected %s, got %s", i, kvs[i].k, gresp.Kvs[i].Key) - } - if string(gresp.Kvs[i].Value) != kvs[i].v { - t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[i].Value) - } + require.Equalf(t, string(gresp.Kvs[i].Key), kvs[i].k, "#%d: key expected %s, got %s", i, kvs[i].k, gresp.Kvs[i].Key) + require.Equalf(t, string(gresp.Kvs[i].Value), kvs[i].v, "#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[i].Value) } } diff --git a/tests/integration/snapshot/v3_snapshot_test.go b/tests/integration/snapshot/v3_snapshot_test.go index 893fa4fd0388..d016d3bced16 100644 --- a/tests/integration/snapshot/v3_snapshot_test.go +++ b/tests/integration/snapshot/v3_snapshot_test.go @@ -88,9 +88,7 @@ func TestSnapshotV3RestoreSingle(t *testing.T) { var gresp *clientv3.GetResponse gresp, err = cli.Get(context.Background(), kvs[i].k) require.NoError(t, err) - if string(gresp.Kvs[0].Value) != kvs[i].v { - t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[0].Value) - } + require.Equalf(t, string(gresp.Kvs[0].Value), kvs[i].v, "#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[0].Value) } } @@ -121,9 +119,7 @@ func TestSnapshotV3RestoreMulti(t *testing.T) { var gresp *clientv3.GetResponse gresp, err = cli.Get(context.Background(), kvs[i].k) require.NoError(t, err) - if string(gresp.Kvs[0].Value) != kvs[i].v { - t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[0].Value) - } + require.Equalf(t, string(gresp.Kvs[0].Value), kvs[i].v, "#%d: value expected %s, got %s", i, kvs[i].v, gresp.Kvs[0].Value) } } } @@ -132,12 +128,11 @@ func TestSnapshotV3RestoreMulti(t *testing.T) { func TestCorruptedBackupFileCheck(t *testing.T) { dbPath := testutils.MustAbsPath("testdata/corrupted_backup.db") integration2.BeforeTest(t) - if _, err := os.Stat(dbPath); err != nil { - t.Fatalf("test file [%s] does not exist: %v", dbPath, err) - } + _, err := os.Stat(dbPath) + require.NoErrorf(t, err, "test file [%s] does not exist: %v", dbPath, err) sp := snapshot.NewV3(zaptest.NewLogger(t)) - _, err := sp.Status(dbPath) + _, err = sp.Status(dbPath) expectedErrKeywords := "snapshot file integrity check failed" /* example error message: snapshot file integrity check failed. 2 errors found. diff --git a/tests/integration/tracing_test.go b/tests/integration/tracing_test.go index d255e958308d..5ecfc12a5b9d 100644 --- a/tests/integration/tracing_test.go +++ b/tests/integration/tracing_test.go @@ -38,6 +38,48 @@ import ( func TestTracing(t *testing.T) { testutil.SkipTestIfShortMode(t, "Wal creation tests are depending on embedded etcd server so are integration-level tests.") + + // Test Unary RPC tracing + t.Run("UnaryRPC", func(t *testing.T) { + testRPCTracing(t, "UnaryRPC", containsUnaryRPCSpan, func(cli *clientv3.Client) error { + // make a request with the instrumented client + resp, err := cli.Get(context.TODO(), "key") + require.NoError(t, err) + require.Empty(t, resp.Kvs) + return nil + }) + }) + + // Test Stream RPC tracing + t.Run("StreamRPC", func(t *testing.T) { + testRPCTracing(t, "StreamRPC", containsStreamRPCSpan, func(cli *clientv3.Client) error { + // Create a context with a reasonable timeout + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Create a watch channel + watchChan := cli.Watch(ctx, "watch-key") + + // Put a value to trigger the watch + _, err := cli.Put(context.TODO(), "watch-key", "watch-value") + require.NoError(t, err) + + // Wait for watch event + select { + case watchResp := <-watchChan: + require.NoError(t, watchResp.Err()) + require.Len(t, watchResp.Events, 1) + t.Log("Received watch event successfully") + case <-time.After(5 * time.Second): + t.Fatal("Timed out waiting for watch event") + } + return nil + }) + }) +} + +// testRPCTracing is a common test function for both Unary and Stream RPC tracing +func testRPCTracing(t *testing.T, testName string, filterFunc func(*traceservice.ExportTraceServiceRequest) bool, clientAction func(*clientv3.Client) error) { // set up trace collector listener, err := net.Listen("tcp", "localhost:") require.NoError(t, err) @@ -48,17 +90,18 @@ func TestTracing(t *testing.T) { srv := grpc.NewServer() traceservice.RegisterTraceServiceServer(srv, &traceServer{ traceFound: traceFound, - filterFunc: containsNodeListSpan, + filterFunc: filterFunc, }) go srv.Serve(listener) defer srv.Stop() cfg := integration.NewEmbedConfig(t, "default") - cfg.ExperimentalEnableDistributedTracing = true - cfg.ExperimentalDistributedTracingAddress = listener.Addr().String() - cfg.ExperimentalDistributedTracingServiceName = "integration-test-tracing" - cfg.ExperimentalDistributedTracingSamplingRatePerMillion = 100 + + cfg.EnableDistributedTracing = true + cfg.DistributedTracingAddress = listener.Addr().String() + cfg.DistributedTracingServiceName = "integration-test-tracing" + cfg.DistributedTracingSamplingRatePerMillion = 100 // start an etcd instance with tracing enabled etcdSrv, err := embed.StartEtcd(cfg) @@ -88,8 +131,7 @@ func TestTracing(t *testing.T) { } dialOptions := []grpc.DialOption{ - grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(tracingOpts...)), - grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(tracingOpts...)), + grpc.WithStatsHandler(otelgrpc.NewClientHandler(tracingOpts...)), } ccfg := clientv3.Config{DialOptions: dialOptions, Endpoints: []string{cfg.AdvertiseClientUrls[0].String()}} cli, err := integration.NewClient(t, ccfg) @@ -99,21 +141,21 @@ func TestTracing(t *testing.T) { } defer cli.Close() - // make a request with the instrumented client - resp, err := cli.Get(context.TODO(), "key") + // Execute the client action (either Unary or Stream RPC) + err = clientAction(cli) require.NoError(t, err) - require.Empty(t, resp.Kvs) // Wait for a span to be recorded from our request select { case <-traceFound: + t.Logf("%s trace found", testName) return case <-time.After(30 * time.Second): t.Fatal("Timed out waiting for trace") } } -func containsNodeListSpan(req *traceservice.ExportTraceServiceRequest) bool { +func containsUnaryRPCSpan(req *traceservice.ExportTraceServiceRequest) bool { for _, resourceSpans := range req.GetResourceSpans() { for _, attr := range resourceSpans.GetResource().GetAttributes() { if attr.GetKey() != "service.name" && attr.GetValue().GetStringValue() != "integration-test-tracing" { @@ -131,6 +173,20 @@ func containsNodeListSpan(req *traceservice.ExportTraceServiceRequest) bool { return false } +// containsStreamRPCSpan checks for Watch/Watch spans in trace data +func containsStreamRPCSpan(req *traceservice.ExportTraceServiceRequest) bool { + for _, resourceSpans := range req.GetResourceSpans() { + for _, scoped := range resourceSpans.GetScopeSpans() { + for _, span := range scoped.GetSpans() { + if span.GetName() == "etcdserverpb.Watch/Watch" { + return true + } + } + } + } + return false +} + // traceServer implements TracesServiceServer type traceServer struct { traceFound chan struct{} @@ -141,7 +197,11 @@ type traceServer struct { func (t *traceServer) Export(ctx context.Context, req *traceservice.ExportTraceServiceRequest) (*traceservice.ExportTraceServiceResponse, error) { emptyValue := traceservice.ExportTraceServiceResponse{} if t.filterFunc(req) { - t.traceFound <- struct{}{} + select { + case t.traceFound <- struct{}{}: + default: + // Channel already notified + } } return &emptyValue, nil } diff --git a/tests/integration/v2store/store_test.go b/tests/integration/v2store/store_test.go index 842b57deb62a..73f8fae83c8b 100644 --- a/tests/integration/v2store/store_test.go +++ b/tests/integration/v2store/store_test.go @@ -101,7 +101,7 @@ func TestSet(t *testing.T) { assert.Equal(t, "set", e.Action) assert.Equal(t, "/foo", e.Node.Key) assert.False(t, e.Node.Dir) - assert.Equal(t, "", *e.Node.Value) + assert.Empty(t, *e.Node.Value) assert.Nil(t, e.Node.Nodes) assert.Nil(t, e.Node.Expiration) assert.Equal(t, int64(0), e.Node.TTL) @@ -123,7 +123,7 @@ func TestSet(t *testing.T) { // check prevNode require.NotNil(t, e.PrevNode) assert.Equal(t, "/foo", e.PrevNode.Key) - assert.Equal(t, "", *e.PrevNode.Value) + assert.Empty(t, *e.PrevNode.Value) assert.Equal(t, uint64(1), e.PrevNode.ModifiedIndex) // Set /foo="baz" (for testing prevNode) eidx = 3 @@ -198,7 +198,7 @@ func TestStoreCreateValue(t *testing.T) { assert.Equal(t, "create", e.Action) assert.Equal(t, "/empty", e.Node.Key) assert.False(t, e.Node.Dir) - assert.Equal(t, "", *e.Node.Value) + assert.Empty(t, *e.Node.Value) assert.Nil(t, e.Node.Nodes) assert.Nil(t, e.Node.Expiration) assert.Equal(t, int64(0), e.Node.TTL) @@ -271,7 +271,7 @@ func TestStoreUpdateValue(t *testing.T) { assert.Equal(t, "update", e.Action) assert.Equal(t, "/foo", e.Node.Key) assert.False(t, e.Node.Dir) - assert.Equal(t, "", *e.Node.Value) + assert.Empty(t, *e.Node.Value) assert.Equal(t, int64(0), e.Node.TTL) assert.Equal(t, uint64(3), e.Node.ModifiedIndex) // check prevNode @@ -282,7 +282,7 @@ func TestStoreUpdateValue(t *testing.T) { e, _ = s.Get("/foo", false, false) assert.Equal(t, eidx, e.EtcdIndex) - assert.Equal(t, "", *e.Node.Value) + assert.Empty(t, *e.Node.Value) } // TestStoreUpdateFailsIfDirectory ensures that the store cannot update a directory. diff --git a/tests/integration/v3_alarm_test.go b/tests/integration/v3_alarm_test.go index 0f723b8f2c91..9c1f7f152d05 100644 --- a/tests/integration/v3_alarm_test.go +++ b/tests/integration/v3_alarm_test.go @@ -110,23 +110,20 @@ func TestV3StorageQuotaApply(t *testing.T) { defer cancel() // small quota machine should reject put - if _, err := kvc0.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { - t.Fatalf("past-quota instance should reject put") - } + _, err = kvc0.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}) + require.Errorf(t, err, "past-quota instance should reject put") // large quota machine should reject put - if _, err := kvc1.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { - t.Fatalf("past-quota instance should reject put") - } + _, err = kvc1.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}) + require.Errorf(t, err, "past-quota instance should reject put") // reset large quota node to ensure alarm persisted clus.Members[1].Stop(t) clus.Members[1].Restart(t) clus.WaitMembersForLeader(t, clus.Members) - if _, err := kvc1.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { - t.Fatalf("alarmed instance should reject put after reset") - } + _, err = kvc1.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}) + require.Errorf(t, err, "alarmed instance should reject put after reset") } // TestV3AlarmDeactivate ensures that space alarms can be deactivated so puts go through. @@ -221,9 +218,7 @@ func TestV3CorruptAlarm(t *testing.T) { resp1, err1 := clus.Client(1).Get(context.TODO(), "abc") require.NoError(t, err1) - if resp0.Kvs[0].ModRevision == resp1.Kvs[0].ModRevision { - t.Fatalf("matching ModRevision values") - } + require.NotEqualf(t, resp0.Kvs[0].ModRevision, resp1.Kvs[0].ModRevision, "matching ModRevision values") for i := 0; i < 5; i++ { presp, perr := clus.Client(0).Put(context.TODO(), "abc", "aaa") @@ -307,9 +302,7 @@ func TestV3CorruptAlarmWithLeaseCorrupted(t *testing.T) { resp1, err1 := clus.Members[2].Client.KV.Get(context.TODO(), "foo") require.NoError(t, err1) - if resp0.Header.Revision == resp1.Header.Revision { - t.Fatalf("matching Revision values") - } + require.NotEqualf(t, resp0.Header.Revision, resp1.Header.Revision, "matching Revision values") // Wait for CorruptCheckTime time.Sleep(time.Second) diff --git a/tests/integration/v3_auth_test.go b/tests/integration/v3_auth_test.go index 8d44d547329e..abd6c5e41631 100644 --- a/tests/integration/v3_auth_test.go +++ b/tests/integration/v3_auth_test.go @@ -44,9 +44,7 @@ func TestV3AuthEmptyUserGet(t *testing.T) { authSetupRoot(t, api.Auth) _, err := api.KV.Range(ctx, &pb.RangeRequest{Key: []byte("abc")}) - if !eqErrGRPC(err, rpctypes.ErrUserEmpty) { - t.Fatalf("got %v, expected %v", err, rpctypes.ErrUserEmpty) - } + require.Truef(t, eqErrGRPC(err, rpctypes.ErrUserEmpty), "got %v, expected %v", err, rpctypes.ErrUserEmpty) } // TestV3AuthEmptyUserPut ensures that a put with an empty user will return an empty user error, @@ -70,9 +68,7 @@ func TestV3AuthEmptyUserPut(t *testing.T) { // cluster terminating. for i := 0; i < 10; i++ { _, err := api.KV.Put(ctx, &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}) - if !eqErrGRPC(err, rpctypes.ErrUserEmpty) { - t.Fatalf("got %v, expected %v", err, rpctypes.ErrUserEmpty) - } + require.Truef(t, eqErrGRPC(err, rpctypes.ErrUserEmpty), "got %v, expected %v", err, rpctypes.ErrUserEmpty) } } @@ -124,9 +120,7 @@ func TestV3AuthRevision(t *testing.T) { aresp, aerr := api.Auth.UserAdd(ctx, &pb.AuthUserAddRequest{Name: "root", Password: "123", Options: &authpb.UserAddOptions{NoPassword: false}}) cancel() require.NoError(t, aerr) - if aresp.Header.Revision != rev { - t.Fatalf("revision expected %d, got %d", rev, aresp.Header.Revision) - } + require.Equalf(t, aresp.Header.Revision, rev, "revision expected %d, got %d", rev, aresp.Header.Revision) } // TestV3AuthWithLeaseRevokeWithRoot ensures that granted leases @@ -333,16 +327,12 @@ func TestV3AuthNonAuthorizedRPCs(t *testing.T) { key := "foo" val := "bar" _, err := nonAuthedKV.Put(context.TODO(), key, val) - if err != nil { - t.Fatalf("couldn't put key (%v)", err) - } + require.NoErrorf(t, err, "couldn't put key (%v)", err) authSetupRoot(t, integration.ToGRPC(clus.Client(0)).Auth) respput, err := nonAuthedKV.Put(context.TODO(), key, val) - if !eqErrGRPC(err, rpctypes.ErrGRPCUserEmpty) { - t.Fatalf("could put key (%v), it should cause an error of permission denied", respput) - } + require.Truef(t, eqErrGRPC(err, rpctypes.ErrGRPCUserEmpty), "could put key (%v), it should cause an error of permission denied", respput) } func TestV3AuthOldRevConcurrent(t *testing.T) { @@ -428,9 +418,7 @@ func TestV3AuthWatchErrorAndWatchId0(t *testing.T) { require.Error(t, watchResponse.Err()) // permission denied _, err := c.Put(ctx, "k1", "val") - if err != nil { - t.Fatalf("Unexpected error from Put: %v", err) - } + require.NoErrorf(t, err, "Unexpected error from Put: %v", err) <-watchEndCh } diff --git a/tests/integration/v3_election_test.go b/tests/integration/v3_election_test.go index 94665dca613a..d1d7e48d21a1 100644 --- a/tests/integration/v3_election_test.go +++ b/tests/integration/v3_election_test.go @@ -132,19 +132,14 @@ func TestElectionFailover(t *testing.T) { // first leader (elected) e := concurrency.NewElection(ss[0], "test-election") - if err := e.Campaign(context.TODO(), "foo"); err != nil { - t.Fatalf("failed volunteer (%v)", err) - } + err := e.Campaign(context.TODO(), "foo") + require.NoErrorf(t, err, "failed volunteer") // check first leader resp, ok := <-e.Observe(cctx) - if !ok { - t.Fatalf("could not wait for first election; channel closed") - } + require.Truef(t, ok, "could not wait for first election; channel closed") s := string(resp.Kvs[0].Value) - if s != "foo" { - t.Fatalf("wrong election result. got %s, wanted foo", s) - } + require.Equalf(t, "foo", s, "wrong election result. got %s, wanted foo", s) // next leader electedErrC := make(chan error, 1) @@ -155,19 +150,15 @@ func TestElectionFailover(t *testing.T) { }() // invoke leader failover - err := ss[0].Close() + err = ss[0].Close() require.NoError(t, err) // check new leader e = concurrency.NewElection(ss[2], "test-election") resp, ok = <-e.Observe(cctx) - if !ok { - t.Fatalf("could not wait for second election; channel closed") - } + require.Truef(t, ok, "could not wait for second election; channel closed") s = string(resp.Kvs[0].Value) - if s != "bar" { - t.Fatalf("wrong election result. got %s, wanted bar", s) - } + require.Equalf(t, "bar", s, "wrong election result. got %s, wanted bar", s) // leader must ack election (otherwise, Campaign may see closed conn) eer := <-electedErrC @@ -288,9 +279,7 @@ func TestElectionObserveCompacted(t *testing.T) { if !ok { t.Fatal("failed to observe on compacted revision") } - if string(v.Kvs[0].Value) != "abc" { - t.Fatalf(`expected leader value "abc", got %q`, string(v.Kvs[0].Value)) - } + require.Equalf(t, "abc", string(v.Kvs[0].Value), `expected leader value "abc", got %q`, string(v.Kvs[0].Value)) } // TestElectionWithAuthEnabled verifies the election interface when auth is enabled. diff --git a/tests/integration/v3_failover_test.go b/tests/integration/v3_failover_test.go index f9d71ffdaeef..03b829b74798 100644 --- a/tests/integration/v3_failover_test.go +++ b/tests/integration/v3_failover_test.go @@ -61,9 +61,7 @@ func TestFailover(t *testing.T) { // Create an etcd client before or after first server down t.Logf("Creating an etcd client [%s]", tc.name) cli, err := tc.testFunc(t, cc, clus) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } + require.NoErrorf(t, err, "Failed to create client") defer cli.Close() // Sanity test @@ -142,12 +140,8 @@ func getWithRetries(t *testing.T, cli *clientv3.Client, key, val string, retryCo t.Logf("Failed to get key (%v)", getErr) return getErr } - if len(resp.Kvs) != 1 { - t.Fatalf("Expected 1 key, got %d", len(resp.Kvs)) - } - if !bytes.Equal([]byte(val), resp.Kvs[0].Value) { - t.Fatalf("Unexpected value, expected: %s, got: %s", val, resp.Kvs[0].Value) - } + require.Lenf(t, resp.Kvs, 1, "Expected 1 key, got %d", len(resp.Kvs)) + require.Truef(t, bytes.Equal([]byte(val), resp.Kvs[0].Value), "Unexpected value, expected: %s, got: %s", val, resp.Kvs[0].Value) return nil }() if err != nil { diff --git a/tests/integration/v3_grpc_test.go b/tests/integration/v3_grpc_test.go index fdf4d14b7307..f7c331a5596a 100644 --- a/tests/integration/v3_grpc_test.go +++ b/tests/integration/v3_grpc_test.go @@ -52,32 +52,22 @@ func TestV3PutOverwrite(t *testing.T) { reqput := &pb.PutRequest{Key: key, Value: []byte("bar"), PrevKv: true} respput, err := kvc.Put(context.TODO(), reqput) - if err != nil { - t.Fatalf("couldn't put key (%v)", err) - } + require.NoErrorf(t, err, "couldn't put key") // overwrite reqput.Value = []byte("baz") respput2, err := kvc.Put(context.TODO(), reqput) - if err != nil { - t.Fatalf("couldn't put key (%v)", err) - } - if respput2.Header.Revision <= respput.Header.Revision { - t.Fatalf("expected newer revision on overwrite, got %v <= %v", - respput2.Header.Revision, respput.Header.Revision) - } + require.NoErrorf(t, err, "couldn't put key") + require.Greaterf(t, respput2.Header.Revision, respput.Header.Revision, "expected newer revision on overwrite, got %v <= %v", + respput2.Header.Revision, respput.Header.Revision) if pkv := respput2.PrevKv; pkv == nil || string(pkv.Value) != "bar" { t.Fatalf("expected PrevKv=bar, got response %+v", respput2) } reqrange := &pb.RangeRequest{Key: key} resprange, err := kvc.Range(context.TODO(), reqrange) - if err != nil { - t.Fatalf("couldn't get key (%v)", err) - } - if len(resprange.Kvs) != 1 { - t.Fatalf("expected 1 key, got %v", len(resprange.Kvs)) - } + require.NoErrorf(t, err, "couldn't get key") + require.Lenf(t, resprange.Kvs, 1, "expected 1 key, got %v", len(resprange.Kvs)) kv := resprange.Kvs[0] if kv.ModRevision <= kv.CreateRevision { @@ -107,9 +97,7 @@ func TestV3PutRestart(t *testing.T) { clus.Members[stopIdx].Stop(t) clus.Members[stopIdx].Restart(t) c, cerr := integration.NewClientV3(clus.Members[stopIdx]) - if cerr != nil { - t.Fatalf("cannot create client: %v", cerr) - } + require.NoErrorf(t, cerr, "cannot create client") clus.Members[stopIdx].ServerClient = c ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) @@ -130,28 +118,21 @@ func TestV3CompactCurrentRev(t *testing.T) { kvc := integration.ToGRPC(clus.RandClient()).KV preq := &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")} for i := 0; i < 3; i++ { - if _, err := kvc.Put(context.Background(), preq); err != nil { - t.Fatalf("couldn't put key (%v)", err) - } + _, err := kvc.Put(context.Background(), preq) + require.NoErrorf(t, err, "couldn't put key") } // get key to add to proxy cache, if any _, err := kvc.Range(context.TODO(), &pb.RangeRequest{Key: []byte("foo")}) require.NoError(t, err) // compact on current revision _, err = kvc.Compact(context.Background(), &pb.CompactionRequest{Revision: 4}) - if err != nil { - t.Fatalf("couldn't compact kv space (%v)", err) - } + require.NoErrorf(t, err, "couldn't compact kv space") // key still exists when linearized? _, err = kvc.Range(context.Background(), &pb.RangeRequest{Key: []byte("foo")}) - if err != nil { - t.Fatalf("couldn't get key after compaction (%v)", err) - } + require.NoErrorf(t, err, "couldn't get key after compaction") // key still exists when serialized? _, err = kvc.Range(context.Background(), &pb.RangeRequest{Key: []byte("foo"), Serializable: true}) - if err != nil { - t.Fatalf("couldn't get serialized key after compaction (%v)", err) - } + require.NoErrorf(t, err, "couldn't get serialized key after compaction") } // TestV3HashKV ensures that multiple calls of HashKV on same node return same hash and compact rev. diff --git a/tests/integration/v3_leadership_test.go b/tests/integration/v3_leadership_test.go index ea2b730c6189..148c2edf8591 100644 --- a/tests/integration/v3_leadership_test.go +++ b/tests/integration/v3_leadership_test.go @@ -236,14 +236,14 @@ func TestFirstCommitNotification(t *testing.T) { func checkFirstCommitNotification( ctx context.Context, - t testing.TB, + tb testing.TB, member *integration.Member, leaderAppliedIndex uint64, notifier <-chan struct{}, ) error { // wait until server applies all the changes of leader for member.Server.AppliedIndex() < leaderAppliedIndex { - t.Logf("member.Server.AppliedIndex():%v <= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex) + tb.Logf("member.Server.AppliedIndex():%v <= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex) select { case <-ctx.Done(): return ctx.Err() @@ -261,7 +261,7 @@ func checkFirstCommitNotification( ) } default: - t.Logf("member.Server.AppliedIndex():%v >= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex) + tb.Logf("member.Server.AppliedIndex():%v >= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex) return fmt.Errorf( "notification was not triggered, member ID: %d", member.ID(), diff --git a/tests/integration/v3_lease_test.go b/tests/integration/v3_lease_test.go index e255567d560c..532e94f3166e 100644 --- a/tests/integration/v3_lease_test.go +++ b/tests/integration/v3_lease_test.go @@ -1031,7 +1031,7 @@ func acquireLeaseAndKey(clus *integration.Cluster, key string) (int64, error) { return 0, err } if lresp.Error != "" { - return 0, fmt.Errorf(lresp.Error) + return 0, errors.New(lresp.Error) } // attach to key put := &pb.PutRequest{Key: []byte(key), Lease: lresp.ID} diff --git a/tests/robustness/makefile.mk b/tests/robustness/Makefile similarity index 76% rename from tests/robustness/makefile.mk rename to tests/robustness/Makefile index c5a1dff14a78..0c60688038fc 100644 --- a/tests/robustness/makefile.mk +++ b/tests/robustness/Makefile @@ -1,68 +1,80 @@ +REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) + .PHONY: test-robustness-reports -test-robustness-reports: export GOTOOLCHAIN := go$(shell cat .go-version) +test-robustness-reports: export GOTOOLCHAIN := go$(shell cat $(REPOSITORY_ROOT)/.go-version) test-robustness-reports: - cd ./tests && go test ./robustness/validate -v --count 1 --run TestDataReports + cd $(REPOSITORY_ROOT)/tests && go test ./robustness/validate -v --count 1 --run TestDataReports # Test main and previous release branches +# Note that executing make at the top-level repository needs a change in the +# directory. So, instead of calling just $(MAKE) or make, use this +# $(TOPLEVEL_MAKE) variable. +TOPLEVEL_MAKE := $(MAKE) --directory=$(REPOSITORY_ROOT) + .PHONY: test-robustness-main -test-robustness-main: /tmp/etcd-main-failpoints/bin /tmp/etcd-release-3.5-failpoints/bin - GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-main-failpoints/bin --bin-last-release=/tmp/etcd-release-3.5-failpoints/bin/etcd" $(MAKE) test-robustness +test-robustness-main: /tmp/etcd-main-failpoints/bin /tmp/etcd-release-3.6-failpoints/bin + GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-main-failpoints/bin --bin-last-release=/tmp/etcd-release-3.6-failpoints/bin/etcd" $(TOPLEVEL_MAKE) test-robustness + +.PHONY: test-robustness-release-3.6 +test-robustness-release-3.6: /tmp/etcd-release-3.6-failpoints/bin /tmp/etcd-release-3.5-failpoints/bin + GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-release-3.6-failpoints/bin --bin-last-release=/tmp/etcd-release-3.5-failpoints/bin/etcd" $(TOPLEVEL_MAKE) test-robustness .PHONY: test-robustness-release-3.5 test-robustness-release-3.5: /tmp/etcd-release-3.5-failpoints/bin /tmp/etcd-release-3.4-failpoints/bin - GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-release-3.5-failpoints/bin --bin-last-release=/tmp/etcd-release-3.4-failpoints/bin/etcd" $(MAKE) test-robustness + GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-release-3.5-failpoints/bin --bin-last-release=/tmp/etcd-release-3.4-failpoints/bin/etcd" $(TOPLEVEL_MAKE) test-robustness .PHONY: test-robustness-release-3.4 test-robustness-release-3.4: /tmp/etcd-release-3.4-failpoints/bin - GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-release-3.4-failpoints/bin" $(MAKE) test-robustness + GO_TEST_FLAGS="$${GO_TEST_FLAGS} --bin-dir=/tmp/etcd-release-3.4-failpoints/bin" $(TOPLEVEL_MAKE) test-robustness # Reproduce historical issues .PHONY: test-robustness-issue14370 test-robustness-issue14370: /tmp/etcd-v3.5.4-failpoints/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue14370 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.4-failpoints/bin' $(MAKE) test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue14370 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.4-failpoints/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue13766 test-robustness-issue13766: /tmp/etcd-v3.5.2-failpoints/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue13766 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.2-failpoints/bin' $(MAKE) test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue13766 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.2-failpoints/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue14685 test-robustness-issue14685: /tmp/etcd-v3.5.5-failpoints/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue14685 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.5-failpoints/bin' $(MAKE) test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue14685 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.5-failpoints/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue15271 test-robustness-issue15271: /tmp/etcd-v3.5.7-failpoints/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue15271 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.7-failpoints/bin' $(MAKE) test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue15271 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.7-failpoints/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue17529 test-robustness-issue17529: /tmp/etcd-v3.5.12-beforeSendWatchResponse/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue17529 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.12-beforeSendWatchResponse/bin' $(MAKE) test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue17529 --count 100 --failfast --bin-dir=/tmp/etcd-v3.5.12-beforeSendWatchResponse/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue17780 test-robustness-issue17780: /tmp/etcd-v3.5.13-compactBeforeSetFinishedCompact/bin - GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue17780 --count 200 --failfast --bin-dir=/tmp/etcd-v3.5.13-compactBeforeSetFinishedCompact/bin' make test-robustness && \ + GO_TEST_FLAGS='-v --run=TestRobustnessRegression/Issue17780 --count 200 --failfast --bin-dir=/tmp/etcd-v3.5.13-compactBeforeSetFinishedCompact/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue18089 test-robustness-issue18089: /tmp/etcd-v3.5.12-beforeSendWatchResponse/bin - GO_TEST_FLAGS='-v -run=TestRobustnessRegression/Issue18089 -count 100 -failfast --bin-dir=/tmp/etcd-v3.5.12-beforeSendWatchResponse/bin' make test-robustness && \ + GO_TEST_FLAGS='-v -run=TestRobustnessRegression/Issue18089 -count 100 -failfast --bin-dir=/tmp/etcd-v3.5.12-beforeSendWatchResponse/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" .PHONY: test-robustness-issue19179 test-robustness-issue19179: /tmp/etcd-v3.5.17-failpoints/bin - GO_TEST_FLAGS='-v -run=TestRobustnessRegression/Issue19179 -count 200 -failfast --bin-dir=/tmp/etcd-v3.5.17-failpoints/bin' make test-robustness && \ + GO_TEST_FLAGS='-v -run=TestRobustnessRegression/Issue19179 -count 200 -failfast --bin-dir=/tmp/etcd-v3.5.17-failpoints/bin' $(TOPLEVEL_MAKE) test-robustness && \ echo "Failed to reproduce" || echo "Successful reproduction" + # Failpoints GOPATH = $(shell go env GOPATH) -GOFAIL_VERSION = $(shell cd tools/mod && go list -m -f {{.Version}} go.etcd.io/gofail) +GOFAIL_VERSION = $(shell cd $(REPOSITORY_ROOT)/tools/mod && go list -m -f {{.Version}} go.etcd.io/gofail) .PHONY:install-gofail install-gofail: $(GOPATH)/bin/gofail @@ -70,20 +82,20 @@ install-gofail: $(GOPATH)/bin/gofail .PHONY: gofail-enable gofail-enable: $(GOPATH)/bin/gofail $(GOPATH)/bin/gofail enable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/ - cd ./server && go get go.etcd.io/gofail@${GOFAIL_VERSION} - cd ./etcdutl && go get go.etcd.io/gofail@${GOFAIL_VERSION} - cd ./etcdctl && go get go.etcd.io/gofail@${GOFAIL_VERSION} - cd ./tests && go get go.etcd.io/gofail@${GOFAIL_VERSION} + cd $(REPOSITORY_ROOT)/server && go get go.etcd.io/gofail@${GOFAIL_VERSION} + cd $(REPOSITORY_ROOT)/etcdutl && go get go.etcd.io/gofail@${GOFAIL_VERSION} + cd $(REPOSITORY_ROOT)/etcdctl && go get go.etcd.io/gofail@${GOFAIL_VERSION} + cd $(REPOSITORY_ROOT)/tests && go get go.etcd.io/gofail@${GOFAIL_VERSION} .PHONY: gofail-disable gofail-disable: $(GOPATH)/bin/gofail $(GOPATH)/bin/gofail disable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/ - cd ./server && go mod tidy - cd ./etcdutl && go mod tidy - cd ./etcdctl && go mod tidy - cd ./tests && go mod tidy + cd $(REPOSITORY_ROOT)/server && go mod tidy + cd $(REPOSITORY_ROOT)/etcdutl && go mod tidy + cd $(REPOSITORY_ROOT)/etcdctl && go mod tidy + cd $(REPOSITORY_ROOT)/tests && go mod tidy -$(GOPATH)/bin/gofail: tools/mod/go.mod tools/mod/go.sum +$(GOPATH)/bin/gofail: $(REPOSITORY_ROOT)/tools/mod/go.mod $(REPOSITORY_ROOT)/tools/mod/go.sum go install go.etcd.io/gofail@${GOFAIL_VERSION} # Build main and previous releases for robustness tests @@ -104,6 +116,14 @@ $(GOPATH)/bin/gofail: tools/mod/go.mod tools/mod/go.sum $(MAKE) gofail-enable; \ $(MAKE) build; +/tmp/etcd-release-3.6-failpoints/bin: $(GOPATH)/bin/gofail + rm -rf /tmp/etcd-release-3.6-failpoints/ + mkdir -p /tmp/etcd-release-3.6-failpoints/ + cd /tmp/etcd-release-3.6-failpoints/; \ + git clone --depth 1 --branch release-3.6 https://github.com/etcd-io/etcd.git .; \ + $(MAKE) gofail-enable; \ + $(MAKE) build; + /tmp/etcd-v3.5.2-failpoints/bin: /tmp/etcd-v3.5.4-failpoints/bin: /tmp/etcd-v3.5.5-failpoints/bin: diff --git a/tests/robustness/README.md b/tests/robustness/README.md index e47e61d158d6..b1ea6bae614a 100644 --- a/tests/robustness/README.md +++ b/tests/robustness/README.md @@ -39,7 +39,7 @@ The purpose of these tests is to rigorously validate that etcd maintains its [KV ## How Robustness Tests Work -Robustness tests compare etcd cluster behavior against a simplified model of its expected behavior. +Robustness tests compare the etcd cluster behavior against a simplified model of its expected behavior. These tests cover various scenarios, including: * **Different etcd cluster setups:** Cluster sizes, configurations, and deployment topologies. @@ -52,8 +52,8 @@ These tests cover various scenarios, including: 2. **Traffic and Failures:** Client traffic is generated and sent to the cluster while failures are injected. 3. **History Collection:** All client operations and their results are recorded. 4. **Validation:** The collected history is validated against the etcd model and a set of validators to ensure consistency and correctness. -5. **Report Generation:** If a failure is detected and a detailed report is generated to help diagnose the issue. - This report includes information about the client operations, etcd data directories. +5. **Report Generation:** If a failure is detected then a detailed report is generated to help diagnose the issue. + This report includes information about the client operations and etcd data directories. ## Key Concepts @@ -96,26 +96,25 @@ Etcd provides strict serializability for KV operations and eventual consistency make gofail-disable ``` 2. Run the tests - ```bash make test-robustness ``` - Optionally you can pass environment variables: + Optionally, you can pass environment variables: * `GO_TEST_FLAGS` - to pass additional arguments to `go test`. It is recommended to run tests multiple times with failfast enabled. this can be done by setting `GO_TEST_FLAGS='--count=100 --failfast'`. * `EXPECT_DEBUG=true` - to get logs from the cluster. - * `RESULTS_DIR` - to change location where results report will be saved. + * `RESULTS_DIR` - to change the location where the results report will be saved. * `PERSIST_RESULTS` - to persist the results report of the test. By default this will not be persisted in the case of a successful run. ## Re-evaluate existing report Robustness test validation is constantly changing and improving. -Errors in etcd model could be causing false positives, which makes the ability to re-evaluate the reports after we fix the issue important. +Errors in the etcd model could be causing false positives, which makes the ability to re-evaluate the reports after we fix the issue important. > Note: Robustness test report format is not stable, and it's expected that not all old reports can be re-evaluated using the newest version. -1. Identify location of the robustness test report. +1. Identify the location of the robustness test report. > Note: By default robustness test report is only generated for failed test. @@ -124,7 +123,7 @@ Errors in etcd model could be causing false positives, which makes the ability t logger.go:146: 2024-04-08T09:45:27.734+0200 INFO Saving robustness test report {"path": "/tmp/TestRobustnessExploratory_Etcd_HighTraffic_ClusterOfSize1"} ``` - * **For remote runs on CI:** you need to go to the [Prow Dashboard](https://prow.k8s.io/job-history/gs/kubernetes-jenkins/logs/ci-etcd-robustness-amd64), go to a build, download one of the Artifacts (`artifacts/results.zip`), and extract it locally. + * **For remote runs on CI:** you need to go to the [Prow Dashboard](https://testgrid.k8s.io/sig-etcd-robustness#Summary), go to a build, download one of the Artifacts (`artifacts/results.zip`), and extract it locally. ![Prow job run page](readme-images/prow_job.png) @@ -144,14 +143,14 @@ Errors in etcd model could be causing false positives, which makes the ability t The `testdata` directory can contain multiple robustness test reports. The name of the report directory doesn't matter, as long as it's unique to prevent clashing with reports already present in `testdata` directory. - For example path for `history.html` file could look like `$REPO_ROOT/tests/robustness/testdata/v3.5_failure_24_April/history.html`. + For example, the path for `history.html` file could look like `$REPO_ROOT/tests/robustness/testdata/v3.5_failure_24_April/history.html`. 3. Run `make test-robustness-reports` to validate all reports in the `testdata` directory. ## Analysing failure -If robustness tests fails we want to analyse the report to confirm if the issue is on etcd side. Location of the directory with the report -is mentioned `Saving robustness test report` log. Logs from report generation should look like: +If robustness tests fail, we want to analyse the report to confirm if the issue is on etcd side. The location of the directory with the report +is mentioned in the `Saving robustness test report` log. Logs from report generation should look like: ``` logger.go:146: 2024-05-08T10:42:54.429+0200 INFO Saving robustness test report {"path": "/tmp/TestRobustnessRegression_Issue14370/1715157774429416550"} logger.go:146: 2024-05-08T10:42:54.429+0200 INFO Saving member data dir {"member": "TestRobustnessRegressionIssue14370-test-0", "path": "/tmp/TestRobustnessRegression_Issue14370/1715157774429416550/server-TestRobustnessRegressionIssue14370-test-0"} @@ -178,21 +177,21 @@ is mentioned `Saving robustness test report` log. Logs from report generation sh logger.go:146: 2024-05-08T10:42:54.441+0200 INFO Saving visualization {"path": "/tmp/TestRobustnessRegression_Issue14370/1715157774429416550/history.html"} ``` -Report follows the hierarchy: +The report follows the hierarchy: * `server-*` - etcd server data directories, can be used to verify disk/memory corruption. * `member` * `wal` - Write Ahead Log (WAL) directory, that can be analysed using `etcd-dump-logs` command line tool available in `tools` directory. * `snap` - Snapshot directory, includes the bbolt database file `db`, that can be analysed using `etcd-dump-db` command line tool available in `tools` directory. * `client-*` - Client request and response dumps in json format. - * `watch.jon` - Watch requests and responses, can be used to validate [watch API guarantees]. + * `watch.json` - Watch requests and responses, can be used to validate [watch API guarantees]. * `operations.json` - KV operation history * `history.html` - Visualization of KV operation history, can be used to validate [KV API guarantees]. -### Example analysis of linearization issue +### Example analysis of a linearization issue Let's reproduce and analyse robustness test report for issue [#14370]. To reproduce the issue by yourself run `make test-robustness-issue14370`. -After a couple of tries robustness tests should fail with a log `Linearization failed` and save report locally. +After a couple of tries robustness tests should fail with a log `Linearization failed` and save the report locally. Example: ``` @@ -211,14 +210,14 @@ Jump to the error in linearization by clicking `[ jump to first error ]` on the You should see a graph similar to the one on the image below. ![issue14370](readme-images/issue14370.png) -Last correct request (connected with grey line) is a `Put` request that succeeded and got revision `168`. +The last correct request (connected with the grey line) is a `Put` request that succeeded and got revision `168`. All following requests are invalid (connected with red line) as they have revision `167`. -Etcd guarantee that revision is non-decreasing, so this shows a bug in etcd as there is no way revision should decrease. -This is consistent with the root cause of [#14370] as it was issue with process crash causing last write to be lost. +Etcd guarantees that revision is non-decreasing, so this shows a bug in etcd as there is no way revision should decrease. +This is consistent with the root cause of [#14370] as it was an issue with the process crash causing the last write to be lost. [#14370]: https://github.com/etcd-io/etcd/issues/14370 -### Example analysis of watch issue +### Example analysis of a watch issue Let's reproduce and analyse robustness test report for issue [#15271]. To reproduce the issue by yourself run `make test-robustness-issue15271`. @@ -236,22 +235,24 @@ Example: ``` Watch issues are easiest to analyse by reading the recorded watch history. -Watch history is recorded for each client separated in different subdirectory under `/tmp/TestRobustnessRegression_Issue15271/1715158215866033806` -Open `watch.json` for client mentioned in log `Broke watch guarantee`. + +Watch history is recorded for each client separated in different subdirectory under `/tmp/TestRobustnessRegression_Issue15271/1715158215866033806`. + +Open `watch.json` for the client mentioned in the log `Broke watch guarantee`. For client `4` that broke the watch guarantee open `/tmp/TestRobustnessRegression_Issue15271/1715158215866033806/client-4/watch.json`. -Each line consists of json blob corresponding to single watch request sent by client. -Look for events with `Revision` equal to revision mentioned in the first log with `Broke watch guarantee`, in this case look for `"Revision":3,`. +Each line consists of json blob corresponding to a single watch request sent by the client. +Look for events with `Revision` equal to revision mentioned in the first log with `Broke watch guarantee`, in this case, look for `"Revision":3,`. You should see watch responses where the `Revision` decreases like ones below: ``` {"Events":[{"Type":"put-operation","Key":"key5","Value":{"Value":"793","Hash":0},"Revision":799,"IsCreate":false,"PrevValue":null}],"IsProgressNotify":false,"Revision":799,"Time":3202907249,"Error":""} {"Events":[{"Type":"put-operation","Key":"key4","Value":{"Value":"1","Hash":0},"Revision":3,"IsCreate":true,"PrevValue":null}, ... ``` -Up to the first response the `Revision` of events only increased up to a value of `799`. +Up to the first response, the `Revision` of events only increased up to a value of `799`. However, the following line includes an event with `Revision` equal `3`. -If you follow the `revision` throughout the file you should notice that watch replayed revisions second time. +If you follow the `revision` throughout the file you should notice that watch replayed revisions for a second time. This is incorrect and breaks `Ordered` [watch API guarantees]. -This is consistent with the root cause of [#14370] where member reconnecting to cluster will resend revisions. +This is consistent with the root cause of [#14370] where the member reconnecting to cluster will resend revisions. [#15271]: https://github.com/etcd-io/etcd/issues/15271 diff --git a/tests/robustness/client/kvhash.go b/tests/robustness/client/kvhash.go new file mode 100644 index 000000000000..e4fc2b482093 --- /dev/null +++ b/tests/robustness/client/kvhash.go @@ -0,0 +1,69 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package client + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/e2e" +) + +func CheckEndOfTestHashKV(ctx context.Context, clus *e2e.EtcdProcessCluster) error { + c, err := clientv3.New(clientv3.Config{ + Endpoints: clus.EndpointsGRPC(), + Logger: zap.NewNop(), + DialKeepAliveTime: 10 * time.Second, + DialKeepAliveTimeout: 100 * time.Millisecond, + }) + if err != nil { + return err + } + defer c.Close() + + hashKV, err := c.HashKV(ctx, clus.EndpointsGRPC()[0], 0) + if err != nil { + return err + } + rev := hashKV.Header.Revision + + hashKVs := make([]*clientv3.HashKVResponse, 0) + hashKVs = append(hashKVs, hashKV) + + for _, member := range clus.Procs { + hashKV, err := c.HashKV(ctx, member.EndpointsGRPC()[0], rev) + if err != nil { + return err + } + if hashKV.Header.Revision != rev { + return fmt.Errorf("latest revision at the end of the test between nodes should be the same. Want %v, get %v", rev, hashKV.Header.Revision) + } + hashKVs = append(hashKVs, hashKV) + } + + for i := 1; i < len(hashKVs); i++ { + if hashKVs[i-1].CompactRevision != hashKVs[i].CompactRevision { + return fmt.Errorf("compactRevision mismatch, node %v has %+v, node %v has %+v", hashKVs[i-1].Header.MemberId, hashKVs[i-1], hashKVs[i].Header.MemberId, hashKVs[i]) + } + if hashKVs[i-1].Hash != hashKVs[i].Hash { + return fmt.Errorf("hash mismatch, node %v has %+v, node %v has %+v", hashKVs[i-1].Header.MemberId, hashKVs[i-1], hashKVs[i].Header.MemberId, hashKVs[i]) + } + } + return nil +} diff --git a/tests/robustness/client/watch.go b/tests/robustness/client/watch.go index c980fdfba055..4b4b0de153f9 100644 --- a/tests/robustness/client/watch.go +++ b/tests/robustness/client/watch.go @@ -16,47 +16,45 @@ package client import ( "context" - "sync" + "errors" + "fmt" "testing" "time" - "github.com/stretchr/testify/require" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" - "go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/robustness/identity" "go.etcd.io/etcd/tests/v3/robustness/report" ) -func CollectClusterWatchEvents(ctx context.Context, t *testing.T, clus *e2e.EtcdProcessCluster, maxRevisionChan <-chan int64, cfg WatchConfig, baseTime time.Time, ids identity.Provider) []report.ClientReport { - mux := sync.Mutex{} - var wg sync.WaitGroup - reports := make([]report.ClientReport, len(clus.Procs)) - memberMaxRevisionChans := make([]chan int64, len(clus.Procs)) - for i, member := range clus.Procs { - c, err := NewRecordingClient(member.EndpointsGRPC(), ids, baseTime) - require.NoError(t, err) +func CollectClusterWatchEvents(ctx context.Context, lg *zap.Logger, endpoints []string, maxRevisionChan <-chan int64, cfg WatchConfig, baseTime time.Time, ids identity.Provider) ([]report.ClientReport, error) { + var g errgroup.Group + reports := make([]report.ClientReport, len(endpoints)) + memberMaxRevisionChans := make([]chan int64, len(endpoints)) + for i, endpoint := range endpoints { memberMaxRevisionChan := make(chan int64, 1) memberMaxRevisionChans[i] = memberMaxRevisionChan - wg.Add(1) - go func(i int, c *RecordingClient) { - defer wg.Done() + g.Go(func() error { + c, err := NewRecordingClient([]string{endpoint}, ids, baseTime) + if err != nil { + return err + } defer c.Close() - watchUntilRevision(ctx, t, c, memberMaxRevisionChan, cfg) - mux.Lock() + err = watchUntilRevision(ctx, lg, c, memberMaxRevisionChan, cfg) reports[i] = c.Report() - mux.Unlock() - }(i, c) + return err + }) } - wg.Add(1) - go func() { - defer wg.Done() + + g.Go(func() error { maxRevision := <-maxRevisionChan for _, memberChan := range memberMaxRevisionChans { memberChan <- maxRevision } - }() - wg.Wait() - return reports + return nil + }) + return reports, g.Wait() } type WatchConfig struct { @@ -64,39 +62,43 @@ type WatchConfig struct { } // watchUntilRevision watches all changes until context is cancelled, it has observed revision provided via maxRevisionChan or maxRevisionChan was closed. -func watchUntilRevision(ctx context.Context, t *testing.T, c *RecordingClient, maxRevisionChan <-chan int64, cfg WatchConfig) { +func watchUntilRevision(ctx context.Context, lg *zap.Logger, c *RecordingClient, maxRevisionChan <-chan int64, cfg WatchConfig) error { var maxRevision int64 var lastRevision int64 = 1 + var closing bool ctx, cancel := context.WithCancel(ctx) defer cancel() resetWatch: for { + if closing { + if maxRevision == 0 { + return errors.New("Client didn't collect all events, max revision not set") + } + if lastRevision < maxRevision { + return fmt.Errorf("Client didn't collect all events, got: %d, expected: %d", lastRevision, maxRevision) + } + return nil + } watch := c.Watch(ctx, "", lastRevision+1, true, true, false) for { select { - case <-ctx.Done(): - if maxRevision == 0 { - t.Errorf("Client didn't collect all events, max revision not set") - } - if lastRevision < maxRevision { - t.Errorf("Client didn't collect all events, revision got %d, expected: %d", lastRevision, maxRevision) - } - return case revision, ok := <-maxRevisionChan: if ok { maxRevision = revision if lastRevision >= maxRevision { + closing = true cancel() } } else { // Only cancel if maxRevision was never set. if maxRevision == 0 { + closing = true cancel() } } case resp, ok := <-watch: if !ok { - t.Logf("Watch channel closed") + lg.Info("Watch channel closed") continue resetWatch } if cfg.RequestProgress { @@ -110,12 +112,13 @@ resetWatch: } continue resetWatch } - t.Errorf("Watch stream received error, err %v", resp.Err()) + return fmt.Errorf("watch stream received error: %w", resp.Err()) } if len(resp.Events) > 0 { lastRevision = resp.Events[len(resp.Events)-1].Kv.ModRevision } if maxRevision != 0 && lastRevision >= maxRevision { + closing = true cancel() } } diff --git a/tests/robustness/failpoint/cluster.go b/tests/robustness/failpoint/cluster.go index 780e0c00635a..eb619036875e 100644 --- a/tests/robustness/failpoint/cluster.go +++ b/tests/robustness/failpoint/cluster.go @@ -174,7 +174,7 @@ func (f memberDowngrade) Inject(ctx context.Context, t *testing.T, lg *zap.Logge time.Sleep(etcdserver.HealthInterval) e2e.DowngradeEnable(t, clus, lastVersion) - err = e2e.DowngradeUpgradeMembers(t, lg, clus, numberOfMembersToDowngrade, currentVersion, lastVersion) + err = e2e.DowngradeUpgradeMembers(t, lg, clus, numberOfMembersToDowngrade, true, currentVersion, lastVersion) time.Sleep(etcdserver.HealthInterval) return nil, err } @@ -228,13 +228,20 @@ func (f memberDowngradeUpgrade) Inject(ctx context.Context, t *testing.T, lg *za e2e.DowngradeEnable(t, clus, lastVersion) // downgrade all members first - err = e2e.DowngradeUpgradeMembers(t, lg, clus, len(clus.Procs), currentVersion, lastVersion) + err = e2e.DowngradeUpgradeMembers(t, lg, clus, len(clus.Procs), true, currentVersion, lastVersion) if err != nil { return nil, err } + + // NOTE: By default, the leader can cancel the downgrade once all members + // have reached the target version. However, determining the final stable + // cluster version after an upgrade can be challenging. To ensure stability, + // we should wait for leader to cancel downgrade process. + e2e.AssertProcessLogs(t, clus.Procs[clus.WaitLeader(t)], "the cluster has been downgraded") + // partial upgrade the cluster numberOfMembersToUpgrade := rand.Int()%len(clus.Procs) + 1 - err = e2e.DowngradeUpgradeMembers(t, lg, clus, numberOfMembersToUpgrade, lastVersion, currentVersion) + err = e2e.DowngradeUpgradeMembers(t, lg, clus, numberOfMembersToUpgrade, false, lastVersion, currentVersion) time.Sleep(etcdserver.HealthInterval) return nil, err } diff --git a/tests/robustness/failpoint/trigger.go b/tests/robustness/failpoint/trigger.go index 9ae9a8084819..a75feb71d2cc 100644 --- a/tests/robustness/failpoint/trigger.go +++ b/tests/robustness/failpoint/trigger.go @@ -75,7 +75,7 @@ func (t triggerCompact) Trigger(ctx context.Context, _ *testing.T, member e2e.Et } rev = resp.Header.Revision - if !t.multiBatchCompaction || rev > int64(clus.Cfg.ServerConfig.ExperimentalCompactionBatchLimit) { + if !t.multiBatchCompaction || rev > int64(clus.Cfg.ServerConfig.CompactionBatchLimit) { break } time.Sleep(50 * time.Millisecond) @@ -97,9 +97,9 @@ func (t triggerCompact) Available(config e2e.EtcdProcessClusterConfig, _ e2e.Etc return false } // For multiBatchCompaction we need to guarantee that there are enough revisions between two compaction requests. - // With addition of compaction requests to traffic this might be hard if experimental-compaction-batch-limit is too high. + // With addition of compaction requests to traffic this might be hard if -compaction-batch-limit is too high. if t.multiBatchCompaction { - return config.ServerConfig.ExperimentalCompactionBatchLimit <= 10 + return config.ServerConfig.CompactionBatchLimit <= 10 } return true } diff --git a/tests/robustness/main_test.go b/tests/robustness/main_test.go index de45d943dbcb..55879cabe9af 100644 --- a/tests/robustness/main_test.go +++ b/tests/robustness/main_test.go @@ -16,7 +16,11 @@ package robustness import ( "context" + "fmt" "math/rand" + "os" + "path/filepath" + "strings" "testing" "time" @@ -30,7 +34,6 @@ import ( "go.etcd.io/etcd/tests/v3/robustness/client" "go.etcd.io/etcd/tests/v3/robustness/failpoint" "go.etcd.io/etcd/tests/v3/robustness/identity" - "go.etcd.io/etcd/tests/v3/robustness/model" "go.etcd.io/etcd/tests/v3/robustness/report" "go.etcd.io/etcd/tests/v3/robustness/scenarios" "go.etcd.io/etcd/tests/v3/robustness/traffic" @@ -55,7 +58,7 @@ func TestRobustnessExploratory(t *testing.T) { t.Run(s.Name, func(t *testing.T) { lg := zaptest.NewLogger(t) s.Cluster.Logger = lg - ctx := context.Background() + ctx := t.Context() c, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithConfig(&s.Cluster)) require.NoError(t, err) defer forcestopCluster(c) @@ -74,7 +77,7 @@ func TestRobustnessRegression(t *testing.T) { t.Run(s.Name, func(t *testing.T) { lg := zaptest.NewLogger(t) s.Cluster.Logger = lg - ctx := context.Background() + ctx := t.Context() c, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithConfig(&s.Cluster)) require.NoError(t, err) defer forcestopCluster(c) @@ -84,26 +87,40 @@ func TestRobustnessRegression(t *testing.T) { } func testRobustness(ctx context.Context, t *testing.T, lg *zap.Logger, s scenarios.TestScenario, c *e2e.EtcdProcessCluster) { - r := report.TestReport{Logger: lg, Cluster: c} + serverDataPaths := report.ServerDataPaths(c) + r := report.TestReport{Logger: lg, ServersDataPath: serverDataPaths} // t.Failed() returns false during panicking. We need to forcibly // save data on panicking. // Refer to: https://github.com/golang/go/issues/49929 panicked := true defer func() { - r.Report(t, panicked) + _, persistResults := os.LookupEnv("PERSIST_RESULTS") + shouldReport := t.Failed() || panicked || persistResults + path := testResultsDirectory(t) + if shouldReport { + if err := r.Report(path); err != nil { + t.Error(err) + } + } }() r.Client = runScenario(ctx, t, s, lg, c) persistedRequests, err := report.PersistedRequestsCluster(lg, c) - require.NoError(t, err) + if err != nil { + t.Error(err) + } failpointImpactingWatch := s.Failpoint == failpoint.SleepBeforeSendWatchResponse if !failpointImpactingWatch { - watchProgressNotifyEnabled := c.Cfg.ServerConfig.ExperimentalWatchProgressNotifyInterval != 0 + watchProgressNotifyEnabled := c.Cfg.ServerConfig.WatchProgressNotifyInterval != 0 client.ValidateGotAtLeastOneProgressNotify(t, r.Client, s.Watch.RequestProgress || watchProgressNotifyEnabled) } validateConfig := validate.Config{ExpectRevisionUnique: s.Traffic.ExpectUniqueRevision()} - r.Visualize = validate.ValidateAndReturnVisualize(t, lg, validateConfig, r.Client, persistedRequests, 5*time.Minute) - + result := validate.ValidateAndReturnVisualize(lg, validateConfig, r.Client, persistedRequests, 5*time.Minute) + r.Visualize = result.Linearization.Visualize + err = result.Error() + if err != nil { + t.Error(err) + } panicked = false } @@ -139,16 +156,26 @@ func runScenario(ctx context.Context, t *testing.T, s scenarios.TestScenario, lg g.Go(func() error { defer close(maxRevisionChan) operationReport = traffic.SimulateTraffic(ctx, t, lg, clus, s.Profile, s.Traffic, failpointInjected, baseTime, ids) - maxRevision := operationsMaxRevision(operationReport) + maxRevision := report.OperationsMaxRevision(operationReport) maxRevisionChan <- maxRevision lg.Info("Finished simulating Traffic", zap.Int64("max-revision", maxRevision)) return nil }) g.Go(func() error { - watchReport = client.CollectClusterWatchEvents(ctx, t, clus, maxRevisionChan, s.Watch, baseTime, ids) - return nil + var err error + endpoints := processEndpoints(clus) + watchReport, err = client.CollectClusterWatchEvents(ctx, lg, endpoints, maxRevisionChan, s.Watch, baseTime, ids) + return err }) - g.Wait() + err := g.Wait() + if err != nil { + t.Error(err) + } + + err = client.CheckEndOfTestHashKV(ctx, clus) + if err != nil { + t.Error(err) + } return append(operationReport, append(failpointClientReport, watchReport...)...) } @@ -156,19 +183,6 @@ func randomizeTime(base time.Duration, jitter time.Duration) time.Duration { return base - jitter + time.Duration(rand.Int63n(int64(jitter)*2)) } -func operationsMaxRevision(reports []report.ClientReport) int64 { - var maxRevision int64 - for _, r := range reports { - for _, op := range r.KeyValue { - resp := op.Output.(model.MaybeEtcdResponse) - if resp.Revision > maxRevision { - maxRevision = resp.Revision - } - } - } - return maxRevision -} - // forcestopCluster stops the etcd member with signal kill. func forcestopCluster(clus *e2e.EtcdProcessCluster) error { for _, member := range clus.Procs { @@ -176,3 +190,30 @@ func forcestopCluster(clus *e2e.EtcdProcessCluster) error { } return clus.ConcurrentStop() } + +func testResultsDirectory(t *testing.T) string { + resultsDirectory, ok := os.LookupEnv("RESULTS_DIR") + if !ok { + resultsDirectory = "/tmp/" + } + resultsDirectory, err := filepath.Abs(resultsDirectory) + if err != nil { + panic(err) + } + path, err := filepath.Abs(filepath.Join( + resultsDirectory, strings.ReplaceAll(t.Name(), "/", "_"), fmt.Sprintf("%v", time.Now().UnixNano()))) + require.NoError(t, err) + err = os.RemoveAll(path) + require.NoError(t, err) + err = os.MkdirAll(path, 0o700) + require.NoError(t, err) + return path +} + +func processEndpoints(clus *e2e.EtcdProcessCluster) []string { + endpoints := make([]string, 0, len(clus.Procs)) + for _, proc := range clus.Procs { + endpoints = append(endpoints, proc.EndpointsGRPC()[0]) + } + return endpoints +} diff --git a/tests/robustness/model/describe.go b/tests/robustness/model/describe.go index 43eb58c06bdc..3d73257f6ed6 100644 --- a/tests/robustness/model/describe.go +++ b/tests/robustness/model/describe.go @@ -82,6 +82,37 @@ func describeEtcdRequest(request EtcdRequest) string { } } +func describeEtcdState(state EtcdState) string { + descHTML := make([]string, 0) + + descHTML = append(descHTML, fmt.Sprintf("

Revision: %d, CompactRevision: %d

", state.Revision, state.CompactRevision)) + + if len(state.KeyValues) > 0 { + descHTML = append(descHTML, "
    ") + + for k, v := range state.KeyValues { + keyValDesc := make([]string, 0) + + keyValDesc = append(keyValDesc, fmt.Sprintf("
  • %s: ", k)) + + if v.Value.Value != "" { + keyValDesc = append(keyValDesc, fmt.Sprintf("Value: %q, ", v.Value.Value)) + } + if v.Value.Hash != 0 { + keyValDesc = append(keyValDesc, fmt.Sprintf("Hash: %d, ", v.Value.Hash)) + } + + keyValDesc = append(keyValDesc, fmt.Sprintf("ModRevision: %d, Version: %d
  • ", v.ModRevision, v.Version)) + + descHTML = append(descHTML, strings.Join(keyValDesc, "")) + } + + descHTML = append(descHTML, "
") + } + + return strings.Join(descHTML, "") +} + func describeGuaranteedTxn(txn *TxnRequest) string { if len(txn.Conditions) != 1 || len(txn.OperationsOnSuccess) != 1 || len(txn.OperationsOnFailure) > 1 { return "" diff --git a/tests/robustness/model/deterministic.go b/tests/robustness/model/deterministic.go index 9b61d13afd0c..70aaa581959b 100644 --- a/tests/robustness/model/deterministic.go +++ b/tests/robustness/model/deterministic.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "hash/fnv" + "html" "maps" "reflect" "sort" @@ -42,36 +43,48 @@ import ( // whole change history as real etcd does. var DeterministicModel = porcupine.Model{ Init: func() any { - data, err := json.Marshal(freshEtcdState()) - if err != nil { - panic(err) - } - return string(data) + return freshEtcdState() }, Step: func(st any, in any, out any) (bool, any) { - var s EtcdState - err := json.Unmarshal([]byte(st.(string)), &s) - if err != nil { - panic(err) - } - ok, s := s.apply(in.(EtcdRequest), out.(EtcdResponse)) - data, err := json.Marshal(s) - if err != nil { - panic(err) - } - return ok, string(data) + return st.(EtcdState).apply(in.(EtcdRequest), out.(EtcdResponse)) + }, + Equal: func(st1, st2 any) bool { + return st1.(EtcdState).Equal(st2.(EtcdState)) }, DescribeOperation: func(in, out any) string { return fmt.Sprintf("%s -> %s", describeEtcdRequest(in.(EtcdRequest)), describeEtcdResponse(in.(EtcdRequest), MaybeEtcdResponse{EtcdResponse: out.(EtcdResponse)})) }, + DescribeState: func(st any) string { + data, err := json.MarshalIndent(st, "", " ") + if err != nil { + panic(err) + } + return "
" + html.EscapeString(string(data)) + "
" + }, } type EtcdState struct { - Revision int64 - CompactRevision int64 - KeyValues map[string]ValueRevision - KeyLeases map[string]int64 - Leases map[int64]EtcdLease + Revision int64 `json:",omitempty"` + CompactRevision int64 `json:",omitempty"` + KeyValues map[string]ValueRevision `json:",omitempty"` + KeyLeases map[string]int64 `json:",omitempty"` + Leases map[int64]EtcdLease `json:",omitempty"` +} + +func (s EtcdState) Equal(other EtcdState) bool { + if s.Revision != other.Revision { + return false + } + if s.CompactRevision != other.CompactRevision { + return false + } + if !reflect.DeepEqual(s.KeyValues, other.KeyValues) { + return false + } + if !reflect.DeepEqual(s.KeyLeases, other.KeyLeases) { + return false + } + return reflect.DeepEqual(s.Leases, other.Leases) } func (s EtcdState) apply(request EtcdRequest, response EtcdResponse) (bool, EtcdState) { @@ -109,21 +122,23 @@ func freshEtcdState() EtcdState { // Step handles a successful request, returning updated state and response it would generate. func (s EtcdState) Step(request EtcdRequest) (EtcdState, MaybeEtcdResponse) { - newState := s.DeepCopy() - - switch request.Type { - case Range: - if request.Range.Revision == 0 || request.Range.Revision == newState.Revision { - resp := newState.getRange(request.Range.RangeOptions) - return newState, MaybeEtcdResponse{EtcdResponse: EtcdResponse{Range: &resp, Revision: newState.Revision}} + // TODO: Avoid copying when TXN only has read operations + if request.Type == Range { + if request.Range.Revision == 0 || request.Range.Revision == s.Revision { + resp := s.getRange(request.Range.RangeOptions) + return s, MaybeEtcdResponse{EtcdResponse: EtcdResponse{Range: &resp, Revision: s.Revision}} } - if request.Range.Revision > newState.Revision { - return newState, MaybeEtcdResponse{Error: ErrEtcdFutureRev.Error()} + if request.Range.Revision > s.Revision { + return s, MaybeEtcdResponse{Error: ErrEtcdFutureRev.Error()} } - if request.Range.Revision < newState.CompactRevision { - return newState, MaybeEtcdResponse{EtcdResponse: EtcdResponse{ClientError: mvcc.ErrCompacted.Error()}} + if request.Range.Revision < s.CompactRevision { + return s, MaybeEtcdResponse{EtcdResponse: EtcdResponse{ClientError: mvcc.ErrCompacted.Error()}} } - return newState, MaybeEtcdResponse{Persisted: true, PersistedRevision: newState.Revision} + return s, MaybeEtcdResponse{Persisted: true, PersistedRevision: s.Revision} + } + + newState := s.DeepCopy() + switch request.Type { case Txn: failure := false for _, cond := range request.Txn.Conditions { @@ -444,14 +459,14 @@ func (el EtcdLease) DeepCopy() EtcdLease { } type ValueRevision struct { - Value ValueOrHash - ModRevision int64 - Version int64 + Value ValueOrHash `json:",omitempty"` + ModRevision int64 `json:",omitempty"` + Version int64 `json:",omitempty"` } type ValueOrHash struct { - Value string - Hash uint32 + Value string `json:",omitempty"` + Hash uint32 `json:",omitempty"` } func ToValueOrHash(value string) ValueOrHash { diff --git a/tests/robustness/model/history.go b/tests/robustness/model/history.go index fc224a8afffe..f6119984f5e6 100644 --- a/tests/robustness/model/history.go +++ b/tests/robustness/model/history.go @@ -41,14 +41,17 @@ type AppendableHistory struct { streamID int // If needed a new streamId is requested from idProvider. idProvider identity.Provider + // lastOperation holds the last operation of each stream. + lastOperation map[int]*porcupine.Operation History } func NewAppendableHistory(ids identity.Provider) *AppendableHistory { return &AppendableHistory{ - streamID: ids.NewStreamID(), - idProvider: ids, + streamID: ids.NewStreamID(), + idProvider: ids, + lastOperation: make(map[int]*porcupine.Operation), History: History{ operations: []porcupine.Operation{}, }, @@ -290,8 +293,6 @@ func (h *AppendableHistory) appendFailed(request EtcdRequest, start, end time.Du } isRead := request.IsRead() if !isRead { - // Failed writes can still be persisted, setting -1 for now as don't know when request has took effect. - op.Return = -1 // Operations of single client needs to be sequential. // As we don't know return time of failed operations, all new writes need to be done with new stream id. h.streamID = h.idProvider.NewStreamID() @@ -300,11 +301,11 @@ func (h *AppendableHistory) appendFailed(request EtcdRequest, start, end time.Du } func (h *AppendableHistory) append(op porcupine.Operation) { - if op.Return != -1 && op.Call >= op.Return { + if op.Call >= op.Return { panic(fmt.Sprintf("Invalid operation, call(%d) >= return(%d)", op.Call, op.Return)) } - if len(h.operations) > 0 { - prev := h.operations[len(h.operations)-1] + + if prev, ok := h.lastOperation[op.ClientId]; ok { if op.Call <= prev.Call { panic(fmt.Sprintf("Out of order append, new.call(%d) <= prev.call(%d)", op.Call, prev.Call)) } @@ -312,6 +313,8 @@ func (h *AppendableHistory) append(op porcupine.Operation) { panic(fmt.Sprintf("Overlapping operations, new.call(%d) <= prev.return(%d)", op.Call, prev.Return)) } } + h.lastOperation[op.ClientId] = &op + h.operations = append(h.operations, op) } @@ -488,34 +491,8 @@ func (h History) Len() int { func (h History) Operations() []porcupine.Operation { operations := make([]porcupine.Operation, 0, len(h.operations)) - maxTime := h.lastObservedTime() - for _, op := range h.operations { - // Failed requests don't have a known return time. - if op.Return == -1 { - // Simulate Infinity by using last observed time. - op.Return = maxTime + time.Second.Nanoseconds() - } - operations = append(operations, op) - } - return operations -} -func (h History) lastObservedTime() int64 { - var maxTime int64 - for _, op := range h.operations { - if op.Return == -1 { - // Collect call time from failed operations - if op.Call > maxTime { - maxTime = op.Call - } - } else { - // Collect return time from successful operations - if op.Return > maxTime { - maxTime = op.Return - } - } - } - return maxTime + return append(operations, h.operations...) } func (h History) MaxRevision() int64 { diff --git a/tests/robustness/model/history_test.go b/tests/robustness/model/history_test.go new file mode 100644 index 000000000000..9b6d22f31553 --- /dev/null +++ b/tests/robustness/model/history_test.go @@ -0,0 +1,245 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package model + +import ( + "testing" + + "github.com/anishathalye/porcupine" + "github.com/stretchr/testify/assert" + + "go.etcd.io/etcd/tests/v3/robustness/identity" +) + +func TestHistoryAppendSuccess(t *testing.T) { + tcs := []struct { + name string + operations []porcupine.Operation + }{ + { + name: "append non-overlapping operations for the same clientId", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 100, + Output: nil, + Return: 200, + }, + { + ClientId: 2, + Input: nil, + Call: 1, + Output: nil, + Return: 2, + }, + }, + }, + { + name: "append overlapping operations in different ClientId", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 1, + Output: nil, + Return: 100, + }, + { + ClientId: 2, + Input: nil, + Call: 1, + Output: nil, + Return: 2, + }, + { + ClientId: 3, + Input: nil, + Call: 10, + Output: nil, + Return: 20, + }, + { + ClientId: 1, + Input: nil, + Call: 101, + Output: nil, + Return: 200, + }, + { + ClientId: 2, + Input: nil, + Call: 3, + Output: nil, + Return: 4, + }, + { + ClientId: 3, + Input: nil, + Call: 30, + Output: nil, + Return: 40, + }, + }, + }, + } + + for _, tc := range tcs { + h := NewAppendableHistory(identity.NewIDProvider()) + + for _, operation := range tc.operations[:len(tc.operations)-1] { + h.append(operation) + } + } +} + +func TestHistoryAppendFailure(t *testing.T) { + tcs := []struct { + name string + operations []porcupine.Operation + expectError string + }{ + { + name: "append operation with call time > return time", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 2, + Output: nil, + Return: 1, + }, + }, + expectError: "Invalid operation, call(2) >= return(1)", + }, + { + name: "out of order append in the same ClientId", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 10, + Output: nil, + Return: 100, + }, + { + ClientId: 1, + Input: nil, + Call: 1, + Output: nil, + Return: 10, + }, + }, + expectError: "Out of order append, new.call(1) <= prev.call(10)", + }, + { + name: "out of order append in one of the ClientIds", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 10, + Output: nil, + Return: 100, + }, + { + ClientId: 1, + Input: nil, + Call: 101, + Output: nil, + Return: 200, + }, + { + ClientId: 2, + Input: nil, + Call: 10, + Output: nil, + Return: 100, + }, + { + ClientId: 2, + Input: nil, + Call: 1, + Output: nil, + Return: 10, + }, + }, + expectError: "Out of order append, new.call(1) <= prev.call(10)", + }, + { + name: "append overlapping operations in the same ClientId", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 1, + Output: nil, + Return: 100, + }, + { + ClientId: 1, + Input: nil, + Call: 10, + Output: nil, + Return: 20, + }, + }, + expectError: "Overlapping operations, new.call(10) <= prev.return(100)", + }, + { + name: "append overlapping operations in one of the ClientIds", + operations: []porcupine.Operation{ + { + ClientId: 1, + Input: nil, + Call: 1, + Output: nil, + Return: 100, + }, + { + ClientId: 1, + Input: nil, + Call: 101, + Output: nil, + Return: 200, + }, + { + ClientId: 2, + Input: nil, + Call: 1, + Output: nil, + Return: 100, + }, + { + ClientId: 2, + Input: nil, + Call: 10, + Output: nil, + Return: 20, + }, + }, + expectError: "Overlapping operations, new.call(10) <= prev.return(100)", + }, + } + + for _, tc := range tcs { + h := NewAppendableHistory(identity.NewIDProvider()) + + for _, operation := range tc.operations[:len(tc.operations)-1] { + h.append(operation) + } + assert.PanicsWithValue(t, tc.expectError, func() { h.append(tc.operations[len(tc.operations)-1]) }) + } +} diff --git a/tests/robustness/model/non_deterministic.go b/tests/robustness/model/non_deterministic.go index 3167e340fd16..68e18002ca73 100644 --- a/tests/robustness/model/non_deterministic.go +++ b/tests/robustness/model/non_deterministic.go @@ -15,9 +15,9 @@ package model import ( - "encoding/json" "fmt" "reflect" + "strings" "github.com/anishathalye/porcupine" ) @@ -28,32 +28,53 @@ import ( // Failed requests fork the possible states, while successful requests merge and filter them. var NonDeterministicModel = porcupine.Model{ Init: func() any { - data, err := json.Marshal(nonDeterministicState{freshEtcdState()}) - if err != nil { - panic(err) - } - return string(data) + return nonDeterministicState{freshEtcdState()} }, Step: func(st any, in any, out any) (bool, any) { - var states nonDeterministicState - err := json.Unmarshal([]byte(st.(string)), &states) - if err != nil { - panic(err) - } - ok, states := states.apply(in.(EtcdRequest), out.(MaybeEtcdResponse)) - data, err := json.Marshal(states) - if err != nil { - panic(err) - } - return ok, string(data) + return st.(nonDeterministicState).apply(in.(EtcdRequest), out.(MaybeEtcdResponse)) + }, + Equal: func(st1, st2 any) bool { + return st1.(nonDeterministicState).Equal(st2.(nonDeterministicState)) }, DescribeOperation: func(in, out any) string { return fmt.Sprintf("%s -> %s", describeEtcdRequest(in.(EtcdRequest)), describeEtcdResponse(in.(EtcdRequest), out.(MaybeEtcdResponse))) }, + DescribeState: func(st any) string { + etcdStates := st.(nonDeterministicState) + desc := make([]string, len(etcdStates)) + + for _, s := range etcdStates { + desc = append(desc, describeEtcdState(s)) + } + + return strings.Join(desc, "\n") + }, } type nonDeterministicState []EtcdState +func (states nonDeterministicState) Equal(other nonDeterministicState) bool { + if len(states) != len(other) { + return false + } + + otherMatched := make([]bool, len(other)) + for _, sItem := range states { + foundMatchInOther := false + for j, otherItem := range other { + if !otherMatched[j] && sItem.Equal(otherItem) { + otherMatched[j] = true + foundMatchInOther = true + break + } + } + if !foundMatchInOther { + return false + } + } + return true +} + func (states nonDeterministicState) apply(request EtcdRequest, response MaybeEtcdResponse) (bool, nonDeterministicState) { var newStates nonDeterministicState switch { diff --git a/tests/robustness/model/replay.go b/tests/robustness/model/replay.go index 7f2be26e8ed2..ab2ca79ebbb7 100644 --- a/tests/robustness/model/replay.go +++ b/tests/robustness/model/replay.go @@ -74,7 +74,7 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd } else { ops = request.Txn.OperationsOnSuccess } - for _, op := range ops { + for i, op := range ops { switch op.Type { case RangeOperation: case DeleteOperation: @@ -85,7 +85,7 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd }, Revision: response.Revision, } - if _, ok := prevState.KeyValues[op.Delete.Key]; ok { + if response.Txn.Results[i].Deleted != 0 { events = append(events, e) } case PutOperation: @@ -134,20 +134,20 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd } type WatchEvent struct { - PersistedEvent - PrevValue *ValueRevision + PersistedEvent `json:",omitempty"` + PrevValue *ValueRevision `json:",omitempty"` } type PersistedEvent struct { - Event - Revision int64 - IsCreate bool + Event `json:",omitempty"` + Revision int64 `json:",omitempty"` + IsCreate bool `json:",omitempty"` } type Event struct { - Type OperationType - Key string - Value ValueOrHash + Type OperationType `json:",omitempty"` + Key string `json:",omitempty"` + Value ValueOrHash `json:",omitempty"` } func (e Event) Match(request WatchRequest) bool { diff --git a/tests/robustness/model/watch.go b/tests/robustness/model/watch.go index fc880e30ede6..de2c54d1cc44 100644 --- a/tests/robustness/model/watch.go +++ b/tests/robustness/model/watch.go @@ -17,14 +17,14 @@ package model import "time" type WatchOperation struct { - Request WatchRequest - Responses []WatchResponse + Request WatchRequest `json:",omitempty"` + Responses []WatchResponse `json:",omitempty"` } type WatchResponse struct { - Events []WatchEvent - IsProgressNotify bool - Revision int64 - Time time.Duration - Error string + Events []WatchEvent `json:",omitempty"` + IsProgressNotify bool `json:",omitempty"` + Revision int64 `json:",omitempty"` + Time time.Duration `json:",omitempty"` + Error string `json:",omitempty"` } diff --git a/tests/robustness/options/server_config_options.go b/tests/robustness/options/server_config_options.go index ade51592cd1c..a0a6b5d5a875 100644 --- a/tests/robustness/options/server_config_options.go +++ b/tests/robustness/options/server_config_options.go @@ -26,9 +26,9 @@ func WithSnapshotCount(input ...uint64) e2e.EPClusterOption { } } -func WithExperimentalCompactionBatchLimit(input ...int) e2e.EPClusterOption { +func WithCompactionBatchLimit(input ...int) e2e.EPClusterOption { return func(c *e2e.EtcdProcessClusterConfig) { - c.ServerConfig.ExperimentalCompactionBatchLimit = input[internalRand.Intn(len(input))] + c.ServerConfig.CompactionBatchLimit = input[internalRand.Intn(len(input))] } } @@ -50,9 +50,9 @@ func WithElectionMs(input ...uint) e2e.EPClusterOption { } } -func WithExperimentalWatchProgressNotifyInterval(input ...time.Duration) e2e.EPClusterOption { +func WithWatchProgressNotifyInterval(input ...time.Duration) e2e.EPClusterOption { return func(c *e2e.EtcdProcessClusterConfig) { - c.ServerConfig.ExperimentalWatchProgressNotifyInterval = input[internalRand.Intn(len(input))] + c.ServerConfig.WatchProgressNotifyInterval = input[internalRand.Intn(len(input))] } } diff --git a/tests/robustness/report/client.go b/tests/robustness/report/client.go index 48d29b8ae818..f604c4f22351 100644 --- a/tests/robustness/report/client.go +++ b/tests/robustness/report/client.go @@ -22,10 +22,8 @@ import ( "sort" "strconv" "strings" - "testing" "github.com/anishathalye/porcupine" - "github.com/stretchr/testify/require" "go.uber.org/zap" "go.etcd.io/etcd/tests/v3/robustness/model" @@ -47,25 +45,32 @@ func (r ClientReport) WatchEventCount() int { return count } -func persistClientReports(t *testing.T, lg *zap.Logger, path string, reports []ClientReport) { +func persistClientReports(lg *zap.Logger, path string, reports []ClientReport) error { sort.Slice(reports, func(i, j int) bool { return reports[i].ClientID < reports[j].ClientID }) for _, r := range reports { clientDir := filepath.Join(path, fmt.Sprintf("client-%d", r.ClientID)) - err := os.MkdirAll(clientDir, 0o700) - require.NoError(t, err) + if err := os.MkdirAll(clientDir, 0o700); err != nil { + return err + } + if len(r.Watch) != 0 { - persistWatchOperations(t, lg, filepath.Join(clientDir, "watch.json"), r.Watch) + if err := persistWatchOperations(lg, filepath.Join(clientDir, "watch.json"), r.Watch); err != nil { + return err + } } else { lg.Info("no watch operations for client, skip persisting", zap.Int("client-id", r.ClientID)) } if len(r.KeyValue) != 0 { - persistKeyValueOperations(t, lg, filepath.Join(clientDir, "operations.json"), r.KeyValue) + if err := persistKeyValueOperations(lg, filepath.Join(clientDir, "operations.json"), r.KeyValue); err != nil { + return err + } } else { lg.Info("no KV operations for client, skip persisting", zap.Int("client-id", r.ClientID)) } } + return nil } func LoadClientReports(path string) ([]ClientReport, error) { @@ -138,11 +143,11 @@ func loadKeyValueOperations(path string) (operations []porcupine.Operation, err if os.IsNotExist(err) { return nil, nil } - return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err) + return nil, fmt.Errorf("failed to open KV operation file: %q, err: %w", path, err) } file, err := os.OpenFile(path, os.O_RDONLY, 0o755) if err != nil { - return nil, fmt.Errorf("failed to open watch operation file: %q, err: %w", path, err) + return nil, fmt.Errorf("failed to open KV operation file: %q, err: %w", path, err) } defer file.Close() decoder := json.NewDecoder(file) @@ -156,7 +161,7 @@ func loadKeyValueOperations(path string) (operations []porcupine.Operation, err } err = decoder.Decode(&operation) if err != nil { - return nil, fmt.Errorf("failed to decode watch operation, err: %w", err) + return nil, fmt.Errorf("failed to decode KV operation, err: %w", err) } operations = append(operations, porcupine.Operation{ ClientId: operation.ClientID, @@ -169,36 +174,51 @@ func loadKeyValueOperations(path string) (operations []porcupine.Operation, err return operations, nil } -func persistWatchOperations(t *testing.T, lg *zap.Logger, path string, responses []model.WatchOperation) { +func persistWatchOperations(lg *zap.Logger, path string, responses []model.WatchOperation) error { lg.Info("Saving watch operations", zap.String("path", path)) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755) if err != nil { - t.Errorf("Failed to save watch operations: %v", err) - return + return fmt.Errorf("failed to save watch operations: %w", err) } defer file.Close() - encoder := json.NewEncoder(file) for _, resp := range responses { - err := encoder.Encode(resp) + data, err := json.MarshalIndent(resp, "", " ") if err != nil { - t.Errorf("Failed to encode operation: %v", err) + return fmt.Errorf("failed to encode operation: %w", err) } + file.Write(data) + file.WriteString("\n") } + return nil } -func persistKeyValueOperations(t *testing.T, lg *zap.Logger, path string, operations []porcupine.Operation) { +func persistKeyValueOperations(lg *zap.Logger, path string, operations []porcupine.Operation) error { lg.Info("Saving operation history", zap.String("path", path)) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755) if err != nil { - t.Errorf("Failed to save operation history: %v", err) - return + return fmt.Errorf("Failed to save KV operation history: %w", err) } defer file.Close() - encoder := json.NewEncoder(file) for _, op := range operations { - err := encoder.Encode(op) + data, err := json.MarshalIndent(op, "", " ") if err != nil { - t.Errorf("Failed to encode operation: %v", err) + return fmt.Errorf("Failed to encode KV operation: %w", err) + } + file.Write(data) + file.WriteString("\n") + } + return nil +} + +func OperationsMaxRevision(reports []ClientReport) int64 { + var maxRevision int64 + for _, r := range reports { + for _, op := range r.KeyValue { + resp := op.Output.(model.MaybeEtcdResponse) + if resp.Revision > maxRevision { + maxRevision = resp.Revision + } } } + return maxRevision } diff --git a/tests/robustness/report/client_test.go b/tests/robustness/report/client_test.go index 07da049c74ea..ce14da9472bd 100644 --- a/tests/robustness/report/client_test.go +++ b/tests/robustness/report/client_test.go @@ -133,7 +133,8 @@ func TestPersistLoadClientReports(t *testing.T) { }, } path := t.TempDir() - persistClientReports(t, zaptest.NewLogger(t), path, reports) + err := persistClientReports(zaptest.NewLogger(t), path, reports) + require.NoError(t, err) got, err := LoadClientReports(path) require.NoError(t, err) if diff := cmp.Diff(reports, got, cmpopts.EquateEmpty()); diff != "" { diff --git a/tests/robustness/report/report.go b/tests/robustness/report/report.go index b3d3d5a5e32c..daf488f7100e 100644 --- a/tests/robustness/report/report.go +++ b/tests/robustness/report/report.go @@ -18,68 +18,53 @@ import ( "fmt" "os" "path/filepath" - "strings" - "testing" - "time" - "github.com/stretchr/testify/require" "go.uber.org/zap" "go.etcd.io/etcd/tests/v3/framework/e2e" ) type TestReport struct { - Logger *zap.Logger - Cluster *e2e.EtcdProcessCluster - Client []ClientReport - Visualize func(path string) error + Logger *zap.Logger + Cluster *e2e.EtcdProcessCluster + ServersDataPath map[string]string + Client []ClientReport + Visualize func(lg *zap.Logger, path string) error } -func testResultsDirectory(t *testing.T) string { - resultsDirectory, ok := os.LookupEnv("RESULTS_DIR") - if !ok { - resultsDirectory = "/tmp/" - } - resultsDirectory, err := filepath.Abs(resultsDirectory) +func (r *TestReport) Report(path string) error { + r.Logger.Info("Saving robustness test report", zap.String("path", path)) + err := os.RemoveAll(path) if err != nil { - panic(err) - } - path, err := filepath.Abs(filepath.Join( - resultsDirectory, strings.ReplaceAll(t.Name(), "/", "_"), fmt.Sprintf("%v", time.Now().UnixNano()))) - require.NoError(t, err) - err = os.RemoveAll(path) - require.NoError(t, err) - err = os.MkdirAll(path, 0o700) - require.NoError(t, err) - return path -} - -func (r *TestReport) Report(t *testing.T, force bool) { - _, persistResultsEnvSet := os.LookupEnv("PERSIST_RESULTS") - if !t.Failed() && !force && !persistResultsEnvSet { - return + r.Logger.Error("Failed to remove report dir", zap.Error(err)) } - path := testResultsDirectory(t) - r.Logger.Info("Saving robustness test report", zap.String("path", path)) - for _, member := range r.Cluster.Procs { - memberDataDir := filepath.Join(path, fmt.Sprintf("server-%s", member.Config().Name)) - persistMemberDataDir(t, r.Logger, member, memberDataDir) + for server, dataPath := range r.ServersDataPath { + serverReportPath := filepath.Join(path, fmt.Sprintf("server-%s", server)) + r.Logger.Info("Saving member data dir", zap.String("member", server), zap.String("data-dir", dataPath), zap.String("path", serverReportPath)) + if err := os.CopyFS(serverReportPath, os.DirFS(dataPath)); err != nil { + return err + } } if r.Client != nil { - persistClientReports(t, r.Logger, path, r.Client) + if err := persistClientReports(r.Logger, path, r.Client); err != nil { + return err + } } if r.Visualize != nil { - err := r.Visualize(filepath.Join(path, "history.html")) - if err != nil { - t.Error(err) + if err := r.Visualize(r.Logger, filepath.Join(path, "history.html")); err != nil { + return err } } + return nil } -func persistMemberDataDir(t *testing.T, lg *zap.Logger, member e2e.EtcdProcess, path string) { - lg.Info("Saving member data dir", zap.String("member", member.Config().Name), zap.String("path", path)) - err := os.Rename(memberDataDir(member), path) - require.NoError(t, err) +func ServerDataPaths(c *e2e.EtcdProcessCluster) map[string]string { + dataPaths := make(map[string]string) + for _, member := range c.Procs { + dataPaths[member.Config().Name] = memberDataDir(member) + } + + return dataPaths } func memberDataDir(member e2e.EtcdProcess) string { diff --git a/tests/robustness/report/wal.go b/tests/robustness/report/wal.go index 077ddc8896aa..83ad793f7e51 100644 --- a/tests/robustness/report/wal.go +++ b/tests/robustness/report/wal.go @@ -46,7 +46,7 @@ func LoadClusterPersistedRequests(lg *zap.Logger, path string) ([]model.EtcdRequ dataDirs = append(dataDirs, filepath.Join(path, file.Name())) } } - return PersistedRequestsDirs(lg, dataDirs) + return PersistedRequests(lg, dataDirs) } func PersistedRequestsCluster(lg *zap.Logger, cluster *e2e.EtcdProcessCluster) ([]model.EtcdRequest, error) { @@ -54,16 +54,22 @@ func PersistedRequestsCluster(lg *zap.Logger, cluster *e2e.EtcdProcessCluster) ( for _, proc := range cluster.Procs { dataDirs = append(dataDirs, memberDataDir(proc)) } - return PersistedRequestsDirs(lg, dataDirs) + return PersistedRequests(lg, dataDirs) } -func PersistedRequestsDirs(lg *zap.Logger, dataDirs []string) ([]model.EtcdRequest, error) { - persistedRequests := []model.EtcdRequest{} +func PersistedRequests(lg *zap.Logger, dataDirs []string) ([]model.EtcdRequest, error) { + return persistedRequests(lg, dataDirs, requestsPersistedInWAL) +} + +func persistedRequests(lg *zap.Logger, dataDirs []string, reader persistedRequestReaderFunc) ([]model.EtcdRequest, error) { + if len(dataDirs) == 0 { + return nil, errors.New("no data dirs") + } // Allow failure in minority of etcd cluster. - // 0 failures in 1 node cluster, 1 failure in 3 node cluster allowedFailures := len(dataDirs) / 2 + memberRequestHistories := make([][]model.EtcdRequest, 0, len(dataDirs)) for _, dir := range dataDirs { - memberRequests, err := requestsPersistedInWAL(lg, dir) + requests, err := reader(lg, dir) if err != nil { if allowedFailures < 1 { return nil, err @@ -71,17 +77,68 @@ func PersistedRequestsDirs(lg *zap.Logger, dataDirs []string) ([]model.EtcdReque allowedFailures-- continue } - minLength := min(len(persistedRequests), len(memberRequests)) - if diff := cmp.Diff(memberRequests[:minLength], persistedRequests[:minLength]); diff != "" { - return nil, fmt.Errorf("unexpected differences between wal entries, diff:\n%s", diff) + memberRequestHistories = append(memberRequestHistories, requests) + } + // Return empty history if all histories were empty/failed to read. + if len(memberRequestHistories) == 0 { + return []model.EtcdRequest{}, nil + } + // Each history collects votes from each history that it matches. + votes := make([]int, len(memberRequestHistories)) + lastDiff := "" + for i := 0; i < len(memberRequestHistories); i++ { + for j := 0; j < len(memberRequestHistories); j++ { + if i == j { + // history votes for itself + votes[i]++ + continue + } + if i > j { + // avoid comparing things twice + continue + } + first := memberRequestHistories[i] + second := memberRequestHistories[j] + minLength := min(len(first), len(second)) + if diff := cmp.Diff(first[:minLength], second[:minLength]); diff == "" { + votes[i]++ + votes[j]++ + } else { + lastDiff = diff + } + } + } + // Select longest history that has votes from quorum. + longestHistory := []model.EtcdRequest{} + quorum := len(dataDirs)/2 + 1 + foundQuorum := false + for i := 0; i < len(memberRequestHistories); i++ { + if votes[i] < quorum { + continue + } + // There cannot be incompabible histories supported by quorum + minLength := min(len(memberRequestHistories[i]), len(longestHistory)) + if diff := cmp.Diff(memberRequestHistories[i][:minLength], longestHistory[:minLength]); diff != "" { + lastDiff = diff + foundQuorum = false + break } - if len(memberRequests) > len(persistedRequests) { - persistedRequests = memberRequests + foundQuorum = true + if len(memberRequestHistories[i]) > len(longestHistory) { + longestHistory = memberRequestHistories[i] } } - return persistedRequests, nil + if !foundQuorum { + if lastDiff != "" { + fmt.Printf("Difference between WAL:\n%s", lastDiff) // zap doesn't nicely writes multiline strings like diff + } + return nil, errors.New("unexpected differences between wal entries") + } + return longestHistory, nil } +type persistedRequestReaderFunc = func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) + func requestsPersistedInWAL(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { _, ents, err := ReadWAL(lg, dataDir) if err != nil { @@ -114,10 +171,13 @@ func ReadWAL(lg *zap.Logger, dataDir string) (state raftpb.HardState, ents []raf _, state, ents, err = w.ReadAll() w.Close() if err != nil { - if errors.Is(err, wal.ErrSnapshotNotFound) || errors.Is(err, wal.ErrSliceOutOfRange) { + if errors.Is(err, wal.ErrSnapshotNotFound) { lg.Info("Error occurred when reading WAL entries", zap.Error(err)) return state, ents, nil } + if errors.Is(err, wal.ErrSliceOutOfRange) { + return state, nil, fmt.Errorf("failed to read WAL, err: %w", err) + } // we can only repair ErrUnexpectedEOF and we never repair twice. if repaired || !errors.Is(err, io.ErrUnexpectedEOF) { return state, nil, fmt.Errorf("failed to read WAL, cannot be repaired, err: %w", err) diff --git a/tests/robustness/report/wal_test.go b/tests/robustness/report/wal_test.go new file mode 100644 index 000000000000..e047f9076ff4 --- /dev/null +++ b/tests/robustness/report/wal_test.go @@ -0,0 +1,363 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package report + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" + + "go.etcd.io/etcd/tests/v3/robustness/model" +) + +func TestPersistedRequests(t *testing.T) { + lg := zaptest.NewLogger(t) + + tcs := []struct { + name string + dataDirs []string + readerFunc func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) + expectErr string + expectRequests []model.EtcdRequest + }{ + { + name: "Error when empty data dir", + dataDirs: []string{}, + expectErr: "no data dirs", + }, + { + name: "Success when no entries", + dataDirs: []string{"etcd0"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + return []model.EtcdRequest{}, nil + }, + expectRequests: []model.EtcdRequest{}, + }, + { + name: "Error when error on single node cluster", + dataDirs: []string{"etcd0"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + return []model.EtcdRequest{}, errors.New("error reading") + }, + expectErr: "error reading", + }, + { + name: "Success when one member cluster", + dataDirs: []string{"etcd0"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Success when three members agree on entries", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Success when three member have no entries", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + return []model.EtcdRequest{}, nil + }, + expectRequests: []model.EtcdRequest{}, + }, + { + name: "Success when one member returned error in three node cluster", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd1": + return []model.EtcdRequest{}, errors.New("error reading") + default: + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + } + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Success when one member returned empty in three node cluster", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd1": + return []model.EtcdRequest{}, nil + default: + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + } + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Error when two members returned error in three node cluster", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd1", "etcd2": + return []model.EtcdRequest{}, errors.New("error reading") + default: + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + } + }, + expectErr: "error reading", + }, + { + name: "Success if members didn't observe whole history", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + }, nil + default: + panic("unexpected") + } + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Success if only one member observed history", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd1", "etcd2": + return []model.EtcdRequest{}, nil + default: + panic("unexpected") + } + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Success when one member observed different last entry", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 4}}, + }, nil + default: + panic("unexpected") + } + }, + expectRequests: []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, + }, + { + name: "Error when one member didn't observe whole history and others observed different last entry", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + }, nil + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 4}}, + }, nil + default: + panic("unexpected") + } + }, + expectErr: "unexpected differences between wal entries", + }, + { + name: "Error when three members observed different last entry", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 4}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 5}}, + }, nil + default: + panic("unexpected") + } + }, + expectErr: "unexpected differences between wal entries", + }, + { + name: "Error when one member returned error and others differ on last entry", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{}, errors.New("error reading") + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 4}}, + }, nil + default: + panic("unexpected") + } + }, + expectErr: "unexpected differences between wal entries", + }, + { + name: "Error when one member observed empty history and others differ on last entry", + dataDirs: []string{"etcd0", "etcd1", "etcd2"}, + readerFunc: func(lg *zap.Logger, dataDir string) ([]model.EtcdRequest, error) { + switch dataDir { + case "etcd0": + return []model.EtcdRequest{}, nil + case "etcd1": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 3}}, + }, nil + case "etcd2": + return []model.EtcdRequest{ + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 1}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 2}}, + {Type: model.Compact, Compact: &model.CompactRequest{Revision: 4}}, + }, nil + default: + panic("unexpected") + } + }, + expectErr: "unexpected differences between wal entries", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + requests, err := persistedRequests(lg, tc.dataDirs, tc.readerFunc) + if tc.expectErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.expectErr) + } + require.Equal(t, tc.expectRequests, requests) + }) + } +} diff --git a/tests/robustness/scenarios/scenarios.go b/tests/robustness/scenarios/scenarios.go index ba33add20c2e..9be207f82e01 100644 --- a/tests/robustness/scenarios/scenarios.go +++ b/tests/robustness/scenarios/scenarios.go @@ -97,8 +97,8 @@ func Exploratory(_ *testing.T) []TestScenario { options.WithSubsetOptions(randomizableOptions...), e2e.WithGoFailEnabled(true), // Set low minimal compaction batch limit to allow for triggering multi batch compaction failpoints. - options.WithExperimentalCompactionBatchLimit(10, 100, 1000), - e2e.WithExperimentalWatchProcessNotifyInterval(100 * time.Millisecond), + options.WithCompactionBatchLimit(10, 100, 1000), + e2e.WithWatchProcessNotifyInterval(100 * time.Millisecond), } if e2e.CouldSetSnapshotCatchupEntries(e2e.BinPath.Etcd) { @@ -219,7 +219,7 @@ func Regression(t *testing.T) []TestScenario { Traffic: traffic.Kubernetes, Cluster: *e2e.NewConfig( e2e.WithClusterSize(1), - e2e.WithExperimentalCompactionBatchLimit(300), + e2e.WithCompactionBatchLimit(300), e2e.WithSnapshotCount(1000), e2e.WithGoFailEnabled(true), ), @@ -250,7 +250,7 @@ func Regression(t *testing.T) []TestScenario { Traffic: traffic.KubernetesCreateDelete, Cluster: *e2e.NewConfig( e2e.WithClusterSize(1), - e2e.WithExperimentalCompactionBatchLimit(50), + e2e.WithCompactionBatchLimit(50), e2e.WithSnapshotCount(1000), e2e.WithGoFailEnabled(true), ), diff --git a/tests/robustness/traffic/etcd.go b/tests/robustness/traffic/etcd.go index 906f3d2cb465..47119646537a 100644 --- a/tests/robustness/traffic/etcd.go +++ b/tests/robustness/traffic/etcd.go @@ -75,6 +75,21 @@ var ( {Choice: Delete, Weight: 50}, }, } + EtcdAntithesis Traffic = etcdTraffic{ + keyCount: 10, + largePutSize: 32769, + leaseTTL: DefaultLeaseTTL, + // Please keep the sum of weights equal 100. + requests: []random.ChoiceWeight[etcdRequestType]{ + {Choice: Get, Weight: 30}, + {Choice: Put, Weight: 35}, + {Choice: StaleGet, Weight: 10}, + {Choice: StaleList, Weight: 10}, + {Choice: Delete, Weight: 5}, + {Choice: MultiOpTxn, Weight: 5}, + {Choice: PutWithLease, Weight: 5}, + }, + } ) type etcdTraffic struct { @@ -109,13 +124,13 @@ func (t etcdTraffic) Name() string { return "Etcd" } -func (t etcdTraffic) RunTrafficLoop(ctx context.Context, c *client.RecordingClient, limiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, finish <-chan struct{}) { +func (t etcdTraffic) RunTrafficLoop(ctx context.Context, c *client.RecordingClient, limiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, keyStore *keyStore, finish <-chan struct{}) { lastOperationSucceeded := true var lastRev int64 var requestType etcdRequestType client := etcdTrafficClient{ etcdTraffic: t, - keyPrefix: "key", + keyStore: keyStore, client: c, limiter: limiter, idProvider: ids, @@ -139,7 +154,7 @@ func (t etcdTraffic) RunTrafficLoop(ctx context.Context, c *client.RecordingClie } requestType = random.PickRandom(choices) } else { - requestType = Get + requestType = List } rev, err := client.Request(ctx, requestType, lastRev) if shouldReturn { @@ -196,7 +211,7 @@ func filterOutNonUniqueEtcdWrites(choices []random.ChoiceWeight[etcdRequestType] type etcdTrafficClient struct { etcdTraffic - keyPrefix string + keyStore *keyStore client *client.RecordingClient limiter *rate.Limiter idProvider identity.Provider @@ -211,57 +226,57 @@ func (c etcdTrafficClient) Request(ctx context.Context, request etcdRequestType, switch request { case StaleGet: var resp *clientv3.GetResponse - resp, err = c.client.Get(opCtx, c.randomKey(), clientv3.WithRev(lastRev)) + resp, err = c.client.Get(opCtx, c.keyStore.GetKey(), clientv3.WithRev(lastRev)) if err == nil { rev = resp.Header.Revision } case Get: var resp *clientv3.GetResponse - resp, err = c.client.Get(opCtx, c.randomKey(), clientv3.WithRev(0)) + resp, err = c.client.Get(opCtx, c.keyStore.GetKey(), clientv3.WithRev(0)) if err == nil { rev = resp.Header.Revision } case List: var resp *clientv3.GetResponse - resp, err = c.client.Range(ctx, c.keyPrefix, clientv3.GetPrefixRangeEnd(c.keyPrefix), 0, limit) + resp, err = c.client.Range(ctx, c.keyStore.GetPrefix(), clientv3.GetPrefixRangeEnd(c.keyStore.GetPrefix()), 0, limit) if resp != nil { rev = resp.Header.Revision } case StaleList: var resp *clientv3.GetResponse - resp, err = c.client.Range(ctx, c.keyPrefix, clientv3.GetPrefixRangeEnd(c.keyPrefix), lastRev, limit) + resp, err = c.client.Range(ctx, c.keyStore.GetPrefix(), clientv3.GetPrefixRangeEnd(c.keyStore.GetPrefix()), lastRev, limit) if resp != nil { rev = resp.Header.Revision } case Put: var resp *clientv3.PutResponse - resp, err = c.client.Put(opCtx, c.randomKey(), fmt.Sprintf("%d", c.idProvider.NewRequestID())) + resp, err = c.client.Put(opCtx, c.keyStore.GetKey(), fmt.Sprintf("%d", c.idProvider.NewRequestID())) if resp != nil { rev = resp.Header.Revision } case LargePut: var resp *clientv3.PutResponse - resp, err = c.client.Put(opCtx, c.randomKey(), random.RandString(c.largePutSize)) + resp, err = c.client.Put(opCtx, c.keyStore.GetKey(), random.RandString(c.largePutSize)) if resp != nil { rev = resp.Header.Revision } case Delete: var resp *clientv3.DeleteResponse - resp, err = c.client.Delete(opCtx, c.randomKey()) + resp, err = c.client.Delete(opCtx, c.keyStore.GetKeyForDelete()) if resp != nil { rev = resp.Header.Revision } case MultiOpTxn: var resp *clientv3.TxnResponse resp, err = c.client.Txn(opCtx).Then( - c.pickMultiTxnOps()..., + c.pickMultiTxnOps(c.keyStore)..., ).Commit() if resp != nil { rev = resp.Header.Revision } case CompareAndSet: var kv *mvccpb.KeyValue - key := c.randomKey() + key := c.keyStore.GetKey() var resp *clientv3.GetResponse resp, err = c.client.Get(opCtx, key, clientv3.WithRev(0)) if err == nil { @@ -303,7 +318,7 @@ func (c etcdTrafficClient) Request(ctx context.Context, request etcdRequestType, if leaseID != 0 { putCtx, putCancel := context.WithTimeout(ctx, RequestTimeout) var resp *clientv3.PutResponse - resp, err = c.client.PutWithLease(putCtx, c.randomKey(), fmt.Sprintf("%d", c.idProvider.NewRequestID()), leaseID) + resp, err = c.client.PutWithLease(putCtx, c.keyStore.GetKey(), fmt.Sprintf("%d", c.idProvider.NewRequestID()), leaseID) putCancel() if resp != nil { rev = resp.Header.Revision @@ -334,8 +349,7 @@ func (c etcdTrafficClient) Request(ctx context.Context, request etcdRequestType, return rev, err } -func (c etcdTrafficClient) pickMultiTxnOps() (ops []clientv3.Op) { - keys := rand.Perm(c.keyCount) +func (c etcdTrafficClient) pickMultiTxnOps(keyStore *keyStore) (ops []clientv3.Op) { opTypes := make([]model.OperationType, 4) atLeastOnePut := false @@ -350,8 +364,10 @@ func (c etcdTrafficClient) pickMultiTxnOps() (ops []clientv3.Op) { opTypes[0] = model.PutOperation } + keys := keyStore.GetKeysForMultiTxnOps(opTypes) + for i, opType := range opTypes { - key := c.key(keys[i]) + key := keys[i] switch opType { case model.RangeOperation: ops = append(ops, clientv3.OpGet(key)) @@ -367,14 +383,6 @@ func (c etcdTrafficClient) pickMultiTxnOps() (ops []clientv3.Op) { return ops } -func (c etcdTrafficClient) randomKey() string { - return c.key(rand.Int()) -} - -func (c etcdTrafficClient) key(i int) string { - return fmt.Sprintf("%s%d", c.keyPrefix, i%c.keyCount) -} - func (t etcdTraffic) pickOperationType() model.OperationType { roll := rand.Int() % 100 if roll < 10 { diff --git a/tests/robustness/traffic/key_store.go b/tests/robustness/traffic/key_store.go new file mode 100644 index 000000000000..19cd8cc189e9 --- /dev/null +++ b/tests/robustness/traffic/key_store.go @@ -0,0 +1,106 @@ +// Copyright 2023 The etcd Authors +// +// Licensed 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. + +package traffic + +import ( + "fmt" + "math/rand" + "sync" + + "go.etcd.io/etcd/tests/v3/robustness/model" +) + +// Stores the key pool to use for operations. This allows keys used in Put and Delete operations to be more unique by probabilistically swapping out used keys. +type keyStore struct { + mu sync.Mutex + + counter int + keys []string + keyPrefix string +} + +func NewKeyStore(size int, keyPrefix string) *keyStore { + k := &keyStore{ + keys: make([]string, size), + counter: 0, + keyPrefix: keyPrefix, + } + + // Fill with default values i.e. key0-key9 + for ; k.counter < len(k.keys); k.counter++ { + k.keys[k.counter] = fmt.Sprintf("%s%d", k.keyPrefix, k.counter) + } + + return k +} + +func (k *keyStore) GetKey() string { + k.mu.Lock() + defer k.mu.Unlock() + + useKey := k.keys[rand.Intn(len(k.keys))] + + return useKey +} + +func (k *keyStore) GetKeyForDelete() string { + k.mu.Lock() + defer k.mu.Unlock() + + useKeyIndex := rand.Intn(len(k.keys)) + useKey := k.keys[useKeyIndex] + + k.replaceKey(useKeyIndex) + + return useKey +} + +func (k *keyStore) GetKeysForMultiTxnOps(ops []model.OperationType) []string { + k.mu.Lock() + defer k.mu.Unlock() + + numOps := len(ops) + + if numOps > len(k.keys) { + panic("GetKeysForMultiTxnOps: number of operations is more than the key pool size") + } + + keys := make([]string, numOps) + + permutedKeyIndexes := rand.Perm(len(k.keys)) + for i, op := range ops { + keys[i] = k.keys[permutedKeyIndexes[i]] + + if op == model.DeleteOperation { + k.replaceKey(permutedKeyIndexes[i]) + } + } + + return keys +} + +func (k *keyStore) GetPrefix() string { + k.mu.Lock() + defer k.mu.Unlock() + + return k.keyPrefix +} + +func (k *keyStore) replaceKey(index int) { + if rand.Intn(100) < 90 { + k.keys[index] = fmt.Sprintf("%s%d", k.keyPrefix, k.counter) + k.counter++ + } +} diff --git a/tests/robustness/traffic/kubernetes.go b/tests/robustness/traffic/kubernetes.go index fe1386fa243c..8d0373919a79 100644 --- a/tests/robustness/traffic/kubernetes.go +++ b/tests/robustness/traffic/kubernetes.go @@ -71,7 +71,7 @@ func (t kubernetesTraffic) ExpectUniqueRevision() bool { return true } -func (t kubernetesTraffic) RunTrafficLoop(ctx context.Context, c *client.RecordingClient, limiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, finish <-chan struct{}) { +func (t kubernetesTraffic) RunTrafficLoop(ctx context.Context, c *client.RecordingClient, limiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, keyStore *keyStore, finish <-chan struct{}) { kc := kubernetes.Client{Client: &clientv3.Client{KV: c}} s := newStorage() keyPrefix := "/registry/" + t.resource + "/" diff --git a/tests/robustness/traffic/traffic.go b/tests/robustness/traffic/traffic.go index c66aa4543620..223b539856b4 100644 --- a/tests/robustness/traffic/traffic.go +++ b/tests/robustness/traffic/traffic.go @@ -29,6 +29,7 @@ import ( "go.etcd.io/etcd/tests/v3/robustness/identity" "go.etcd.io/etcd/tests/v3/robustness/model" "go.etcd.io/etcd/tests/v3/robustness/report" + "go.etcd.io/etcd/tests/v3/robustness/validate" ) var ( @@ -59,19 +60,19 @@ func SimulateTraffic(ctx context.Context, t *testing.T, lg *zap.Logger, clus *e2 endpoints := clus.EndpointsGRPC() lm := identity.NewLeaseIDStorage() - reports := []report.ClientReport{} // Use the highest MaximalQPS of all traffic profiles as burst otherwise actual traffic may be accidentally limited limiter := rate.NewLimiter(rate.Limit(profile.MaximalQPS), profile.BurstableQPS) - cc, err := client.NewRecordingClient(endpoints, ids, baseTime) + r, err := CheckEmptyDatabaseAtStart(ctx, lg, endpoints, ids, baseTime) require.NoError(t, err) - defer cc.Close() - // Ensure that first operation succeeds - _, err = cc.Put(ctx, "start", "true") - require.NoErrorf(t, err, "First operation failed, validation requires first operation to succeed") + reports := []report.ClientReport{r} + wg := sync.WaitGroup{} nonUniqueWriteLimiter := NewConcurrencyLimiter(profile.MaxNonUniqueRequestConcurrency) finish := make(chan struct{}) + + keyStore := NewKeyStore(10, "key") + lg.Info("Start traffic") startTime := time.Since(baseTime) for i := 0; i < profile.ClientCount; i++ { @@ -82,7 +83,7 @@ func SimulateTraffic(ctx context.Context, t *testing.T, lg *zap.Logger, clus *e2 defer wg.Done() defer c.Close() - traffic.RunTrafficLoop(ctx, c, limiter, ids, lm, nonUniqueWriteLimiter, finish) + traffic.RunTrafficLoop(ctx, c, limiter, ids, lm, nonUniqueWriteLimiter, keyStore, finish) mux.Lock() reports = append(reports, c.Report()) mux.Unlock() @@ -124,19 +125,22 @@ func SimulateTraffic(ctx context.Context, t *testing.T, lg *zap.Logger, clus *e2 time.Sleep(time.Second) // Ensure that last operation succeeds + cc, err := client.NewRecordingClient(endpoints, ids, baseTime) + require.NoError(t, err) + defer cc.Close() _, err = cc.Put(ctx, "tombstone", "true") require.NoErrorf(t, err, "Last operation failed, validation requires last operation to succeed") reports = append(reports, cc.Report()) - totalStats := calculateStats(reports, startTime, endTime) - beforeFailpointStats := calculateStats(reports, startTime, fr.Start) - duringFailpointStats := calculateStats(reports, fr.Start, fr.End) - afterFailpointStats := calculateStats(reports, fr.End, endTime) + totalStats := CalculateStats(reports, startTime, endTime) + beforeFailpointStats := CalculateStats(reports, startTime, fr.Start) + duringFailpointStats := CalculateStats(reports, fr.Start, fr.End) + afterFailpointStats := CalculateStats(reports, fr.End, endTime) - lg.Info("Reporting complete traffic", zap.Int("successes", totalStats.successes), zap.Int("failures", totalStats.failures), zap.Float64("successRate", totalStats.successRate()), zap.Duration("period", totalStats.period), zap.Float64("qps", totalStats.QPS())) - lg.Info("Reporting traffic before failure injection", zap.Int("successes", beforeFailpointStats.successes), zap.Int("failures", beforeFailpointStats.failures), zap.Float64("successRate", beforeFailpointStats.successRate()), zap.Duration("period", beforeFailpointStats.period), zap.Float64("qps", beforeFailpointStats.QPS())) - lg.Info("Reporting traffic during failure injection", zap.Int("successes", duringFailpointStats.successes), zap.Int("failures", duringFailpointStats.failures), zap.Float64("successRate", duringFailpointStats.successRate()), zap.Duration("period", duringFailpointStats.period), zap.Float64("qps", duringFailpointStats.QPS())) - lg.Info("Reporting traffic after failure injection", zap.Int("successes", afterFailpointStats.successes), zap.Int("failures", afterFailpointStats.failures), zap.Float64("successRate", afterFailpointStats.successRate()), zap.Duration("period", afterFailpointStats.period), zap.Float64("qps", afterFailpointStats.QPS())) + lg.Info("Reporting complete traffic", zap.Int("successes", totalStats.Successes), zap.Int("failures", totalStats.Failures), zap.Float64("successRate", totalStats.SuccessRate()), zap.Duration("period", totalStats.Period), zap.Float64("qps", totalStats.QPS())) + lg.Info("Reporting traffic before failure injection", zap.Int("successes", beforeFailpointStats.Successes), zap.Int("failures", beforeFailpointStats.Failures), zap.Float64("successRate", beforeFailpointStats.SuccessRate()), zap.Duration("period", beforeFailpointStats.Period), zap.Float64("qps", beforeFailpointStats.QPS())) + lg.Info("Reporting traffic during failure injection", zap.Int("successes", duringFailpointStats.Successes), zap.Int("failures", duringFailpointStats.Failures), zap.Float64("successRate", duringFailpointStats.SuccessRate()), zap.Duration("period", duringFailpointStats.Period), zap.Float64("qps", duringFailpointStats.QPS())) + lg.Info("Reporting traffic after failure injection", zap.Int("successes", afterFailpointStats.Successes), zap.Int("failures", afterFailpointStats.Failures), zap.Float64("successRate", afterFailpointStats.SuccessRate()), zap.Duration("period", afterFailpointStats.Period), zap.Float64("qps", afterFailpointStats.QPS())) if beforeFailpointStats.QPS() < profile.MinimalQPS { t.Errorf("Requiring minimal %f qps before failpoint injection for test results to be reliable, got %f qps", profile.MinimalQPS, beforeFailpointStats.QPS()) @@ -145,8 +149,8 @@ func SimulateTraffic(ctx context.Context, t *testing.T, lg *zap.Logger, clus *e2 return reports } -func calculateStats(reports []report.ClientReport, start, end time.Duration) (ts trafficStats) { - ts.period = end - start +func CalculateStats(reports []report.ClientReport, start, end time.Duration) (ts trafficStats) { + ts.Period = end - start for _, r := range reports { for _, op := range r.KeyValue { @@ -155,9 +159,9 @@ func calculateStats(reports []report.ClientReport, start, end time.Duration) (ts } resp := op.Output.(model.MaybeEtcdResponse) if resp.Error == "" { - ts.successes++ + ts.Successes++ } else { - ts.failures++ + ts.Failures++ } } } @@ -165,16 +169,16 @@ func calculateStats(reports []report.ClientReport, start, end time.Duration) (ts } type trafficStats struct { - successes, failures int - period time.Duration + Successes, Failures int + Period time.Duration } -func (ts *trafficStats) successRate() float64 { - return float64(ts.successes) / float64(ts.successes+ts.failures) +func (ts *trafficStats) SuccessRate() float64 { + return float64(ts.Successes) / float64(ts.Successes+ts.Failures) } func (ts *trafficStats) QPS() float64 { - return float64(ts.successes) / ts.period.Seconds() + return float64(ts.Successes) / ts.Period.Seconds() } type Profile struct { @@ -198,7 +202,29 @@ func (p Profile) WithCompactionPeriod(cp time.Duration) Profile { } type Traffic interface { - RunTrafficLoop(ctx context.Context, c *client.RecordingClient, qpsLimiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, finish <-chan struct{}) + RunTrafficLoop(ctx context.Context, c *client.RecordingClient, qpsLimiter *rate.Limiter, ids identity.Provider, lm identity.LeaseIDStorage, nonUniqueWriteLimiter ConcurrencyLimiter, keyStore *keyStore, finish <-chan struct{}) RunCompactLoop(ctx context.Context, c *client.RecordingClient, period time.Duration, finish <-chan struct{}) ExpectUniqueRevision() bool } + +func CheckEmptyDatabaseAtStart(ctx context.Context, lg *zap.Logger, endpoints []string, ids identity.Provider, baseTime time.Time) (report.ClientReport, error) { + c, err := client.NewRecordingClient(endpoints, ids, baseTime) + if err != nil { + return report.ClientReport{}, err + } + defer c.Close() + for { + rCtx, cancel := context.WithTimeout(ctx, RequestTimeout) + resp, err := c.Get(rCtx, "key") + cancel() + if err != nil { + lg.Warn("Failed to check if database empty at start, retrying", zap.Error(err)) + continue + } + if resp.Header.Revision != 1 { + return report.ClientReport{}, validate.ErrNotEmptyDatabase + } + break + } + return c.Report(), nil +} diff --git a/tests/robustness/validate/operations.go b/tests/robustness/validate/operations.go index 9f39407ad818..2d5597d5757a 100644 --- a/tests/robustness/validate/operations.go +++ b/tests/robustness/validate/operations.go @@ -24,7 +24,6 @@ import ( "go.uber.org/zap" "go.etcd.io/etcd/tests/v3/robustness/model" - "go.etcd.io/etcd/tests/v3/robustness/report" ) var ( @@ -32,32 +31,63 @@ var ( errFutureRevRespRequested = errors.New("request about a future rev with response") ) -func validateLinearizableOperationsAndVisualize(lg *zap.Logger, operations []porcupine.Operation, timeout time.Duration) (result porcupine.CheckResult, visualize func(basepath string) error) { - lg.Info("Validating linearizable operations", zap.Duration("timeout", timeout)) - start := time.Now() - result, info := porcupine.CheckOperationsVerbose(model.NonDeterministicModel, operations, timeout) - switch result { +type Result struct { + Assumptions error + Linearization LinearizationResult + WatchError error + SerializableError error +} + +func (r Result) Error() error { + if r.Assumptions != nil { + return fmt.Errorf("validation assumptions failed: %w", r.Assumptions) + } + switch r.Linearization.Linearizable { case porcupine.Illegal: - lg.Error("Linearization failed", zap.Duration("duration", time.Since(start))) + return errors.New("linearization failed") case porcupine.Unknown: - lg.Error("Linearization has timed out", zap.Duration("duration", time.Since(start))) + return errors.New("linearization timed out") case porcupine.Ok: - lg.Info("Linearization success", zap.Duration("duration", time.Since(start))) default: - panic(fmt.Sprintf("Unknown Linearization result %s", result)) + return fmt.Errorf("unknown linearization result %q", r.Linearization.Linearizable) } - return result, func(path string) error { - lg.Info("Saving visualization", zap.String("path", path)) - err := porcupine.VisualizePath(model.NonDeterministicModel, info, path) - if err != nil { - return fmt.Errorf("failed to visualize, err: %w", err) - } - return nil + if r.WatchError != nil { + return fmt.Errorf("watch validation failed: %w", r.WatchError) + } + if r.SerializableError != nil { + return fmt.Errorf("serializable validation failed: %w", r.SerializableError) + } + return nil +} + +type LinearizationResult struct { + Info porcupine.LinearizationInfo + Model porcupine.Model + Linearizable porcupine.CheckResult +} + +func (r LinearizationResult) Visualize(lg *zap.Logger, path string) error { + lg.Info("Saving visualization", zap.String("path", path)) + err := porcupine.VisualizePath(r.Model, r.Info, path) + if err != nil { + return fmt.Errorf("failed to visualize, err: %w", err) + } + return nil +} + +func validateLinearizableOperationsAndVisualize( + operations []porcupine.Operation, + timeout time.Duration, +) (results LinearizationResult) { + result, info := porcupine.CheckOperationsVerbose(model.NonDeterministicModel, operations, timeout) + return LinearizationResult{ + Info: info, + Model: model.NonDeterministicModel, + Linearizable: result, } } func validateSerializableOperations(lg *zap.Logger, operations []porcupine.Operation, replay *model.EtcdReplay) (lastErr error) { - lg.Info("Validating serializable operations") for _, read := range operations { request := read.Input.(model.EtcdRequest) response := read.Output.(model.MaybeEtcdResponse) @@ -69,19 +99,6 @@ func validateSerializableOperations(lg *zap.Logger, operations []porcupine.Opera return lastErr } -func filterSerializableOperations(clients []report.ClientReport) []porcupine.Operation { - resp := []porcupine.Operation{} - for _, client := range clients { - for _, op := range client.KeyValue { - request := op.Input.(model.EtcdRequest) - if request.Type == model.Range && request.Range.Revision != 0 { - resp = append(resp, op) - } - } - } - return resp -} - func validateSerializableRead(lg *zap.Logger, replay *model.EtcdReplay, request model.EtcdRequest, response model.MaybeEtcdResponse) error { if response.Persisted || response.Error != "" { return nil diff --git a/tests/robustness/validate/operations_test.go b/tests/robustness/validate/operations_test.go index a244d9f75339..d096be6f1c69 100644 --- a/tests/robustness/validate/operations_test.go +++ b/tests/robustness/validate/operations_test.go @@ -17,9 +17,12 @@ package validate import ( "fmt" + "math/rand/v2" "testing" + "time" "github.com/anishathalye/porcupine" + "go.uber.org/zap" "go.uber.org/zap/zaptest" "go.etcd.io/etcd/tests/v3/robustness/model" @@ -294,3 +297,107 @@ func keyValueRevision(key, value string, rev int64) model.KeyValue { }, } } + +func BenchmarkValidateLinearizableOperations(b *testing.B) { + lg := zap.NewNop() + b.Run("Successes", func(b *testing.B) { + history := allPutSuccesses(1000) + shuffles := shuffleHistory(history, b.N) + b.ResetTimer() + validateShuffles(b, lg, shuffles, time.Second) + }) + b.Run("AllFailures", func(b *testing.B) { + history := allPutFailures(10) + shuffles := shuffleHistory(history, b.N) + b.ResetTimer() + validateShuffles(b, lg, shuffles, time.Second) + }) + b.Run("PutFailuresWithRead", func(b *testing.B) { + history := putFailuresWithRead(b, 8) + shuffles := shuffleHistory(history, b.N) + b.ResetTimer() + validateShuffles(b, lg, shuffles, time.Second) + }) +} + +func allPutSuccesses(concurrencyCount int) []porcupine.Operation { + ops := []porcupine.Operation{} + for i := 0; i < concurrencyCount; i++ { + ops = append(ops, porcupine.Operation{ + ClientId: i, + Input: putRequest("key", "value"), + Output: putResponse(int64(i)+2, model.EtcdOperationResult{}), + Call: int64(i), + Return: int64(i) + int64(concurrencyCount), + }) + } + return ops +} + +func putFailuresWithRead(b *testing.B, concurrencyCount int) []porcupine.Operation { + ops := []porcupine.Operation{} + for i := 0; i < concurrencyCount; i++ { + ops = append(ops, porcupine.Operation{ + ClientId: i, + Input: putRequest(fmt.Sprintf("key%d", i), "value"), + Output: errorResponse(fmt.Errorf("timeout")), + Call: int64(i), + Return: int64(i) + int64(concurrencyCount), + }) + } + requests := []model.EtcdRequest{} + for _, op := range ops { + requests = append(requests, op.Input.(model.EtcdRequest)) + } + replay := model.NewReplay(requests) + state, err := replay.StateForRevision(int64(concurrencyCount) + 1) + if err != nil { + b.Fatal(err) + } + request := rangeRequest("key", "kez", 0, 0) + _, resp := state.Step(request) + ops = append(ops, porcupine.Operation{ + ClientId: 0, + Input: request, + Output: resp, + Call: int64(concurrencyCount) + 1, + Return: int64(concurrencyCount) + 2, + }) + return ops +} + +func allPutFailures(concurrencyCount int) []porcupine.Operation { + ops := []porcupine.Operation{} + for i := 0; i < concurrencyCount; i++ { + ops = append(ops, porcupine.Operation{ + ClientId: i, + Input: putRequest("key", "value"), + Output: errorResponse(fmt.Errorf("timeout")), + Call: int64(i), + Return: int64(i) + int64(concurrencyCount), + }) + } + return ops +} + +func shuffleHistory(history []porcupine.Operation, shuffleCount int) [][]porcupine.Operation { + shuffles := make([][]porcupine.Operation, shuffleCount) + for i := 0; i < shuffleCount; i++ { + historyCopy := make([]porcupine.Operation, len(history)) + copy(historyCopy, history) + rand.Shuffle(len(historyCopy), func(i, j int) { + historyCopy[i], historyCopy[j] = historyCopy[j], historyCopy[i] + }) + shuffles[i] = historyCopy + } + return shuffles +} + +func validateShuffles(b *testing.B, lg *zap.Logger, shuffles [][]porcupine.Operation, duration time.Duration) { + for i := 0; i < len(shuffles); i++ { + result := validateLinearizableOperationsAndVisualize(shuffles[i], duration) + if result.Linearizable != porcupine.Ok { + b.Fatalf("Not linearizable: %v", result.Linearizable) + } + } +} diff --git a/tests/robustness/validate/patch_history.go b/tests/robustness/validate/patch_history.go index 5ec3bd2ae2ed..da75212b83e2 100644 --- a/tests/robustness/validate/patch_history.go +++ b/tests/robustness/validate/patch_history.go @@ -16,6 +16,7 @@ package validate import ( "fmt" + "math" "github.com/anishathalye/porcupine" @@ -23,28 +24,12 @@ import ( "go.etcd.io/etcd/tests/v3/robustness/report" ) -func patchLinearizableOperations(reports []report.ClientReport, persistedRequests []model.EtcdRequest) []porcupine.Operation { - allOperations := relevantOperations(reports) +func patchLinearizableOperations(operations []porcupine.Operation, reports []report.ClientReport, persistedRequests []model.EtcdRequest) []porcupine.Operation { putRevision := putRevision(reports) - putReturnTime := putReturnTime(allOperations, reports, persistedRequests) - clientPutCount := countClientPuts(reports) persistedPutCount := countPersistedPuts(persistedRequests) - return patchOperations(allOperations, putRevision, putReturnTime, clientPutCount, persistedPutCount) -} - -func relevantOperations(reports []report.ClientReport) []porcupine.Operation { - var ops []porcupine.Operation - for _, r := range reports { - for _, op := range r.KeyValue { - request := op.Input.(model.EtcdRequest) - resp := op.Output.(model.MaybeEtcdResponse) - // Remove failed read requests as they are not relevant for linearization. - if resp.Error == "" || !request.IsRead() { - ops = append(ops, op) - } - } - } - return ops + clientPutCount := countClientPuts(reports) + putReturnTime := uniquePutReturnTime(operations, persistedRequests, clientPutCount) + return patchOperations(operations, putRevision, putReturnTime, clientPutCount, persistedPutCount) } func putRevision(reports []report.ClientReport) map[keyValue]int64 { @@ -155,7 +140,7 @@ func hasUniqueWriteOperation(ops []model.EtcdOperation, clientRequestCount map[k return false } -func putReturnTime(allOperations []porcupine.Operation, reports []report.ClientReport, persistedRequests []model.EtcdRequest) map[keyValue]int64 { +func uniquePutReturnTime(allOperations []porcupine.Operation, persistedRequests []model.EtcdRequest, clientPutCount map[keyValue]int64) map[keyValue]int64 { earliestReturnTime := map[keyValue]int64{} var lastReturnTime int64 for _, op := range allOperations { @@ -167,6 +152,9 @@ func putReturnTime(allOperations []porcupine.Operation, reports []report.ClientR continue } kv := keyValue{Key: etcdOp.Put.Key, Value: etcdOp.Put.Value} + if count := clientPutCount[kv]; count > 1 { + continue + } if returnTime, ok := earliestReturnTime[kv]; !ok || returnTime > op.Return { earliestReturnTime[kv] = op.Return } @@ -184,36 +172,21 @@ func putReturnTime(allOperations []porcupine.Operation, reports []report.ClientR } } - for _, client := range reports { - for _, watch := range client.Watch { - for _, resp := range watch.Responses { - for _, event := range resp.Events { - switch event.Type { - case model.RangeOperation: - case model.PutOperation: - kv := keyValue{Key: event.Key, Value: event.Value} - if t, ok := earliestReturnTime[kv]; !ok || t > resp.Time.Nanoseconds() { - earliestReturnTime[kv] = resp.Time.Nanoseconds() - } - case model.DeleteOperation: - default: - panic(fmt.Sprintf("unknown event type %q", event.Type)) - } - } - } - } - } - for i := len(persistedRequests) - 1; i >= 0; i-- { request := persistedRequests[i] switch request.Type { case model.Txn: - lastReturnTime-- + if lastReturnTime != math.MaxInt64 { + lastReturnTime-- + } for _, op := range request.Txn.OperationsOnSuccess { if op.Type != model.PutOperation { continue } kv := keyValue{Key: op.Put.Key, Value: op.Put.Value} + if count := clientPutCount[kv]; count > 1 { + continue + } returnTime, ok := earliestReturnTime[kv] if ok { lastReturnTime = min(returnTime, lastReturnTime) diff --git a/tests/robustness/validate/patch_history_test.go b/tests/robustness/validate/patch_history_test.go index bb104b0125fe..0ab448ea0f9d 100644 --- a/tests/robustness/validate/patch_history_test.go +++ b/tests/robustness/validate/patch_history_test.go @@ -17,6 +17,7 @@ package validate import ( "errors" + "math" "testing" "time" @@ -30,7 +31,7 @@ import ( "go.etcd.io/etcd/tests/v3/robustness/report" ) -const infinite = 1000000000 +const infinite = math.MaxInt64 func TestPatchHistory(t *testing.T) { for _, tc := range []struct { @@ -58,25 +59,25 @@ func TestPatchHistory(t *testing.T) { putRequest("key", "value"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: 200, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed put remains if there is a matching event, return time untouched", historyFunc: func(h *model.AppendableHistory) { - h.AppendPut("key", "value", 100, infinite, nil, errors.New("failed")) + h.AppendPut("key", "value", 100, 200, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key", "value"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 99, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true}}, }, }, { name: "failed put remains if there is a matching event, uniqueness allows for return time to be based on next persisted request", historyFunc: func(h *model.AppendableHistory) { - h.AppendPut("key1", "value", 100, infinite, nil, errors.New("failed")) + h.AppendPut("key1", "value", 100, 200, nil, errors.New("failed")) h.AppendPut("key2", "value", 300, 400, &clientv3.PutResponse{}, nil) }, persistedRequest: []model.EtcdRequest{ @@ -85,20 +86,20 @@ func TestPatchHistory(t *testing.T) { }, expectedRemainingOperations: []porcupine.Operation{ {Return: 399, Output: model.MaybeEtcdResponse{Persisted: true}}, - {Return: 400, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 400, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { - name: "failed put remains if there is a matching persisted request, uniqueness allows for revision and return time to be based on watch", + name: "failed put remains if there is a matching persisted request, uniqueness allows for revision to be based on watch", historyFunc: func(h *model.AppendableHistory) { - h.AppendPut("key", "value", 100, infinite, nil, errors.New("failed")) + h.AppendPut("key", "value", 100, 200, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key", "value"), }, watchOperations: watchResponse(300, putEvent("key", "value", 2)), expectedRemainingOperations: []porcupine.Operation{ - {Return: 300, Output: model.MaybeEtcdResponse{Persisted: true, PersistedRevision: 2}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true, PersistedRevision: 2}}, }, }, { @@ -113,52 +114,52 @@ func TestPatchHistory(t *testing.T) { }, watchOperations: watchResponse(3, putEvent("key", "value", 2), putEvent("key", "value", 3)), expectedRemainingOperations: []porcupine.Operation{ - {Return: 1000000004, Output: model.MaybeEtcdResponse{Error: "failed"}}, - {Return: 4, Output: putResponse(model.EtcdOperationResult{})}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: 4, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed put is dropped if event has different key", historyFunc: func(h *model.AppendableHistory) { h.AppendPut("key2", "value", 100, 200, &clientv3.PutResponse{}, nil) - h.AppendPut("key1", "value", 300, infinite, nil, errors.New("failed")) + h.AppendPut("key1", "value", 300, 400, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key2", "value"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: 200, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed put is dropped if event has different value", historyFunc: func(h *model.AppendableHistory) { h.AppendPut("key", "value2", 100, 200, &clientv3.PutResponse{}, nil) - h.AppendPut("key", "value1", 300, infinite, nil, errors.New("failed")) + h.AppendPut("key", "value1", 300, 400, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key", "value2"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: 200, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed put with lease remains if there is a matching event, return time untouched", historyFunc: func(h *model.AppendableHistory) { - h.AppendPutWithLease("key", "value", 123, 100, infinite, nil, errors.New("failed")) + h.AppendPutWithLease("key", "value", 123, 100, 200, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequestWithLease("key", "value", 123), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 99, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true}}, }, }, { name: "failed put with lease remains if there is a matching event, uniqueness allows return time to be based on next persisted request", historyFunc: func(h *model.AppendableHistory) { - h.AppendPutWithLease("key1", "value", 123, 100, infinite, nil, errors.New("failed")) + h.AppendPutWithLease("key1", "value", 123, 100, 200, nil, errors.New("failed")) h.AppendPutWithLease("key2", "value", 234, 300, 400, &clientv3.PutResponse{}, nil) }, persistedRequest: []model.EtcdRequest{ @@ -167,11 +168,11 @@ func TestPatchHistory(t *testing.T) { }, expectedRemainingOperations: []porcupine.Operation{ {Return: 399, Output: model.MaybeEtcdResponse{Persisted: true}}, - {Return: 400, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 400, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { - name: "failed put with lease remains if there is a matching event, uniqueness allows for revision and return time to be based on watch", + name: "failed put with lease remains if there is a matching event, uniqueness allows for revision to be based on watch", historyFunc: func(h *model.AppendableHistory) { h.AppendPutWithLease("key", "value", 123, 1, 2, nil, errors.New("failed")) }, @@ -180,7 +181,7 @@ func TestPatchHistory(t *testing.T) { }, watchOperations: watchResponse(3, putEvent("key", "value", 2)), expectedRemainingOperations: []porcupine.Operation{ - {Return: 3, Output: model.MaybeEtcdResponse{Persisted: true, PersistedRevision: 2}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true, PersistedRevision: 2}}, }, }, { @@ -195,21 +196,21 @@ func TestPatchHistory(t *testing.T) { }, watchOperations: watchResponse(3, putEvent("key", "value", 2), putEvent("key", "value", 3)), expectedRemainingOperations: []porcupine.Operation{ - {Return: 1000000004, Output: model.MaybeEtcdResponse{Error: "failed"}}, - {Return: 4, Output: putResponse(model.EtcdOperationResult{})}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: 4, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed put is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendPut("key", "value", 100, infinite, nil, errors.New("failed")) + h.AppendPut("key", "value", 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed put with lease is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendPutWithLease("key", "value", 123, 100, infinite, nil, errors.New("failed")) + h.AppendPutWithLease("key", "value", 123, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, @@ -219,13 +220,13 @@ func TestPatchHistory(t *testing.T) { h.AppendDelete("key", 100, 200, &clientv3.DeleteResponse{}, nil) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: 200, Output: putResponse(model.EtcdOperationResult{})}, + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed delete remains, time untouched regardless of persisted event and watch", historyFunc: func(h *model.AppendableHistory) { - h.AppendDelete("key", 100, infinite, nil, errors.New("failed")) + h.AppendDelete("key", 100, 200, nil, errors.New("failed")) h.AppendPut("key", "value", 300, 400, &clientv3.PutResponse{}, nil) }, persistedRequest: []model.EtcdRequest{ @@ -233,43 +234,43 @@ func TestPatchHistory(t *testing.T) { }, watchOperations: watchResponse(3, deleteEvent("key", 2)), expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 400, Output: model.MaybeEtcdResponse{Error: "failed"}}, - {Return: 400, Output: putResponse(model.EtcdOperationResult{})}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: 400, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed empty txn is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed txn put is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed txn put remains if there is a matching event", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key", "value"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 99, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true}}, }, }, { name: "failed txn delete remains", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpDelete("key")}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpDelete("key")}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 100, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, }, }, { @@ -278,73 +279,125 @@ func TestPatchHistory(t *testing.T) { h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{clientv3.OpDelete("key")}, 100, 200, &clientv3.TxnResponse{Succeeded: true}, nil) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: 200, Output: putResponse()}, + {Return: 200, Output: putResponse(0)}, }, }, { name: "failed txn put/delete remains", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{clientv3.OpDelete("key")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{clientv3.OpDelete("key")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 100, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, }, }, { name: "failed txn delete/put remains", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpDelete("key")}, []clientv3.Op{clientv3.OpPut("key", "value")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpDelete("key")}, []clientv3.Op{clientv3.OpPut("key", "value")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 100, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, }, }, { name: "failed txn empty/put is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpPut("key", "value")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpPut("key", "value")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed txn empty/put remains if there is a matching event", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value")}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, persistedRequest: []model.EtcdRequest{ putRequest("key", "value"), }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 99, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true}}, + }, + }, + { + name: "failed put remains if there is a matching persisted request, uniqueness of this operation and following operation allows patching based on following operation", + historyFunc: func(h *model.AppendableHistory) { + h.AppendPut("key1", "value1", 300, 400, nil, errors.New("failed")) + h.AppendPut("key2", "value2", 500, 600, &clientv3.PutResponse{}, nil) + }, + persistedRequest: []model.EtcdRequest{ + putRequest("key1", "value1"), + putRequest("key2", "value2"), + }, + expectedRemainingOperations: []porcupine.Operation{ + {Return: 599, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: 600, Output: putResponse(0, model.EtcdOperationResult{})}, + }, + }, + { + name: "failed put remains if there is a matching persisted request, lack of uniqueness of this operation prevents patching based on following operation", + historyFunc: func(h *model.AppendableHistory) { + h.AppendPut("key1", "value1", 100, 200, &clientv3.PutResponse{}, nil) + h.AppendPut("key1", "value1", 300, 400, nil, errors.New("failed")) + h.AppendPut("key2", "value2", 500, 600, &clientv3.PutResponse{}, nil) + }, + persistedRequest: []model.EtcdRequest{ + putRequest("key1", "value1"), + putRequest("key1", "value1"), + putRequest("key2", "value2"), + }, + expectedRemainingOperations: []porcupine.Operation{ + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: 600, Output: putResponse(0, model.EtcdOperationResult{})}, + }, + }, + { + name: "failed put remains if there is a matching persisted request, lack of uniqueness of following operation prevents patching based on following operation", + historyFunc: func(h *model.AppendableHistory) { + h.AppendPut("key2", "value2", 100, 200, &clientv3.PutResponse{}, nil) + h.AppendPut("key1", "value1", 300, 400, nil, errors.New("failed")) + h.AppendPut("key2", "value2", 500, 600, &clientv3.PutResponse{}, nil) + }, + persistedRequest: []model.EtcdRequest{ + putRequest("key2", "value2"), + putRequest("key1", "value1"), + putRequest("key2", "value2"), + }, + expectedRemainingOperations: []porcupine.Operation{ + {Return: 200, Output: putResponse(0, model.EtcdOperationResult{})}, + // TODO: We can infer that failed operation finished before last operation matching following. + {Return: infinite, Output: model.MaybeEtcdResponse{Persisted: true}}, + {Return: 600, Output: putResponse(0, model.EtcdOperationResult{})}, }, }, { name: "failed txn empty/delete remains", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpDelete("key")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpDelete("key")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{ - {Return: infinite + 100, Output: model.MaybeEtcdResponse{Error: "failed"}}, + {Return: infinite, Output: model.MaybeEtcdResponse{Error: "failed"}}, }, }, { name: "failed txn put&delete is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, []clientv3.Op{}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, []clientv3.Op{}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed txn empty/put&delete is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{}, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, { name: "failed txn put&delete/put&delete is dropped", historyFunc: func(h *model.AppendableHistory) { - h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, []clientv3.Op{clientv3.OpPut("key", "value2"), clientv3.OpDelete("key")}, 100, infinite, nil, errors.New("failed")) + h.AppendTxn(nil, []clientv3.Op{clientv3.OpPut("key", "value1"), clientv3.OpDelete("key")}, []clientv3.Op{clientv3.OpPut("key", "value2"), clientv3.OpDelete("key")}, 100, 200, nil, errors.New("failed")) }, expectedRemainingOperations: []porcupine.Operation{}, }, @@ -352,14 +405,16 @@ func TestPatchHistory(t *testing.T) { t.Run(tc.name, func(t *testing.T) { history := model.NewAppendableHistory(identity.NewIDProvider()) tc.historyFunc(history) - operations := patchLinearizableOperations([]report.ClientReport{ + reports := []report.ClientReport{ { ClientID: 0, KeyValue: history.History.Operations(), Watch: tc.watchOperations, }, - }, tc.persistedRequest) - if diff := cmp.Diff(tc.expectedRemainingOperations, operations, + } + operations, _ := prepareAndCategorizeOperations(reports) + patched := patchLinearizableOperations(operations, reports, tc.persistedRequest) + if diff := cmp.Diff(tc.expectedRemainingOperations, patched, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(porcupine.Operation{}, "Input", "Call", "ClientId"), ); diff != "" { @@ -369,8 +424,8 @@ func TestPatchHistory(t *testing.T) { } } -func putResponse(result ...model.EtcdOperationResult) model.MaybeEtcdResponse { - return model.MaybeEtcdResponse{EtcdResponse: model.EtcdResponse{Txn: &model.TxnResponse{Results: result}}} +func putResponse(rev int64, result ...model.EtcdOperationResult) model.MaybeEtcdResponse { + return model.MaybeEtcdResponse{EtcdResponse: model.EtcdResponse{Revision: rev, Txn: &model.TxnResponse{Results: result}}} } func watchResponse(responseTime int64, events ...model.WatchEvent) []model.WatchOperation { diff --git a/tests/robustness/validate/validate.go b/tests/robustness/validate/validate.go index 5918ec0df83c..1b9ed77908bd 100644 --- a/tests/robustness/validate/validate.go +++ b/tests/robustness/validate/validate.go @@ -15,59 +15,112 @@ package validate import ( - "encoding/json" + "errors" "fmt" - "testing" + "math" "time" "github.com/anishathalye/porcupine" - "github.com/stretchr/testify/require" "go.uber.org/zap" "go.etcd.io/etcd/tests/v3/robustness/model" "go.etcd.io/etcd/tests/v3/robustness/report" ) -// ValidateAndReturnVisualize returns visualize as porcupine.linearizationInfo used to generate visualization is private. -func ValidateAndReturnVisualize(t *testing.T, lg *zap.Logger, cfg Config, reports []report.ClientReport, persistedRequests []model.EtcdRequest, timeout time.Duration) (visualize func(basepath string) error) { - err := checkValidationAssumptions(reports, persistedRequests) - require.NoErrorf(t, err, "Broken validation assumptions") - linearizableOperations := patchLinearizableOperations(reports, persistedRequests) - serializableOperations := filterSerializableOperations(reports) - - linearizable, visualize := validateLinearizableOperationsAndVisualize(lg, linearizableOperations, timeout) - if linearizable != porcupine.Ok { - t.Error("Failed linearization, skipping further validation") - return visualize +var ErrNotEmptyDatabase = errors.New("non empty database at start, required by model used for linearizability validation") + +func ValidateAndReturnVisualize(lg *zap.Logger, cfg Config, reports []report.ClientReport, persistedRequests []model.EtcdRequest, timeout time.Duration) (result Result) { + result.Assumptions = checkValidationAssumptions(reports) + if result.Assumptions != nil { + return result + } + linearizableOperations, serializableOperations := prepareAndCategorizeOperations(reports) + // We are passing in the original reports and linearizableOperations with modified return time. + // The reason is that linearizableOperations are those dedicated for linearization, which requires them to have returnTime set to infinity as required by pourcupine. + // As for the report, the original report is used so the consumer doesn't need to track what patching was done or not. + if len(persistedRequests) != 0 { + linearizableOperations = patchLinearizableOperations(linearizableOperations, reports, persistedRequests) + } + + lg.Info("Validating linearizable operations", zap.Duration("timeout", timeout)) + start := time.Now() + result.Linearization = validateLinearizableOperationsAndVisualize(linearizableOperations, timeout) + switch result.Linearization.Linearizable { + case porcupine.Illegal: + lg.Error("Linearization failed", zap.Duration("duration", time.Since(start))) + case porcupine.Unknown: + lg.Error("Linearization has timed out", zap.Duration("duration", time.Since(start))) + case porcupine.Ok: + lg.Info("Linearization success", zap.Duration("duration", time.Since(start))) + default: + panic(fmt.Sprintf("Unknown Linearization result %s", result.Linearization.Linearizable)) + } + + // Skip other validations if model is not linearizable, as they are expected to fail too and obfuscate the logs. + if result.Linearization.Linearizable != porcupine.Ok { + lg.Info("Skipping other validations as linearization failed") + return result + } + if len(persistedRequests) == 0 { + lg.Info("Skipping other validations as persisted requests were empty") + return result } - // TODO: Use requests from linearization for replay. replay := model.NewReplay(persistedRequests) - err = validateWatch(lg, cfg, reports, replay) - if err != nil { - t.Errorf("Failed validating watch history, err: %s", err) + lg.Info("Validating watch") + start = time.Now() + result.WatchError = validateWatch(lg, cfg, reports, replay) + if result.WatchError == nil { + lg.Info("Watch validation success", zap.Duration("duration", time.Since(start))) + } else { + lg.Error("Watch validation failed", zap.Duration("duration", time.Since(start)), zap.Error(result.WatchError)) } - err = validateSerializableOperations(lg, serializableOperations, replay) - if err != nil { - t.Errorf("Failed validating serializable operations, err: %s", err) + + lg.Info("Validating serializable operations") + start = time.Now() + result.SerializableError = validateSerializableOperations(lg, serializableOperations, replay) + if result.SerializableError == nil { + lg.Info("Serializable validation success", zap.Duration("duration", time.Since(start))) + } else { + lg.Error("Serializable validation failed", zap.Duration("duration", time.Since(start)), zap.Error(result.SerializableError)) } - return visualize + + return result } type Config struct { ExpectRevisionUnique bool } -func checkValidationAssumptions(reports []report.ClientReport, persistedRequests []model.EtcdRequest) error { - err := validateEmptyDatabaseAtStart(reports) - if err != nil { - return err +func prepareAndCategorizeOperations(reports []report.ClientReport) (linearizable []porcupine.Operation, serializable []porcupine.Operation) { + for _, report := range reports { + for _, op := range report.KeyValue { + request := op.Input.(model.EtcdRequest) + response := op.Output.(model.MaybeEtcdResponse) + // serializable operations include only Range requests on non-zero revision + if request.Type == model.Range && request.Range.Revision != 0 { + serializable = append(serializable, op) + } + // Remove failed read requests as they are not relevant for linearization. + if response.Error == "" || !request.IsRead() { + // For linearization, we set the return time of failed requests to MaxInt64. + // Failed requests can still be persisted, however we don't know when request has taken effect. + if response.Error != "" { + op.Return = math.MaxInt64 + } + linearizable = append(linearizable, op) + } + } } + return linearizable, serializable +} - err = validatePersistedRequestMatchClientRequests(reports, persistedRequests) +func checkValidationAssumptions(reports []report.ClientReport) error { + err := validateEmptyDatabaseAtStart(reports) if err != nil { return err } + err = validateNonConcurrentClientRequests(reports) if err != nil { return err @@ -76,84 +129,19 @@ func checkValidationAssumptions(reports []report.ClientReport, persistedRequests } func validateEmptyDatabaseAtStart(reports []report.ClientReport) error { - for _, r := range reports { - for _, op := range r.KeyValue { - request := op.Input.(model.EtcdRequest) - response := op.Output.(model.MaybeEtcdResponse) - if response.Revision == 2 && !request.IsRead() { - return nil - } - } + if len(reports) == 0 { + return nil } - return fmt.Errorf("non empty database at start or first write didn't succeed, required by model implementation") -} - -func validatePersistedRequestMatchClientRequests(reports []report.ClientReport, persistedRequests []model.EtcdRequest) error { - persistedRequestSet := map[string]model.EtcdRequest{} - for _, request := range persistedRequests { - data, err := json.Marshal(request) - if err != nil { - return err - } - persistedRequestSet[string(data)] = request - } - clientRequests := map[string]porcupine.Operation{} - for _, r := range reports { - for _, op := range r.KeyValue { - request := op.Input.(model.EtcdRequest) - data, err := json.Marshal(request) - if err != nil { - return err - } - clientRequests[string(data)] = op - } - } - - for requestDump, request := range persistedRequestSet { - _, found := clientRequests[requestDump] - // We cannot validate if persisted leaseGrant was sent by client as failed leaseGrant will not return LeaseID to clients. - if request.Type == model.LeaseGrant { - continue - } - - if !found { - return fmt.Errorf("request %+v was not sent by client, required to validate", requestDump) - } - } - - var firstOp, lastOp porcupine.Operation for _, r := range reports { for _, op := range r.KeyValue { request := op.Input.(model.EtcdRequest) response := op.Output.(model.MaybeEtcdResponse) - if response.Error != "" || request.IsRead() { - continue - } - if firstOp.Call == 0 || op.Call < firstOp.Call { - firstOp = op - } - if lastOp.Call == 0 || op.Call > lastOp.Call { - lastOp = op + if response.Revision == 1 && request.IsRead() { + return nil } } } - firstOpData, err := json.Marshal(firstOp.Input.(model.EtcdRequest)) - if err != nil { - return err - } - _, found := persistedRequestSet[string(firstOpData)] - if !found { - return fmt.Errorf("first succesful client write %s was not persisted, required to validate", firstOpData) - } - lastOpData, err := json.Marshal(lastOp.Input.(model.EtcdRequest)) - if err != nil { - return err - } - _, found = persistedRequestSet[string(lastOpData)] - if !found { - return fmt.Errorf("last succesful client write %s was not persisted, required to validate", lastOpData) - } - return nil + return ErrNotEmptyDatabase } func validateNonConcurrentClientRequests(reports []report.ClientReport) error { diff --git a/tests/robustness/validate/validate_test.go b/tests/robustness/validate/validate_test.go index e847af672a90..22ff3509ac1e 100644 --- a/tests/robustness/validate/validate_test.go +++ b/tests/robustness/validate/validate_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/anishathalye/porcupine" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -44,15 +45,153 @@ func TestDataReports(t *testing.T) { require.NoError(t, err) persistedRequests, err := report.LoadClusterPersistedRequests(lg, path) - require.NoError(t, err) - visualize := ValidateAndReturnVisualize(t, zaptest.NewLogger(t), Config{}, reports, persistedRequests, 5*time.Minute) + if err != nil { + t.Error(err) + } + result := ValidateAndReturnVisualize(zaptest.NewLogger(t), Config{}, reports, persistedRequests, 5*time.Minute) + err = result.Error() + if err != nil { + t.Error(err) + } + + err = result.Linearization.Visualize(lg, filepath.Join(path, "history.html")) + if err != nil { + t.Error(err) + } + }) + } +} - err = visualize(filepath.Join(path, "history.html")) +func TestValidateAndReturnVisualize(t *testing.T) { + tcs := []struct { + name string + reports []report.ClientReport + persistedRequests []model.EtcdRequest + config Config + expectError string + }{ + { + name: "Success with no persisted requests", + reports: []report.ClientReport{ + { + KeyValue: []porcupine.Operation{ + { + ClientId: 0, + Input: getRequest("key"), + Call: 100, + Output: getResponse(1), + Return: 200, + }, + }, + }, + }, + expectError: "", + }, + { + name: "Failure with not empty database", + reports: []report.ClientReport{ + { + KeyValue: []porcupine.Operation{ + { + ClientId: 0, + Input: getRequest("key"), + Call: 100, + // Empty database should have revision 1 + Output: getResponse(2), + Return: 200, + }, + }, + }, + }, + expectError: "non empty database at start, required by model used for linearizability validation", + }, + { + name: "Success", + reports: []report.ClientReport{ + { + KeyValue: []porcupine.Operation{ + { + ClientId: 0, + Input: getRequest("key"), + Call: 100, + Output: getResponse(1), + Return: 200, + }, + { + ClientId: 0, + Input: putRequest("key", "value"), + Call: 300, + Output: putResponse(2, model.EtcdOperationResult{}), + Return: 400, + }, + }, + Watch: []model.WatchOperation{ + { + Request: model.WatchRequest{Key: "key"}, + Responses: []model.WatchResponse{ + {Events: []model.WatchEvent{watchEvent(2, true, model.PutOperation, "key", "value")}}, + }, + }, + }, + }, + }, + persistedRequests: []model.EtcdRequest{putRequest("key", "value")}, + expectError: "", + }, + { + name: "Failure of watch", + reports: []report.ClientReport{ + { + KeyValue: []porcupine.Operation{ + { + ClientId: 0, + Input: getRequest("key"), + Call: 100, + Output: getResponse(1), + Return: 200, + }, + { + ClientId: 0, + Input: putRequest("key", "value"), + Call: 300, + Output: putResponse(2, model.EtcdOperationResult{}), + Return: 400, + }, + }, + Watch: []model.WatchOperation{ + { + Request: model.WatchRequest{Key: "key"}, + Responses: []model.WatchResponse{ + {Events: []model.WatchEvent{watchEvent(2, true, model.PutOperation, "key", "value2")}}, + }, + }, + }, + }, + }, + persistedRequests: []model.EtcdRequest{putRequest("key", "value")}, + expectError: "watch validation failed", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + lg := zaptest.NewLogger(t) + result := ValidateAndReturnVisualize(lg, Config{}, tc.reports, tc.persistedRequests, 5*time.Second) + + if tc.expectError != "" { + require.ErrorContains(t, result.Error(), tc.expectError) + } else { + require.NoError(t, result.Error()) + } + err := result.Linearization.Visualize(lg, filepath.Join(t.TempDir(), "history.html")) require.NoError(t, err) }) } } +func watchEvent(rev int64, isCreate bool, eventType model.OperationType, key, value string) model.WatchEvent { + return model.WatchEvent{PersistedEvent: model.PersistedEvent{Revision: rev, IsCreate: isCreate, Event: model.Event{Type: eventType, Key: key, Value: model.ToValueOrHash(value)}}} +} + func TestValidateWatch(t *testing.T) { tcs := []struct { name string @@ -1915,6 +2054,21 @@ func deletePersistedEvent(key string, rev int64) model.PersistedEvent { } } +func getRequest(key string) model.EtcdRequest { + return model.EtcdRequest{ + Type: model.Range, + Range: &model.RangeRequest{ + RangeOptions: model.RangeOptions{ + Start: "key", + }, + }, + } +} + +func getResponse(rev int64) model.MaybeEtcdResponse { + return model.MaybeEtcdResponse{EtcdResponse: model.EtcdResponse{Revision: rev, Range: &model.RangeResponse{KVs: []model.KeyValue{}}}} +} + func putRequest(key, value string) model.EtcdRequest { return model.EtcdRequest{ Type: model.Txn, diff --git a/tests/robustness/validate/watch.go b/tests/robustness/validate/watch.go index 506cbeca431f..bba43c66eb19 100644 --- a/tests/robustness/validate/watch.go +++ b/tests/robustness/validate/watch.go @@ -38,7 +38,6 @@ var ( ) func validateWatch(lg *zap.Logger, cfg Config, reports []report.ClientReport, replay *model.EtcdReplay) error { - lg.Info("Validating watch") // Validate etcd watch properties defined in https://etcd.io/docs/v3.6/learning/api_guarantees/#watch-apis for _, r := range reports { err := validateFilter(lg, r) diff --git a/tools/.golangci.yaml b/tools/.golangci.yaml index 60925e0a467f..d5a87964ef7c 100644 --- a/tools/.golangci.yaml +++ b/tools/.golangci.yaml @@ -30,12 +30,14 @@ linters: - revive - staticcheck - stylecheck - - tenv - testifylint + - thelper - unconvert # Remove unnecessary type conversions - unparam - unused - usestdlibvars + # Disabled in the meanwhile, it raises several issues with new Go 1.24 functions + # - usetesting - whitespace linters-settings: # please keep this alphabetized goimports: @@ -47,49 +49,30 @@ linters-settings: # please keep this alphabetized confidence: 0.8 rules: - name: blank-imports - disabled: false - name: context-as-argument - disabled: false - name: context-keys-type - disabled: false - name: dot-imports - disabled: false - name: early-return - disabled: false arguments: - "preserveScope" - name: error-return - disabled: false - name: error-naming - disabled: false - name: error-strings - disabled: false - name: errorf - disabled: false - name: if-return - disabled: false - name: increment-decrement - disabled: false - name: indent-error-flow - disabled: false - name: package-comments - disabled: false - name: range - disabled: false - name: receiver-naming - disabled: false - name: superfluous-else - disabled: false arguments: - "preserveScope" - name: time-naming - disabled: false + - name: unnecessary-stmt - name: use-any - disabled: false - name: var-declaration - disabled: false - name: var-naming - disabled: false arguments: # The following is the configuration for var-naming rule, the first element is the allow list and the second element is the deny list. - [] # AllowList: leave it empty to use the default (empty, too). This means that we're not relaxing the rule in any way, i.e. elementId will raise a violation, it should be elementID, refer to the next line to see the list of denied initialisms. @@ -120,3 +103,18 @@ linters-settings: # please keep this alphabetized # to always require f-functions for stretchr/testify, but not for golang standard lib. # Also refer to https://github.com/etcd-io/etcd/pull/18741#issuecomment-2422395914 require-f-funcs: true + thelper: + test: + first: false + begin: false + benchmark: + first: false + begin: false + tb: + first: false + begin: false + fuzz: + first: false + begin: false + usetesting: + os-mkdir-temp: false diff --git a/tools/.markdownlint.jsonc b/tools/.markdownlint.jsonc new file mode 100644 index 000000000000..d6b36639cfc1 --- /dev/null +++ b/tools/.markdownlint.jsonc @@ -0,0 +1,282 @@ +// Example markdownlint configuration with all properties set to their default value +{ + + // Default state for all rules + "default": true, + + // Path to configuration file to extend + "extends": null, + + // MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md001.md + "MD001": true, + + // MD003/heading-style : Heading style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md003.md + "MD003": { + // Heading style + "style": "consistent" + }, + + // MD004/ul-style : Unordered list style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md004.md + "MD004": { + // List style + "style": "consistent" + }, + + // MD005/list-indent : Inconsistent indentation for list items at the same level : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md005.md + "MD005": true, + + // MD007/ul-indent : Unordered list indentation : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md007.md + "MD007": { + // Spaces for indent + "indent": 2, + // Whether to indent the first level of the list + "start_indented": false, + // Spaces for first level indent (when start_indented is set) + "start_indent": 2 + }, + + // MD009/no-trailing-spaces : Trailing spaces : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md + "MD009": { + // Spaces for line break + "br_spaces": 2, + // Allow spaces for empty lines in list items + "list_item_empty_lines": false, + // Include unnecessary breaks + "strict": false + }, + + // MD010/no-hard-tabs : Hard tabs : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md010.md + "MD010": { + // Include code blocks + "code_blocks": true, + // Fenced code languages to ignore + "ignore_code_languages": [], + // Number of spaces for each hard tab + "spaces_per_tab": 1 + }, + + // MD011/no-reversed-links : Reversed link syntax : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md011.md + "MD011": true, + + // MD012/no-multiple-blanks : Multiple consecutive blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md012.md + "MD012": { + // Consecutive blank lines + "maximum": 1 + }, + + "MD013": false, + + // MD014/commands-show-output : Dollar signs used before commands without showing output : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md014.md + "MD014": true, + + // MD018/no-missing-space-atx : No space after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md018.md + "MD018": true, + + // MD019/no-multiple-space-atx : Multiple spaces after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md019.md + "MD019": true, + + // MD020/no-missing-space-closed-atx : No space inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md020.md + "MD020": true, + + // MD021/no-multiple-space-closed-atx : Multiple spaces inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md021.md + "MD021": true, + + // MD022/blanks-around-headings : Headings should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md022.md + "MD022": { + // Blank lines above heading + "lines_above": 1, + // Blank lines below heading + "lines_below": 1 + }, + + // MD023/heading-start-left : Headings must start at the beginning of the line : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md023.md + "MD023": true, + + // MD024/no-duplicate-heading : Multiple headings with the same content : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md024.md + "MD024": { + // Only check sibling headings + "siblings_only": false + }, + + // MD025/single-title/single-h1 : Multiple top-level headings in the same document : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md025.md + "MD025": { + // Heading level + "level": 1, + // RegExp for matching title in front matter + "front_matter_title": "^\\s*title\\s*[:=]" + }, + + // MD026/no-trailing-punctuation : Trailing punctuation in heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md026.md + "MD026": { + // Punctuation characters + "punctuation": ".,;:!。,;:!" + }, + + // MD027/no-multiple-space-blockquote : Multiple spaces after blockquote symbol : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md027.md + "MD027": true, + + // MD028/no-blanks-blockquote : Blank line inside blockquote : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md028.md + "MD028": true, + + // MD029/ol-prefix : Ordered list item prefix : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md029.md + "MD029": { + // List style + "style": "one_or_ordered" + }, + + // MD030/list-marker-space : Spaces after list markers : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md030.md + "MD030": { + // Spaces for single-line unordered list items + "ul_single": 1, + // Spaces for single-line ordered list items + "ol_single": 1, + // Spaces for multi-line unordered list items + "ul_multi": 1, + // Spaces for multi-line ordered list items + "ol_multi": 1 + }, + + // MD031/blanks-around-fences : Fenced code blocks should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md031.md + "MD031": { + // Include list items + "list_items": true + }, + + // MD032/blanks-around-lists : Lists should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md032.md + "MD032": true, + + // MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md033.md + "MD033": { + // Allowed elements + "allowed_elements": [] + }, + + // MD034/no-bare-urls : Bare URL used : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md034.md + "MD034": true, + + // MD035/hr-style : Horizontal rule style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md035.md + "MD035": { + // Horizontal rule style + "style": "consistent" + }, + + // MD036/no-emphasis-as-heading : Emphasis used instead of a heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md036.md + "MD036": { + // Punctuation characters + "punctuation": ".,;:!?。,;:!?" + }, + + // MD037/no-space-in-emphasis : Spaces inside emphasis markers : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md037.md + "MD037": true, + + // MD038/no-space-in-code : Spaces inside code span elements : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md038.md + "MD038": true, + + // MD039/no-space-in-links : Spaces inside link text : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md039.md + "MD039": true, + + // MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md040.md + "MD040": { + // List of languages + "allowed_languages": [], + // Require language only + "language_only": false + }, + + // MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md041.md + "MD041": false, + + // MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md042.md + "MD042": true, + + // MD043/required-headings : Required heading structure : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md043.md + "MD043": false, + + // MD044/proper-names : Proper names should have the correct capitalization : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md044.md + "MD044": { + // List of proper names + "names": [], + // Include code blocks + "code_blocks": true, + // Include HTML elements + "html_elements": true + }, + + // MD045/no-alt-text : Images should have alternate text (alt text) : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md045.md + "MD045": true, + + // MD046/code-block-style : Code block style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md046.md + "MD046": { + // Block style + "style": "consistent" + }, + + // MD047/single-trailing-newline : Files should end with a single newline character : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md047.md + "MD047": true, + + // MD048/code-fence-style : Code fence style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md048.md + "MD048": { + // Code fence style + "style": "consistent" + }, + + // MD049/emphasis-style : Emphasis style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md049.md + "MD049": { + // Emphasis style + "style": "consistent" + }, + + // MD050/strong-style : Strong style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md050.md + "MD050": { + // Strong style + "style": "consistent" + }, + + // MD051/link-fragments : Link fragments should be valid : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md051.md + "MD051": { + // Ignore case of fragments + "ignore_case": false + }, + + // MD052/reference-links-images : Reference links and images should use a label that is defined : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md052.md + "MD052": { + // Include shortcut syntax + "shortcut_syntax": false + }, + + // MD053/link-image-reference-definitions : Link and image reference definitions should be needed : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md053.md + "MD053": { + // Ignored definitions + "ignored_definitions": [ + "//" + ] + }, + + // MD054/link-image-style : Link and image style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md054.md + "MD054": { + // Allow autolinks + "autolink": true, + // Allow inline links and images + "inline": true, + // Allow full reference links and images + "full": true, + // Allow collapsed reference links and images + "collapsed": true, + // Allow shortcut reference links and images + "shortcut": true, + // Allow URLs as inline links + "url_inline": true + }, + + // MD055/table-pipe-style : Table pipe style : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md055.md + "MD055": { + // Table pipe style + "style": "consistent" + }, + + // MD056/table-column-count : Table column count : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md056.md + "MD056": true, + + // MD058/blanks-around-tables : Tables should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md058.md + "MD058": true + } diff --git a/tools/etcd-dump-logs/README.md b/tools/etcd-dump-logs/README.md index 0922f2878fb1..58ed80df6d6c 100644 --- a/tools/etcd-dump-logs/README.md +++ b/tools/etcd-dump-logs/README.md @@ -52,7 +52,10 @@ Flags: IRRRange, IRRPut, IRRDeleteRange, IRRTxn, IRRCompaction, IRRLeaseGrant, IRRLeaseRevoke -start-index uint - The index to start dumping + The index to start dumping (inclusive) + If unspecified, dumps from the index of the last snapshot. + -end-index uint + The index to stop dumping (exclusive) -start-snap string The base name of snapshot file to start dumping -stream-decoder string @@ -146,11 +149,11 @@ Entry types () count is : 8 ``` #### etcd-dump-logs -start-index [data dir] -Only shows WAL log entries after the specified start-index number, exclusively. +Only shows WAL log entries after the specified start-index number, inclusively. ``` -$ etcd-dump-logs -start-index 30 /tmp/datadir -Start dumping log entries from index 30. +$ etcd-dump-logs -start-index 31 /tmp/datadir +Start dumping log entries from index 31. WAL metadata: nodeID=0 clusterID=0 term=0 commitIndex=0 vote=0 WAL entries: @@ -162,4 +165,23 @@ term index type data 27 34 norm ??? Entry types () count is : 4 ``` + +#### etcd-dump-logs -start-index -end-index [data dir] + +Only shows WAL log entries from the specified start-index number (inclusively) to the specified end-index number (exclusively). + +``` +$ etcd-dump-logs -start-index 930 -end-index 932 /tmp/datadir +Start dumping log entries from index 930. +WAL metadata: +nodeID=0 clusterID=0 term=5 commitIndex=2448 vote=0 +WAL entries: 2 +lastIndex=931 +term index type data + 3 930 norm header: put: + 3 931 norm header: put: + +Entry types (Normal,ConfigChange) count is : 2 +``` + [decoder_correctoutputformat.sh]: ./testdecoder/decoder_correctoutputformat.sh diff --git a/tools/etcd-dump-logs/etcd-dump-log_test.go b/tools/etcd-dump-logs/etcd-dump-log_test.go index bb08ec11403e..c2581cebb78e 100644 --- a/tools/etcd-dump-logs/etcd-dump-log_test.go +++ b/tools/etcd-dump-logs/etcd-dump-log_test.go @@ -82,10 +82,10 @@ func TestEtcdDumpLogEntryType(t *testing.T) { expected, err := os.ReadFile(path.Join(binDir, argtest.fileExpected)) require.NoError(t, err) - assert.EqualValues(t, string(expected), string(actual)) + assert.Equal(t, string(expected), string(actual)) // The output files contains a lot of trailing whitespaces... difficult to diagnose without printing them explicitly. // TODO(ptabor): Get rid of the whitespaces both in code and the test-files. - assert.EqualValues(t, strings.ReplaceAll(string(expected), " ", "_"), strings.ReplaceAll(string(actual), " ", "_")) + assert.Equal(t, strings.ReplaceAll(string(expected), " ", "_"), strings.ReplaceAll(string(actual), " ", "_")) }) } } diff --git a/tools/etcd-dump-logs/main.go b/tools/etcd-dump-logs/main.go index f0bba446762a..f0d2a29c4ac7 100644 --- a/tools/etcd-dump-logs/main.go +++ b/tools/etcd-dump-logs/main.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "log" + "math" "os" "os/exec" "path/filepath" @@ -51,7 +52,8 @@ const ( func main() { snapfile := flag.String("start-snap", "", "The base name of snapshot file to start dumping") waldir := flag.String("wal-dir", "", "If set, dumps WAL from the informed path, rather than following the standard 'data_dir/member/wal/' location") - index := flag.Uint64("start-index", 0, "The index to start dumping") + startIndex := flag.Uint64("start-index", 0, "The index to start dumping (inclusive). If unspecified, dumps from the index of the last snapshot.") + endIndex := flag.Uint64("end-index", math.MaxUint64, "The index to stop dumping (exclusive)") // Default entry types are Normal and ConfigChange entrytype := flag.String("entry-type", defaultEntryTypes, `If set, filters output by entry type. Must be one or more than one of: ConfigChange, Normal, Request, InternalRaftRequest, @@ -70,7 +72,7 @@ and output a hex encoded line of binary for each input line`) } dataDir := flag.Args()[0] - if *snapfile != "" && *index != 0 { + if *snapfile != "" && *startIndex != 0 { log.Fatal("start-snap and start-index flags cannot be used together.") } @@ -82,7 +84,7 @@ and output a hex encoded line of binary for each input line`) }) if !*raw { - ents := readUsingReadAll(lg, startFromIndex, index, snapfile, dataDir, waldir) + ents := readUsingReadAll(lg, startFromIndex, startIndex, endIndex, snapfile, dataDir, waldir) fmt.Printf("WAL entries: %d\n", len(ents)) if len(ents) > 0 { @@ -107,20 +109,25 @@ and output a hex encoded line of binary for each input line`) if wd == "" { wd = walDir(dataDir) } - readRaw(index, wd, os.Stdout) + readRaw(startIndex, wd, os.Stdout) } } -func readUsingReadAll(lg *zap.Logger, startFromIndex bool, index *uint64, snapfile *string, dataDir string, waldir *string) []raftpb.Entry { +func readUsingReadAll(lg *zap.Logger, startFromIndex bool, startIndex *uint64, endIndex *uint64, snapfile *string, dataDir string, waldir *string) []raftpb.Entry { var ( walsnap walpb.Snapshot snapshot *raftpb.Snapshot err error ) + endAtIndex := *endIndex < math.MaxUint64 if startFromIndex { - fmt.Printf("Start dumping log entries from index %d.\n", *index) - walsnap.Index = *index + fmt.Printf("Start dumping log entries from index %d.\n", *startIndex) + // ReadAll() reads entries from the index after walsnap.Index, so we need to move walsnap.Index back one. + if *startIndex > 0 { + *startIndex-- + } + walsnap.Index = *startIndex } else { if *snapfile == "" { ss := snap.New(lg, snapDir(dataDir)) @@ -160,12 +167,29 @@ func readUsingReadAll(lg *zap.Logger, startFromIndex bool, index *uint64, snapfi wmetadata, state, ents, err := w.ReadAll() w.Close() if err != nil && (!startFromIndex || !errors.Is(err, wal.ErrSnapshotNotFound)) { - log.Fatalf("Failed reading WAL: %v", err) + // ReadAll might return ErrSliceOutOfRange and the first series of entries if the server is offline for a while and receives a snapshot from leader. + // It is ok to ignore ErrSliceOutOfRange if just requesting a specific range of entries + if !endAtIndex || !errors.Is(err, wal.ErrSliceOutOfRange) { + log.Fatalf("Failed reading WAL: %v", err) + } + log.Printf("Failed reading all WAL: %v", err) } id, cid := parseWALMetadata(wmetadata) vid := types.ID(state.Vote) fmt.Printf("WAL metadata:\nnodeID=%s clusterID=%s term=%d commitIndex=%d vote=%s\n", id, cid, state.Term, state.Commit, vid) + if endAtIndex { + entries := make([]raftpb.Entry, 0) + for _, e := range ents { + // WAL might contain entries with e.Index >= *endIndex from prev term, then e.Index < *endIndex in the next term. + // We cannot break when e.Index >= *endIndex. + if e.Index >= *endIndex { + continue + } + entries = append(entries, e) + } + return entries + } return ents } diff --git a/tools/mod/go.mod b/tools/mod/go.mod index c44b0d10be39..0d868bb42efd 100644 --- a/tools/mod/go.mod +++ b/tools/mod/go.mod @@ -1,61 +1,61 @@ module go.etcd.io/etcd/tools/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( - github.com/alexfalkowski/gocovmerge v1.3.18 + github.com/alexfalkowski/gocovmerge v1.8.0 github.com/appscodelabs/license-bill-of-materials v0.0.0-20220707232035-6018e0c5287c github.com/chzchzchz/goword v0.0.0-20170907005317-a9744cb52b03 github.com/cloudflare/cfssl v1.6.5 github.com/gogo/protobuf v1.3.2 - github.com/golangci/golangci-lint v1.63.4 + github.com/golangci/golangci-lint v1.64.8 github.com/google/addlicense v1.1.1 - github.com/google/yamlfmt v0.15.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 + github.com/google/yamlfmt v0.17.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 go.etcd.io/gofail v0.2.0 go.etcd.io/protodoc v0.0.0-20180829002748-484ab544e116 go.etcd.io/raft/v3 v3.6.0 - gotest.tools/gotestsum v1.12.0 - gotest.tools/v3 v3.5.1 - honnef.co/go/tools v0.5.1 + gotest.tools/gotestsum v1.12.2 + gotest.tools/v3 v3.5.2 + honnef.co/go/tools v0.6.1 ) require ( - 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect - 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.4.1 // indirect + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.1 // indirect github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.5.3 // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect github.com/akhenakh/hunspellgo v0.0.0-20160221122622-9db38fa26e19 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/alingse/nilnesserr v0.1.1 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bitfield/gotestdox v0.2.2 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/bombsimon/wsl/v4 v4.5.0 // indirect - github.com/braydonk/yaml v0.7.0 // indirect github.com/breml/bidichk v0.3.2 // indirect github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.7.1 // indirect + github.com/catenacyber/perfsprint v0.8.2 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect @@ -63,19 +63,19 @@ require ( github.com/ckaznocha/intrange v0.3.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.8 // indirect - github.com/go-critic/go-critic v0.11.5 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-logr/logr v1.4.2 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -88,58 +88,55 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect + github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect - github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.3 // indirect + github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/certificate-transparency-go v1.1.7 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/certificate-transparency-go v1.1.8 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.4 // indirect github.com/jmhodges/clock v1.2.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect - github.com/kisielk/errcheck v1.8.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 // indirect - github.com/kkHAIKE/contextcheck v1.1.5 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect - github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.3.1 // indirect - github.com/ldez/gomoddirectives v0.6.0 // indirect - github.com/ldez/grignotin v0.7.0 // indirect + github.com/ldez/exptostd v0.4.2 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect github.com/ldez/tagliatelle v0.7.1 // indirect github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lib/pq v1.10.9 // indirect github.com/macabu/inamedparam v0.1.3 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect + github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/mgechev/revive v1.5.1 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/mgechev/revive v1.7.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect @@ -147,15 +144,17 @@ require ( github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.4 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/nunnatsa/ginkgolinter v0.19.1 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect + github.com/olekukonko/tablewriter v1.0.6 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.7.0 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect @@ -164,34 +163,34 @@ require ( github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect - github.com/securego/gosec/v2 v2.21.4 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/securego/gosec/v2 v2.22.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/spf13/viper v1.12.0 // indirect + github.com/spf13/viper v1.20.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.3.0 // indirect - github.com/tetafro/godot v1.4.20 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect @@ -200,38 +199,37 @@ require ( github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect - github.com/uudashr/iface v1.3.0 // indirect - github.com/weppos/publicsuffix-go v0.30.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect + github.com/weppos/publicsuffix-go v0.30.3-0.20240510084413-5f1d03393b3d // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect - github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 // indirect - github.com/zmap/zlint/v3 v3.5.0 // indirect + github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect + github.com/zmap/zlint/v3 v3.6.0 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.13.0 // indirect - go-simpler.org/sloglint v0.7.2 // indirect + go-simpler.org/sloglint v0.9.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.29.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.70.0 // indirect - google.golang.org/protobuf v1.36.4 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/tools v0.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/grpc v1.72.2 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/mod/go.sum b/tools/mod/go.sum index 70c1bedde9b4..acdfe37bce1b 100644 --- a/tools/mod/go.sum +++ b/tools/mod/go.sum @@ -1,9 +1,12 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -github.com/4meepo/tagalign v1.4.1 h1:GYTu2FaPGOGb/xJalcqHeD4il5BiCywyEYZOA55P6J4= -github.com/4meepo/tagalign v1.4.1/go.mod h1:2H9Yu6sZ67hmuraFgfZkNcg5Py9Ch/Om9l2K/2W1qS4= +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= @@ -14,16 +17,17 @@ github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5l github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= -github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/akhenakh/hunspellgo v0.0.0-20160221122622-9db38fa26e19 h1:bYOD6QJnBJY79MJQR1i9cyQePG5oNDZXDKL2bhN/uvE= github.com/akhenakh/hunspellgo v0.0.0-20160221122622-9db38fa26e19/go.mod h1:HcqyLXmWoESd/vPSbCPqvgw5l5cMM5PtoqFOnXLjSeM= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= @@ -32,16 +36,16 @@ github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsr github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alexfalkowski/gocovmerge v1.3.18 h1:GRJz7uNUHuumvWQtS2zBjDkLdIiCcYE1rfxYYWwYvs8= -github.com/alexfalkowski/gocovmerge v1.3.18/go.mod h1:TnngCLiVe3kIrJ8v12zHP8aQkRWpjTomZm18uQrRDvE= +github.com/alexfalkowski/gocovmerge v1.8.0 h1:N5e33J3oMAweMBsJTOv0GwOuAoD3CdR5NhNOt7aJSVs= +github.com/alexfalkowski/gocovmerge v1.8.0/go.mod h1:RaTPWgDVJlySp1sgyUz5w+ZLh17fIptLDZD5100epAI= github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/alingse/nilnesserr v0.1.1 h1:7cYuJewpy9jFNMEA72Q1+3Nm3zKHzg+Q28D5f2bBFUA= -github.com/alingse/nilnesserr v0.1.1/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/appscodelabs/license-bill-of-materials v0.0.0-20220707232035-6018e0c5287c h1:xv0ICJ4AO52aNZ+vI2KFUYZBMh7dHvROixZ1vzMMfu8= github.com/appscodelabs/license-bill-of-materials v0.0.0-20220707232035-6018e0c5287c/go.mod h1:Y5/1I+0gnnhHKyX4z65mgaGTJ08tnz9WUgkoymA/cws= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= @@ -57,12 +61,10 @@ github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8 github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= -github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= -github.com/braydonk/yaml v0.7.0 h1:ySkqO7r0MGoCNhiRJqE0Xe9yhINMyvOAB3nFjgyJn2k= -github.com/braydonk/yaml v0.7.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4= github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= @@ -71,8 +73,9 @@ github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= +github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -87,16 +90,18 @@ github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7ZeI= github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= @@ -105,32 +110,28 @@ github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= -github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= -github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -162,60 +163,65 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 h1:t5wybL6RtO83VwoMOb7U/Peqe3gGKQlPIC66wXmnkvM= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9/go.mod h1:Ag3L7sh7E28qAp/5xnpMMTuGYqxLZoSaEHZDkZB1RgU= -github.com/golangci/golangci-lint v1.63.4 h1:bJQFQ3hSfUto597dkL7ipDzOxsGEpiWdLiZ359OWOBI= -github.com/golangci/golangci-lint v1.63.4/go.mod h1:Hx0B7Lg5/NXbaOHem8+KU+ZUIzMI6zNj/7tFwdnn10I= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= +github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= -github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/addlicense v1.1.1 h1:jpVf9qPbU8rz5MxKo7d+RMcNHkqxi4YJi/laauX4aAE= github.com/google/addlicense v1.1.1/go.mod h1:Sm/DHu7Jk+T5miFHHehdIjbi4M5+dJDRS3Cq0rncIxA= -github.com/google/certificate-transparency-go v1.1.7 h1:IASD+NtgSTJLPdzkthwvAG1ZVbF2WtFg4IvoA68XGSw= -github.com/google/certificate-transparency-go v1.1.7/go.mod h1:FSSBo8fyMVgqptbfF6j5p/XNdgQftAhSmXcIxV9iphE= +github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= +github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/yamlfmt v0.15.0 h1:8VqeHp87EEyfAeCTp3QpV/8bnhDh04jKN6EeifiTM70= -github.com/google/yamlfmt v0.15.0/go.mod h1:MPmHSVetV8ofpKmeiEZAh2p4jbDapC+FNqilNN7JQVk= +github.com/google/yamlfmt v0.17.0 h1:/tdp01rIlvLz3LgJ2NtMLnqgAadZm33P7GcPU680b+w= +github.com/google/yamlfmt v0.17.0/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= @@ -225,8 +231,6 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -239,24 +243,21 @@ github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpR github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= -github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= -github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= -github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -268,53 +269,43 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.3.1 h1:90yWWoAKMFHeovTK8uzBms9Ppp8Du/xQ20DRO26Ymrw= -github.com/ldez/exptostd v0.3.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= -github.com/ldez/gomoddirectives v0.6.0 h1:Jyf1ZdTeiIB4dd+2n4qw+g4aI9IJ6JyfOZ8BityWvnA= -github.com/ldez/gomoddirectives v0.6.0/go.mod h1:TuwOGYoPAoENDWQpe8DMqEm5nIfjrxZXmxX/CExWyZ4= -github.com/ldez/grignotin v0.7.0 h1:vh0dI32WhHaq6LLPZ38g7WxXuZ1+RzyrJ7iPG9JMa8c= -github.com/ldez/grignotin v0.7.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= +github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= -github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -331,14 +322,18 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.4 h1:zmX4KUR+6fk/vhUFt8DOP6KwznekhkmVSzzVJve2vyM= -github.com/nunnatsa/ginkgolinter v0.18.4/go.mod h1:AMEane4QQ6JwFz5GgjI5xLUM9S/CylO+UyM97fN2iBI= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo= +github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= +github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= @@ -351,18 +346,19 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= -github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= @@ -380,9 +376,8 @@ github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtz github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= @@ -390,6 +385,8 @@ github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9f github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= @@ -398,10 +395,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= -github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= +github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -415,21 +410,21 @@ github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= +github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= @@ -451,16 +446,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.3.0 h1:LqDGgZdholxZMaJgpM6b0U9CFIjDCbFdUF00bDnBKOQ= -github.com/tdakkota/asciicheck v0.3.0/go.mod h1:KoJKXuX/Z/lt6XzLo8WMBfQGzak0SrAKZlvRr4tg8Ac= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.20 h1:z/p8Ek55UdNvzt4TFn2zx2KscpW4rWqcnUrdmvWJj7E= -github.com/tetafro/godot v1.4.20/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= @@ -477,13 +472,12 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= -github.com/uudashr/iface v1.3.0 h1:zwPch0fs9tdh9BmL5kcgSpvnObV+yHjO4JjVBl8IA10= -github.com/uudashr/iface v1.3.0/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= -github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/weppos/publicsuffix-go v0.30.0 h1:QHPZ2GRu/YE7cvejH9iyavPOkVCB4dNxp2ZvtT+vQLY= -github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY= -github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE= +github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ= +github.com/weppos/publicsuffix-go v0.30.3-0.20240510084413-5f1d03393b3d h1:q80YKUcDWRNvvQcziH63e3ammTWARwrhohBCunHaYAg= +github.com/weppos/publicsuffix-go v0.30.3-0.20240510084413-5f1d03393b3d/go.mod h1:vLdXKydr/OJssAXmjY0XBgLXUfivBMrNRIBljgtqCnw= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -505,19 +499,19 @@ github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54t github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= -github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 h1:DZH5n7L3L8RxKdSyJHZt7WePgwdhHnPhQFdQSJaHF+o= -github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w= +github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c h1:U1b4THKcgOpJ+kILupuznNwPiURtwVW3e9alJvji9+s= +github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c/go.mod h1:GSDpFDD4TASObxvfZfvpZZ3OWHIUHMlhVWlkOe4ewVk= github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= -github.com/zmap/zlint/v3 v3.5.0 h1:Eh2B5t6VKgVH0DFmTwOqE50POvyDhUaU9T2mJOe1vfQ= -github.com/zmap/zlint/v3 v3.5.0/go.mod h1:JkNSrsDJ8F4VRtBZcYUQSvnWFL7utcjDIn+FE64mlBI= +github.com/zmap/zlint/v3 v3.6.0 h1:vTEaDRtYN0d/1Ax60T+ypvbLQUHwHxbvYRnUMVr35ug= +github.com/zmap/zlint/v3 v3.6.0/go.mod h1:NVgiIWssgzp0bNl8P4Gz94NHV2ep/4Jyj9V69uTmZyg= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= -go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= -go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= go.etcd.io/protodoc v0.0.0-20180829002748-484ab544e116 h1:QQiUXlqz+d96jyNG71NE+IGTgOK6Xlhdx+PzvfbLHlQ= @@ -539,41 +533,37 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -583,20 +573,18 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -606,9 +594,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -622,43 +609,39 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -666,18 +649,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= @@ -692,50 +673,49 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/gotestsum v1.12.0 h1:CmwtaGDkHxrZm4Ib0Vob89MTfpc3GrEFMJKovliPwGk= -gotest.tools/gotestsum v1.12.0/go.mod h1:fAvqkSptospfSbQw26CTYzNwnsE/ztqLeyhP0h67ARY= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= -honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +gotest.tools/gotestsum v1.12.2 h1:eli4tu9Q2D/ogDsEGSr8XfQfl7mT0JsGOG6DFtUiZ/Q= +gotest.tools/gotestsum v1.12.2/go.mod h1:kjRtCglPZVsSU0hFHX3M5VWBM6Y63emHuB14ER1/sow= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= diff --git a/tools/rw-heatmaps/cmd/root.go b/tools/rw-heatmaps/cmd/root.go index 49b98a9fca29..722bddb20806 100644 --- a/tools/rw-heatmaps/cmd/root.go +++ b/tools/rw-heatmaps/cmd/root.go @@ -33,6 +33,8 @@ var ( ErrMissingInputFileArg = fmt.Errorf("missing input file argument") // ErrInvalidOutputFormat is returned when the output format is invalid. ErrInvalidOutputFormat = fmt.Errorf("invalid output format, must be one of png, jpg, jpeg, tiff") + // ErrInvalidChartTYpe is returned when the chart type is invalid. + ErrInvalidChartType = fmt.Errorf("invalid chart type, must be one of line, heatmap") ) // NewRootCommand returns the root command for the rw-heatmaps tool. @@ -56,6 +58,10 @@ func NewRootCommand() *cobra.Command { } } + if o.chartType == "line" { + return chart.PlotLineCharts(datasets, o.title, o.outputImageFile, o.outputFormat) + } + return chart.PlotHeatMaps(datasets, o.title, o.outputImageFile, o.outputFormat, o.zeroCentered) }, } @@ -70,6 +76,7 @@ type options struct { outputImageFile string outputFormat string zeroCentered bool + chartType string } // newOptions returns a new options for the command with the default values applied. @@ -77,6 +84,7 @@ func newOptions() options { return options{ outputFormat: "jpg", zeroCentered: true, + chartType: "heatmap", } } @@ -86,6 +94,7 @@ func (o *options) AddFlags(fs *pflag.FlagSet) { fs.StringVarP(&o.outputImageFile, "output-image-file", "o", o.outputImageFile, "output image filename (required)") fs.StringVarP(&o.outputFormat, "output-format", "f", o.outputFormat, "output image file format") fs.BoolVar(&o.zeroCentered, "zero-centered", o.zeroCentered, "plot the improvement graph with white color represents 0.0") + fs.StringVarP(&o.chartType, "chart-type", "c", o.chartType, `type of chart to plot ["line", or "heatmap"]`) } // Validate returns an error if the options are invalid. @@ -101,5 +110,10 @@ func (o *options) Validate() error { default: return ErrInvalidOutputFormat } + switch o.chartType { + case "line", "heatmap": + default: + return ErrInvalidChartType + } return nil } diff --git a/tools/rw-heatmaps/go.mod b/tools/rw-heatmaps/go.mod index db9b79d6e660..70d03fdc3235 100644 --- a/tools/rw-heatmaps/go.mod +++ b/tools/rw-heatmaps/go.mod @@ -1,11 +1,11 @@ module go.etcd.io/etcd/tools/rw-heatmaps/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 gonum.org/v1/plot v0.14.0 ) @@ -19,8 +19,8 @@ require ( github.com/go-pdf/fpdf v0.8.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/image v0.18.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.25.0 // indirect ) diff --git a/tools/rw-heatmaps/go.sum b/tools/rw-heatmaps/go.sum index 6f9fbced088a..3256affc31b9 100644 --- a/tools/rw-heatmaps/go.sum +++ b/tools/rw-heatmaps/go.sum @@ -9,7 +9,7 @@ github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyR github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM= @@ -25,20 +25,20 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -53,8 +53,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= diff --git a/tools/rw-heatmaps/pkg/chart/line_chart.go b/tools/rw-heatmaps/pkg/chart/line_chart.go new file mode 100644 index 000000000000..b7421433cc61 --- /dev/null +++ b/tools/rw-heatmaps/pkg/chart/line_chart.go @@ -0,0 +1,226 @@ +// Copyright 2025 The etcd Authors +// +// Licensed 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. + +package chart + +import ( + "cmp" + "fmt" + "image/color" + "slices" + "sort" + + "gonum.org/v1/plot" + "gonum.org/v1/plot/font" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/plotutil" + "gonum.org/v1/plot/vg" + "gonum.org/v1/plot/vg/draw" + "gonum.org/v1/plot/vg/vgimg" + + "go.etcd.io/etcd/tools/rw-heatmaps/v3/pkg/dataset" +) + +// PlotLineCharts creates a new line chart. +func PlotLineCharts(datasets []*dataset.DataSet, title, outputImageFile, outputFormat string) error { + plot.DefaultFont = font.Font{ + Typeface: "Liberation", + Variant: "Sans", + } + + canvas := plotLineChart(datasets, title) + return saveCanvas(canvas, "readwrite", outputImageFile, outputFormat) +} + +func plotLineChart(datasets []*dataset.DataSet, title string) *vgimg.Canvas { + ratiosLength := func() int { + max := slices.MaxFunc(datasets, func(a, b *dataset.DataSet) int { + return cmp.Compare(len(a.GetSortedRatios()), len(b.GetSortedRatios())) + }) + return len(max.GetSortedRatios()) + }() + + // Make a nx1 grid of line charts. + const cols = 1 + rows := ratiosLength + + // Set the width and height of the canvas. + width, height := 30*vg.Centimeter, 15*font.Length(ratiosLength)*vg.Centimeter + + canvas := vgimg.New(width, height) + dc := draw.New(canvas) + + // Create a tiled layout for the plots. + t := draw.Tiles{ + Rows: rows, + Cols: cols, + PadX: vg.Millimeter * 4, + PadY: vg.Millimeter * 4, + PadTop: vg.Millimeter * 15, + PadBottom: vg.Millimeter * 2, + PadLeft: vg.Millimeter * 2, + PadRight: vg.Millimeter * 2, + } + + plots := make([][]*plot.Plot, rows) + legends := make([]plot.Legend, rows) + for i := range plots { + plots[i] = make([]*plot.Plot, cols) + } + + // Load records into the grid. + ratios := slices.MaxFunc(datasets, func(a, b *dataset.DataSet) int { + return cmp.Compare(len(a.GetSortedRatios()), len(b.GetSortedRatios())) + }).GetSortedRatios() + + for row, ratio := range ratios { + var records [][]dataset.DataRecord + var fileNames []string + for _, d := range datasets { + records = append(records, d.Records[ratio]) + fileNames = append(fileNames, d.FileName) + } + p, l := plotIndividualLineChart(fmt.Sprintf("R/W Ratio %0.04f", ratio), records, fileNames) + plots[row] = []*plot.Plot{p} + legends[row] = l + } + + // Fill the canvas with the plots and legends. + canvases := plot.Align(plots, t, dc) + for i := 0; i < rows; i++ { + // Continue if there is no plot in the current cell (incomplete data). + if plots[i][0] == nil { + continue + } + + l := legends[i] + r := l.Rectangle(canvases[i][0]) + legendWidth := r.Max.X - r.Min.X + // Adjust the legend down a little. + l.YOffs = plots[i][0].Title.TextStyle.FontExtents().Height * 3 + l.Draw(canvases[i][0]) + + c := draw.Crop(canvases[i][0], 0, -legendWidth-vg.Millimeter, 0, 0) + plots[i][0].Draw(c) + } + + // Add the title and parameter legend. + l := plot.NewLegend() + l.Add(title) + for _, d := range datasets { + l.Add(fmt.Sprintf("%s: %s", d.FileName, d.Param)) + } + l.Top = true + l.Left = true + l.Draw(dc) + + return canvas +} + +func plotIndividualLineChart(title string, records [][]dataset.DataRecord, fileNames []string) (*plot.Plot, plot.Legend) { + p := plot.New() + p.Title.Text = title + p.X.Label.Text = "Connections Amount" + p.X.Scale = plot.LogScale{} + p.X.Tick.Marker = pow2Ticks{} + p.Y.Label.Text = "QPS (Requests/sec)" + p.Y.Scale = plot.LogScale{} + p.Y.Tick.Marker = pow2Ticks{} + + legend := plot.NewLegend() + + values := getSortedValueSizes(records...) + for i, rs := range records { + rec := make(map[int64][]dataset.DataRecord) + for _, r := range rs { + rec[r.ValueSize] = append(rec[r.ValueSize], r) + } + if len(records) > 1 { + addValues(p, &legend, values, rec, i, fileNames[i]) + } else { + addValues(p, &legend, values, rec, i, "") + } + } + + return p, legend +} + +func getSortedValueSizes(records ...[]dataset.DataRecord) []int { + valueMap := make(map[int64]struct{}) + for _, rs := range records { + for _, r := range rs { + valueMap[r.ValueSize] = struct{}{} + } + } + + var values []int + for v := range valueMap { + values = append(values, int(v)) + } + sort.Ints(values) + + return values +} + +func addValues(p *plot.Plot, legend *plot.Legend, values []int, rec map[int64][]dataset.DataRecord, index int, fileName string) { + for i, value := range values { + r := rec[int64(value)] + readPts := make(plotter.XYs, len(r)) + writePts := make(plotter.XYs, len(r)) + for i, record := range r { + writePts[i].X = float64(record.ConnSize) + readPts[i].X = writePts[i].X + readPts[i].Y = record.AvgRead + writePts[i].Y = record.AvgWrite + } + + readLine, s, err := plotter.NewLinePoints(readPts) + if err != nil { + panic(err) + } + if index == 0 { + readLine.Color = plotutil.Color(0) + } else { + readLine.Color = plotutil.Color(2) + } + readLine.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1)) + readLine.Dashes = []vg.Length{vg.Points(6), vg.Points(2)} + s.Color = readLine.Color + p.Add(readLine, s) + + writeLine, s, err := plotter.NewLinePoints(writePts) + if err != nil { + panic(err) + } + if index == 0 { + writeLine.Color = plotutil.Color(0) + } else { + writeLine.Color = plotutil.Color(2) + } + writeLine.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1)) + s.Color = writeLine.Color + p.Add(writeLine, s) + + if index == 0 { + l, _, _ := plotter.NewLinePoints(writePts) + l.Color = color.RGBA{0, 0, 0, 255} + l.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1)) + legend.Add(fmt.Sprintf("%d", value), plot.Thumbnailer(l)) + } + if i == len(values)-1 { + legend.Add(fmt.Sprintf("read %s", fileName), plot.Thumbnailer(readLine)) + legend.Add(fmt.Sprintf("write %s", fileName), plot.Thumbnailer(writeLine)) + } + } +} diff --git a/tools/testgrid-analysis/go.mod b/tools/testgrid-analysis/go.mod index b1be135c28d0..7f0fb2f1a053 100644 --- a/tools/testgrid-analysis/go.mod +++ b/tools/testgrid-analysis/go.mod @@ -1,25 +1,26 @@ module go.etcd.io/etcd/tools/testgrid-analysis/v3 -go 1.23 +go 1.24 -toolchain go1.23.6 +toolchain go1.24.3 require ( github.com/GoogleCloudPlatform/testgrid v0.0.173 github.com/google/go-github/v60 v60.0.0 - github.com/spf13/cobra v1.8.1 - google.golang.org/protobuf v1.36.4 + github.com/spf13/cobra v1.9.1 + google.golang.org/protobuf v1.36.6 ) require ( + github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.70.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/grpc v1.72.2 // indirect ) diff --git a/tools/testgrid-analysis/go.sum b/tools/testgrid-analysis/go.sum index 61808c87e9ec..7686bdf98dba 100644 --- a/tools/testgrid-analysis/go.sum +++ b/tools/testgrid-analysis/go.sum @@ -809,7 +809,7 @@ github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -935,8 +935,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk= github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= @@ -1140,8 +1140,8 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1180,16 +1180,16 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1336,8 +1336,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1484,8 +1484,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1516,8 +1516,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1841,8 +1841,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230720185612-659f7aaaa771/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1888,8 +1888,8 @@ google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGO google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1909,8 +1909,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=