diff --git a/.github/actions/build-bootstrap/action.yml b/.github/actions/build-bootstrap/action.yml index 826063ff570..05123e38dba 100644 --- a/.github/actions/build-bootstrap/action.yml +++ b/.github/actions/build-bootstrap/action.yml @@ -10,21 +10,25 @@ inputs: description: "Bootstrap image flavor (e.g. mysql84)" required: false default: "mysql84" + cache-export: + description: "Export the build cache to the GitHub Actions cache. Only one job per push should do this, and only on branches whose cache other builds can read (main and release branches)." + required: false + default: "false" runs: using: "composite" steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0 - name: Build bootstrap images - uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0 + uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0 with: files: docker/bootstrap/docker-bake.hcl load: true set: | *.cache-from=type=gha,scope=bootstrap - *.cache-to=type=gha,scope=bootstrap,mode=max + ${{ inputs.cache-export == 'true' && '*.cache-to=type=gha,scope=bootstrap,mode=max' || '' }} env: BOOTSTRAP_VERSION: ${{ inputs.bootstrap-version }} BOOTSTRAP_FLAVOR: ${{ inputs.bootstrap-flavor }} diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 5547a0e47cf..427e2df7d6f 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -1,8 +1,8 @@ name: "Set up Go" -description: "Set up Go with shared module caching" +description: "Set up Go with shared module and build caching" inputs: cache: - description: "Whether to cache Go modules" + description: "Whether to restore and save the Go module and build caches" default: "true" runs: using: "composite" @@ -12,19 +12,89 @@ runs: uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod - # Disable built-in caching; its key includes ImageOS which - # creates duplicate cache entries across runner types. + # Disable built-in caching: its key includes ImageOS, which duplicates + # entries across runner types, it stores the platform-independent + # module cache once per platform, and it saves from pull request runs, + # which crowds the branch-scoped entries pull requests restore from + # out of the repository cache quota. cache: false - - name: Cache Go modules - if: inputs.cache == 'true' && (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + # Only pushes to main and the release branches save caches; everything + # else restores. Caches saved from any other run are scoped to a ref that + # pull requests do not restore from, while still counting against the + # repository quota and evicting the shared entries. + - name: Determine cache mode + id: cache-mode + shell: bash + env: + SAVE: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) }} + run: echo "save=${SAVE}" >> "${GITHUB_OUTPUT}" + + - name: Restore Go module cache + if: inputs.cache == 'true' && steps.cache-mode.outputs.save == 'false' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/go/pkg/mod + key: go-mod-${{ hashFiles('go.sum', 'tools/**/go.sum') }} + restore-keys: | + go-mod- + + - name: Restore Go build cache + if: inputs.cache == 'true' && steps.cache-mode.outputs.save == 'false' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - # Same paths cached by actions/setup-go internally. - path: | - ~/go/pkg/mod - ~/.cache/go-build - # Cache by version/arch/hash. Construct hash using same method as actions/setup-go. - key: go-${{ steps.setup-go.outputs.go-version }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum', 'tools/**/go.sum') }} + path: ~/.cache/go-build + key: go-build-${{ runner.os }}-${{ runner.arch }}-go${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('go.sum', 'tools/**/go.sum') }} restore-keys: | - go-${{ steps.setup-go.outputs.go-version }}-${{ runner.os }}-${{ runner.arch }}- + go-build-${{ runner.os }}-${{ runner.arch }}-go${{ steps.setup-go.outputs.go-version }}- + + # Pushes to main and the release branches save the entries every other run + # restores from. The saves deliberately start from an empty cache instead + # of a restored one, so a go.sum change produces a minimal entry without + # stale module versions or dead build cache contents. actions/cache saves + # in its post step, after the job has populated the caches, and only when + # the job succeeds and the key does not exist yet. + - name: Cache Go modules + if: inputs.cache == 'true' && steps.cache-mode.outputs.save == 'true' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/go/pkg/mod + key: go-mod-${{ hashFiles('go.sum', 'tools/**/go.sum') }} + + - name: Cache Go build cache + if: inputs.cache == 'true' && steps.cache-mode.outputs.save == 'true' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/go-build + key: go-build-${{ runner.os }}-${{ runner.arch }}-go${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('go.sum', 'tools/**/go.sum') }} + + # Download the root module and every tool module, so a saved module cache + # always contains the full dependency set its key covers, no matter which + # job saves it first; the tool modules are otherwise only fetched on first + # use. On a cache hit this is a fast no-op. Retried because + # proxy.golang.org occasionally drops connections mid-download. + - name: Download Go modules + shell: bash + run: | + download() { + go mod download || return 1 + local modfile + for modfile in tools/*/go.mod; do + # Older release trees, which the upgrade/downgrade workflows also + # set up, have no tool modules; the unmatched glob stays literal. + [ -f "${modfile}" ] || continue + go mod download -modfile="${modfile}" || return 1 + done + } + + for attempt in 1 2 3; do + if download; then + break + fi + if [ "${attempt}" = 3 ]; then + echo "go mod download failed after ${attempt} attempts" + exit 1 + fi + echo "go mod download failed (attempt ${attempt}); retrying" + sleep 10 + done diff --git a/.github/actions/tune-os/action.yml b/.github/actions/tune-os/action.yml index d3522c38d8a..1d4aa77254a 100644 --- a/.github/actions/tune-os/action.yml +++ b/.github/actions/tune-os/action.yml @@ -6,6 +6,9 @@ runs: - name: Tune the OS shell: bash -e {0} run: | + # Before the first apt run, so that all of the job's apt runs use it. + "${GITHUB_ACTION_PATH}/pin-ubuntu-mirror.sh" + # Install eatmydata and ensure it's used for every operation sudo apt-get update && sudo apt-get install -y eatmydata echo "/usr/\${LIB}/libeatmydata.so" | sudo tee -a /etc/ld.so.preload diff --git a/.github/actions/tune-os/pin-ubuntu-mirror.sh b/.github/actions/tune-os/pin-ubuntu-mirror.sh new file mode 100755 index 00000000000..05a1b9b503a --- /dev/null +++ b/.github/actions/tune-os/pin-ubuntu-mirror.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +# Copyright 2026 The Vitess 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. + +# Points apt at a responsive Ubuntu archive server before the job's first apt +# run. The archive's host name resolves to several servers, some of which +# intermittently answer only after 10-20 seconds and then send 50 kB/s, and apt +# keeps downloading from them however long that takes. The Azure mirror that +# the runner images list first sometimes does not answer at all, or sends +# packages at 40 kB/s while it serves the release file instantly, so a probe +# cannot tell when to trust it. This times a download of the release file over +# HTTP and HTTPS from each of the archive's addresses, pins the archive's host +# names in /etc/hosts to every address that answered both within 1 second or +# within 3 times the fastest one's time, so that whatever resolves them still +# has other addresses to fail over to even when one address is much closer than +# the others, and points apt at the archive instead of the Azure mirror. +# When no address answers, the archive itself is down, so it leaves apt on the +# Azure mirror. + +set -uo pipefail + +if [[ "$(dpkg --print-architecture)" == "arm64" ]]; then + archive=ports.ubuntu.com + path=ubuntu-ports +else + archive=archive.ubuntu.com + path=ubuntu +fi +codename="$(awk -F= '$1 == "VERSION_CODENAME" {print $2}' /etc/os-release)" + +# Prints HOST's IPv4 addresses. The resolver of GitHub's hosted runners returns +# only one of the archive's addresses, which may be a slow one, so this also +# asks two public resolvers over HTTPS for all of them. +resolve() { + local host="$1" + { + getent ahostsv4 "${host}" | awk '{print $1}' + curl -fsS --max-time 3 -H 'accept: application/dns-json' \ + "https://cloudflare-dns.com/dns-query?name=${host}&type=A" 2> /dev/null | + grep -oE '"data": ?"[0-9.]+"' | grep -oE '[0-9.]+' + curl -fsS --max-time 3 "https://dns.google/resolve?name=${host}&type=A" 2> /dev/null | + grep -oE '"data": ?"[0-9.]+"' | grep -oE '[0-9.]+' + } | grep -xE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | sort -u +} + +# Prints the seconds it took to download the release file from HOST via IP +# over SCHEME, or nothing if that failed or took longer than 3 seconds. +probe() { + local host="$1" ip="$2" root="$3" scheme="$4" port=80 + if [[ "${scheme}" == https ]]; then + port=443 + fi + curl -fsS -o /dev/null --connect-timeout 2 --max-time 3 --resolve "${host}:${port}:${ip}" \ + -w '%{time_total}\n' "${scheme}://${host}/${root}/dists/${codename}/InRelease" 2> /dev/null +} + +# Prints the slower of the HTTP and HTTPS times, or nothing if either failed. +probe_both() { + local http https + http="$(probe "$@" http)" && https="$(probe "$@" https)" && + awk -v a="${http}" -v b="${https}" 'BEGIN { print (a > b ? a : b) }' +} + +results="$(mktemp)" +trap 'rm -f "${results}"' EXIT +for ip in $(resolve "${archive}"); do + ( + if t="$(probe_both "${archive}" "${ip}" "${path}")"; then + echo "${t} ${ip}" >> "${results}" + else + echo "Ubuntu archive probe: ${ip} did not answer within 3 seconds" + fi + ) & +done +wait + +sort -n "${results}" | sed 's/^/Ubuntu archive probe: /' +best="$(sort -n "${results}" | head -1 | cut -d' ' -f2)" +if [[ -z "${best}" ]]; then + echo "No Ubuntu archive server answered within 3 seconds; leaving apt as it is." + exit 0 +fi +# security.ubuntu.com is served by the same servers as archive.ubuntu.com. +security="$(resolve security.ubuntu.com)" +fastest="$(sort -n "${results}" | head -1 | cut -d' ' -f1)" +sort -n "${results}" | awk -v max="$(awk -v t="${fastest}" 'BEGIN { print (3 * t > 1 ? 3 * t : 1) }')" '$1 <= max { print $2 }' | + while read -r ip; do + names="${archive}" + if grep -qx "${ip}" <<< "${security}"; then + names="${names} security.ubuntu.com" + fi + echo "Pinning ${names} to ${ip}" + echo "${ip} ${names}" | sudo tee -a /etc/hosts > /dev/null + done +files="$(sudo grep -rlF azure.archive.ubuntu.com /etc/apt 2> /dev/null || true)" +if [[ -n "${files}" ]]; then + echo "${files}" | xargs sudo sed -i 's|azure\.archive\.ubuntu\.com|archive.ubuntu.com|g' +fi diff --git a/.github/workflows/assign_milestone.yml b/.github/workflows/assign_milestone.yml index 398e7ed44ae..ef4a11c9a11 100644 --- a/.github/workflows/assign_milestone.yml +++ b/.github/workflows/assign_milestone.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # We must explicitly checkout the base's SHA to avoid executing any code coming from # the PR's SHA - Which would be executed in the base branch's context. diff --git a/.github/workflows/auto_approve_pr.yml b/.github/workflows/auto_approve_pr.yml index d5b5cf50a89..cbfaaf88220 100644 --- a/.github/workflows/auto_approve_pr.yml +++ b/.github/workflows/auto_approve_pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/check_make_vtadmin_authz_testgen.yml b/.github/workflows/check_make_vtadmin_authz_testgen.yml index 49afae01796..14e97abce5c 100644 --- a/.github/workflows/check_make_vtadmin_authz_testgen.yml +++ b/.github/workflows/check_make_vtadmin_authz_testgen.yml @@ -23,15 +23,27 @@ jobs: fi - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | vtadmin_changes: - 'bootstrap.sh' @@ -49,7 +61,7 @@ jobs: - name: Set up Go # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 if: steps.changes.outputs.vtadmin_changes == 'true' with: go-version-file: go.mod diff --git a/.github/workflows/check_make_vtadmin_web_proto.yml b/.github/workflows/check_make_vtadmin_web_proto.yml index 3556488b1f7..cbfaeb02338 100644 --- a/.github/workflows/check_make_vtadmin_web_proto.yml +++ b/.github/workflows/check_make_vtadmin_web_proto.yml @@ -23,18 +23,30 @@ jobs: fi - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' - name: Tune the OS uses: $/.github/actions/tune-os + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | proto_changes: - 'bootstrap.sh' @@ -50,7 +62,7 @@ jobs: - name: Set up Go # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 if: steps.changes.outputs.proto_changes == 'true' with: go-version-file: go.mod @@ -59,7 +71,7 @@ jobs: - name: Setup Node if: steps.changes.outputs.proto_changes == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: # node-version should match package.json node-version: '22.13.1' diff --git a/.github/workflows/close_stale_pull_requests.yml b/.github/workflows/close_stale_pull_requests.yml index 45b37babc76..8ffe22a956f 100644 --- a/.github/workflows/close_stale_pull_requests.yml +++ b/.github/workflows/close_stale_pull_requests.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-stale: 30 # Do not handle issues at all. We only want to handle PRs. diff --git a/.github/workflows/cluster_endtoend.yml b/.github/workflows/cluster_endtoend.yml index 9d835982673..8af0e22b609 100644 --- a/.github/workflows/cluster_endtoend.yml +++ b/.github/workflows/cluster_endtoend.yml @@ -34,8 +34,10 @@ jobs: end_to_end: ${{ steps.changes.outputs.end_to_end }} steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: "false" # Generated before the path filter so that a filter failure still leaves @@ -73,11 +75,21 @@ jobs: echo "matrix={\"include\":$matrix}" >> $GITHUB_OUTPUT echo "shard_count=$(echo "$matrix" | jq length)" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: "" + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -96,6 +108,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/cluster_endtoend.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - '.github/actions/setup-microceph/action.yml' @@ -125,7 +138,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit @@ -152,21 +165,21 @@ jobs: - name: Check out code if: needs.generate-matrix.outputs.end_to_end == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: "false" - name: Set up Go if: needs.generate-matrix.outputs.end_to_end == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} - name: Set up python if: needs.generate-matrix.outputs.end_to_end == 'true' - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Tune the OS if: needs.generate-matrix.outputs.end_to_end == 'true' @@ -291,7 +304,7 @@ jobs: - name: Test Summary if: needs.generate-matrix.outputs.end_to_end == 'true' && failure() - uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6 with: paths: "_test/junit/*.xml" show: "fail" diff --git a/.github/workflows/cluster_endtoend_disk_health_monitor.yml b/.github/workflows/cluster_endtoend_disk_health_monitor.yml new file mode 100644 index 00000000000..f3ca0b4782c --- /dev/null +++ b/.github/workflows/cluster_endtoend_disk_health_monitor.yml @@ -0,0 +1,146 @@ +name: Cluster End-to-End Tests (tabletmanager_disk_health_monitor) + +on: + push: + branches: + - "main" + - "release-[0-9]+.[0-9]" + tags: "**" + pull_request: + branches: "**" + +concurrency: + group: format('{0}-{1}', ${{ github.ref }}, 'Cluster End-to-End Tests (tabletmanager_disk_health_monitor)') + cancel-in-progress: true + +permissions: read-all + +env: + LAUNCHABLE_ORGANIZATION: "vitess" + LAUNCHABLE_WORKSPACE: "vitess-app" + GITHUB_PR_HEAD_SHA: "${{ github.event.pull_request.head.sha }}" + +jobs: + test: + name: Run endtoend tests on Cluster (tabletmanager_disk_health_monitor) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 + with: + egress-policy: audit + + - name: Skip CI + run: | + if [[ "${{ contains(github.event.pull_request.labels.*.name, 'Skip CI') }}" == "true" ]]; then + echo "skipping CI due to the 'Skip CI' label" + exit 1 + fi + + - name: Check out code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 + persist-credentials: "false" + + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + + - name: Check for changes in relevant files + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 + id: changes + with: + token: "" + base: ${{ steps.merge-base.outputs.sha }} + filters: | + end_to_end: + - 'go/vt/vttablet/tabletserver/**' + - 'go/test/endtoend/cluster/**' + - 'go/test/endtoend/tabletmanager/disk_health_monitor/**' + - 'test/config.json' + - 'test.go' + - 'Makefile' + - 'build.env' + - 'go.sum' + - 'go.mod' + - 'tools/**' + - 'config/**' + - 'bootstrap.sh' + - '.github/workflows/cluster_endtoend_disk_health_monitor.yml' + - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' + - '.github/actions/setup-go/action.yml' + + - name: Set up Go + if: steps.changes.outputs.end_to_end == 'true' + uses: $/.github/actions/setup-go + + - name: Set up python + if: steps.changes.outputs.end_to_end == 'true' + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + + - name: Tune the OS + if: steps.changes.outputs.end_to_end == 'true' + timeout-minutes: 5 + uses: $/.github/actions/tune-os + + - name: Setup MySQL + if: steps.changes.outputs.end_to_end == 'true' + timeout-minutes: 8 + uses: $/.github/actions/setup-mysql + with: + flavor: mysql-8.4 + + - name: Get dependencies + if: steps.changes.outputs.end_to_end == 'true' + timeout-minutes: 10 + run: | + sudo apt-get -qq install -y mysql-shell + sudo apt-get -qq install -y make unzip g++ etcd-client etcd-server curl git wget xz-utils libncurses6 fuse3 + sudo service etcd stop + + - name: Setup launchable dependencies + if: | + github.event_name == 'pull_request' && + github.event.pull_request.draft == false && + steps.changes.outputs.end_to_end == 'true' && + github.base_ref == 'main' + run: | + pip3 install --user launchable~=1.0 > /dev/null + launchable verify || true + launchable record build --name "$GITHUB_RUN_ID" --no-commit-collection --source . + + - name: Run cluster endtoend test + if: steps.changes.outputs.end_to_end == 'true' + timeout-minutes: 45 + run: | + export VTDATAROOT="/tmp/" + source build.env + set -exo pipefail + + go run test.go -docker=false -follow -shard tabletmanager_disk_health_monitor + + - name: Record test results in launchable + if: | + github.event_name == 'pull_request' && + github.event.pull_request.draft == false && + steps.changes.outputs.end_to_end == 'true' && + github.base_ref == 'main' && + !cancelled() + run: | + launchable record tests --build "$GITHUB_RUN_ID" go-test . || true + + - name: Test Summary + if: steps.changes.outputs.end_to_end == 'true' && failure() + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6 + with: + paths: "_test/junit/*.xml" diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 9f8e1f4c83b..8284f809bd9 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -27,7 +27,7 @@ jobs: packages: ${{ steps.packages.outputs.packages }} steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to diff the pull request's base and head. fetch-depth: 0 @@ -42,16 +42,27 @@ jobs: echo "is_full_run=false" >> "$GITHUB_OUTPUT" fi + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in files relevant to code coverage if: steps.mode.outputs.is_full_run != 'true' - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | changed_files: - .github/workflows/codecov.yml - .github/actions/setup-mysql/action.yml + - '.github/actions/tune-os/**' - .github/actions/setup-go/action.yml - 'go/**' - go.mod @@ -62,7 +73,7 @@ jobs: - name: Set up Go if: steps.mode.outputs.is_full_run != 'true' && steps.changes.outputs.changed_files == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -114,12 +125,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -128,7 +139,7 @@ jobs: tools/**/go.sum - name: Set up python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Tune the OS uses: $/.github/actions/tune-os @@ -171,7 +182,7 @@ jobs: fi - name: Upload coverage reports - uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: files: coverage.out fail_ci_if_error: true diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml index 71ecdbd763e..65ee653b1b1 100644 --- a/.github/workflows/codeql_analysis.yml +++ b/.github/workflows/codeql_analysis.yml @@ -27,13 +27,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Set up Go # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: ${{ matrix.language }} # If you wish to specify cu stom queries, you can do so here or in a config file. @@ -75,11 +75,11 @@ jobs: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 - name: Slack Workflow Notification if: ${{ failure() }} - uses: Gamesight/slack-workflow-status@68bf00d0dbdbcb206c278399aa1ef6c14f74347a # v1.3.0 + uses: Gamesight/slack-workflow-status@2e709ce51fd47f02b7221db8547e6e6f49f81f1d # v2.0.0 with: repo_token: ${{secrets.GITHUB_TOKEN}} slack_webhook_url: ${{secrets.SLACK_WEBHOOK_URL}} diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 90179299416..464637e60cc 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -17,12 +17,12 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Set up Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: 'false' @@ -64,7 +64,8 @@ jobs: ./tools/make-release-packages.sh - name: Upload Files - uses: csexton/release-asset-action@3567794e918fa3068116688122a76cdeb57b5f09 # v3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - pattern: "releases/*.{tar.gz,rpm,deb}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + gh release upload "$TAG" releases/*.{tar.gz,rpm,deb} --repo "$GITHUB_REPOSITORY" diff --git a/.github/workflows/docker_build_images.yml b/.github/workflows/docker_build_images.yml index cf6891bf2a0..fe51d299a0d 100644 --- a/.github/workflows/docker_build_images.yml +++ b/.github/workflows/docker_build_images.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Login to Docker Hub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -43,7 +43,7 @@ jobs: - name: Build and push on main if: startsWith(github.ref, 'refs/tags/') == false - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . file: ${{ env.DOCKERFILE }} @@ -64,7 +64,7 @@ jobs: - name: Build and push on tags if: startsWith(github.ref, 'refs/tags/') - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . file: ${{ env.DOCKERFILE }} @@ -83,12 +83,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Login to Docker Hub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -103,7 +103,7 @@ jobs: - name: Build and push on main if: startsWith(github.ref, 'refs/tags/') == false - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . file: ${{ env.DOCKERFILE }} @@ -128,7 +128,7 @@ jobs: - name: Build and push on tags if: startsWith(github.ref, 'refs/tags/') - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . file: ${{ env.DOCKERFILE }} @@ -150,12 +150,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Login to Docker Hub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -166,7 +166,7 @@ jobs: - name: Build and push on main latest tag if: startsWith(github.ref, 'refs/tags/') == false && matrix.debian == 'bookworm' - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: ${{ env.DOCKER_CTX }} push: true @@ -177,7 +177,7 @@ jobs: - name: Build and push on main debian specific tag if: startsWith(github.ref, 'refs/tags/') == false - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: ${{ env.DOCKER_CTX }} push: true @@ -209,7 +209,7 @@ jobs: # Build and Push component image to DOCKER_TAG, applies to both debian version - name: Build and push on tags using Debian extension if: startsWith(github.ref, 'refs/tags/') - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: ${{ env.DOCKER_CTX }} push: true @@ -222,7 +222,7 @@ jobs: # It is fine to build a second time here when "matrix.debian == 'bookworm'" as we have cached the first build already - name: Build and push on tags without Debian extension if: startsWith(github.ref, 'refs/tags/') && matrix.debian == 'bookworm' - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: ${{ env.DOCKER_CTX }} push: true @@ -243,7 +243,7 @@ jobs: if: ${{ failure() }} steps: - name: Slack Workflow Notification - uses: Gamesight/slack-workflow-status@68bf00d0dbdbcb206c278399aa1ef6c14f74347a # v1.3.0 + uses: Gamesight/slack-workflow-status@2e709ce51fd47f02b7221db8547e6e6f49f81f1d # v2.0.0 with: repo_token: ${{secrets.GITHUB_TOKEN}} slack_webhook_url: ${{secrets.SLACK_WEBHOOK_URL}} diff --git a/.github/workflows/docker_ci.yml b/.github/workflows/docker_ci.yml index d2c07c7ef4b..5b73e59d9f3 100644 --- a/.github/workflows/docker_ci.yml +++ b/.github/workflows/docker_ci.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit @@ -45,15 +45,27 @@ jobs: fi - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: "false" + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: "" + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -77,11 +89,12 @@ jobs: with: bootstrap-version: ${{ env.BOOTSTRAP_VERSION }} bootstrap-flavor: ${{ env.BOOTSTRAP_FLAVOR }} + cache-export: ${{ matrix.shard == 'java' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) }} - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/.github/workflows/docker_lite_build_check.yml b/.github/workflows/docker_lite_build_check.yml index 1e68f37b043..360a6bc5ff7 100644 --- a/.github/workflows/docker_lite_build_check.yml +++ b/.github/workflows/docker_lite_build_check.yml @@ -23,7 +23,7 @@ jobs: dockerfiles: ${{ steps.generate-matrix.outputs.dockerfiles }} steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -45,7 +45,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit @@ -57,12 +57,12 @@ jobs: fi - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Build lite image - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . file: ${{ matrix.dockerfile }} diff --git a/.github/workflows/e2e_race.yml b/.github/workflows/e2e_race.yml index a868924f742..73fff9f1f55 100644 --- a/.github/workflows/e2e_race.yml +++ b/.github/workflows/e2e_race.yml @@ -26,15 +26,27 @@ jobs: fi - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'go/**/*.go' @@ -49,12 +61,13 @@ jobs: - 'bootstrap.sh' - '.github/workflows/e2e_race.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/.github/workflows/endtoend.yml b/.github/workflows/endtoend.yml index 71dfa0b8bd2..86c21af2615 100644 --- a/.github/workflows/endtoend.yml +++ b/.github/workflows/endtoend.yml @@ -26,15 +26,27 @@ jobs: fi - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'go/**/*.go' @@ -49,12 +61,13 @@ jobs: - 'bootstrap.sh' - '.github/workflows/endtoend.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/.github/workflows/local_example.yml b/.github/workflows/local_example.yml index a8eea949006..17f63525353 100644 --- a/.github/workflows/local_example.yml +++ b/.github/workflows/local_example.yml @@ -30,15 +30,27 @@ jobs: examples: ${{ steps.changes.outputs.examples }} steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | examples: - 'test/config.json' @@ -85,7 +97,7 @@ jobs: - name: Check out code if: needs.changes.outputs.examples == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -96,7 +108,7 @@ jobs: - name: Set up Go if: needs.changes.outputs.examples == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/.github/workflows/region_example.yml b/.github/workflows/region_example.yml index ae73dfe6b55..78c45b730a5 100644 --- a/.github/workflows/region_example.yml +++ b/.github/workflows/region_example.yml @@ -30,15 +30,27 @@ jobs: examples: ${{ steps.changes.outputs.examples }} steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | examples: - 'test/config.json' @@ -85,7 +97,7 @@ jobs: - name: Check out code if: needs.changes.outputs.examples == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -96,7 +108,7 @@ jobs: - name: Set up Go if: needs.changes.outputs.examples == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d142f390af7..b5bc7b2ac96 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -27,12 +27,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif @@ -54,7 +54,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif @@ -63,6 +63,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: results.sarif diff --git a/.github/workflows/static_checks_etc.yml b/.github/workflows/static_checks_etc.yml index 025d075e63e..a774c1f411c 100644 --- a/.github/workflows/static_checks_etc.yml +++ b/.github/workflows/static_checks_etc.yml @@ -29,8 +29,10 @@ jobs: fi - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' # Only uploads dependency data for FOSSA's license tracking; the policy @@ -38,17 +40,27 @@ jobs: # Pushes keep main, the release branches and the tags scanned. - name: Run FOSSA scan and upload build data if: github.event_name == 'push' - uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1 + uses: fossa-contrib/fossa-action@d28f0c947e7406706981a62a91ecc009bcc39fd7 # v4.0.0 with: # This is a push-only API token: https://github.com/fossa-contrib/fossa-action#push-only-api-token fossa-api-key: f62c11ef0c249fef239947f01279aa0f github-token: ${{ github.token }} + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in Go files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | go_files: - '**/*.go' @@ -129,7 +141,7 @@ jobs: - name: Set up Go if: (steps.changes.outputs.go_files == 'true' || steps.changes.outputs.parser_changes == 'true' || steps.changes.outputs.proto_changes == 'true') # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -149,7 +161,9 @@ jobs: - name: Install goimports if: (steps.changes.outputs.go_files == 'true' || steps.changes.outputs.visitor == 'true') run: | - go install golang.org/x/tools/cmd/goimports@latest + # actions/setup-go sets GOTOOLCHAIN=local, so pin an x/tools release + # that builds with this branch's Go 1.25 (v0.50.0 and later need Go 1.26). + go install golang.org/x/tools/cmd/goimports@v0.49.0 - name: Run goimports if: steps.changes.outputs.go_files == 'true' @@ -243,7 +257,7 @@ jobs: - name: Setup Node if: steps.changes.outputs.proto_changes == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: # make proto requires newer node than the pre-installed one node-version: '22.13.1' @@ -274,7 +288,7 @@ jobs: exit 1 - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 with: advanced-security: false annotations: true diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 1a14e36dba0..7708259d543 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -32,15 +32,27 @@ jobs: unit_tests: ${{ steps.changes.outputs.unit_tests }} steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: "false" + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: "" + base: ${{ steps.merge-base.outputs.sha }} filters: | unit_tests: - 'test/config.json' @@ -56,6 +68,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/unit_test.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' test: @@ -115,7 +128,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit @@ -133,24 +146,17 @@ jobs: - name: Check out code if: needs.changes.outputs.unit_tests == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: "false" - name: Set up Go if: needs.changes.outputs.unit_tests == 'true' - # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 - with: - go-version-file: go.mod - cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} - cache-dependency-path: | - go.sum - tools/**/go.sum + uses: $/.github/actions/setup-go - name: Set up python if: needs.changes.outputs.unit_tests == 'true' - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Tune the OS if: needs.changes.outputs.unit_tests == 'true' @@ -172,8 +178,6 @@ jobs: curl --max-time 10 --retry 3 --retry-max-time 45 -s -L https://github.com/coreos/etcd/releases/download/v3.5.25/etcd-v3.5.25-linux-amd64.tar.gz | tar -zxC dist mv dist/etcd-v3.5.25-linux-amd64/{etcd,etcdctl} bin/ - go mod download - - name: Run make tools if: needs.changes.outputs.unit_tests == 'true' run: | @@ -215,7 +219,7 @@ jobs: - name: Test Summary if: needs.changes.outputs.unit_tests == 'true' && failure() - uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6 with: paths: "report.xml" show: "fail" diff --git a/.github/workflows/update_golang_dependencies.yml b/.github/workflows/update_golang_dependencies.yml index a469521e5dc..84bb89fb51a 100644 --- a/.github/workflows/update_golang_dependencies.yml +++ b/.github/workflows/update_golang_dependencies.yml @@ -18,13 +18,13 @@ jobs: timeout-minutes: 15 steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: main persist-credentials: 'false' - name: Set up Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -42,7 +42,7 @@ jobs: go mod tidy - name: Create Pull Request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.CREATE_PR_VITESS_BOT }} branch: "upgrade-go-deps-on-main" diff --git a/.github/workflows/update_golang_version.yml b/.github/workflows/update_golang_version.yml index f3c0e4b8173..013fe8a0927 100644 --- a/.github/workflows/update_golang_version.yml +++ b/.github/workflows/update_golang_version.yml @@ -21,13 +21,13 @@ jobs: timeout-minutes: 10 steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ matrix.branch }} persist-credentials: 'false' - name: Set up Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} @@ -70,7 +70,7 @@ jobs: - name: Create Pull Request if: steps.detect-and-update.outputs.create-pr == 'true' - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.CREATE_PR_VITESS_BOT }} branch: "upgrade-go-to-${{steps.detect-and-update.outputs.go-version}}-on-${{ matrix.branch }}" diff --git a/.github/workflows/upgrade_downgrade_test_backups_e2e.yml b/.github/workflows/upgrade_downgrade_test_backups_e2e.yml index d07ba7a426f..dbcb0494376 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_e2e.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_e2e.yml @@ -27,7 +27,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -43,11 +43,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -64,6 +74,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_backups_e2e.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test_e2e: @@ -74,10 +85,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Backups - E2E + name: Run Upgrade Downgrade Test - Backups - E2E (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vttablet=N-1, vtbackup=N + other_version_binaries: vttablet mysqlctl mysqlctld + - name: vttablet=N, vtbackup=N-1 + other_version_binaries: vtbackup steps: - name: Skip CI @@ -92,7 +113,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -100,7 +121,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -127,7 +148,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -149,7 +170,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -168,19 +189,22 @@ jobs: mkdir -p /tmp/vitess-build-current/ cp -R bin /tmp/vitess-build-current/ - # Swap binaries, use last release's VTTablet - - name: Use last release's VTTablet + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vttablet --version + vtbackup --version - # Run test with VTTablet at version N-1 and VTBackup at version N - - name: Run backups tests (vttablet=N-1, vtbackup=N) + - name: Run backups tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -188,24 +212,19 @@ jobs: source build.env go run test.go -skip-build -docker=false -print-log -follow -tag upgrade_downgrade_backups - # Swap binaries again, use current version's VTTablet, and last release's VTBackup - - name: Use current version VTTablet, and other version VTBackup - run: | - source build.env - - rm -f $PWD/bin/vtbackup $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtbackup $PWD/bin/vtbackup - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld - vtbackup --version - vttablet --version - - # Run test again with VTTablet at version N, and VTBackup at version N-1 - - name: Run backups tests (vttablet=N, vtbackup=N-1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_e2e_result: + needs: upgrade_downgrade_test_e2e + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Backups - E2E + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test_e2e.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - set -x - source build.env - go run test.go -skip-build -keep-data -docker=false -print-log -follow -tag upgrade_downgrade_backups + echo "upgrade_downgrade_test_e2e: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml b/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml index 21519927d38..a1c0c17946d 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml @@ -28,7 +28,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -44,11 +44,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -65,6 +75,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_backups_e2e_next_release.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test_e2e: @@ -75,10 +86,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true' && needs.changes.outputs.next_release_ref != '')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Backups - E2E - Next Release + name: Run Upgrade Downgrade Test - Backups - E2E - Next Release (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vttablet=N+1, vtbackup=N + other_version_binaries: vttablet mysqlctl mysqlctld + - name: vttablet=N, vtbackup=N+1 + other_version_binaries: vtbackup steps: - name: Skip CI @@ -93,7 +114,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -101,7 +122,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -128,7 +149,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -150,7 +171,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -169,19 +190,22 @@ jobs: mkdir -p /tmp/vitess-build-current/ cp -R bin /tmp/vitess-build-current/ - # Swap binaries, use next release's VTTablet - - name: Use next release's VTTablet + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vttablet --version + vtbackup --version - # Run test with VTTablet at version N+1 and VTBackup at version N - - name: Run backups tests (vttablet=N+1, vtbackup=N) + - name: Run backups tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -189,24 +213,19 @@ jobs: source build.env go run test.go -skip-build -docker=false -print-log -follow -tag upgrade_downgrade_backups - # Swap binaries again, use current version's VTTablet, and next release's VTBackup - - name: Use current version VTTablet, and other version VTBackup - run: | - source build.env - - rm -f $PWD/bin/vtbackup $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-other/bin/vtbackup $PWD/bin/vtbackup - cp /tmp/vitess-build-current/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-current/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-current/bin/mysqlctld $PWD/bin/mysqlctld - vtbackup --version - vttablet --version - - # Run test again with VTTablet at version N, and VTBackup at version N+1 - - name: Run backups tests (vttablet=N, vtbackup=N+1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_e2e_result: + needs: upgrade_downgrade_test_e2e + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Backups - E2E - Next Release + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test_e2e.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - set -x - source build.env - go run test.go -skip-build -keep-data -docker=false -print-log -follow -tag upgrade_downgrade_backups + echo "upgrade_downgrade_test_e2e: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_backups_manual.yml b/.github/workflows/upgrade_downgrade_test_backups_manual.yml index 4c4a64c5f08..4894553fc0e 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_manual.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_manual.yml @@ -28,7 +28,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -44,11 +44,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -65,6 +75,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_backups_manual.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - 'examples/**' @@ -96,7 +107,7 @@ jobs: # Checkout to this build's commit - name: Checkout to commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -131,7 +142,7 @@ jobs: # Checkout to the last release of Vitess - name: Checkout to the other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -153,7 +164,7 @@ jobs: # Checkout to this build's commit - name: Checkout to commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml b/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml index 39a9a0743b0..af974891214 100644 --- a/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml @@ -28,7 +28,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -44,11 +44,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -65,6 +75,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_backups_manual_next_release.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - 'examples/**' @@ -96,7 +107,7 @@ jobs: # Checkout to this build's commit - name: Checkout to commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -131,7 +142,7 @@ jobs: # Checkout to the next release of Vitess - name: Checkout to the other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -153,7 +164,7 @@ jobs: # Checkout to this build's commit - name: Checkout to commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_onlineddl_flow.yml b/.github/workflows/upgrade_downgrade_test_onlineddl_flow.yml index c4d979d83e0..044c9eb5db1 100644 --- a/.github/workflows/upgrade_downgrade_test_onlineddl_flow.yml +++ b/.github/workflows/upgrade_downgrade_test_onlineddl_flow.yml @@ -32,7 +32,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -58,11 +58,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -79,6 +89,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_onlineddl_flow.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -107,7 +118,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -115,7 +126,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -134,7 +145,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out last version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -157,7 +168,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out next version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -180,7 +191,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml index fbb1136315d..d1ac38dcd07 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml @@ -31,7 +31,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_queries.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -78,10 +89,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Queries) + name: Run Upgrade Downgrade Test - Query Serving (Queries) (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N-1, vttablet=N, vtctld=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N-1, vtctld=N-1 + other_version_binaries: vttablet mysqlctl mysqlctld vtctld vtctldclient vtctl vtctlclient steps: - name: Skip CI @@ -96,7 +117,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -111,7 +132,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -140,7 +161,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -165,18 +186,22 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n-1 and keep vttablet at version n - - name: Use last release's VTGate + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version + vttablet --version - # Running a test with vtgate at version n-1 and vttablet/vtctld at version n - - name: Run query serving tests (vtgate=N-1, vttablet=N, vtctld=N) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -184,30 +209,19 @@ jobs: source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries - # Swap the binaries again. This time, vtgate will be at version n, and vttablet/vtctld will be at version n-1 - - name: Use current version VTGate, and other version VTTablet/VTctld - run: | - source build.env - - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - - cp /tmp/vitess-build-other/bin/vtctld $PWD/bin - cp /tmp/vitess-build-other/bin/vtctldclient $PWD/bin - cp /tmp/vitess-build-other/bin/vtctl $PWD/bin - cp /tmp/vitess-build-other/bin/vtctlclient $PWD/bin - - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld - vtgate --version - vttablet --version - - # Running a test with vtgate at version n and vttablet/vtctld at version n-1 - - name: Run query serving tests (vtgate=N, vttablet=N-1, vtctld=N-1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Queries) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries_2.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries_2.yml index a0473165e57..6d22e14460e 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries_2.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries_2.yml @@ -30,7 +30,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -46,11 +46,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -67,6 +77,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_queries_2.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -77,10 +88,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) + name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N-1, vttablet=N, vtctld=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N-1, vtctld=N-1 + other_version_binaries: vttablet mysqlctl mysqlctld vtctld vtctldclient vtctl vtctlclient steps: - name: Skip CI @@ -95,7 +116,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -110,7 +131,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -139,7 +160,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -164,18 +185,22 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n-1 and keep vttablet at version n - - name: Use last release's VTGate + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version + vttablet --version - # Running a test with vtgate at version n-1 and vttablet/vtctld at version n - - name: Run query serving tests (vtgate=N-1, vttablet=N, vtctld=N) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -183,30 +208,19 @@ jobs: source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries_2 - # Swap the binaries again. This time, vtgate will be at version n, and vttablet/vtctld will be at version n-1 - - name: Use current version VTGate, and other version VTTablet/VTctld - run: | - source build.env - - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - - cp /tmp/vitess-build-other/bin/vtctld $PWD/bin - cp /tmp/vitess-build-other/bin/vtctldclient $PWD/bin - cp /tmp/vitess-build-other/bin/vtctl $PWD/bin - cp /tmp/vitess-build-other/bin/vtctlclient $PWD/bin - - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld - vtgate --version - vttablet --version - - # Running a test with vtgate at version n and vttablet/vtctld at version n-1 - - name: Run query serving tests (vtgate=N, vttablet=N-1, vtctld=N-1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries_2 + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries_2_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries_2_next_release.yml index f56e4250258..8692696aec7 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries_2_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries_2_next_release.yml @@ -31,7 +31,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_queries_2_next_release.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -78,10 +89,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true' && needs.changes.outputs.next_release_ref != '')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) Next Release + name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) Next Release (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N+1, vttablet=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N+1 + other_version_binaries: vttablet mysqlctl mysqlctld steps: - name: Skip CI @@ -96,7 +117,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +125,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +144,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -145,7 +166,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -169,41 +190,42 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n+1 and keep vttablet at version n - - name: Use next release's VTGate - run: | - source build.env - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate - vtgate --version - - # Running a test with vtgate at version n+1 and vttablet at version n - - name: Run query serving tests (vtgate=N+1, vttablet=N) - run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries_2 - - # Swap the binaries again. This time, vtgate will be at version n, and vttablet will be at version n+1 - - name: Use current version VTGate, and other version VTTablet + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version vttablet --version - # Running a test with vtgate at version n and vttablet at version n+1 - - name: Run query serving tests (vtgate=N, vttablet=N+1) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries_2 + + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Queries - 2) Next Release + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} + run: | + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml index 2a2be95d882..9fce6e9bf9a 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml @@ -31,7 +31,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -78,10 +89,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true' && needs.changes.outputs.next_release_ref != '')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Queries) Next Release + name: Run Upgrade Downgrade Test - Query Serving (Queries) Next Release (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N+1, vttablet=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N+1 + other_version_binaries: vttablet mysqlctl mysqlctld steps: - name: Skip CI @@ -96,7 +117,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +125,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +144,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -145,7 +166,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -169,41 +190,42 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n+1 and keep vttablet at version n - - name: Use next release's VTGate - run: | - source build.env - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate - vtgate --version - - # Running a test with vtgate at version n+1 and vttablet at version n - - name: Run query serving tests (vtgate=N+1, vttablet=N) - run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries - - # Swap the binaries again. This time, vtgate will be at version n, and vttablet will be at version n+1 - - name: Use current version VTGate, and other version VTTablet + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version vttablet --version - # Running a test with vtgate at version n and vttablet at version n+1 - - name: Run query serving tests (vtgate=N, vttablet=N+1) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries + + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Queries) Next Release + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} + run: | + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml b/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml index 5827e817b4e..384e4d3f9c2 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_schema.yml @@ -31,7 +31,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_schema.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -78,10 +89,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Schema) + name: Run Upgrade Downgrade Test - Query Serving (Schema) (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N-1, vttablet=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N-1 + other_version_binaries: vttablet mysqlctl mysqlctld steps: - name: Skip CI @@ -96,7 +117,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +125,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +144,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -145,7 +166,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -169,17 +190,22 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n-1 and keep vttablet at version n - - name: Use last release's VTGate + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version + vttablet --version - # Running a test with vtgate at version n-1 and vttablet at version n - - name: Run query serving tests (vtgate=N-1, vttablet=N) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -187,24 +213,19 @@ jobs: source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_schema - # Swap the binaries again. This time, vtgate will be at version n, and vttablet will be at version n-1 - - name: Use current version VTGate, and other version VTTablet - run: | - source build.env - - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld - vtgate --version - vttablet --version - - # Running a test with vtgate at version n and vttablet at version n-1 - - name: Run query serving tests (vtgate=N, vttablet=N-1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Schema) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_schema + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml index 27a6761122b..ab2e2ccfce4 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml @@ -31,7 +31,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_query_serving_schema_next_release.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -78,10 +89,20 @@ jobs: # a required status check. if: ${{ !cancelled() && (needs.changes.result != 'success' || contains(github.event.pull_request.labels.*.name, 'Skip CI') || (needs.changes.outputs.end_to_end == 'true' && needs.changes.outputs.next_release_ref != '')) }} timeout-minutes: 60 - name: Run Upgrade Downgrade Test - Query Serving (Schema) Next Release + name: Run Upgrade Downgrade Test - Query Serving (Schema) Next Release (${{ matrix.name }}) # The large runner is only claimed when the tests will actually run; a # failed or cancelled gate and the 'Skip CI' label only report a failure. runs-on: ${{ needs.changes.result == 'success' && needs.changes.outputs.end_to_end == 'true' && !contains(github.event.pull_request.labels.*.name, 'Skip CI') && github.repository == 'vitessio/vitess' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-24.04' }} + # Each version mix runs the whole suite, so the mixes run as separate + # jobs in parallel instead of one after the other in a single job. + strategy: + fail-fast: false + matrix: + include: + - name: vtgate=N+1, vttablet=N + other_version_binaries: vtgate + - name: vtgate=N, vttablet=N+1 + other_version_binaries: vttablet mysqlctl mysqlctld steps: - name: Skip CI @@ -96,7 +117,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +125,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +144,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -145,7 +166,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -169,17 +190,22 @@ jobs: find ./go/test/endtoend -name '*.go' -exec sed -i 's/ErrorContains/Error/g' {} + find ./go/test/endtoend -name '*.go' -exec sed -i 's/EqualError/Error/g' {} + - # Swap the binaries in the bin. Use vtgate version n+1 and keep vttablet at version n - - name: Use next release's VTGate + # Start from this commit's binaries, then swap in the other version's + # binaries for this matrix entry's version mix. + - name: Use the other version's ${{ matrix.other_version_binaries }} + env: + OTHER_VERSION_BINARIES: ${{ matrix.other_version_binaries }} run: | source build.env - rm -f $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ + for binary in $OTHER_VERSION_BINARIES; do + cp "/tmp/vitess-build-other/bin/$binary" "$PWD/bin/$binary" + done vtgate --version + vttablet --version - # Running a test with vtgate at version n+1 and vttablet at version n - - name: Run query serving tests (vtgate=N+1, vttablet=N) + - name: Run query serving tests (${{ matrix.name }}) run: | rm -rf /tmp/vtdataroot mkdir -p /tmp/vtdataroot @@ -187,24 +213,19 @@ jobs: source build.env go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_schema - # Swap the binaries again. This time, vtgate will be at version n, and vttablet will be at version n+1 - - name: Use current version VTGate, and other version VTTablet - run: | - source build.env - - rm -f $PWD/bin/vtgate $PWD/bin/vttablet $PWD/bin/mysqlctl $PWD/bin/mysqlctld - cp /tmp/vitess-build-current/bin/vtgate $PWD/bin/vtgate - cp /tmp/vitess-build-other/bin/vttablet $PWD/bin/vttablet - cp /tmp/vitess-build-other/bin/mysqlctl $PWD/bin/mysqlctl - cp /tmp/vitess-build-other/bin/mysqlctld $PWD/bin/mysqlctld - vtgate --version - vttablet --version - - # Running a test with vtgate at version n and vttablet at version n+1 - - name: Run query serving tests (vtgate=N, vttablet=N+1) + # The required status check is the name the single job had, so this job + # keeps it. It passes only when every version mix passed, or when they were + # skipped because nothing relevant changed. + upgrade_downgrade_test_result: + needs: upgrade_downgrade_test + if: ${{ !cancelled() }} + name: Run Upgrade Downgrade Test - Query Serving (Schema) Next Release + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the result of every version mix + env: + RESULT: ${{ needs.upgrade_downgrade_test.result }} run: | - rm -rf /tmp/vtdataroot - mkdir -p /tmp/vtdataroot - - source build.env - go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_schema + echo "upgrade_downgrade_test: $RESULT" + [[ "$RESULT" == "success" || "$RESULT" == "skipped" ]] diff --git a/.github/workflows/upgrade_downgrade_test_reparent_new_vtctl.yml b/.github/workflows/upgrade_downgrade_test_reparent_new_vtctl.yml index f9d595887cc..848ad065533 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_new_vtctl.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_new_vtctl.yml @@ -31,7 +31,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_reparent_new_vtctl.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -96,7 +107,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +134,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -145,7 +156,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml b/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml index f054403c94f..3f0f2bcf4a3 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml @@ -31,7 +31,7 @@ jobs: next_release_ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $next_release_ref echo "next_release_ref=${next_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_reparent_new_vttablet.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -96,7 +107,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -131,7 +142,7 @@ jobs: # Checkout to the next release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.next_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.next_release_ref }} persist-credentials: 'false' @@ -153,7 +164,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_reparent_old_vtctl.yml b/.github/workflows/upgrade_downgrade_test_reparent_old_vtctl.yml index f36e5303bde..a9c1b730616 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_old_vtctl.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_old_vtctl.yml @@ -31,7 +31,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_reparent_old_vtctl.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -96,7 +107,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +134,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -145,7 +156,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml b/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml index 04fb2cdaaca..821917afdd3 100644 --- a/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml +++ b/.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml @@ -31,7 +31,7 @@ jobs: previous_release_ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} steps: - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history is needed to list the release branches. fetch-depth: 0 @@ -47,11 +47,21 @@ jobs: echo $previous_release_ref echo "previous_release_ref=${previous_release_ref}" >> $GITHUB_OUTPUT + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -68,6 +78,7 @@ jobs: - 'bootstrap.sh' - '.github/workflows/upgrade_downgrade_test_reparent_old_vttablet.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' upgrade_downgrade_test: @@ -96,7 +107,7 @@ jobs: run: exit 1 - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -104,7 +115,7 @@ jobs: uses: $/.github/actions/tune-os - name: Set up python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL uses: $/.github/actions/setup-mysql @@ -123,7 +134,7 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ needs.changes.outputs.previous_release_ref }}) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.changes.outputs.previous_release_ref }} persist-credentials: 'false' @@ -145,7 +156,7 @@ jobs: # Checkout to this build's commit - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' diff --git a/.github/workflows/upgrade_downgrade_test_semi_sync.yml b/.github/workflows/upgrade_downgrade_test_semi_sync.yml index 0d906b58728..6b4df30d019 100644 --- a/.github/workflows/upgrade_downgrade_test_semi_sync.yml +++ b/.github/workflows/upgrade_downgrade_test_semi_sync.yml @@ -29,16 +29,26 @@ jobs: fi - name: Check out commit's code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -72,7 +82,7 @@ jobs: - name: Set up python if: steps.changes.outputs.end_to_end == 'true' - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Setup MySQL if: steps.changes.outputs.end_to_end == 'true' @@ -102,14 +112,14 @@ jobs: # Checkout to the last release of Vitess - name: Check out other version's code (${{ steps.output-previous-release-ref.outputs.previous_release_ref }}) if: steps.changes.outputs.end_to_end == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} persist-credentials: 'false' - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: false @@ -132,13 +142,13 @@ jobs: # Checkout to this build's commit - name: Check out commit's code if: steps.changes.outputs.end_to_end == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: false diff --git a/.github/workflows/vitess_tester_vtgate.yml b/.github/workflows/vitess_tester_vtgate.yml index 7428f5fed48..74f320c5458 100644 --- a/.github/workflows/vitess_tester_vtgate.yml +++ b/.github/workflows/vitess_tester_vtgate.yml @@ -32,15 +32,27 @@ jobs: fi - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: 'false' + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: '' + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'test/config.json' @@ -58,19 +70,20 @@ jobs: - 'bootstrap.sh' - '.github/workflows/vitess_tester_vtgate.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' - name: Set up Go if: steps.changes.outputs.end_to_end == 'true' # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} - name: Set up python if: steps.changes.outputs.end_to_end == 'true' - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - name: Tune the OS if: steps.changes.outputs.end_to_end == 'true' @@ -151,7 +164,7 @@ jobs: - name: Test Summary if: steps.changes.outputs.end_to_end == 'true' && !cancelled() - uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2.6 with: paths: "report*.xml" show: "fail" diff --git a/.github/workflows/vtadmin_web_build.yml b/.github/workflows/vtadmin_web_build.yml index 095ae004573..a169e0f7719 100644 --- a/.github/workflows/vtadmin_web_build.yml +++ b/.github/workflows/vtadmin_web_build.yml @@ -30,7 +30,7 @@ jobs: exit 1 fi - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -39,7 +39,7 @@ jobs: - name: Setup Node # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: # node-version should match package.json node-version: '22.13.1' diff --git a/.github/workflows/vtadmin_web_lint.yml b/.github/workflows/vtadmin_web_lint.yml index 29d32b42fd6..ae3493e53f7 100644 --- a/.github/workflows/vtadmin_web_lint.yml +++ b/.github/workflows/vtadmin_web_lint.yml @@ -30,7 +30,7 @@ jobs: exit 1 fi - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -39,7 +39,7 @@ jobs: - name: Setup Node # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: # node-version should match package.json node-version: '22.13.1' diff --git a/.github/workflows/vtadmin_web_unit_tests.yml b/.github/workflows/vtadmin_web_unit_tests.yml index 9225f96f6d1..dd3f7fb10c0 100644 --- a/.github/workflows/vtadmin_web_unit_tests.yml +++ b/.github/workflows/vtadmin_web_unit_tests.yml @@ -30,7 +30,7 @@ jobs: exit 1 fi - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: 'false' @@ -39,7 +39,7 @@ jobs: - name: Setup Node # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: # node-version should match package.json node-version: '22.13.1' diff --git a/.github/workflows/vtop_example.yml b/.github/workflows/vtop_example.yml index 20c42fbf55b..0fb922eafef 100644 --- a/.github/workflows/vtop_example.yml +++ b/.github/workflows/vtop_example.yml @@ -26,15 +26,27 @@ jobs: end_to_end: ${{ steps.changes.outputs.end_to_end }} steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # The merge commit and its first parent, for the path filter. + fetch-depth: 2 persist-credentials: "false" + - name: Find the base of the pull request's merge commit + # github.event.pull_request.base.sha is not updated when the base branch + # moves, so paths-filter would also count the base branch's newer commits. + # With token: '', paths-filter diffs against `base` on pull_request + # events too (see its README); action.yml's input description is stale. + id: merge-base + if: github.event_name == 'pull_request' + run: echo "sha=$(git rev-parse HEAD^1)" >> "$GITHUB_OUTPUT" + - name: Check for changes in relevant files - uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a # v3.0.1 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: changes with: token: "" + base: ${{ steps.merge-base.outputs.sha }} filters: | end_to_end: - 'go/**/*.go' @@ -51,6 +63,7 @@ jobs: - 'test/**' - '.github/workflows/vtop_example.yml' - '.github/actions/setup-mysql/action.yml' + - '.github/actions/tune-os/**' - '.github/actions/setup-go/action.yml' build: @@ -78,13 +91,13 @@ jobs: run: exit 1 - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: "false" - name: Set up Go # zizmor: ignore[cache-poisoning] this workflow publishes no artifacts - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod cache: ${{ (github.base_ref == 'main' || (github.base_ref == '' && github.ref_name == 'main')) && 'true' || 'false' }} diff --git a/doc/internal/release/how-to-release.md b/doc/internal/release/how-to-release.md index 97e58a074d7..ea59018f23d 100644 --- a/doc/internal/release/how-to-release.md +++ b/doc/internal/release/how-to-release.md @@ -354,11 +354,10 @@ You will need administrator privileges on the vitess repository to be able to ma 3. Export following to avoid any version conflicts ```bash - export MAVEN_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED - --add-opens=java.desktop/java.awt.font=ALL-UNNAMED" + export MAVEN_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED" ``` -4. Deploy (upload) the Java code to the oss.sonatype.org repository: +4. Deploy (upload) the Java code to Maven Central through the [Central Portal](https://central.sonatype.com/): > **Warning:** After the deployment, the Java packages will be automatically released. Once released, you cannot delete them. The only option is to upload a newer version (e.g. increment the patch level).

diff --git a/doc/internal/release/java-packages.md b/doc/internal/release/java-packages.md index 3b3d2a38472..247a1d373e9 100644 --- a/doc/internal/release/java-packages.md +++ b/doc/internal/release/java-packages.md @@ -1,27 +1,56 @@ # Java Packages -We publish binary packages for our [JDBC driver and Java client on Maven Central](https://search.maven.org/#search|ga|1|g:"io.vitess"). +We publish binary packages for our [JDBC driver and Java client on Maven Central](https://central.sonatype.com/namespace/io.vitess). -To do so, we use the http://oss.sonatype.org/ repository. -New packages must be uploaded there ("deployed") and will be automatically published ("released"). -Once they are released there, they will be automatically synchronized with Maven Central. -The synchronization takes only several minutes, but the update on http://search.maven.org may take up to two hours. +To do so, we use the [Central Portal](https://central.sonatype.com/). +The `central-publishing-maven-plugin`, configured in the `release` profile of `java/pom.xml`, uploads ("deploys") the packages there as a single bundle. +It must be version 0.12.0 or later to publish with Maven 3.10 ([#21566](https://github.com/vitessio/vitess/issues/21566)). +After the Central Portal validates the bundle, it automatically publishes ("releases") the packages to Maven Central. +Publishing usually takes several minutes, but the packages may take longer to show up in search results. -## Access to oss.sonatype.org +## Access to the Central Portal -[Sign up here.](https://issues.sonatype.org/secure/Signup!default.jspa) -Then you must be added as member to our `io.vitess` namespace. -Therefore, file a JIRA ticket with Sonatype to get added ([example for a different namespace](https://issues.sonatype.org/browse/OSSRH-30604)). +[Create a Central Portal account.](https://central.sonatype.org/register/central-portal/) +Then you must be given access to our `io.vitess` namespace. +Ask an administrator of the organization that owns `io.vitess` to [invite you and grant you access to the namespace](https://central.sonatype.org/publish/publish-portal-organizations/). +If they don't see the Organizations tab, they can ask [Central Support](https://central.sonatype.org/faq/get-support/) to enable organization management. ## One-time setup ### Set up GPG -Follow [Sonatype's GPG instructions](https://central.sonatype.org/pages/working-with-pgp-signatures.html). +Follow [Sonatype's GPG instructions](https://central.sonatype.org/publish/requirements/gpg/). Install `gpg-agent` (needed below) e.g. on Ubuntu via: `sudo apt-get install gnupg-agent`. for Mac you need to install 'gnupg' via 'brew install gnupg' ### Login configuration -Create the `settings.xml` in the `$HOME/.m2/` directory as described in their [instructions](https://central.sonatype.org/pages/apache-maven.html). +[Generate a user token](https://central.sonatype.org/publish/generate-portal-token/) in the Central Portal. +Then add it to the `settings.xml` in the `$HOME/.m2/` directory, using the server ID `central`: + +```xml + + + + central + + + + + +``` + +## Publishing a release + +> **Warning:** Once the packages are published, you cannot delete them. The only option is to publish a newer version (e.g. increment the patch level). + +1. Check out the release tag, e.g. `git checkout v24.0.5`. +2. Start `gpg-agent`, so that Maven doesn't keep prompting for your private key's passphrase (see [How to Release](how-to-release.md#java-packages-deploy--release)). +3. Go into the `java` directory, then build and publish the packages: + + ```bash + cd java + export MAVEN_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED" + mvn clean deploy -P release + ``` diff --git a/docker/binaries/logtail/Dockerfile b/docker/binaries/logtail/Dockerfile index b64fe5b3b6f..a488d294d80 100644 --- a/docker/binaries/logtail/Dockerfile +++ b/docker/binaries/logtail/Dockerfile @@ -16,7 +16,7 @@ ARG DEBIAN_VER=stable-slim FROM debian:${DEBIAN_VER} -ENV TAIL_FILEPATH /dev/null +ENV TAIL_FILEPATH=/dev/null COPY tail.sh /vt/tail.sh diff --git a/docker/binaries/mysqlctl/Dockerfile b/docker/binaries/mysqlctl/Dockerfile index 72c80c6363b..e56832e73c6 100644 --- a/docker/binaries/mysqlctl/Dockerfile +++ b/docker/binaries/mysqlctl/Dockerfile @@ -20,8 +20,8 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vtdataroot +ENV VTROOT=/vt +ENV VTDATAROOT=/vtdataroot # Prepare directory structure. RUN mkdir -p /vt/bin && \ diff --git a/docker/binaries/mysqlctld/Dockerfile b/docker/binaries/mysqlctld/Dockerfile index 81909b9f092..f1bb82c8fd9 100644 --- a/docker/binaries/mysqlctld/Dockerfile +++ b/docker/binaries/mysqlctld/Dockerfile @@ -27,8 +27,8 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vtdataroot +ENV VTROOT=/vt +ENV VTDATAROOT=/vtdataroot # Prepare directory structure. RUN mkdir -p /vt/bin && \ diff --git a/docker/binaries/vtadmin/Dockerfile b/docker/binaries/vtadmin/Dockerfile index c51527c1f39..0c79284650b 100644 --- a/docker/binaries/vtadmin/Dockerfile +++ b/docker/binaries/vtadmin/Dockerfile @@ -34,7 +34,7 @@ FROM nginxinc/nginx-unprivileged:1.22 AS nginx ENV VTADMIN_WEB_PORT=14201 # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Copy binaries COPY --from=lite /vt/bin/vtadmin /vt/bin/ diff --git a/docker/binaries/vtbackup/Dockerfile b/docker/binaries/vtbackup/Dockerfile index 3b429928cb4..5ed2f1a0898 100644 --- a/docker/binaries/vtbackup/Dockerfile +++ b/docker/binaries/vtbackup/Dockerfile @@ -21,8 +21,8 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vtdataroot +ENV VTROOT=/vt +ENV VTDATAROOT=/vtdataroot # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/binaries/vtctl/Dockerfile b/docker/binaries/vtctl/Dockerfile index 21ca6aaf77c..5e00b867932 100644 --- a/docker/binaries/vtctl/Dockerfile +++ b/docker/binaries/vtctl/Dockerfile @@ -20,7 +20,7 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/binaries/vtctld/Dockerfile b/docker/binaries/vtctld/Dockerfile index a33297fe0ff..9981960717e 100644 --- a/docker/binaries/vtctld/Dockerfile +++ b/docker/binaries/vtctld/Dockerfile @@ -20,7 +20,7 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Prepare directory structure. RUN mkdir -p /vt/bin && \ diff --git a/docker/binaries/vtexplain/Dockerfile b/docker/binaries/vtexplain/Dockerfile index 58e2bd040db..a201e5a7830 100644 --- a/docker/binaries/vtexplain/Dockerfile +++ b/docker/binaries/vtexplain/Dockerfile @@ -20,7 +20,7 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/binaries/vtgate/Dockerfile b/docker/binaries/vtgate/Dockerfile index 761700c1673..1d2341b20b2 100644 --- a/docker/binaries/vtgate/Dockerfile +++ b/docker/binaries/vtgate/Dockerfile @@ -20,7 +20,7 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/binaries/vtorc/Dockerfile b/docker/binaries/vtorc/Dockerfile index b24588f8ee4..1e24f36ab15 100644 --- a/docker/binaries/vtorc/Dockerfile +++ b/docker/binaries/vtorc/Dockerfile @@ -20,7 +20,7 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt +ENV VTROOT=/vt # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/binaries/vttablet/Dockerfile b/docker/binaries/vttablet/Dockerfile index 1c6ff92a588..72b2647c8df 100644 --- a/docker/binaries/vttablet/Dockerfile +++ b/docker/binaries/vttablet/Dockerfile @@ -20,8 +20,8 @@ FROM vitess/lite:${VT_BASE_VER} AS lite FROM debian:${DEBIAN_VER} # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vtdataroot +ENV VTROOT=/vt +ENV VTDATAROOT=/vtdataroot # Prepare directory structure. RUN mkdir -p /vt/bin && mkdir -p /vtdataroot diff --git a/docker/mini/Dockerfile b/docker/mini/Dockerfile index 0bb3aac7561..8e25d50a7a1 100644 --- a/docker/mini/Dockerfile +++ b/docker/mini/Dockerfile @@ -38,9 +38,9 @@ RUN echo "hostname=127.0.0.1" >> /vt/dist/scripts/env.sh RUN cat /vt/dist/scripts/env.sh | egrep "^alias" >> /etc/bash.bashrc # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt/src/vitess.io/vitess -ENV VTDATAROOT /vt/vtdataroot -ENV PATH $VTROOT/bin:$PATH +ENV VTROOT=/vt/src/vitess.io/vitess +ENV VTDATAROOT=/vt/vtdataroot +ENV PATH=$VTROOT/bin:$PATH ENV PATH="/vt/bin:${PATH}" ENV PATH="/var/opt/etcd:${PATH}" ENV TOPO="etcd" diff --git a/docker/vttestserver/Dockerfile.mysql80 b/docker/vttestserver/Dockerfile.mysql80 index e970bdb89b0..0d64423da52 100644 --- a/docker/vttestserver/Dockerfile.mysql80 +++ b/docker/vttestserver/Dockerfile.mysql80 @@ -42,9 +42,9 @@ RUN groupadd -r vitess && useradd -r -g vitess vitess RUN mkdir -p /vt/vtdataroot && chown -R vitess:vitess /vt # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vt/vtdataroot -ENV PATH $VTROOT/bin:$PATH +ENV VTROOT=/vt +ENV VTDATAROOT=/vt/vtdataroot +ENV PATH=$VTROOT/bin:$PATH # Copy artifacts from builder layer. COPY --from=builder --chown=vitess:vitess /vt/install /vt diff --git a/docker/vttestserver/Dockerfile.mysql84 b/docker/vttestserver/Dockerfile.mysql84 index 6632d4a6469..32d4f7b057f 100644 --- a/docker/vttestserver/Dockerfile.mysql84 +++ b/docker/vttestserver/Dockerfile.mysql84 @@ -42,9 +42,9 @@ RUN groupadd -r vitess && useradd -r -g vitess vitess RUN mkdir -p /vt/vtdataroot && chown -R vitess:vitess /vt # Set up Vitess environment (just enough to run pre-built Go binaries) -ENV VTROOT /vt -ENV VTDATAROOT /vt/vtdataroot -ENV PATH $VTROOT/bin:$PATH +ENV VTROOT=/vt +ENV VTDATAROOT=/vt/vtdataroot +ENV PATH=$VTROOT/bin:$PATH # Copy artifacts from builder layer. COPY --from=builder --chown=vitess:vitess /vt/install /vt diff --git a/go.mod b/go.mod index 86319b68bc9..5d2ff47dcd7 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/evanphx/json-patch v5.9.11+incompatible github.com/fsnotify/fsnotify v1.9.0 github.com/go-sql-driver/mysql v1.7.1 - github.com/go-viper/mapstructure/v2 v2.4.0 + github.com/go-viper/mapstructure/v2 v2.5.0 github.com/golang/glog v1.2.5 github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 @@ -38,7 +38,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/serf v0.10.2 // indirect github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 - github.com/klauspost/compress v1.18.0 + github.com/klauspost/compress v1.18.4 github.com/klauspost/pgzip v1.2.6 github.com/krishicks/yaml-patch v0.0.10 github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1 @@ -53,16 +53,16 @@ require ( github.com/planetscale/pargzip v0.0.0-20201116224723-90c7fc03ea8a github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.66.1 + github.com/prometheus/common v0.67.5 github.com/sjmudd/stopwatch v0.1.1 github.com/soheilhy/cmux v0.1.5 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/tchap/go-patricia v2.3.0+incompatible github.com/tidwall/gjson v1.18.0 - github.com/tinylib/msgp v1.4.0 // indirect + github.com/tinylib/msgp v1.6.3 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 @@ -70,67 +70,72 @@ require ( go.etcd.io/etcd/api/v3 v3.5.25 go.etcd.io/etcd/client/pkg/v3 v3.5.25 go.etcd.io/etcd/client/v3 v3.5.25 - go.uber.org/mock v0.5.0 - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 - golang.org/x/oauth2 v0.33.0 - golang.org/x/sys v0.38.0 - golang.org/x/term v0.37.0 - golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.13.0 - golang.org/x/tools v0.39.0 - google.golang.org/api v0.251.0 - google.golang.org/genproto v0.0.0-20250929231259-57b25ae835d4 // indirect - google.golang.org/grpc v1.75.1 + go.uber.org/mock v0.6.0 + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.58.0 + golang.org/x/oauth2 v0.36.0 + golang.org/x/sys v0.47.0 + golang.org/x/term v0.45.0 + golang.org/x/text v0.41.0 // indirect + golang.org/x/time v0.15.0 + golang.org/x/tools v0.48.0 + google.golang.org/api v0.264.0 + google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/grpc v1.83.2 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 - google.golang.org/protobuf v1.36.9 - gopkg.in/DataDog/dd-trace-go.v1 v1.74.6 + google.golang.org/protobuf v1.36.11 + gopkg.in/DataDog/dd-trace-go.v1 v1.74.8 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/ldap.v2 v2.5.1 sigs.k8s.io/yaml v1.6.0 ) require ( - github.com/DataDog/datadog-go/v5 v5.8.0 + github.com/DataDog/datadog-go/v5 v5.8.3 github.com/Shopify/toxiproxy/v2 v2.12.0 - github.com/aws/aws-sdk-go-v2 v1.39.2 + github.com/aws/aws-sdk-go-v2 v1.41.5 github.com/aws/aws-sdk-go-v2/config v1.31.12 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.10 - github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 - github.com/aws/smithy-go v1.23.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 + github.com/aws/smithy-go v1.24.2 github.com/bndr/gotabulate v1.1.2 github.com/dustin/go-humanize v1.0.1 github.com/gammazero/deque v1.1.0 github.com/google/go-containerregistry v0.20.6 github.com/google/safehtml v0.1.0 - github.com/hashicorp/go-version v1.7.0 + github.com/hashicorp/go-version v1.8.0 github.com/kr/pretty v0.3.1 github.com/kr/text v0.2.0 github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 - github.com/shirou/gopsutil/v4 v4.25.8 + github.com/shirou/gopsutil/v4 v4.26.2 github.com/spf13/afero v1.15.0 github.com/spf13/jwalterweatherman v1.1.0 github.com/xlab/treeprint v1.2.0 go.uber.org/goleak v1.3.0 - golang.org/x/exp v0.0.0-20250911091902-df9299821621 - golang.org/x/sync v0.18.0 - gonum.org/v1/gonum v0.16.0 + golang.org/x/exp v0.0.0-20260209203927-2842357ff358 + golang.org/x/sync v0.22.0 + gonum.org/v1/gonum v0.17.0 modernc.org/sqlite v1.39.0 ) require ( - github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/proto v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/trace v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/util/log v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/util/scrubber v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/version v0.70.2 // indirect - github.com/DataDog/dd-trace-go/v2 v2.2.3 // indirect - github.com/DataDog/go-libddwaf/v4 v4.5.0 // indirect - github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 // indirect - github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.29.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/proto v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/template v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/log v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/otel v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/stats v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/log v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/scrubber v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/version v0.77.0 // indirect + github.com/DataDog/dd-trace-go/v2 v2.8.2 // indirect + github.com/DataDog/go-libddwaf/v4 v4.9.0 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/cilium/ebpf v0.19.0 // indirect github.com/clipperhouse/uax29/v2 v2.2.0 // indirect @@ -144,6 +149,9 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/linkdata/deadlock v0.5.5 // indirect + github.com/minio/simdjson-go v0.4.5 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect @@ -152,75 +160,73 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 // indirect github.com/vbatts/tar-split v0.12.2 // indirect - go.opentelemetry.io/collector/component v1.42.0 // indirect - go.opentelemetry.io/collector/featuregate v1.42.0 // indirect - go.opentelemetry.io/collector/internal/telemetry v0.136.0 // indirect - go.opentelemetry.io/collector/pdata v1.42.0 // indirect - go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect - go.opentelemetry.io/otel/log v0.14.0 // indirect + go.opentelemetry.io/collector/component v1.51.1-0.20260205185216-81bc641f26c0 // indirect + go.opentelemetry.io/collector/featuregate v1.51.1-0.20260205185216-81bc641f26c0 // indirect + go.opentelemetry.io/collector/pdata v1.51.1-0.20260205185216-81bc641f26c0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.145.1-0.20260205185216-81bc641f26c0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 // indirect - k8s.io/apimachinery v0.33.4 // indirect ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.2 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.16.5 // indirect + cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.2 // indirect - cloud.google.com/go/monitoring v1.24.2 // indirect - github.com/DataDog/appsec-internal-go v1.14.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.70.2 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.70.2 // indirect - github.com/DataDog/go-sqllexer v0.1.8 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.77.0 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.77.0 // indirect + github.com/DataDog/go-sqllexer v0.1.13 // indirect github.com/DataDog/go-tuf v1.1.1-0.5.2 // indirect - github.com/DataDog/sketches-go v1.4.7 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/DataDog/sketches-go v1.4.8 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.16 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/containerd/cgroups v1.1.0 github.com/containerd/cgroups/v3 v3.0.5 github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect - github.com/ebitengine/purego v0.9.0 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.2 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.5 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -229,7 +235,7 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect + github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-ieproxy v0.0.12 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -238,43 +244,43 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/gomega v1.36.2 // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/outcaste-io/ristretto v0.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + gopkg.in/ini.v1 v1.67.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.66.10 // indirect diff --git a/go.sum b/go.sum index 0f00ce02eee..2688fd598b9 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,27 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= -cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= +cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= +cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= -cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= +cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/storage v1.57.0 h1:4g7NB7Ta7KetVbOMpCqy89C+Vg5VE8scqlSHUPm7Rds= cloud.google.com/go/storage v1.57.0/go.mod h1:329cwlpzALLgJuu8beyJ/uvQznDHpa2U5lGjWednkzg= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= @@ -40,45 +40,51 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/appsec-internal-go v1.14.0 h1:MIEZ015kdpeSZSFYBQteSmg8f7zkQTWbMDHbSL9zBx8= -github.com/DataDog/appsec-internal-go v1.14.0/go.mod h1:9YppRCpElfGX+emXOKruShFYsdPq7WEPq/Fen4tYYpk= -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.70.2 h1:kUc5YY7AIvhWyW2m/7Mmr4TfQrrG7L4kXpDFiS0kcAc= -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.70.2/go.mod h1:C32ImT3ZcOaEG8xARqPF5uVBTUd4Q9XSZ8qZUDKn+V8= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.70.2 h1:Xrmg13fi5wOm7+fhKRsMQGAKcZJIv/k1R/3czUnBL08= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.70.2/go.mod h1:lpp+8NC8/ggIah/F89kN4A7zzEWsIpDy0L9v8jsTpwg= -github.com/DataDog/datadog-agent/pkg/proto v0.70.2 h1:1/DbqEqvyhDe9j/V5RHDZndhKLa6GF2zHnwSHCYuWbo= -github.com/DataDog/datadog-agent/pkg/proto v0.70.2/go.mod h1:04uBfKTbFuA9muKbgBE6yC/+955fGjnpwkTPV2LjREY= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.70.2 h1:8WERtPbezb94idOvUmEyzB7pznpSn3/bEoOcGRyEi4w= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.70.2/go.mod h1:RaAja1FpZs9KoDQsqGKkq3VQ9phpFXqkjFf/ZxRmr3A= -github.com/DataDog/datadog-agent/pkg/trace v0.70.2 h1:T3qzpw1dgD7LsmQMGhToljFhW6rgH2ueOefvAFSM5/s= -github.com/DataDog/datadog-agent/pkg/trace v0.70.2/go.mod h1:b/wPDq+xdftLlgy5re3KbND+lZEnul641O5O8amsDIU= -github.com/DataDog/datadog-agent/pkg/util/log v0.70.2 h1:4Abe5dyhdyv9cwA2tAFWCyX5pEjk2fQ89q6Q8NLtQ5Y= -github.com/DataDog/datadog-agent/pkg/util/log v0.70.2/go.mod h1:tO7knmYyZGGoFPC8huxl4vtwgLYdVRQS0PoLdpZI8Lg= -github.com/DataDog/datadog-agent/pkg/util/scrubber v0.70.2 h1:dM8yjDH0e1DLzh8sDiquRftGdFO87XLaTZ6t+KC9bpE= -github.com/DataDog/datadog-agent/pkg/util/scrubber v0.70.2/go.mod h1:3b9n1n6Cs8RGjvvnn4xc4pE4JwtPb2SjMKwUgZ65XPw= -github.com/DataDog/datadog-agent/pkg/version v0.70.2 h1:31BJbziYgvaJrUjhv33otDFi2O+Co+E8sWzFMYdE5fU= -github.com/DataDog/datadog-agent/pkg/version v0.70.2/go.mod h1:XPERomJsscI4LZk5xVZ8O3fjmIg9gmEq08ixSi5wI3I= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.77.0 h1:Lu/HEo5svx/UwE7XWh8vOrEHCrVRsein9X1N0jGK5bo= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.77.0/go.mod h1:+Ty3r23MjcmMSkr8JbFeqA3utgtc1wxsZ0KaQ9CzoWA= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.77.0 h1:mrHaNnDAIOFAVYhCqDpkenUtbadswHN68ZlG5krv40o= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.77.0/go.mod h1:E6RGAcEOr/d8wsV5/khYHvaHkijWex6dfNEvsBIgR7A= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.77.0 h1:g1d9d1CfG54WjXgvkysTFL9yjXexWeDbYssQaf1PG6c= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.77.0/go.mod h1:N/AB9VGpVwHxCcyX+7GLNYMfnTZvn65vp5cHb5Ed0ow= +github.com/DataDog/datadog-agent/pkg/proto v0.77.0 h1:21nDAKD+LdxZz2pMsLAQTZ+w9Z4JqecjKpt8xY1b7Ig= +github.com/DataDog/datadog-agent/pkg/proto v0.77.0/go.mod h1:g2QYJe1CheZdssiDQpSYWra9hORkh+S3WO8aOqDNLkg= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.77.0 h1:SxFGFN/Dd/uREaUTxuVTi0R7fRABzvUtu32YOXcjf6c= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.77.0/go.mod h1:TpW5ZwsQTrlRBPjtZH5/OFwpmOqxV/v2i9BiF4Xfcac= +github.com/DataDog/datadog-agent/pkg/template v0.77.0 h1:sUbTCoQyU9kXMc6/aDd4YTP2pe9PlNkgySM11ydMEbE= +github.com/DataDog/datadog-agent/pkg/template v0.77.0/go.mod h1:ZUjICHSlN0of0cmWrYk9Pof0DV0eqHSpTUK1NTnN26Y= +github.com/DataDog/datadog-agent/pkg/trace v0.77.0 h1:B7M6IW0sd60XnLfeP6HEneeR4lRnaNtI9bboU4R1cV0= +github.com/DataDog/datadog-agent/pkg/trace v0.77.0/go.mod h1:+7zMNPjHTDidiphECirrpq5jLK09S9kmLtGRv7di29Q= +github.com/DataDog/datadog-agent/pkg/trace/log v0.77.0 h1:2VY1byEA2XnYVg7+eLQSTgX2f76ZCf/AwpCBJDXCiDc= +github.com/DataDog/datadog-agent/pkg/trace/log v0.77.0/go.mod h1:thnxBOGfMU9uRlFUClXud6J7DdI8qWtElELSds5jqts= +github.com/DataDog/datadog-agent/pkg/trace/otel v0.77.0 h1:9zSto72E+wSETaKs47Yiq5D9du9H71IqWgbDJzGpzSs= +github.com/DataDog/datadog-agent/pkg/trace/otel v0.77.0/go.mod h1:IxBidgqUt8aBrKYq4VKynBHWYZYNoflk+0+m7w+lfbI= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.77.0 h1:InA6JO5R8TFAUcKRxsdmIF1hJpVZqVO5Aqux+Iw/2V0= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.77.0/go.mod h1:iZVotmInV8qaU6Q5h+tsKk4CBYupDcOgTzonzLlMi0k= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.77.0 h1:b/2+uA/cG2xEV0LzgwnxloMFWe5sdJa3xtaQhSGN0+s= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.77.0/go.mod h1:csT+8o3GOUjhKPs/GqWMb5Zh4iQpuZ/HZQ4Z5ls8Sak= +github.com/DataDog/datadog-agent/pkg/util/log v0.77.0 h1:YFa+8kIg2qQZca9zvowtwCPdHDhGMcTIF+PMIQsLSRs= +github.com/DataDog/datadog-agent/pkg/util/log v0.77.0/go.mod h1:DFK2U5RcB8/BcObgmVEZ4VxqXUi2t7y2svLTtJwQqeo= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.77.0 h1:dd0W9e39rv0R3DSgnaurVnQ43/jX/juqQPwLpGAJgFs= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.77.0/go.mod h1:nkhevws2pJvoXSGhjc8wuTbptNQ9ECRBjwVr4hSvoq0= +github.com/DataDog/datadog-agent/pkg/version v0.77.0 h1:fxpMWuoaRHS5vHzCNHftvJ6wdQrGhEmuozjjl8wZG5k= +github.com/DataDog/datadog-agent/pkg/version v0.77.0/go.mod h1:h9eJjfeTHlYYv+kzq6n3rQ07qXGirdCCacn1Ryu4TFQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go/v5 v5.8.0 h1:pKZtux5CfqkqGYGvKCM3wV5i8sYAzcddK7nkrChUtxo= -github.com/DataDog/datadog-go/v5 v5.8.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= -github.com/DataDog/dd-trace-go/v2 v2.2.3 h1:6RvVdY9suR/rYYYZHjx4txrtSYcRZ5u5Cs2sXMsIBf4= -github.com/DataDog/dd-trace-go/v2 v2.2.3/go.mod h1:1LcqWELgQwgk6x7sO0MXUgsvxcAVjxSA423cUjvUqR0= -github.com/DataDog/go-libddwaf/v4 v4.5.0 h1:dXD9xL9T02Mqu9AH7imXpGThKAYqO3BmT2QoXbMFxow= -github.com/DataDog/go-libddwaf/v4 v4.5.0/go.mod h1:/AZqP6zw3qGJK5mLrA0PkfK3UQDk1zCI2fUNCt4xftE= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 h1:ZRLR9Lbym748e8RznWzmSoK+OfV+8qW6SdNYA4/IqdA= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633/go.mod h1:YFoTl1xsMzdSRFIu33oCSPS/3+HZAPGpO3oOM96wXCM= -github.com/DataDog/go-sqllexer v0.1.8 h1:ku9DpghFHeyyviR28W/3R4cCJwzpsuC08YIoltnx5ds= -github.com/DataDog/go-sqllexer v0.1.8/go.mod h1:GGpo1h9/BVSN+6NJKaEcJ9Jn44Hqc63Rakeb+24Mjgo= +github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= +github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DataDog/dd-trace-go/v2 v2.8.2 h1:ZqF2M7j5DPG7PxkJpLIjF4L62LU/QnI86oOSAZjQC/U= +github.com/DataDog/dd-trace-go/v2 v2.8.2/go.mod h1:o+fhXzd1mPT4Ji5YYcqIjORnNKWcS6m2eW4xqdJplRA= +github.com/DataDog/go-libddwaf/v4 v4.9.0 h1:a788e37iuH7sR9uIYHkulvTnp2FkXTiZ3yY/kuaHgZE= +github.com/DataDog/go-libddwaf/v4 v4.9.0/go.mod h1:/AZqP6zw3qGJK5mLrA0PkfK3UQDk1zCI2fUNCt4xftE= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d h1:cH9Bm0tJ8FEQbA4FRi0iRm7Zr/5Lata/Or31c+Dth0E= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d/go.mod h1:yDuvU+Ak1TKwgd4K8DNcpJmUrrK8ONLkBMGNAppmBRk= +github.com/DataDog/go-sqllexer v0.1.13 h1:HhT2G21y7SDZYQx9i1b+3Sy/CHhESHet/YKMSm06XcE= +github.com/DataDog/go-sqllexer v0.1.13/go.mod h1:vOw7Ia7z+z6nl3zGZlLIZe0vQlPtCPR906WIPBJadxc= github.com/DataDog/go-tuf v1.1.1-0.5.2 h1:YWvghV4ZvrQsPcUw8IOUMSDpqc3W5ruOIC+KJxPknv0= github.com/DataDog/go-tuf v1.1.1-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= -github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= -github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.29.1 h1:RoHver8/fv4qKZtmb11TJ/zcOMd0SqOKT8H9ZQIjjPA= -github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.29.1/go.mod h1:yN8VBQrExGN/nbIpn8E2jrMJn94e69mnq8a5PDCKY/g= -github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= -github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/DataDog/sketches-go v1.4.8 h1:pFk9BNn+Rzv8IMIoPUttoOpOr3bJOqU3P6EP5wK+Lv8= +github.com/DataDog/sketches-go v1.4.8/go.mod h1:a/wjRUqzqtGS8qRHRPDCs4EAQfmvPDZGDlMIF5mxXOE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= @@ -89,8 +95,6 @@ github.com/HdrHistogram/hdrhistogram-go v0.9.0 h1:dpujRju0R4M/QZzcnR1LH1qm+TVG3U github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -106,10 +110,10 @@ github.com/aquarapid/vaultlib v0.5.1 h1:vuLWR6bZzLHybjJBSUYPgZlIp6KZ+SXeHLRRYTuk github.com/aquarapid/vaultlib v0.5.1/go.mod h1:yT7AlEXtuabkxylOc/+Ulyp18tff1+QjgNLTnFWTlOs= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= -github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= @@ -118,32 +122,32 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wg github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.10 h1:i9EmUTyCWtUp1KsBwKGTtZUxaRym4LmiFiBcb//i8Kw= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.10/go.mod h1:IPS1CSYQ8lfLYGytpMEPW4erZmVFUdxLpC0RCI/RCn8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 h1:by3nYZLR9l8bUH7kgaMU4dJgYFjyRdFEfORlDpPILB4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 h1:P18I4ipbk+b/3dZNq5YYh+Hq6XC0vp5RWkLp1tJldDA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3/go.mod h1:Rm3gw2Jov6e6kDuamDvyIlZJDMYk97VeCZ82wz/mVZ0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -153,6 +157,8 @@ github.com/bndr/gotabulate v1.1.2 h1:yC9izuZEphojb9r+KYL4W9IJKO/ceIO8HDwxMA24U4c github.com/bndr/gotabulate v1.1.2/go.mod h1:0+8yUgaPTtLRTjf49E8oju7ojpU11YmXyvq1LbPAb3U= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/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.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -167,8 +173,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -208,22 +214,20 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= -github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= -github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= -github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -241,8 +245,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo= github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg= -github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= -github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= +github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -250,8 +254,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -263,8 +267,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -321,10 +325,10 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= @@ -364,8 +368,8 @@ github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0Yg github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -394,8 +398,10 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -413,8 +419,10 @@ github.com/krishicks/yaml-patch v0.0.10 h1:H4FcHpnNwVmw8u0MjPRjWyIXtco6zM2F78t+5 github.com/krishicks/yaml-patch v0.0.10/go.mod h1:Sm5TchwZS6sm7RJoyg87tzxm2ZcKzdRE4Q7TjNhPrME= 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/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= -github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/linkdata/deadlock v0.5.5 h1:d6O+rzEqasSfamGDA8u7bjtaq7hOX8Ha4Zn36Wxrkvo= +github.com/linkdata/deadlock v0.5.5/go.mod h1:tXb28stzAD3trzEEK0UJWC+rZKuobCoPktPYzebb1u0= +github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= +github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -431,12 +439,14 @@ github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1 h1:jw16EimP5oAEM/2wt+SiEUov/YDyTCTDuPtIKgQIvk0= github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1/go.mod h1:vuvdOZLJuf5HmJAJrKV64MmozrSsk+or0PB5dzdfspg= +github.com/minio/simdjson-go v0.4.5 h1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A= +github.com/minio/simdjson-go v0.4.5/go.mod h1:eoNz0DcLQRyEDeaPr4Ru6JpjlZPzbA0IodxVJk8lO8E= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -473,12 +483,12 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -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/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.131.0 h1:5sPZErUo231/wBoKp/HiEieW8uhiQXHIHnII3HXIcYM= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.131.0/go.mod h1:0eTDOk4+HDSo+3oA2N+2Q41ypGZDTvZpzODQsOHQ4cw= -github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.131.0 h1:6afPSMz1XbtFYGhT/BCsbgP5dIx/lbmKrndW8WuOBV8= -github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.131.0/go.mod h1:hM44pzNNfE4qpj9bNdaxSs6EMSvMUl/W6N8YcJIo3LU= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.145.0 h1:7rdLY2Ewa1WVnjMfJTEKwQ5uPDHYeA1tqNPNROi957U= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.145.0/go.mod h1:jYlQAaJO4ZyJAW2jcKAbjN+nt5BRCyu49mlZv4Rui7U= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.145.0 h1:12mxn+8YLeAjMZ1kLGulBcvHrdhRNUmxLVIDnaLkJbQ= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.145.0/go.mod h1:V87HYJpfmvCeQ6Cjy3Q4xylxfCn2wVSS80wvv5ECc0s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -498,6 +508,9 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= @@ -535,15 +548,15 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -561,10 +574,10 @@ github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88ee github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= -github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= -github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= -github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= +github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= +github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -583,8 +596,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -592,8 +605,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= -github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= +github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -609,6 +622,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -623,12 +637,14 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8= -github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 h1:qqa3P9AtNn6RMe90l/lxd3eJWnIRxjI4eb5Rx8xqCLA= +github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561/go.mod h1:GA3+Mq3kt3tYAfM0WZCu7ofy+GW9PuGysHfhr+6JX7s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= @@ -659,68 +675,68 @@ go.etcd.io/etcd/client/v3 v3.5.25 h1:Secqbl/DeksaY8Tg+984FDdRR8XWnxgXksjPrSFm5Lc go.etcd.io/etcd/client/v3 v3.5.25/go.mod h1:6QqI2Y9iqtIaUTS55285OyZqpF1QLCghATMEx1mhS0I= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/component v1.42.0 h1:on4XJ/NT1oPnuCVKDEtlpcr3GGPAS9taWBe8woHSTmY= -go.opentelemetry.io/collector/component v1.42.0/go.mod h1:mehIbkABLhEEs3kmAqer2GRmLwcQLoeF7C48CR6lxP0= -go.opentelemetry.io/collector/component/componentstatus v0.131.0 h1:IVsyN0melBQU3QAabLj3ey1QQ+K2e8PhIcPRXH+LfiI= -go.opentelemetry.io/collector/component/componentstatus v0.131.0/go.mod h1:DotgEZNwPF9Ug2YKk2+zBlmGW4hRTJ7k7YBkZoM4xL4= -go.opentelemetry.io/collector/component/componenttest v0.131.0 h1:pvBENFUdOSIikdIExUP2+2B4K3LbZIqdUI7Kh7jNGxI= -go.opentelemetry.io/collector/component/componenttest v0.131.0/go.mod h1:5RdiTb/UaiCp1RvKH2+B6SyggGNvcY8Yd5799lJcEe4= -go.opentelemetry.io/collector/consumer v1.37.0 h1:RqTqEcc95Fg7T3MRPPjUX2nxzn1X88yfFUQV+AjdMK0= -go.opentelemetry.io/collector/consumer v1.37.0/go.mod h1:vDA1JDXeb7vnQ02PXIjjR6dI9LTaya+Qr89Nyt2Gl7Y= -go.opentelemetry.io/collector/consumer/consumertest v0.131.0 h1:+lgAblWlItsaWhUW10mKCmt3vTrmwvAWRSTrvrPgN/Q= -go.opentelemetry.io/collector/consumer/consumertest v0.131.0/go.mod h1:t7eH0dWqxAeIPtyvzT7mOJTKM9km2YEMjFCtaIeIl/w= -go.opentelemetry.io/collector/consumer/xconsumer v0.131.0 h1:PgCoBVF5FN87Ef2wDqLpRU7QxxIDs8dNiy9jKNdpWzk= -go.opentelemetry.io/collector/consumer/xconsumer v0.131.0/go.mod h1:xh1XRXcwk4Hxm3KSUCw/IOA0dyEoZr7Q/h0gzLnYaQo= -go.opentelemetry.io/collector/featuregate v1.42.0 h1:uCVwumVBVex46DsG/fvgiTGuf9f53bALra7vGyKaqFI= -go.opentelemetry.io/collector/featuregate v1.42.0/go.mod h1:d0tiRzVYrytB6LkcYgz2ESFTv7OktRPQe0QEQcPt1L4= -go.opentelemetry.io/collector/internal/telemetry v0.136.0 h1:3TcnxyUFs6jJZeLo5ju3fMWS4lRmIApl9To2XWk922M= -go.opentelemetry.io/collector/internal/telemetry v0.136.0/go.mod h1:dTykH9zv/zOnlyUvqfGIqpaQZhmayW7NssD7TPU4paE= -go.opentelemetry.io/collector/pdata v1.42.0 h1:XEzisp/SNfKDcY4aRU6qrHeLzGypRUdYHjbBqkDFOO4= -go.opentelemetry.io/collector/pdata v1.42.0/go.mod h1:nnOmgf+RI/D5xYWgFPZ5nKuhf2E0Qy9Nx/mxoTvIq3k= -go.opentelemetry.io/collector/pdata/pprofile v0.131.0 h1:eQ2Yq1g6wOWHjRXum9Fm0dZax/klNmjtpL7UPsEXrPo= -go.opentelemetry.io/collector/pdata/pprofile v0.131.0/go.mod h1:g4IuRFVGC89n/2bTdw0CuMJkkCY4zDb0Hu37wCKlx0c= -go.opentelemetry.io/collector/pdata/testdata v0.131.0 h1:ARWgM7MMg5D4qwp1hLTfd8BS3H1tUWwQ9iVCMeAoJ+o= -go.opentelemetry.io/collector/pdata/testdata v0.131.0/go.mod h1:cagnzOua8bdn2m4zz0DQSehR5vVe7M5JazkZs8J5nMo= -go.opentelemetry.io/collector/pipeline v1.42.0 h1:jqn1lPwUdCn+lsyNubCtwzXZLEm+R3kRWxLpDkhlvvs= -go.opentelemetry.io/collector/pipeline v1.42.0/go.mod h1:xUrAqiebzYbrgxyoXSkk6/Y3oi5Sy3im2iCA51LwUAI= -go.opentelemetry.io/collector/processor v1.37.0 h1:yUQfHHZFs94BZNCamYx+WoN0VoN7MVEDwlU1H/pHISU= -go.opentelemetry.io/collector/processor v1.37.0/go.mod h1:TdCjl4QiiQ/JIcvonAGbXB7/cU1Sb8O7KrkN0sBmW3s= -go.opentelemetry.io/collector/processor/processorhelper v0.131.0 h1:4otHyECTsRsz9yd4d6diTKb11j4fYtjgz6+PbR0CF7A= -go.opentelemetry.io/collector/processor/processorhelper v0.131.0/go.mod h1:KAkP+oNGkZxOmfafz8osEerOY/lHJm/R55TaVyncBUM= -go.opentelemetry.io/collector/processor/processortest v0.131.0 h1:Yj5LHMHjpd795k6KR0iyRWOZ+/LT6un4IVh41xgFsr4= -go.opentelemetry.io/collector/processor/processortest v0.131.0/go.mod h1:CNdxqDm+QOEpgovxOG2YrFZ5ldqe5R5lTOUMAByl5wI= -go.opentelemetry.io/collector/processor/xprocessor v0.131.0 h1:l2BjdmCr+1H7dat42fhxq45Um5Tbq7BQqjCIVTD5nyU= -go.opentelemetry.io/collector/processor/xprocessor v0.131.0/go.mod h1:uNo0JRtxJNepop+QB105ASX8MkvyusoIZYIUTm00epE= -go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 h1:aBKdhLVieqvwWe9A79UHI/0vgp2t/s2euY8X59pGRlw= -go.opentelemetry.io/contrib/bridges/otelzap v0.13.0/go.mod h1:SYqtxLQE7iINgh6WFuVi2AI70148B8EI35DSk0Wr8m4= -go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= -go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/collector/component v1.51.1-0.20260205185216-81bc641f26c0 h1:ZSlXxE90IY0Cl53RTqzyEgRgRPLTeTNBdGhaTmvj9eY= +go.opentelemetry.io/collector/component v1.51.1-0.20260205185216-81bc641f26c0/go.mod h1:944C7vEIdk13Pn1fBbyaU8C1qKf2XC0jRBlc69NAsRY= +go.opentelemetry.io/collector/component/componentstatus v0.145.0 h1:EwUZfSaagdpRXnlrb0TqReJXXW2p9HWBU5YiIeXPCAE= +go.opentelemetry.io/collector/component/componentstatus v0.145.0/go.mod h1:OiYb8rT4FtSJPFSGCKYvOaajdueDUTJZncixGrmy5aM= +go.opentelemetry.io/collector/component/componenttest v0.145.1-0.20260205185216-81bc641f26c0 h1:+VCK6wX/WN170dcaWJweRAkxpmAEyVucfrUV13NwUlY= +go.opentelemetry.io/collector/component/componenttest v0.145.1-0.20260205185216-81bc641f26c0/go.mod h1:U2wUjKMGwgqM49/q8ORkzzYzSWY2m6zpG/e606eK1wc= +go.opentelemetry.io/collector/consumer v1.51.1-0.20260205185216-81bc641f26c0 h1:WNkJ1bKnRVAEJtBm1bwEkoLG2x7GyANc3/OnErZJ338= +go.opentelemetry.io/collector/consumer v1.51.1-0.20260205185216-81bc641f26c0/go.mod h1:Erk6qdfVj+24QTrGCpurcrF+qdUlHkb4dgMy5wJxLvY= +go.opentelemetry.io/collector/consumer/consumertest v0.145.1-0.20260205185216-81bc641f26c0 h1:FHyDIlTbqt0Y6tDI9EbI3hr9uWthwkeLY7uGF1jZYqQ= +go.opentelemetry.io/collector/consumer/consumertest v0.145.1-0.20260205185216-81bc641f26c0/go.mod h1:IFc/FeaIHQClb8KK0aVn0tFDNMc+/MmfQ+aBT1cJNeo= +go.opentelemetry.io/collector/consumer/xconsumer v0.145.1-0.20260205185216-81bc641f26c0 h1:zg2Jqfy7n7o/LEmLsXB4sFhxWtOEMFCKRyQUFLFUS9M= +go.opentelemetry.io/collector/consumer/xconsumer v0.145.1-0.20260205185216-81bc641f26c0/go.mod h1:SryDCLP2ZaFeZJtA2CSksJ0XvjH8k3LmlfXvy/kC7Wc= +go.opentelemetry.io/collector/featuregate v1.51.1-0.20260205185216-81bc641f26c0 h1:fOXhfT2xKqNhfalTXaT/Wic9EBRK8+9ZH0y8phReQS4= +go.opentelemetry.io/collector/featuregate v1.51.1-0.20260205185216-81bc641f26c0/go.mod h1:/1bclXgP91pISaEeNulRxzzmzMTm4I5Xih2SnI4HRSo= +go.opentelemetry.io/collector/internal/componentalias v0.145.1-0.20260205185216-81bc641f26c0 h1:s4/vCxeIxgQpuWmX1AK1DRbZmEdmNBq925EKES8ebiI= +go.opentelemetry.io/collector/internal/componentalias v0.145.1-0.20260205185216-81bc641f26c0/go.mod h1:Z0TtMbzaMp2qhj1dw4toya8toyQzqoTF46/WhJXplVw= +go.opentelemetry.io/collector/internal/testutil v0.145.0 h1:H/KL0GH3kGqSMKxZvnQ0B0CulfO9xdTg4DZf28uV7fY= +go.opentelemetry.io/collector/internal/testutil v0.145.0/go.mod h1:YAD9EAkwh/l5asZNbEBEUCqEjoL1OKMjAMoPjPqH76c= +go.opentelemetry.io/collector/pdata v1.51.1-0.20260205185216-81bc641f26c0 h1:8tgf9W3aW3vFabyVxPNHKsaoyUytudfVOQbqZI9xBHQ= +go.opentelemetry.io/collector/pdata v1.51.1-0.20260205185216-81bc641f26c0/go.mod h1:GoX1bjKDR++mgFKdT7Hynv9+mdgQ1DDXbjs7/Ww209Q= +go.opentelemetry.io/collector/pdata/pprofile v0.145.1-0.20260205185216-81bc641f26c0 h1:MJcnK8txYZlqHZyfZ1rVf66kt5/kEveIdR6KX//BY1Y= +go.opentelemetry.io/collector/pdata/pprofile v0.145.1-0.20260205185216-81bc641f26c0/go.mod h1:a60GC7wQPhLAixWzKbbP51QLwwc+J0Cmp4SurOlhGUk= +go.opentelemetry.io/collector/pdata/testdata v0.145.0 h1:iFsxsCMtE3lnAc/5kZbhZHpRv1OMmM+O5ry46xdQHbg= +go.opentelemetry.io/collector/pdata/testdata v0.145.0/go.mod h1:0y2ERArdzqmYdJHdKLKue+AUubSEGlwK49F+23+Mbic= +go.opentelemetry.io/collector/pipeline v1.51.1-0.20260205185216-81bc641f26c0 h1:1KP5gXGF9qN1mEzJupZDUQVIND35qe/0Hy6Cptvdk0s= +go.opentelemetry.io/collector/pipeline v1.51.1-0.20260205185216-81bc641f26c0/go.mod h1:xUrAqiebzYbrgxyoXSkk6/Y3oi5Sy3im2iCA51LwUAI= +go.opentelemetry.io/collector/processor v1.51.0 h1:PKpCzkLQmqaW08TOVh/zM0qx07Ihq+DR5J/OBkPiL9o= +go.opentelemetry.io/collector/processor v1.51.0/go.mod h1:rtIPFS+EFRAkG+CSwtjxs2IsIkuZStObvALeueD02XI= +go.opentelemetry.io/collector/processor/processorhelper v0.145.0 h1:vXdv6lHz20Tm3ZEsg0i6jPZJBQgy9kzk/PuqWhHWiiM= +go.opentelemetry.io/collector/processor/processorhelper v0.145.0/go.mod h1:3Ecpe5jHRHGf24EvJHeJ/ekK/a1DLByyq0CSUxjjURg= +go.opentelemetry.io/collector/processor/processortest v0.145.0 h1:RDGBmyZnHk7XVK/EdLt/8iPWj+QLStbbVi1nFTNR01s= +go.opentelemetry.io/collector/processor/processortest v0.145.0/go.mod h1:WAvxAzSojkdoZB915Z1lsVHCPDJBb2fepjJBjenrzjg= +go.opentelemetry.io/collector/processor/xprocessor v0.145.0 h1:DaIE7MxRlg0OL1o2P0GQZtmZeExAmVso3qWv8S0RLps= +go.opentelemetry.io/collector/processor/xprocessor v0.145.0/go.mod h1:kUwRyKBU/kjCmXodd+0z7CpvcP0A9G9/QL+MaJt4U2o= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= -go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= -go.opentelemetry.io/otel/log/logtest v0.14.0 h1:BGTqNeluJDK2uIHAY8lRqxjVAYfqgcaTbVk1n3MWe5A= -go.opentelemetry.io/otel/log/logtest v0.14.0/go.mod h1:IuguGt8XVP4XA4d2oEEDMVDBBCesMg8/tSGWDjuKfoA= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/slim/otlp v1.8.0 h1:afcLwp2XOeCbGrjufT1qWyruFt+6C9g5SOuymrSPUXQ= -go.opentelemetry.io/proto/slim/otlp v1.8.0/go.mod h1:Yaa5fjYm1SMCq0hG0x/87wV1MP9H5xDuG/1+AhvBcsI= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0 h1:Uc+elixz922LHx5colXGi1ORbsW8DTIGM+gg+D9V7HE= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0/go.mod h1:VyU6dTWBWv6h9w/+DYgSZAPMabWbPTFTuxp25sM8+s0= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0 h1:i8YpvWGm/Uq1koL//bnbJ/26eV3OrKWm09+rDYo7keU= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0/go.mod h1:pQ70xHY/ZVxNUBPn+qUWPl8nwai87eWdqL3M37lNi9A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/proto/slim/otlp v1.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE= +go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0/go.mod h1:Gyb6Xe7FTi/6xBHwMmngGoHqL0w29Y4eW8TGFzpefGA= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.2.0 h1:EiUYvtwu6PMrMHVjcPfnsG3v+ajPkbUeH+IL93+QYyk= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.2.0/go.mod h1:mUUHKFiN2SST3AhJ8XhJxEoeVW12oqfXog0Bo8W3Ec4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -728,14 +744,14 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -747,11 +763,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= 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= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -759,8 +775,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -780,12 +796,12 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= 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.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -794,8 +810,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -834,19 +850,19 @@ golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= 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= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= 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= @@ -858,18 +874,18 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= 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= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.251.0 h1:6lea5nHRT8RUmpy9kkC2PJYnhnDAB13LqrLSVQlMIE8= -google.golang.org/api v0.251.0/go.mod h1:Rwy0lPf/TD7+T2VhYcffCHhyyInyuxGjICxdfLqT7KI= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.264.0 h1:+Fo3DQXBK8gLdf8rFZ3uLu39JpOnhvzJrLMQSoSYZJM= +google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -877,19 +893,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ 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 v0.0.0-20250929231259-57b25ae835d4 h1:HmI33/XNQ1jVwhb5ZUgot40oiwFHa2l5ZNkQpj8VaEg= -google.golang.org/genproto v0.0.0-20250929231259-57b25ae835d4/go.mod h1:OqVwZqqGV3h7k+YCVWXoTtwC2cs55RnDEUVMMadhxrc= -google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= -google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= 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.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 h1:ExN12ndbJ608cboPYflpTny6mXSzPrDLh0iTaVrRrds= @@ -902,10 +918,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -gopkg.in/DataDog/dd-trace-go.v1 v1.74.6 h1:VBxCK/WkaNjsM9Ygn57scwmiwMqF0gEbuE4C5c2TU5E= -gopkg.in/DataDog/dd-trace-go.v1 v1.74.6/go.mod h1:jQL1vSDZhH+DJWUOYjkRQ+kU1HUXPvUK41gS1AvHOTE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/DataDog/dd-trace-go.v1 v1.74.8 h1:h96ji92t9eXbPvSWhJ+lrPWetHiQNYlt48JKRO09NFA= +gopkg.in/DataDog/dd-trace-go.v1 v1.74.8/go.mod h1:LpHbtHsCZBlm1HWrlVOUQcEXwMWZnU6yMvmtd1GvSDI= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= @@ -916,8 +932,8 @@ 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= +gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -938,8 +954,8 @@ 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.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= -k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= -k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= +k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= diff --git a/go/cmd/vtbackup/cli/vtbackup.go b/go/cmd/vtbackup/cli/vtbackup.go index 8c085e9a83b..3adfe4f52d4 100644 --- a/go/cmd/vtbackup/cli/vtbackup.go +++ b/go/cmd/vtbackup/cli/vtbackup.go @@ -203,7 +203,7 @@ func init() { utils.SetFlagDurationVar(Main.Flags(), &minBackupInterval, "min-backup-interval", minBackupInterval, "Only take a new backup if it's been at least this long since the most recent backup.") utils.SetFlagDurationVar(Main.Flags(), &minRetentionTime, "min-retention-time", minRetentionTime, "Keep each old backup for at least this long before removing it. Set to 0 to disable pruning of old backups.") - utils.SetFlagIntVar(Main.Flags(), &minRetentionCount, "min-retention-count", minRetentionCount, "Always keep at least this many of the most recent backups in this backup storage location, even if some are older than the min_retention_time. This must be at least 1 since a backup must always exist to allow new backups to be made") + utils.SetFlagIntVar(Main.Flags(), &minRetentionCount, "min-retention-count", minRetentionCount, "Always keep at least this many of the most recent backups in this backup storage location, even if some are older than --min-retention-time. This must be at least 1 since a backup must always exist to allow new backups to be made") utils.SetFlagBoolVar(Main.Flags(), &initialBackup, "initial-backup", initialBackup, "Instead of restoring from backup, initialize an empty database with the provided init-db-sql-file and upload a backup of that for the shard, if the shard has no backups yet. This can be used to seed a brand new shard with an initial, empty backup. If any backups already exist for the shard, this will be considered a successful no-op. This can only be done before the shard exists in topology (i.e. before any tablets are deployed).") utils.SetFlagBoolVar(Main.Flags(), &allowFirstBackup, "allow-first-backup", allowFirstBackup, "Allow this job to take the first backup of an existing shard.") utils.SetFlagBoolVar(Main.Flags(), &restartBeforeBackup, "restart-before-backup", restartBeforeBackup, "Perform a mysqld clean/full restart after applying binlogs, but before taking the backup. Only makes sense to work around xtrabackup bugs.") @@ -467,7 +467,7 @@ func takeBackup(ctx, backgroundCtx context.Context, topoServer *topo.Server, bac case mysqlctl.ErrNoBackup: // There is no backup found, but we may be taking the initial backup of a shard if !allowFirstBackup { - return fmt.Errorf("no backup found; not starting up empty since --initial_backup flag was not enabled") + return fmt.Errorf("no backup found; not starting up empty since --allow-first-backup flag was not enabled") } restorePos = replication.Position{} default: @@ -854,14 +854,14 @@ func shouldBackup(ctx context.Context, topoServer *topo.Server, backupStorage ba // We need at least one backup so we can restore first, unless the user explicitly says we don't if len(backups) == 0 && !allowFirstBackup { - return false, fmt.Errorf("no existing backups to restore from; backup is not possible since --initial_backup flag was not enabled") + return false, fmt.Errorf("no existing backups to restore from; backup is not possible since --allow-first-backup flag was not enabled") } if lastBackup == nil { if allowFirstBackup { // There's no complete backup, but we were told to take one from scratch anyway. return true, nil } - return false, fmt.Errorf("no complete backups to restore from; backup is not possible since --initial_backup flag was not enabled") + return false, fmt.Errorf("no complete backups to restore from; backup is not possible since --allow-first-backup flag was not enabled") } // Has it been long enough since the last complete backup to need a new one? diff --git a/go/cmd/vtclient/cli/vtclient.go b/go/cmd/vtclient/cli/vtclient.go index 7f4590d127a..e2d80879675 100644 --- a/go/cmd/vtclient/cli/vtclient.go +++ b/go/cmd/vtclient/cli/vtclient.go @@ -73,7 +73,7 @@ For query bound variables, we assume place-holders in the query string in the form of :v1, :v2, etc.`, Example: `vtclient --server vtgate:15991 "SELECT * FROM messages" -vtclient --server vtgate:15991 --target '@primary' --bind_variables '[ 12345, 1, "msg 12345" ]' "INSERT INTO messages (page,time_created_ns,message) VALUES (:v1, :v2, :v3)"`, +vtclient --server vtgate:15991 --target '@primary' --bind-variables '[ 12345, 1, "msg 12345" ]' "INSERT INTO messages (page,time_created_ns,message) VALUES (:v1, :v2, :v3)"`, Args: cobra.ExactArgs(1), Version: servenv.AppVersion.String(), RunE: run, diff --git a/go/cmd/vtcombo/cli/main.go b/go/cmd/vtcombo/cli/main.go index d2b2dd62df8..0425449c722 100644 --- a/go/cmd/vtcombo/cli/main.go +++ b/go/cmd/vtcombo/cli/main.go @@ -116,7 +116,7 @@ func init() { Main.Flags().StringVar(&vschemaPersistenceDir, "vschema-persistence-dir", vschemaPersistenceDir, "If set, per-keyspace vschema will be persisted in this directory "+ "and reloaded into the in-memory topology server across restarts. Bookkeeping is performed using a simple watcher goroutine. "+ "This is useful when running vtcombo as an application development container (e.g. vttestserver) where you want to keep the same "+ - "vschema even if developer's machine reboots. This works in tandem with vttestserver's --persistent_mode flag. Needless to say, "+ + "vschema even if developer's machine reboots. This works in tandem with vttestserver's --persistent-mode flag. Needless to say, "+ "this is neither a perfect nor a production solution for vschema persistence. Consider using the --external-topo-server flag if "+ "you require a more complete solution. This flag is ignored if --external-topo-server is set.") diff --git a/go/cmd/vtctldclient/command/vreplication/common/cancel.go b/go/cmd/vtctldclient/command/vreplication/common/cancel.go index 756aee968bc..245b2704955 100644 --- a/go/cmd/vtctldclient/command/vreplication/common/cancel.go +++ b/go/cmd/vtctldclient/command/vreplication/common/cancel.go @@ -70,7 +70,7 @@ func commandCancel(cmd *cobra.Command, args []string) error { resp, err := GetClient().WorkflowDelete(GetCommandCtx(), req) if err != nil { if grpcerr, ok := status.FromError(err); ok && (grpcerr.Code() == codes.DeadlineExceeded) { - return fmt.Errorf("Cancel action timed out. Please try again and the work will pick back up where it left off. Note that you can control the timeout using the --action_timeout flag and the delete batch size with --delete-batch-size.") + return fmt.Errorf("Cancel action timed out. Please try again and the work will pick back up where it left off. Note that you can control the timeout using the --action-timeout flag and the delete batch size with --delete-batch-size.") } return err } diff --git a/go/cmd/vtctldclient/plugin_grpctabletconn.go b/go/cmd/vtctldclient/plugin_grpctabletconn.go new file mode 100644 index 00000000000..a4700adb704 --- /dev/null +++ b/go/cmd/vtctldclient/plugin_grpctabletconn.go @@ -0,0 +1,26 @@ +/* +Copyright 2026 The Vitess 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 main + +// Imports and registers the gRPC tabletconn client. +// This is needed when --server=internal as some commands, such as +// switching writes (SwitchTraffic --tablet-types primary), open +// queryservice connections to tablets. + +import ( + _ "vitess.io/vitess/go/vt/vttablet/grpctabletconn" +) diff --git a/go/cmd/vtctldclient/plugin_grpctabletconn_test.go b/go/cmd/vtctldclient/plugin_grpctabletconn_test.go new file mode 100644 index 00000000000..5d6115ae9fa --- /dev/null +++ b/go/cmd/vtctldclient/plugin_grpctabletconn_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2026 The Vitess 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 main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/vt/vttablet/tabletconn" +) + +// TestTabletDialerRegistered checks that the binary registers a dialer for the +// default tablet protocol, which --server=internal needs to dial tablets when +// switching primary traffic. GetDialer exits the process instead of returning +// nil when no dialer is registered, so without the plugin this test fails by +// exiting. +func TestTabletDialerRegistered(t *testing.T) { + assert.NotNil(t, tabletconn.GetDialer()) +} diff --git a/go/cmd/vttestserver/cli/main.go b/go/cmd/vttestserver/cli/main.go index 7732cb5a011..0b68c6f48b7 100644 --- a/go/cmd/vttestserver/cli/main.go +++ b/go/cmd/vttestserver/cli/main.go @@ -150,12 +150,12 @@ func New() (cmd *cobra.Command) { " random data (--initialize-with-random-data option) will only run during"+ " cluster startup if the data directory does not already exist. "+ " Changes to VSchema are persisted across cluster restarts using a simple"+ - " watcher if the --data_dir argument is specified.") + " watcher if the --data-dir argument is specified.") utils.SetFlagBoolVar(cmd.Flags(), &doSeed, "initialize-with-random-data", false, "If this flag is each table-shard will be initialized"+ - " with random data. See also the 'rng_seed' and 'min_shard_size'"+ - " and 'max_shard_size' flags.") + " with random data. See also the --rng-seed, --min-table-shard-size"+ + " and --max-table-shard-size flags.") utils.SetFlagIntVar(cmd.Flags(), &seed.RngSeed, "rng-seed", 123, "The random number generator seed to use when initializing"+ diff --git a/go/flags/endtoend/mysqlctld.txt b/go/flags/endtoend/mysqlctld.txt index 61a4e850b1f..90865dccc13 100644 --- a/go/flags/endtoend/mysqlctld.txt +++ b/go/flags/endtoend/mysqlctld.txt @@ -63,7 +63,7 @@ Flags: --dba-pool-size int Size of the connection pool for dba connections (default 20) --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check diff --git a/go/flags/endtoend/vtbackup.txt b/go/flags/endtoend/vtbackup.txt index 552bf059042..b7a477fdf05 100644 --- a/go/flags/endtoend/vtbackup.txt +++ b/go/flags/endtoend/vtbackup.txt @@ -135,7 +135,7 @@ Flags: --file-backup-storage-root string Root directory for the file backup storage. --gcs-backup-storage-bucket string Google Cloud Storage bucket to use for backups. --gcs-backup-storage-root string Root prefix for all backup-related object names. - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-dial-concurrency-limit int Maximum concurrency of grpc dial operations. This should be less than the golang max thread limit of 10000. (default 1024) --grpc-enable-tracing Enable gRPC tracing. @@ -163,7 +163,7 @@ Flags: --logtostderr log to standard error instead of files --manifest-external-decompressor string command with arguments to store in the backup manifest when compressing a backup with an external compression engine. --min-backup-interval duration Only take a new backup if it's been at least this long since the most recent backup. - --min-retention-count int Always keep at least this many of the most recent backups in this backup storage location, even if some are older than the min_retention_time. This must be at least 1 since a backup must always exist to allow new backups to be made (default 1) + --min-retention-count int Always keep at least this many of the most recent backups in this backup storage location, even if some are older than --min-retention-time. This must be at least 1 since a backup must always exist to allow new backups to be made (default 1) --min-retention-time duration Keep each old backup for at least this long before removing it. Set to 0 to disable pruning of old backups. --mycnf-bin-log-path string mysql binlog path --mycnf-data-dir string data directory for mysql diff --git a/go/flags/endtoend/vtbench.txt b/go/flags/endtoend/vtbench.txt index f1449e468e1..c498da24024 100644 --- a/go/flags/endtoend/vtbench.txt +++ b/go/flags/endtoend/vtbench.txt @@ -62,7 +62,7 @@ Flags: --db-credentials-vault-tokenfile string Path to file containing Vault auth token; token can also be passed using VAULT_TOKEN environment variable --db-credentials-vault-ttl duration How long to cache DB credentials from the Vault server (default 30m0s) --deadline duration Maximum duration for the test run (default 5 minutes) (default 5m0s) - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-dial-concurrency-limit int Maximum concurrency of grpc dial operations. This should be less than the golang max thread limit of 10000. (default 1024) --grpc-enable-tracing Enable gRPC tracing. diff --git a/go/flags/endtoend/vtclient.txt b/go/flags/endtoend/vtclient.txt index b79139b1aad..c696c58a76c 100644 --- a/go/flags/endtoend/vtclient.txt +++ b/go/flags/endtoend/vtclient.txt @@ -9,7 +9,7 @@ Usage: Examples: vtclient --server vtgate:15991 "SELECT * FROM messages" -vtclient --server vtgate:15991 --target '@primary' --bind_variables '[ 12345, 1, "msg 12345" ]' "INSERT INTO messages (page,time_created_ns,message) VALUES (:v1, :v2, :v3)" +vtclient --server vtgate:15991 --target '@primary' --bind-variables '[ 12345, 1, "msg 12345" ]' "INSERT INTO messages (page,time_created_ns,message) VALUES (:v1, :v2, :v3)" Flags: --alsologtostderr log to standard error as well as files @@ -24,7 +24,7 @@ Flags: --datadog-agent-host string host to send spans to. if empty, no tracing will be done --datadog-agent-port string port to send spans to. if empty, no tracing will be done --datadog-trace-debug-mode enable debug mode for datadog tracing - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-dial-concurrency-limit int Maximum concurrency of grpc dial operations. This should be less than the golang max thread limit of 10000. (default 1024) --grpc-enable-tracing Enable gRPC tracing. diff --git a/go/flags/endtoend/vtcombo.txt b/go/flags/endtoend/vtcombo.txt index 0b87393fd4f..80dad918a6f 100644 --- a/go/flags/endtoend/vtcombo.txt +++ b/go/flags/endtoend/vtcombo.txt @@ -23,7 +23,7 @@ Flags: --binlog-in-memory-decompressor-max-size uint This value sets the uncompressed transaction payload size at which we switch from in-memory buffer based decompression to the slower streaming mode. (default 134217728) --binlog-player-protocol string the protocol to download binlogs from a vttablet (default "grpc") --buffer-drain-concurrency int Maximum number of requests retried simultaneously. More concurrency will increase the load on the PRIMARY vttablet when draining the buffer. (default 1) - --buffer-keyspace-shards string If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable_buffer=true. + --buffer-keyspace-shards string If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable-buffer=true. --buffer-max-failover-duration duration Stop buffering completely if a failover takes longer than this duration. (default 20s) --buffer-min-time-between-failovers duration Minimum time between the end of a failover and the start of the next one (tracked per shard). Faster consecutive failovers will not trigger buffering. (default 1m0s) --buffer-size int Maximum number of buffered requests in flight (across all ongoing failovers). (default 1000) @@ -389,7 +389,7 @@ Flags: --transaction-limit-by-principal Include CallerID.principal when considering who the user is for the purpose of transaction limit. (default true) --transaction-limit-by-subcomponent Include CallerID.subcomponent when considering who the user is for the purpose of transaction limit. --transaction-limit-by-username Include VTGateCallerID.username when considering who the user is for the purpose of transaction limit. (default true) - --transaction-limit-per-user float Maximum number of transactions a single user is allowed to use at any time, represented as fraction of -transaction_cap. (default 0.4) + --transaction-limit-per-user float Maximum number of transactions a single user is allowed to use at any time, represented as fraction of --queryserver-config-transaction-cap. (default 0.4) --transaction-log-stream-handler string URL handler for streaming transactions log (default "/debug/txlog") --transaction-mode string SINGLE: disallow multi-db transactions, MULTI: allow multi-db transactions with best effort commit, TWOPC: allow multi-db transactions with 2pc commit (default "MULTI") --truncate-error-len int truncate errors sent to client if they are longer than this value (0 means do not truncate) @@ -418,7 +418,7 @@ Flags: --vreplication-retry-delay duration delay before retrying a failed workflow event in the replication phase (default 5s) --vreplication-store-compressed-gtid Store compressed gtids in the pos column of the sidecar database's vreplication table --vschema-ddl-authorized-users string List of users authorized to execute vschema ddl operations, or '%' to allow all users. - --vschema-persistence-dir string If set, per-keyspace vschema will be persisted in this directory and reloaded into the in-memory topology server across restarts. Bookkeeping is performed using a simple watcher goroutine. This is useful when running vtcombo as an application development container (e.g. vttestserver) where you want to keep the same vschema even if developer's machine reboots. This works in tandem with vttestserver's --persistent_mode flag. Needless to say, this is neither a perfect nor a production solution for vschema persistence. Consider using the --external-topo-server flag if you require a more complete solution. This flag is ignored if --external-topo-server is set. + --vschema-persistence-dir string If set, per-keyspace vschema will be persisted in this directory and reloaded into the in-memory topology server across restarts. Bookkeeping is performed using a simple watcher goroutine. This is useful when running vtcombo as an application development container (e.g. vttestserver) where you want to keep the same vschema even if developer's machine reboots. This works in tandem with vttestserver's --persistent-mode flag. Needless to say, this is neither a perfect nor a production solution for vschema persistence. Consider using the --external-topo-server flag if you require a more complete solution. This flag is ignored if --external-topo-server is set. --vstream-binlog-rotation-threshold int Byte size at which a VStreamer will attempt to rotate the source's open binary log before starting a GTID snapshot based stream (e.g. a ResultStreamer or RowStreamer) (default 67108864) --vstream-dynamic-packet-size Enable dynamic packet sizing for vstreamers. This will adjust the packet size in vreplication workflows to improve performance. (default true) --vstream-packet-size int Suggested packet size for vstreamers. The actual packet size may be more or less than this amount. (default 250000) diff --git a/go/flags/endtoend/vtctlclient.txt b/go/flags/endtoend/vtctlclient.txt index ac6b76fcf42..eb78315b777 100644 --- a/go/flags/endtoend/vtctlclient.txt +++ b/go/flags/endtoend/vtctlclient.txt @@ -10,7 +10,7 @@ Usage of vtctlclient: --datadog-agent-host string host to send spans to. if empty, no tracing will be done --datadog-agent-port string port to send spans to. if empty, no tracing will be done --datadog-trace-debug-mode enable debug mode for datadog tracing - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-dial-concurrency-limit int Maximum concurrency of grpc dial operations. This should be less than the golang max thread limit of 10000. (default 1024) --grpc-enable-tracing Enable gRPC tracing. diff --git a/go/flags/endtoend/vtctld.txt b/go/flags/endtoend/vtctld.txt index b591b78a79f..830f2e24a6c 100644 --- a/go/flags/endtoend/vtctld.txt +++ b/go/flags/endtoend/vtctld.txt @@ -58,7 +58,7 @@ Flags: --gcs-backup-storage-root string Root prefix for all backup-related object names. --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check diff --git a/go/flags/endtoend/vtctldclient.txt b/go/flags/endtoend/vtctldclient.txt index 97214868b41..f7cc1502f10 100644 --- a/go/flags/endtoend/vtctldclient.txt +++ b/go/flags/endtoend/vtctldclient.txt @@ -122,7 +122,7 @@ Flags: --action-timeout duration timeout to use for the command (default 1h0m0s) --alsologtostderr log to standard error as well as files --compact use compact format for otherwise verbose outputs - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-enable-tracing Enable gRPC tracing. --grpc-initial-conn-window-size int gRPC initial connection window size diff --git a/go/flags/endtoend/vtgate.txt b/go/flags/endtoend/vtgate.txt index 56185cb6fad..581b3237c67 100644 --- a/go/flags/endtoend/vtgate.txt +++ b/go/flags/endtoend/vtgate.txt @@ -32,7 +32,7 @@ Flags: --balancer-vtgate-cells strings When in balanced mode, a comma-separated list of cells that contain vtgates (required) --bind-address string Bind address for the server. If empty, the server will listen on all available unicast and anycast IP addresses of the local system. --buffer-drain-concurrency int Maximum number of requests retried simultaneously. More concurrency will increase the load on the PRIMARY vttablet when draining the buffer. (default 1) - --buffer-keyspace-shards string If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable_buffer=true. + --buffer-keyspace-shards string If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable-buffer=true. --buffer-max-failover-duration duration Stop buffering completely if a failover takes longer than this duration. (default 20s) --buffer-min-time-between-failovers duration Minimum time between the end of a failover and the start of the next one (tracked per shard). Faster consecutive failovers will not trigger buffering. (default 1m0s) --buffer-size int Maximum number of buffered requests in flight (across all ongoing failovers). (default 1000) @@ -53,7 +53,7 @@ Flags: --dbddl-plugin string controls how to handle CREATE/DROP DATABASE. use it if you are using your own database provisioning service (default "fail") --ddl-strategy string Set default strategy for DDL statements. Override with @@ddl_strategy session variable (default "direct") --default-tablet-type topodatapb.TabletType The default tablet type to set for queries, when one is not explicitly selected. (default PRIMARY) - --discovery-high-replication-lag-minimum-serving duration Threshold above which replication lag is considered too high when applying the min_number_serving_vttablets flag. (default 2h0m0s) + --discovery-high-replication-lag-minimum-serving duration Threshold above which replication lag is considered too high when applying the --min-number-serving-vttablets flag. (default 2h0m0s) --discovery-low-replication-lag duration Threshold below which replication lag is considered low enough to be healthy. (default 30s) --emit-stats If set, emit stats to push-based monitoring and stats backends --enable-balancer Enable the tablet balancer to evenly spread query load for a given tablet type @@ -70,7 +70,7 @@ Flags: --gateway-initial-tablet-timeout duration At startup, the tabletGateway will wait up to this duration to get at least one tablet per keyspace/shard/tablet type (default 30s) --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check @@ -122,7 +122,7 @@ Flags: --max-payload-size int The threshold for query payloads in bytes. A payload greater than this threshold will result in a failure to handle the query. --max-stack-size int configure the maximum stack size in bytes (default 67108864) --message-stream-grace-period duration the amount of time to give for a vttablet to resume if it ends a message stream, usually because of a reparent. (default 30s) - --min-number-serving-vttablets int The minimum number of vttablets for each replicating tablet_type (e.g. replica, rdonly) that will be continue to be used even with replication lag above discovery_low_replication_lag, but still below discovery_high_replication_lag_minimum_serving. (default 2) + --min-number-serving-vttablets int The minimum number of vttablets for each replicating tablet_type (e.g. replica, rdonly) that will be continue to be used even with replication lag above --discovery-low-replication-lag, but still below --discovery-high-replication-lag-minimum-serving. (default 2) --mysql-allow-clear-text-without-tls If set, the server will allow the use of a clear text password over non-SSL connections. --mysql-auth-server-impl string Which auth server implementation to use. Options: none, ldap, clientcert, static, vault. (default "static") --mysql-auth-server-static-file string JSON File to read the users/passwords from. diff --git a/go/flags/endtoend/vtgateclienttest.txt b/go/flags/endtoend/vtgateclienttest.txt index ec9a2a3347a..0f56ab5e796 100644 --- a/go/flags/endtoend/vtgateclienttest.txt +++ b/go/flags/endtoend/vtgateclienttest.txt @@ -16,7 +16,7 @@ Flags: --default-tablet-type topodatapb.TabletType The default tablet type to set for queries, when one is not explicitly selected. (default PRIMARY) --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check diff --git a/go/flags/endtoend/vtorc.txt b/go/flags/endtoend/vtorc.txt index 18a1d89ec80..91c5ec4d22c 100644 --- a/go/flags/endtoend/vtorc.txt +++ b/go/flags/endtoend/vtorc.txt @@ -38,7 +38,7 @@ Flags: --discovery-workers int Number of workers used for tablet discovery (default 300) --emit-stats If set, emit stats to push-based monitoring and stats backends --enable-primary-disk-stalled-recovery Whether VTOrc should detect a stalled disk on the primary and failover - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy --grpc-dial-concurrency-limit int Maximum concurrency of grpc dial operations. This should be less than the golang max thread limit of 10000. (default 1024) --grpc-enable-tracing Enable gRPC tracing. diff --git a/go/flags/endtoend/vttablet.txt b/go/flags/endtoend/vttablet.txt index 0047c81eddc..35000362539 100644 --- a/go/flags/endtoend/vttablet.txt +++ b/go/flags/endtoend/vttablet.txt @@ -163,7 +163,7 @@ Flags: --gcs-backup-storage-root string Root prefix for all backup-related object names. --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check @@ -397,7 +397,7 @@ Flags: --transaction-limit-by-principal Include CallerID.principal when considering who the user is for the purpose of transaction limit. (default true) --transaction-limit-by-subcomponent Include CallerID.subcomponent when considering who the user is for the purpose of transaction limit. --transaction-limit-by-username Include VTGateCallerID.username when considering who the user is for the purpose of transaction limit. (default true) - --transaction-limit-per-user float Maximum number of transactions a single user is allowed to use at any time, represented as fraction of -transaction_cap. (default 0.4) + --transaction-limit-per-user float Maximum number of transactions a single user is allowed to use at any time, represented as fraction of --queryserver-config-transaction-cap. (default 0.4) --transaction-log-stream-handler string URL handler for streaming transactions log (default "/debug/txlog") --twopc-abandon-age time.Duration Any unresolved transaction older than this time will be sent to the coordinator to be resolved. NOTE: Providing time as seconds (float64) is deprecated. Use time.Duration format (e.g., '1s', '2m', '1h'). (default 15m0s) --tx-throttler-config string The configuration of the transaction throttler as a text-formatted throttlerdata.Configuration protocol buffer message. (default "target_replication_lag_sec:2 max_replication_lag_sec:10 initial_rate:100 max_increase:1 emergency_decrease:0.5 min_duration_between_increases_sec:40 max_duration_between_increases_sec:62 min_duration_between_decreases_sec:20 spread_backlog_across_sec:20 age_bad_rate_after_sec:180 bad_rate_increase:0.1 max_rate_approach_threshold:0.9") diff --git a/go/flags/endtoend/vttestserver.txt b/go/flags/endtoend/vttestserver.txt index 76907be34c0..e2e0303a558 100644 --- a/go/flags/endtoend/vttestserver.txt +++ b/go/flags/endtoend/vttestserver.txt @@ -46,7 +46,7 @@ Flags: --foreign-key-mode string This is to provide how to handle foreign key constraint in create/alter table. Valid values are: allow, disallow (default "allow") --grpc-auth-mode string Which auth plugin implementation to use (eg: static) --grpc-auth-mtls-allowed-substrings string List of substrings of at least one of the client certificate names (separated by colon). - --grpc-auth-static-client-creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc-auth-static-client-creds string When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server. --grpc-auth-static-password-file string JSON File to read the users/passwords from. --grpc-bind-address string Bind address for gRPC calls. If empty, listen on all addresses. --grpc-ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check @@ -75,7 +75,7 @@ Flags: --grpc-server-keepalive-time duration After a duration of this time, if the server doesn't see any activity, it pings the client to see if the transport is still alive. (default 10s) --grpc-server-keepalive-timeout duration After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that the connection is closed. (default 10s) -h, --help help for vttestserver - --initialize-with-random-data If this flag is each table-shard will be initialized with random data. See also the 'rng_seed' and 'min_shard_size' and 'max_shard_size' flags. + --initialize-with-random-data If this flag is each table-shard will be initialized with random data. See also the --rng-seed, --min-table-shard-size and --max-table-shard-size flags. --initialize-with-vt-dba-tcp If this flag is enabled, MySQL will be initialized with an additional user named vt_dba_tcp, who will have access via TCP/IP connection. --keep-logs duration keep logs for this long (using ctime) (zero to keep forever) --keep-logs-by-mtime duration keep logs for this long (using mtime) (zero to keep forever) @@ -106,7 +106,7 @@ Flags: --num-shards strings Comma separated shard count (one per keyspace) (default [2]) --onclose-timeout duration wait no more than this for OnClose handlers before stopping (default 10s) --onterm-timeout duration wait no more than this for OnTermSync handlers before stopping (default 10s) - --persistent-mode If this flag is set, the MySQL data directory is not cleaned up when LocalCluster.TearDown() is called. This is useful for running vttestserver as a database container in local developer environments. Note that db migration files (--schema-dir option) and seeding of random data (--initialize-with-random-data option) will only run during cluster startup if the data directory does not already exist. Changes to VSchema are persisted across cluster restarts using a simple watcher if the --data_dir argument is specified. + --persistent-mode If this flag is set, the MySQL data directory is not cleaned up when LocalCluster.TearDown() is called. This is useful for running vttestserver as a database container in local developer environments. Note that db migration files (--schema-dir option) and seeding of random data (--initialize-with-random-data option) will only run during cluster startup if the data directory does not already exist. Changes to VSchema are persisted across cluster restarts using a simple watcher if the --data-dir argument is specified. --pid-file string If set, the process will write its pid to the named file, and delete it on graceful shutdown. --planner-version string Sets the default planner to use when the session has not changed it. Valid values are: Gen4, Gen4Greedy, Gen4Left2Right --pool-hostname-resolve-interval duration if set force an update to all hostnames and reconnect if changed, defaults to 0 (disabled) diff --git a/go/mysql/vault/auth_server_vault.go b/go/mysql/vault/auth_server_vault.go index 3a6df3b6ca4..5f959a1764c 100644 --- a/go/mysql/vault/auth_server_vault.go +++ b/go/mysql/vault/auth_server_vault.go @@ -39,7 +39,7 @@ type AuthServerVault struct { methods []mysql.AuthMethod mu sync.Mutex // users, passwords and user data - // We use the same JSON format as for --mysql_auth_server_static + // We use the same JSON format as for --mysql-auth-server-static-file // Acts as a cache for the in-Vault data entries map[string][]*mysql.AuthServerStaticEntry vaultCacheExpireTicker *time.Ticker @@ -54,11 +54,11 @@ type AuthServerVault struct { func InitAuthServerVault(vaultAddr string, vaultTimeout time.Duration, vaultCACert, vaultPath string, vaultCacheTTL time.Duration, vaultTokenFile, vaultRoleID, vaultRoleSecretIDFile, vaultRoleMountPoint string) { // Check critical parameters. if vaultAddr == "" { - log.Infof("Not configuring AuthServerVault, as --mysql_auth_vault_addr is empty.") + log.Infof("Not configuring AuthServerVault, as --mysql-auth-vault-addr is empty.") return } if vaultPath == "" { - log.Exitf("If using Vault auth server, --mysql_auth_vault_path is required.") + log.Exitf("If using Vault auth server, --mysql-auth-vault-path is required.") } registerAuthServerVault(vaultAddr, vaultTimeout, vaultCACert, vaultPath, vaultCacheTTL, vaultTokenFile, vaultRoleID, vaultRoleSecretIDFile, vaultRoleMountPoint) @@ -76,11 +76,11 @@ func newAuthServerVault(addr string, timeout time.Duration, caCertPath string, p // Validate more parameters token, err := readFromFile(tokenFilePath) if err != nil { - return nil, fmt.Errorf("No Vault token in provided filename for --mysql_auth_vault_tokenfile") + return nil, fmt.Errorf("No Vault token in provided filename for --mysql-auth-vault-tokenfile") } secretID, err := readFromFile(secretIDPath) if err != nil { - return nil, fmt.Errorf("No Vault secret_id in provided filename for --mysql_auth_vault_role_secretidfile") + return nil, fmt.Errorf("No Vault secret_id in provided filename for --mysql-auth-vault-role-secretidfile") } config := vaultapi.NewConfig() diff --git a/go/test/endtoend/backup/vtbackup/backup_only_test.go b/go/test/endtoend/backup/vtbackup/backup_only_test.go index c841ef43a12..a0541e88853 100644 --- a/go/test/endtoend/backup/vtbackup/backup_only_test.go +++ b/go/test/endtoend/backup/vtbackup/backup_only_test.go @@ -241,16 +241,19 @@ func startVtBackup(t *testing.T, initialBackup bool, restartBeforeBackup, disabl // Prepare opentsdb stats file path. statsPath := path.Join(t.TempDir(), fmt.Sprintf("opentsdb.%s.txt", t.Name())) + vtbackupVer, err := cluster.GetMajorVersion("vtbackup") + require.NoError(t, err) + // Take the back using vtbackup executable extraArgs := []string{ "--allow_first_backup", "--db-credentials-file", dbCredentialFile, - utils.GetFlagVariantForTests("--mysql-socket"), mysqlSocket.Name(), + utils.GetFlagVariantForTestsByVersion("--mysql-socket", vtbackupVer), mysqlSocket.Name(), // Use opentsdb for stats. - utils.GetFlagVariantForTests("--stats-backend"), "opentsdb", + utils.GetFlagVariantForTestsByVersion("--stats-backend", vtbackupVer), "opentsdb", // Write stats to file for reading afterwards. - utils.GetFlagVariantForTests("--opentsdb-uri"), fmt.Sprintf("file://%s", statsPath), + utils.GetFlagVariantForTestsByVersion("--opentsdb-uri", vtbackupVer), fmt.Sprintf("file://%s", statsPath), } if restartBeforeBackup { extraArgs = append(extraArgs, "--restart_before_backup") diff --git a/go/test/endtoend/cluster/cluster_process.go b/go/test/endtoend/cluster/cluster_process.go index 99dde80359a..3d112244bb8 100644 --- a/go/test/endtoend/cluster/cluster_process.go +++ b/go/test/endtoend/cluster/cluster_process.go @@ -79,6 +79,23 @@ var ( PerfTest = flag.Bool("perf-test", false, "include end-to-end performance tests") ) +// IsCoverage reports whether this is a coverage run (-is-coverage). Binaries +// then write their coverage profiles on a graceful exit, so teardown must not +// kill them. +func IsCoverage() bool { + return *isCoverage +} + +// KillDiscardedMySQL reports whether a mysqld whose data is never read again +// is killed instead of shut down gracefully. On CI the data directory is +// discarded after the test run, so nothing needs to be flushed to disk and a +// graceful shutdown buys nothing: killing skips the shutdown ack wait that +// can stall a semi-sync primary for many seconds. Coverage runs still shut +// down gracefully so that the mysqlctl shutdown path stays exercised. +func KillDiscardedMySQL() bool { + return os.Getenv("CI") == "true" && !*isCoverage +} + // LocalProcessCluster Testcases need to use this to iniate a cluster type LocalProcessCluster struct { Keyspaces []Keyspace @@ -1069,13 +1086,19 @@ func (cluster *LocalProcessCluster) Teardown() { log.Errorf("Error in vtadmin teardown: %v", err) } + killMySQL := KillDiscardedMySQL() + var mysqlctlProcessList []*exec.Cmd var mysqlctlTabletUIDs []int for _, keyspace := range cluster.Keyspaces { for _, shard := range keyspace.Shards { for _, tablet := range shard.Vttablets { if tablet.MysqlctlProcess.TabletUID > 0 { - if proc, err := tablet.MysqlctlProcess.StopProcess(); err != nil { + if killMySQL { + if err := tablet.MysqlctlProcess.Kill(); err != nil { + log.Errorf("Error in mysqlctl teardown: %v", err) + } + } else if proc, err := tablet.MysqlctlProcess.StopProcess(); err != nil { log.Errorf("Error in mysqlctl teardown: %v", err) } else { mysqlctlProcessList = append(mysqlctlProcessList, proc) diff --git a/go/test/endtoend/cluster/cluster_util.go b/go/test/endtoend/cluster/cluster_util.go index 492e5b22c0b..6b34a1d36df 100644 --- a/go/test/endtoend/cluster/cluster_util.go +++ b/go/test/endtoend/cluster/cluster_util.go @@ -225,6 +225,7 @@ func filterResultWhenRunsForCoverage(input string) string { } func ValidateReplicationIsHealthy(t *testing.T, tablet *Vttablet) bool { + require.NotNil(t, tablet) query := "show replica status" rs, err := tablet.VttabletProcess.QueryTablet(query, "", true) assert.NoError(t, err) diff --git a/go/test/endtoend/cluster/mysqlctl_process.go b/go/test/endtoend/cluster/mysqlctl_process.go index ca741c2814c..edf76ac13e2 100644 --- a/go/test/endtoend/cluster/mysqlctl_process.go +++ b/go/test/endtoend/cluster/mysqlctl_process.go @@ -197,6 +197,14 @@ func (mysqlctl *MysqlctlProcess) Stop() (err error) { } break } + return mysqlctl.Kill() +} + +// Kill kills the mysqld and any associated mysqld_safe processes without a +// graceful shutdown. This is safe when the data directory is discarded right +// after: nothing needs to be flushed, and it skips the semi-sync ack wait +// that can stall a graceful shutdown of a primary for many seconds. +func (mysqlctl *MysqlctlProcess) Kill() error { pidFile := path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/mysql.pid", mysqlctl.TabletUID)) pidBytes, err := os.ReadFile(pidFile) if err != nil { @@ -210,16 +218,26 @@ func (mysqlctl *MysqlctlProcess) Stop() (err error) { } // We first need to try and kill any associated mysqld_safe process or // else it will immediately restart the mysqld process when we kill it. - mspidb, err := exec.Command("sh", "-c", - fmt.Sprintf("ps auxww | grep -E 'mysqld_safe|mariadbd-safe' | grep vt_%010d | awk '{print $2}'", mysqlctl.TabletUID)).Output() + // mysqld_safe runs with --defaults-file=/my.cnf, so matching + // the full tablet directory keeps another cluster's tablet with the same + // UID from matching. The process list is filtered here rather than in a + // shell pipeline, so the path needs no quoting. + tabletDir := path.Dir(pidFile) + "/" + psOut, err := exec.Command("ps", "-eo", "pid=,args=").Output() if err != nil { return err } - mysqldSafePID, err := strconv.Atoi(strings.TrimSpace(string(mspidb))) - // If we found a valid associated mysqld_safe process then let's kill - // it first. - if err == nil && mysqldSafePID > 0 { - if err = syscallutil.Kill(mysqldSafePID, syscall.SIGKILL); err != nil { + for line := range strings.Lines(string(psOut)) { + pidField, args, _ := strings.Cut(strings.TrimSpace(line), " ") + if !strings.Contains(args, tabletDir) || + (!strings.Contains(args, "mysqld_safe") && !strings.Contains(args, "mariadbd-safe")) { + continue + } + mysqldSafePID, err := strconv.Atoi(pidField) + if err != nil { + return err + } + if err := syscallutil.Kill(mysqldSafePID, syscall.SIGKILL); err != nil { return err } } diff --git a/go/test/endtoend/cluster/vtbackup_process.go b/go/test/endtoend/cluster/vtbackup_process.go index 8114924ee2a..e9cde27f116 100644 --- a/go/test/endtoend/cluster/vtbackup_process.go +++ b/go/test/endtoend/cluster/vtbackup_process.go @@ -60,36 +60,28 @@ func (vtbackup *VtbackupProcess) Setup() (err error) { if err != nil { return err } - flags := map[string]string{ - "--topo-implementation": vtbackup.TopoImplementation, - "--topo-global-server-address": vtbackup.TopoGlobalAddress, - "--topo-global-root": vtbackup.TopoGlobalRoot, - "--log_dir": vtbackup.LogDir, + // The dashed flag names only exist from v23 on, and vtbackup may be the + // previous release in upgrade/downgrade tests. + flagFor := func(name string) string { + return utils.GetFlagVariantForTestsByVersion(name, vtbackupVer) + } + vtbackup.proc = exec.Command( + vtbackup.Binary, + flagFor("--topo-implementation"), vtbackup.TopoImplementation, + flagFor("--topo-global-server-address"), vtbackup.TopoGlobalAddress, + flagFor("--topo-global-root"), vtbackup.TopoGlobalRoot, + "--log_dir", vtbackup.LogDir, //initDBfile is required to run vtbackup - "--mysql-port": fmt.Sprintf("%d", vtbackup.MysqlPort), - "--init-db-sql-file": vtbackup.initDBfile, - "--init-keyspace": vtbackup.Keyspace, - "--init-shard": vtbackup.Shard, + flagFor("--mysql-port"), fmt.Sprintf("%d", vtbackup.MysqlPort), + flagFor("--init-db-sql-file"), vtbackup.initDBfile, + flagFor("--init-keyspace"), vtbackup.Keyspace, + flagFor("--init-shard"), vtbackup.Shard, //Backup Arguments are not optional - utils.GetFlagVariantForTestsByVersion("--file-backup-storage-root", vtbackupVer): vtbackup.BackupStorageImplementation, - "--file-backup-storage-root": vtbackup.FileBackupStorageRoot, - } - - utils.SetFlagVariantsForTests(flags, "--topo-implementation", vtbackup.TopoImplementation) - utils.SetFlagVariantsForTests(flags, "--topo-global-server-address", vtbackup.TopoGlobalAddress) - utils.SetFlagVariantsForTests(flags, "--topo-global-root", vtbackup.TopoGlobalRoot) - utils.SetFlagVariantsForTests(flags, "--mysql-port", fmt.Sprintf("%d", vtbackup.MysqlPort)) - utils.SetFlagVariantsForTests(flags, "--init-db-sql-file", vtbackup.initDBfile) - utils.SetFlagVariantsForTests(flags, "--init-keyspace", vtbackup.Keyspace) - utils.SetFlagVariantsForTests(flags, "--init-shard", vtbackup.Shard) - utils.SetFlagVariantsForTests(flags, "--backup-storage-implementation", vtbackup.BackupStorageImplementation) - - vtbackup.proc = exec.Command(vtbackup.Binary) - for k, v := range flags { - vtbackup.proc.Args = append(vtbackup.proc.Args, k, v) - } + flagFor("--backup-storage-implementation"), vtbackup.BackupStorageImplementation, + flagFor("--file-backup-storage-root"), vtbackup.FileBackupStorageRoot, + ) if vtbackup.initialBackup { vtbackup.proc.Args = append(vtbackup.proc.Args, "--initial_backup") diff --git a/go/test/endtoend/cluster/vtgate_process.go b/go/test/endtoend/cluster/vtgate_process.go index 2946f5444f6..12faf3ab391 100644 --- a/go/test/endtoend/cluster/vtgate_process.go +++ b/go/test/endtoend/cluster/vtgate_process.go @@ -239,12 +239,14 @@ func (vtgate *VtgateProcess) Setup() (err error) { if err != nil { return } - vtgate.exit = make(chan error) + // The waiter captures this process and channel: TearDown returns once it + // receives the exit result, and a restart then replaces both fields, so + // the waiter must not read them again when it closes the channel. + proc, exit := vtgate.proc, make(chan error) + vtgate.exit = exit go func() { - if vtgate.proc != nil { - vtgate.exit <- vtgate.proc.Wait() - close(vtgate.exit) - } + exit <- proc.Wait() + close(exit) }() timeout := time.Now().Add(60 * time.Second) @@ -354,16 +356,20 @@ func (vtgate *VtgateProcess) TearDown() error { // We are not checking vtgate's exit code because it sometimes // returns exit code 2, even though vtgate terminates cleanly. + // Since the exit result is ignored, a long wait for a graceful exit + // buys nothing: force-kill after a short grace period. The forced kill + // is not an error either, so that callers like RestartVtgate go on to + // start the new vtgate. select { case <-vtgate.exit: vtgate.proc = nil return nil - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): vtgate.proc.Process.Kill() - err := <-vtgate.exit + <-vtgate.exit vtgate.proc = nil - return err + return nil } } diff --git a/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go b/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go index a1bf634334a..e193e27d8ea 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go +++ b/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go @@ -342,30 +342,37 @@ func testOnlineDDLStatement(t *testing.T, alterStatement string, ddlStrategy str return uuid } -func readMigration(t *testing.T, uuid string) sqltypes.RowNamedValues { - rs := onlineddl.ReadMigrations(t, &vtParams, uuid) - require.NotNil(t, rs) - row := rs.Named().Row() - require.NotNil(t, row) - return row -} - func waitForMigration(t *testing.T, uuid string, timeout time.Duration) sqltypes.RowNamedValues { - var status string - sleepDuration := time.Second - for timeout > 0 { - row := readMigration(t, uuid) - status = row["migration_status"].ToString() - switch status { - case string(schema.OnlineDDLStatusComplete), string(schema.OnlineDDLStatusFailed), string(schema.OnlineDDLStatusCancelled): - // migration is complete, either successful or not - return row + terminalStatuses := []string{string(schema.OnlineDDLStatusComplete), string(schema.OnlineDDLStatusFailed), string(schema.OnlineDDLStatusCancelled)} + query, err := sqlparser.ParseAndBind("show vitess_migrations like %a", sqltypes.StringBindVariable(uuid)) + require.NoError(t, err) + var row sqltypes.RowNamedValues + require.EventuallyWithT(t, func(c *assert.CollectT) { + conn, err := mysql.Connect(t.Context(), &vtParams) + if !assert.NoError(c, err) { + return } - time.Sleep(sleepDuration) - timeout = timeout - sleepDuration - } - require.NoError(t, fmt.Errorf("timeout in waitForMigration(%s). status is: %s", uuid, status)) - return nil + defer conn.Close() + rs, err := conn.ExecuteFetch(query, -1, true) + if !assert.NoError(c, err) || !assert.NotNil(c, rs) { + return + } + migration := rs.Named().Row() + if !assert.NotNil(c, migration) { + return + } + // The migration is complete, either successful or not. + status := migration["migration_status"].ToString() + if !assert.Contains(c, terminalStatuses, status, "migration %s is not complete", uuid) { + return + } + // A failing migration gets its status before its message, so wait for the message too. + if status == string(schema.OnlineDDLStatusFailed) && !assert.NotEmpty(c, migration["message"].ToString(), "migration %s has no failure message yet", uuid) { + return + } + row = migration + }, timeout, 100*time.Millisecond) + return row } func getTablet() *cluster.Vttablet { diff --git a/go/test/endtoend/reparent/emergencyreparent/ers_test.go b/go/test/endtoend/reparent/emergencyreparent/ers_test.go index cf0adaf507f..d8eb4a791ce 100644 --- a/go/test/endtoend/reparent/emergencyreparent/ers_test.go +++ b/go/test/endtoend/reparent/emergencyreparent/ers_test.go @@ -19,6 +19,7 @@ package emergencyreparent import ( "context" "os/exec" + "slices" "sync" "testing" "time" @@ -48,7 +49,12 @@ func TestTrivialERS(t *testing.T) { out, err := utils.Ers(clusterInstance, nil, "60s", "30s") log.Infof("ERS loop %d. EmergencyReparentShard Output: %v", i, out) require.NoError(t, err) - time.Sleep(5 * time.Second) + + // ERS repoints some replicas after it returns: wait until every + // tablet replicates from the new primary before the next ERS. + primary := utils.GetShardPrimary(t, clusterInstance) + replicas := slices.DeleteFunc(slices.Clone(tablets), func(tablet *cluster.Vttablet) bool { return tablet == primary }) + utils.ConfirmReplication(t, primary, replicas) } // We should do the same for vtctl binary for i := 1; i <= 4; i++ { @@ -211,7 +217,7 @@ func TestEmergencyReparentWithBlockedPrimary(t *testing.T) { }() wg.Add(1) - waitReplicasTimeout := time.Second * 10 + waitReplicasTimeout := time.Second * 5 go func() { defer wg.Done() @@ -228,7 +234,7 @@ func TestEmergencyReparentWithBlockedPrimary(t *testing.T) { wg.Wait() - // We need to wait at least 10 seconds here to ensure the wait-for-replicas-timeout has passed, + // We need to wait until the wait-for-replicas-timeout has passed (twice it, for margin), // before we resume the old primary - otherwise the old primary will receive a `SetReplicationSource` call. time.Sleep(waitReplicasTimeout * 2) @@ -366,7 +372,7 @@ func TestERSPromoteRdonly(t *testing.T) { utils.ConfirmReplication(t, tablets[0], tablets[1:]) // Make the current primary agent and database unavailable. - utils.StopTablet(t, tablets[0], true) + utils.KillTablet(t, tablets[0]) // We expect this one to fail because we have ignored all the replicas and have only the rdonly's which should not be promoted out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "30s", "30s", []*cluster.Vttablet{tablets[3]}, false) @@ -388,7 +394,7 @@ func TestERSPreventCrossCellPromotion(t *testing.T) { utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{tablets[1], tablets[2], tablets[3]}) // Make the current primary agent and database unavailable. - utils.StopTablet(t, tablets[0], true) + utils.KillTablet(t, tablets[0]) // We expect that tablets[2] will be promoted since it is in the same cell as the previous primary out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "60s", "30s", []*cluster.Vttablet{tablets[1]}, true) @@ -437,7 +443,7 @@ func TestPullFromRdonly(t *testing.T) { insertVal := utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{rdonly}) // Make the current primary agent and database unavailable. - utils.StopTablet(t, tablets[0], true) + utils.KillTablet(t, tablets[0]) // start the replication back on the two tablets err = clusterInstance.VtctldClientProcess.ExecuteCommand("StartReplication", tablets[2].Alias) @@ -585,8 +591,8 @@ func TestRecoverWithMultipleFailures(t *testing.T) { utils.ConfirmReplication(t, tablets[0], tablets[1:]) // Make the rdonly and primary tablets and databases unavailable. - utils.StopTablet(t, tablets[1], true) - utils.StopTablet(t, tablets[0], true) + utils.KillTablet(t, tablets[1]) + utils.KillTablet(t, tablets[0]) // We expect this to succeed since we only have 1 primary eligible tablet which is down out, err := utils.Ers(clusterInstance, nil, "30s", "10s") diff --git a/go/test/endtoend/reparent/newfeaturetest/reparent_test.go b/go/test/endtoend/reparent/newfeaturetest/reparent_test.go index 6d4a5fb0f2b..b8010877e51 100644 --- a/go/test/endtoend/reparent/newfeaturetest/reparent_test.go +++ b/go/test/endtoend/reparent/newfeaturetest/reparent_test.go @@ -83,8 +83,8 @@ func TestSingleReplicaERS(t *testing.T) { // Delete and stop two tablets. We only want to have 2 tablets for this test. utils.DeleteTablet(t, clusterInstance, tablets[2]) utils.DeleteTablet(t, clusterInstance, tablets[3]) - utils.StopTablet(t, tablets[2], true) - utils.StopTablet(t, tablets[3], true) + utils.KillTablet(t, tablets[2]) + utils.KillTablet(t, tablets[3]) // Reparent to the other replica output, err := utils.Prs(t, clusterInstance, tablets[1]) @@ -94,7 +94,7 @@ func TestSingleReplicaERS(t *testing.T) { utils.ConfirmReplication(t, tablets[1], []*cluster.Vttablet{tablets[0]}) // Make the current primary vttablet unavailable. - utils.StopTablet(t, tablets[1], true) + utils.KillTablet(t, tablets[1]) // Run an ERS with only one replica reachable. Also, this replica is such that it was a primary before. output, err = utils.Ers(clusterInstance, tablets[0], "", "") diff --git a/go/test/endtoend/reparent/newfeaturetest/reparent_with_open_tx_test.go b/go/test/endtoend/reparent/newfeaturetest/reparent_with_open_tx_test.go index 541378a5604..c718ae32307 100644 --- a/go/test/endtoend/reparent/newfeaturetest/reparent_with_open_tx_test.go +++ b/go/test/endtoend/reparent/newfeaturetest/reparent_with_open_tx_test.go @@ -189,6 +189,10 @@ func TestErrorsInTransaction(t *testing.T) { clusterInstance := utils.SetupShardedReparentCluster(t, policy.DurabilitySemiSync, []string{ "--queryserver-config-transaction-timeout", "5m", "--queryserver-config-query-timeout", "5m", + // vtgate keeps a health stream open to every tablet, so each vttablet + // stop waits out the whole OnTermSync timeout (10s by default) for the + // gRPC server to stop gracefully. + "--onterm-timeout", "2s", }) defer utils.TeardownCluster(clusterInstance) diff --git a/go/test/endtoend/reparent/plannedreparent/reparent_test.go b/go/test/endtoend/reparent/plannedreparent/reparent_test.go index 520a08cd42e..2a4936eb58f 100644 --- a/go/test/endtoend/reparent/plannedreparent/reparent_test.go +++ b/go/test/endtoend/reparent/plannedreparent/reparent_test.go @@ -31,7 +31,6 @@ import ( "vitess.io/vitess/go/mysql/replication" "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/reparent/utils" - "vitess.io/vitess/go/vt/log" replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata" "vitess.io/vitess/go/vt/vtctl/reparentutil/policy" ) @@ -102,8 +101,16 @@ func TestPRSWithDrainedLaggingTablet(t *testing.T) { res := utils.RunSQL(context.Background(), t, `select msg from vt_insert_test`, tablets[1]) assert.Equal(t, 1, len(res.Rows)) - // Perform a graceful reparent operation - utils.Prs(t, clusterInstance, tablets[2]) + // Perform a graceful reparent operation. The drained tablet can't reach + // the reparent journal within the wait-replicas-timeout because of its + // replication delay, so PRS may report it as failed, but no other tablet. + out, err := utils.PrsWithTimeout(t, clusterInstance, tablets[2], false, "", "5s") + if err != nil { + assert.Contains(t, out, fmt.Sprintf("tablet %s failed to SetReplicationSource", tablets[1].Alias)) + for _, tablet := range []*cluster.Vttablet{tablets[0], tablets[3]} { + assert.NotContains(t, out, fmt.Sprintf("tablet %s failed", tablet.Alias)) + } + } utils.ValidateTopology(t, clusterInstance, false) utils.CheckPrimaryTablet(t, clusterInstance, tablets[2]) } @@ -155,7 +162,7 @@ func TestReparentAvoid(t *testing.T) { utils.CheckPrimaryTablet(t, clusterInstance, tablets[1]) // If we kill the tablet in the same cell as primary then reparent --avoid_tablet will fail. - utils.StopTablet(t, tablets[0], true) + utils.KillTablet(t, tablets[0]) out, err := utils.PrsAvoid(t, clusterInstance, tablets[1]) require.Error(t, err) vtctldVersion, err := cluster.GetMajorVersion("vtctld") @@ -194,20 +201,8 @@ func TestReparentFromOutside(t *testing.T) { func TestReparentFromOutsideWithNoPrimary(t *testing.T) { clusterInstance := utils.SetupReparentCluster(t, policy.DurabilitySemiSync) defer utils.TeardownCluster(clusterInstance) - tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets reparentFromOutside(t, clusterInstance, true) - - // FIXME: @Deepthi: is this needed, since we teardown the cluster, does this achieve any additional test coverage? - // We will have to restart mysql to avoid hanging/locks due to external Reparent - for _, tablet := range tablets { - log.Infof("Restarting MySql for tablet %v", tablet.Alias) - err := tablet.MysqlctlProcess.Stop() - require.NoError(t, err) - tablet.MysqlctlProcess.InitMysql = false - err = tablet.MysqlctlProcess.Start() - require.NoError(t, err) - } } func reparentFromOutside(t *testing.T, clusterInstance *cluster.LocalProcessCluster, downPrimary bool) { diff --git a/go/test/endtoend/reparent/utils/utils.go b/go/test/endtoend/reparent/utils/utils.go index 1ab4ce543b6..a549fb57bc6 100644 --- a/go/test/endtoend/reparent/utils/utils.go +++ b/go/test/endtoend/reparent/utils/utils.go @@ -19,11 +19,14 @@ package utils import ( "context" "encoding/json" + "errors" "fmt" + "net" "os" "os/exec" "path" "reflect" + "strconv" "strings" "testing" "time" @@ -37,6 +40,7 @@ import ( "vitess.io/vitess/go/vt/log" querypb "vitess.io/vitess/go/vt/proto/query" topodatapb "vitess.io/vitess/go/vt/proto/topodata" + "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/utils" "vitess.io/vitess/go/vt/vtctl/reparentutil/policy" "vitess.io/vitess/go/vt/vttablet/tabletconn" @@ -578,6 +582,37 @@ func StopTablet(t *testing.T, tab *cluster.Vttablet, stopDatabase bool) { } } +// KillTablet stops the tablet's vttablet gracefully, then kills its mysqld +// when cluster.KillDiscardedMySQL allows it, and shuts mysqld down +// gracefully otherwise. Use it only for a tablet whose mysqld is never +// started or read again: a kill models a dead primary at least as well as a +// clean shutdown, which takes many seconds for a semi-sync primary. +func KillTablet(t *testing.T, tab *cluster.Vttablet) { + err := tab.VttabletProcess.TearDownWithTimeout(30 * time.Second) + require.NoError(t, err) + if !cluster.KillDiscardedMySQL() { + require.NoError(t, tab.MysqlctlProcess.Stop()) + return + } + require.NoError(t, tab.MysqlctlProcess.Kill()) + // Kill only sends the signal, so wait until mysqld refuses connections. + addr := net.JoinHostPort(Hostname, strconv.Itoa(tab.MySQLPort)) + require.Eventually(t, func() bool { + conn, err := net.DialTimeout("tcp", addr, time.Second) + if err != nil { + return true + } + conn.Close() + return false + }, 30*time.Second, 10*time.Millisecond, "mysqld of %s still accepts connections", tab.Alias) + // The teardown kills mysqld by the pid in this file, which may belong to + // another process by then. + err = os.Remove(path.Join(tab.MysqlctlProcess.BasePath(), "mysql.pid")) + if !errors.Is(err, os.ErrNotExist) { + require.NoError(t, err) + } +} + // RestartTablet restarts the tablet func RestartTablet(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet) { tab.MysqlctlProcess.InitMysql = false @@ -631,6 +666,20 @@ func GetNewPrimary(t *testing.T, clusterInstance *cluster.LocalProcessCluster) * return newPrimary } +// GetShardPrimary returns the tablet that the shard record names as primary. +func GetShardPrimary(t *testing.T, clusterInstance *cluster.LocalProcessCluster) *cluster.Vttablet { + shard, err := clusterInstance.VtctldClientProcess.GetShard(KeyspaceName, ShardName) + require.NoError(t, err) + alias := topoproto.TabletAliasString(shard.GetShard().GetPrimaryAlias()) + for _, tablet := range clusterInstance.Keyspaces[0].Shards[0].Vttablets { + if tablet.Alias == alias { + return tablet + } + } + require.FailNow(t, "the shard primary is not a tablet of the cluster", "primary alias: %q", alias) + return nil +} + // GetShardReplicationPositions gets the shards replication positions. // This should not generally be called directly, instead use the WaitForReplicationToCatchup method. func GetShardReplicationPositions(t *testing.T, clusterInstance *cluster.LocalProcessCluster, keyspaceName, shardName string, doPrint bool) []string { @@ -651,19 +700,19 @@ func GetShardReplicationPositions(t *testing.T, clusterInstance *cluster.LocalPr } func WaitForReplicationToStart(t *testing.T, clusterInstance *cluster.LocalProcessCluster, keyspaceName, shardName string, tabletCnt int, doPrint bool) { - tkr := time.NewTicker(500 * time.Millisecond) + timeout := time.After(replicationWaitTimeout) + tkr := time.NewTicker(100 * time.Millisecond) defer tkr.Stop() for { + strArray := GetShardReplicationPositions(t, clusterInstance, keyspaceName, shardName, true) + if len(strArray) == tabletCnt && strings.Contains(strArray[0], "primary") { // primary first + return + } select { case <-tkr.C: - strArray := GetShardReplicationPositions(t, clusterInstance, KeyspaceName, shardName, true) - if len(strArray) == tabletCnt && strings.Contains(strArray[0], "primary") { // primary first - return - } - case <-time.After(replicationWaitTimeout): + case <-timeout: require.FailNow(t, fmt.Sprintf("replication did not start everywhere in %s/%s within the timeout of %v", keyspaceName, shardName, replicationWaitTimeout)) - return } } } diff --git a/go/test/endtoend/vreplication/cluster_test.go b/go/test/endtoend/vreplication/cluster_test.go index 6f0952830d3..876671ae0c1 100644 --- a/go/test/endtoend/vreplication/cluster_test.go +++ b/go/test/endtoend/vreplication/cluster_test.go @@ -790,7 +790,13 @@ func (vc *VitessCluster) teardown() { } for _, keyspace := range keyspaces { - _ = vc.TearDownKeyspace(keyspace) + if cluster.IsCoverage() { + // Coverage runs need a graceful exit so that the instrumented + // binaries write their coverage profiles. + _ = vc.TearDownKeyspace(keyspace) + } else { + _ = vc.killKeyspaceProcesses(keyspace) + } } if err := vc.Vtctld.TearDown(); err != nil { log.Infof("Error stopping Vtctld: %s", err.Error()) @@ -813,6 +819,34 @@ func (vc *VitessCluster) teardown() { } } +// killKeyspaceProcesses kills the keyspace's vttablet and mysqld processes +// without a graceful shutdown. This is only safe during the final cluster +// teardown, where the data directory is deleted right afterwards: nothing +// needs to be flushed, and killing skips both the semi-sync ack wait that +// stalls a graceful primary mysqld shutdown and the vttablet termination +// timeout once its mysqld is gone. Mid-test decommissioning of a keyspace +// must keep using TearDownKeyspace. +func (vc *VitessCluster) killKeyspaceProcesses(ks *Keyspace) error { + eg := errgroup.Group{} + for _, shard := range ks.Shards { + for _, tablet := range shard.Tablets { + eg.Go(func() error { + // The returned error is the process exit status: after a kill + // that is always non-nil ("signal: killed"), so it is not + // logged here. + _ = tablet.Vttablet.Kill() + if tablet.DbServer != nil && tablet.DbServer.TabletUID > 0 { + if err := tablet.DbServer.Kill(); err != nil { + log.Infof("Error killing mysql process associated with vttablet %s: %v", tablet.Name, err) + } + } + return nil + }) + } + } + return eg.Wait() +} + func (vc *VitessCluster) TearDownKeyspace(ks *Keyspace) error { eg := errgroup.Group{} for _, shard := range ks.Shards { @@ -861,8 +895,7 @@ func (vc *VitessCluster) TearDown() { case <-time.After(1 * time.Minute): log.Infof("TearDown() timed out") } - // some processes seem to hang around for a bit - time.Sleep(5 * time.Second) + // Straggling processes are covered by CleanupDataroot's retry loop. vc.CleanupDataroot(vc.t, false) } diff --git a/go/test/endtoend/vreplication/vstream_test.go b/go/test/endtoend/vreplication/vstream_test.go index 0a4205a722e..2d896651036 100644 --- a/go/test/endtoend/vreplication/vstream_test.go +++ b/go/test/endtoend/vreplication/vstream_test.go @@ -20,6 +20,8 @@ import ( "context" "fmt" "io" + "slices" + "strconv" "strings" "sync" "sync/atomic" @@ -29,6 +31,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sets" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/utils" @@ -36,6 +39,7 @@ import ( "vitess.io/vitess/go/vt/vtgate/vtgateconn" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" + querypb "vitess.io/vitess/go/vt/proto/query" topodatapb "vitess.io/vitess/go/vt/proto/topodata" vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" ) @@ -98,7 +102,8 @@ func TestVStreamWithTablesToSkipCopyFlag(t *testing.T) { // Stream events from the VStream API reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) require.NoError(t, err) - var numRowEvents int64 + var numRowEvents atomic.Int64 + var sentinelSeen atomic.Bool copyPhaseCompleted := atomic.Bool{} copyPhaseCompleted.Store(false) @@ -120,7 +125,10 @@ func TestVStreamWithTablesToSkipCopyFlag(t *testing.T) { require.Len(t, escapedTableNameParts, 2) copiedTables.Insert(escapedTableNameParts[1]) } - numRowEvents++ + numRowEvents.Add(1) + if isSentinelRow(ev.RowEvent) { + sentinelSeen.Store(true) + } } if ev.Type == binlogdatapb.VEventType_COPY_COMPLETED { copyPhaseCompleted.Store(true) @@ -150,7 +158,8 @@ func TestVStreamWithTablesToSkipCopyFlag(t *testing.T) { stopInserting := atomic.Bool{} stopInserting.Store(false) var insertMu sync.Mutex - go func() { + var inserts sync.WaitGroup + inserts.Go(func() { for { if stopInserting.Load() { return @@ -162,11 +171,12 @@ func TestVStreamWithTablesToSkipCopyFlag(t *testing.T) { execVtgateQuery(t, vtgateConn, defaultSourceKs, fmt.Sprintf("insert into merchant (mname, category) values ('mname%d', 'category%d')", id+100, id)) insertMu.Unlock() } - }() + }) time.Sleep(100 * time.Millisecond) stopInserting.Store(true) - time.Sleep(10 * time.Second) // Give the vstream plenty of time to catchup + inserts.Wait() + waitForSentinelRow(t, vtgateConn, defaultSourceKs, &sentinelSeen) done.Store(true) qr1 := execVtgateQuery(t, vtgateConn, defaultSourceKs, "select count(*) from customer") @@ -197,7 +207,7 @@ func TestVStreamWithTablesToSkipCopyFlag(t *testing.T) { // Since we don't expect merchant table to be part of copy phase, we can // subtract 10 from the total rows found in the 3 tables. wantTotalRows := insertedRows1 + insertedRows2 + insertedRows3 - 10 - assert.Equal(t, wantTotalRows, numRowEvents) + assert.Equal(t, wantTotalRows, numRowEvents.Load()) } // Validates that we have a working VStream API @@ -253,7 +263,8 @@ func testVStreamWithFailover(t *testing.T, failover bool) { defer vtgateConn.Close() // first goroutine that keeps inserting rows into table being streamed until some time elapses after second PRS - go func() { + var inserts sync.WaitGroup + inserts.Go(func() { for { if stopInserting.Load() { return @@ -263,12 +274,13 @@ func testVStreamWithFailover(t *testing.T, failover bool) { execVtgateQuery(t, vtgateConn, defaultSourceKs, fmt.Sprintf("insert into customer (cid, name) values (%d, 'customer%d')", id+100, id)) insertMu.Unlock() } - }() + }) // stream events from the VStream API reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) require.NoError(t, err) - var numRowEvents int64 + var numRowEvents atomic.Int64 + var sentinelSeen atomic.Bool // second goroutine that continuously receives events via VStream API and should be resilient to the two PRS events go func() { for { @@ -278,7 +290,10 @@ func testVStreamWithFailover(t *testing.T, failover bool) { case nil: for _, ev := range evs { if ev.Type == binlogdatapb.VEventType_ROW { - numRowEvents++ + numRowEvents.Add(1) + if isSentinelRow(ev.RowEvent) { + sentinelSeen.Store(true) + } } } case io.EOF: @@ -297,7 +312,7 @@ func testVStreamWithFailover(t *testing.T, failover bool) { ticker := time.NewTicker(1 * time.Second) tickCount := 0 // this for loop implements a mini state machine that does the two PRSs, waits a bit after the second PRS, - // stops the insertions, waits for a bit again for the vstream to catchup and signals the test to stop + // stops the insertions, waits for the vstream to catch up and signals the test to stop for { <-ticker.C tickCount++ @@ -320,7 +335,8 @@ func testVStreamWithFailover(t *testing.T, failover bool) { } time.Sleep(100 * time.Millisecond) stopInserting.Store(true) - time.Sleep(10 * time.Second) // Give the vstream plenty of time to catchup + inserts.Wait() + waitForSentinelRow(t, vtgateConn, defaultSourceKs, &sentinelSeen) done.Store(true) } @@ -334,7 +350,7 @@ func testVStreamWithFailover(t *testing.T, failover bool) { // total number of row events found by the VStream API should match the rows inserted insertedRows, err := qr.Rows[0][0].ToCastInt64() require.NoError(t, err) - require.Equal(t, insertedRows, numRowEvents) + require.Equal(t, insertedRows, numRowEvents.Load()) } const schemaUnsharded = ` @@ -393,6 +409,47 @@ func insertRow(keyspace, table string, id int) { vtgateConn.ExecuteFetch("commit", 1000, false) } +// sentinelCID is the cid of the customer row that a test inserts after its +// other inserts. A single-shard stream delivers commits in order, so once it +// delivers the sentinel row it has delivered every earlier insert. It is +// negative so that it collides with neither explicit nor auto-increment ids. +const sentinelCID = -1 + +// rowValues returns the column values of row, with NULL as an empty string. +func rowValues(row *querypb.Row) []string { + values := make([]string, 0, len(row.GetLengths())) + var offset int64 + for _, length := range row.GetLengths() { + if length < 0 { + values = append(values, "") + continue + } + values = append(values, string(row.Values[offset:offset+length])) + offset += length + } + return values +} + +// isSentinelRow reports whether ev inserts the customer row with cid sentinelCID. +func isSentinelRow(ev *binlogdatapb.RowEvent) bool { + if !strings.HasSuffix(ev.GetTableName(), ".customer") { + return false + } + for _, rowChange := range ev.GetRowChanges() { + if values := rowValues(rowChange.GetAfter()); len(values) > 0 && values[0] == strconv.Itoa(sentinelCID) { + return true + } + } + return false +} + +// waitForSentinelRow inserts the sentinel customer row into keyspace and waits +// until the stream reports it in seen. +func waitForSentinelRow(t *testing.T, vtgateConn *mysql.Conn, keyspace string, seen *atomic.Bool) { + execVtgateQuery(t, vtgateConn, keyspace, fmt.Sprintf("insert into customer (cid, name) values (%d, 'sentinel')", sentinelCID)) + require.Eventually(t, seen.Load, 30*time.Second, 10*time.Millisecond, "the vstream did not deliver the sentinel row") +} + type numEvents struct { numRowEvents, numJournalEvents int64 numDash80Events, num80DashEvents int64 @@ -442,23 +499,26 @@ func testVStreamStopOnReshardFlag(t *testing.T, stopOnReshard bool, baseTabletID }}, } flags := &vtgatepb.VStreamFlags{HeartbeatInterval: 3600, StopOnReshard: stopOnReshard} - done := false + var done, streamEnded, stopInserts atomic.Bool - id := 1000 - // first goroutine that keeps inserting rows into table being streamed until a minute after reshard + // first goroutine that keeps inserting rows into table being streamed until the test has seen the reshard // * if StopOnReshard is false we should keep getting events on the new shards // * if StopOnReshard is true we should get a journal event and no events on the new shards - go func() { - for { - if done { - return - } - id++ - time.Sleep(1 * time.Second) + var inserts sync.WaitGroup + inserts.Go(func() { + for id := 1001; !stopInserts.Load(); id++ { + time.Sleep(250 * time.Millisecond) insertRow("sharded", "customer", id) } - }() + }) + stopInserting := func() { + stopInserts.Store(true) + inserts.Wait() + } + // Stop inserting before the deferred teardown, also when the test fails. + defer stopInserting() // stream events from the VStream API + var mu sync.Mutex // protects ne var ne numEvents go func() { var reader vtgateconn.VStreamReader @@ -476,6 +536,7 @@ func testVStreamStopOnReshardFlag(t *testing.T, stopOnReshard bool, baseTabletID switch err { case nil: + mu.Lock() for _, ev := range evs { switch ev.Type { case binlogdatapb.VEventType_VGTID: @@ -497,13 +558,16 @@ func testVStreamStopOnReshardFlag(t *testing.T, stopOnReshard bool, baseTabletID ne.numJournalEvents++ } } + mu.Unlock() case io.EOF: log.Infof("Stream Ended") - done = true + streamEnded.Store(true) + done.Store(true) default: log.Infof("%s:: remote error: %v", time.Now(), err) numErrors++ if numErrors > 10 { // if vtgate is continuously unavailable error the test + done.Store(true) return } if strings.Contains(strings.ToLower(err.Error()), "unavailable") { @@ -513,32 +577,41 @@ func testVStreamStopOnReshardFlag(t *testing.T, stopOnReshard bool, baseTabletID connect = true } else { // failure, stop test - done = true + done.Store(true) } } - if done { + if done.Load() { return } } }() - ticker := time.NewTicker(1 * time.Second) - tickCount := 0 - for { - <-ticker.C - tickCount++ - switch tickCount { - case 1: - reshard(t, "sharded", "customer", "vstreamStopOnReshard", "-80,80-", - "-40,40-", baseTabletID+400, nil, nil, nil, nil, defaultCellName, 1) - case 60: - done = true - } - if done { - break + reshard(t, "sharded", "customer", "vstreamStopOnReshard", "-80,80-", + "-40,40-", baseTabletID+400, nil, nil, nil, nil, defaultCellName, 1) + + // With StopOnReshard the stream ends cleanly on the reshard journal. + // Without it, the stream must keep going: vtgate starts streaming the new + // shards only after every old shard has reached the journal, and it sends + // a journal to the client before that. Once rows from both new shards + // have arrived, a journal sent by mistake would already have arrived too. + require.EventuallyWithT(t, func(c *assert.CollectT) { + if stopOnReshard { + assert.True(c, streamEnded.Load(), "the vstream did not end with io.EOF after the reshard") + return } - } - return &ne + assert.False(c, done.Load(), "the vstream ended") + mu.Lock() + defer mu.Unlock() + assert.Positive(c, ne.numDash40Events, "no row events from shard -40") + assert.Positive(c, ne.num40DashEvents, "no row events from shard 40-") + }, time.Minute, 100*time.Millisecond) + stopInserting() + done.Store(true) + + mu.Lock() + defer mu.Unlock() + result := ne + return &result } // Validate that we can continue streaming from multiple keyspaces after first copying some tables and then resharding one of the keyspaces @@ -582,40 +655,45 @@ func testVStreamCopyMultiKeyspaceReshard(t *testing.T, baseTabletID int) numEven }}, } flags := &vtgatepb.VStreamFlags{} - done := false + var done, stopInserts, reshardDone atomic.Bool - id := 1000 - // First goroutine that keeps inserting rows into the table being streamed until a minute after reshard + // First goroutine that keeps inserting rows into the table being streamed until the new shards have streamed rows // We should keep getting events on the new shards - go func() { - for { - if done { - return - } - id++ - time.Sleep(1 * time.Second) + var inserts sync.WaitGroup + inserts.Go(func() { + for id := 1001; !stopInserts.Load(); id++ { + time.Sleep(250 * time.Millisecond) insertRow("sharded", "customer", id) } - }() + }) + stopInserting := func() { + stopInserts.Store(true) + inserts.Wait() + } + // Stop inserting before the deferred teardown, also when the test fails. + defer stopInserting() // stream events from the VStream API + var mu sync.Mutex // protects ne and streamedNames var ne numEvents - reshardDone := false + streamedNames := make(map[string]bool) // names of the streamed rows of the sharded customer table go func() { - var reader vtgateconn.VStreamReader - reader, err = vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) - require.NoError(t, err) + reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) + if !assert.NoError(t, err) { + return + } for { evs, err := reader.Recv() switch err { case nil: + mu.Lock() for _, ev := range evs { switch ev.Type { case binlogdatapb.VEventType_ROW: shard := ev.RowEvent.Shard switch shard { case "0": - if reshardDone { + if reshardDone.Load() { ne.numShard0AfterReshardEvents++ } else { ne.numShard0BeforeReshardEvents++ @@ -629,41 +707,63 @@ func testVStreamCopyMultiKeyspaceReshard(t *testing.T, baseTabletID int) numEven case "40-": ne.num40DashEvents++ } + if shard != "0" { + for _, rowChange := range ev.RowEvent.RowChanges { + if values := rowValues(rowChange.GetAfter()); len(values) > 1 { + streamedNames[values[1]] = true + } + } + } ne.numRowEvents++ case binlogdatapb.VEventType_JOURNAL: ne.numJournalEvents++ } } + mu.Unlock() case io.EOF: log.Infof("Stream Ended") - done = true + done.Store(true) default: log.Errorf("Returned err %v", err) - done = true + done.Store(true) } - if done { + if done.Load() { return } } }() - ticker := time.NewTicker(1 * time.Second) - tickCount := 0 - for { - <-ticker.C - tickCount++ - switch tickCount { - case 1: - reshard(t, "sharded", defaultTargetKs, "vstreamCopyMultiKeyspaceReshard", "-80,80-", "-40,40-", baseTabletID+400, nil, nil, nil, nil, defaultCellName, 1) - reshardDone = true - case 60: - done = true + reshard(t, "sharded", defaultTargetKs, "vstreamCopyMultiKeyspaceReshard", "-80,80-", "-40,40-", baseTabletID+400, nil, nil, nil, nil, defaultCellName, 1) + reshardDone.Store(true) + + // Once rows from both new shards have arrived, vtgate has finished the + // reshard transition, so a journal sent by mistake would already have + // arrived (see testVStreamStopOnReshardFlag). + require.EventuallyWithT(t, func(c *assert.CollectT) { + assert.False(c, done.Load(), "the vstream ended") + mu.Lock() + defer mu.Unlock() + assert.Positive(c, ne.numDash40Events, "no row events from shard -40") + assert.Positive(c, ne.num40DashEvents, "no row events from shard 40-") + assert.Positive(c, ne.numShard0AfterReshardEvents, "no row events from the unsharded keyspace after the reshard") + }, time.Minute, 100*time.Millisecond) + stopInserting() + + // Wait until every inserted row has been streamed. + nameResult := execVtgateQuery(t, vtgateConn, "sharded", "select name from customer") + require.EventuallyWithT(t, func(c *assert.CollectT) { + mu.Lock() + defer mu.Unlock() + for _, row := range nameResult.Rows { + assert.Truef(c, streamedNames[row[0].ToString()], "row %s was not streamed", row[0].ToString()) } - if done { - break - } - } - log.Infof("ne=%v", ne) + }, 30*time.Second, 10*time.Millisecond) + done.Store(true) + + mu.Lock() + result := ne + mu.Unlock() + log.Infof("ne=%v", result) // The number of row events streamed by the VStream API should match the number of rows inserted. // This is important for sharded tables, where we need to ensure that no row events are missed during the resharding process. @@ -675,8 +775,8 @@ func testVStreamCopyMultiKeyspaceReshard(t *testing.T, baseTabletID int) numEven customerResult := execVtgateQuery(t, vtgateConn, "sharded", "select count(*) from customer") insertedCustomerRows, err := customerResult.Rows[0][0].ToCastInt64() require.NoError(t, err) - require.Equal(t, insertedCustomerRows, ne.numDash80Events+ne.num80DashEvents+ne.numDash40Events+ne.num40DashEvents) - return ne + require.Equal(t, insertedCustomerRows, result.numDash80Events+result.num80DashEvents+result.numDash40Events+result.num40DashEvents) + return result } // Validate that we can resume a VStream when the keyspace has been resharded @@ -726,24 +826,22 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { // Coordinate go-routines. streamCtx, streamCancel := context.WithTimeout(ctx, 1*time.Minute) defer streamCancel() - done := make(chan struct{}) + stopInserts := make(chan struct{}) + insertsDone := make(chan struct{}) // First goroutine that keeps inserting rows into the table being streamed until the - // stream context is cancelled. + // resumed stream has crossed the reshard, or the stream context is cancelled. go func() { - id := 1 - for { + defer close(insertsDone) + for id := 1; ; id++ { select { case <-streamCtx.Done(): - // Give the VStream a little catch-up time before telling it to stop - // via the done channel. - time.Sleep(10 * time.Second) - close(done) + return + case <-stopInserts: return default: insertRow(ks, "customer", id) time.Sleep(250 * time.Millisecond) - id++ } } }() @@ -756,6 +854,15 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { ShardGtids: []*binlogdatapb.ShardGtid{{ Keyspace: "/.*", // Match all keyspaces just to be more realistic. }}} + // The names of the customer rows seen in either stream. + streamedNames := make(map[string]bool) + recordNames := func(ev *binlogdatapb.RowEvent) { + for _, rowChange := range ev.RowChanges { + if values := rowValues(rowChange.GetAfter()); len(values) > 1 { + streamedNames[values[1]] = true + } + } + } filter := &binlogdatapb.Filter{ Rules: []*binlogdatapb.Rule{{ @@ -785,6 +892,7 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { switch shard { case "-80", "80-": oldShardRowEvents++ + recordNames(ev.RowEvent) case "0": // We expect some for the sequence backing table, but don't care. default: @@ -826,9 +934,14 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { reshardAction(t, "SwitchTraffic", wf, ks, oldShards, newShards, defaultCellName, tabletType) // Now start a new VStream from our previous VGTID which only has the old/original shards. + // Once it has crossed the reshard, stop inserting and stream until every inserted + // row has been seen. func() { - reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, newVGTID, filter, flags) + resumeCtx, resumeCancel := context.WithTimeout(ctx, 2*time.Minute) + defer resumeCancel() + reader, err := vstreamConn.VStream(resumeCtx, topodatapb.TabletType_PRIMARY, newVGTID, filter, flags) require.NoError(t, err) + var insertedNames []string // nil until the inserts have stopped for { evs, err := reader.Recv() @@ -841,8 +954,10 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { switch shard { case "-80", "80-": oldShardRowEvents++ + recordNames(ev.RowEvent) case "-40", "40-80", "80-c0", "c0-": newShardRowEvents++ + recordNames(ev.RowEvent) case "0": // Again, we expect some for the sequence backing table, but don't care. default: @@ -854,12 +969,21 @@ func TestMultiVStreamsKeyspaceReshard(t *testing.T) { } } default: - require.FailNow(t, fmt.Sprintf("VStream returned unexpected error: %v", err)) + require.FailNow(t, fmt.Sprintf("VStream returned unexpected error: %v", err), + "old shard rows: %d, new shard rows: %d, journal events: %d, inserts stopped: %t", + oldShardRowEvents, newShardRowEvents, journalEvents, insertedNames != nil) } - select { - case <-done: + if insertedNames == nil && newShardRowEvents > 0 && journalEvents > 0 { + close(stopInserts) + <-insertsDone + qr := execVtgateQuery(t, vtgateConn, ks, "select name from customer") + insertedNames = make([]string, 0, len(qr.Rows)) + for _, row := range qr.Rows { + insertedNames = append(insertedNames, row[0].ToString()) + } + } + if insertedNames != nil && !slices.ContainsFunc(insertedNames, func(name string) bool { return !streamedNames[name] }) { return - default: } } }() @@ -1128,15 +1252,16 @@ const ( numExpectedHeartbeats = 10 ) -func doVStream(t *testing.T, vc *VitessCluster, flags *vtgatepb.VStreamFlags) (numRowEvents map[string]int, numFieldEvents map[string]int) { - // Stream for a while to ensure heartbeats are sent. - ctx, cancel := context.WithTimeout(context.Background(), vstreamHeartbeatsTestContextTimeout) - defer cancel() - +// doVStream streams the customer and, if flags ask for them, heartbeat events +// until ctx ends. It calls onHeartbeat, if set, with the number of heartbeats +// received so far. It returns an error only if the stream could not start. +func doVStream(t *testing.T, ctx context.Context, vc *VitessCluster, flags *vtgatepb.VStreamFlags, onHeartbeat func(int)) (numRowEvents map[string]int, numFieldEvents map[string]int, err error) { numRowEvents = make(map[string]int) numFieldEvents = make(map[string]int) vstreamConn, err := vtgateconn.Dial(ctx, fmt.Sprintf("%s:%d", vc.ClusterConfig.hostname, vc.ClusterConfig.vtgateGrpcPort)) - require.NoError(t, err) + if err != nil { + return nil, nil, err + } defer vstreamConn.Close() done := false @@ -1155,7 +1280,9 @@ func doVStream(t *testing.T, vc *VitessCluster, flags *vtgatepb.VStreamFlags) (n } // Stream events from the VStream API. reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) - require.NoError(t, err) + if err != nil { + return nil, nil, err + } for !done { evs, err := reader.Recv() switch err { @@ -1165,19 +1292,26 @@ func doVStream(t *testing.T, vc *VitessCluster, flags *vtgatepb.VStreamFlags) (n case binlogdatapb.VEventType_ROW: rowEvent := ev.RowEvent arr := strings.Split(rowEvent.TableName, ".") - require.Equal(t, len(arr), 2) + if !assert.Len(t, arr, 2) { + continue + } tableName := arr[1] - require.Equal(t, defaultSourceKs, rowEvent.Keyspace) - require.Equal(t, "0", rowEvent.Shard) + assert.Equal(t, defaultSourceKs, rowEvent.Keyspace) + assert.Equal(t, "0", rowEvent.Shard) numRowEvents[tableName]++ + if tableName == "heartbeat" && onHeartbeat != nil { + onHeartbeat(numRowEvents[tableName]) + } case binlogdatapb.VEventType_FIELD: fieldEvent := ev.FieldEvent arr := strings.Split(fieldEvent.TableName, ".") - require.Equal(t, len(arr), 2) + if !assert.Len(t, arr, 2) { + continue + } tableName := arr[1] - require.Equal(t, defaultSourceKs, fieldEvent.Keyspace) - require.Equal(t, "0", fieldEvent.Shard) + assert.Equal(t, defaultSourceKs, fieldEvent.Keyspace) + assert.Equal(t, "0", fieldEvent.Shard) numFieldEvents[tableName]++ default: } @@ -1186,11 +1320,11 @@ func doVStream(t *testing.T, vc *VitessCluster, flags *vtgatepb.VStreamFlags) (n log.Infof("Stream Ended") done = true default: - log.Errorf("remote error: %v", err) + log.Infof("stream ended: %v", err) done = true } } - return numRowEvents, numFieldEvents + return numRowEvents, numFieldEvents, nil } // TestVStreamHeartbeats enables streaming of the internal Vitess heartbeat tables in the VStream API and @@ -1243,16 +1377,44 @@ func TestVStreamHeartbeats(t *testing.T) { }, } - for _, tc := range testCases { + // Run both streams at the same time, and stop them once the stream with + // heartbeats on has received the expected heartbeats. The stream with + // heartbeats off was open while those heartbeats were written. + ctx, cancel := context.WithTimeout(t.Context(), vstreamHeartbeatsTestContextTimeout) + defer cancel() + gotNumRowEvents := make([]map[string]int, len(testCases)) + gotNumFieldEvents := make([]map[string]int, len(testCases)) + streamErrs := make([]error, len(testCases)) + var streams sync.WaitGroup + for i, tc := range testCases { + var onHeartbeat func(int) + if tc.expectedHeartbeats > 0 { + onHeartbeat = func(heartbeats int) { + if heartbeats >= tc.expectedHeartbeats { + cancel() + } + } + } + streams.Go(func() { + gotNumRowEvents[i], gotNumFieldEvents[i], streamErrs[i] = doVStream(t, ctx, vc, tc.flags, onHeartbeat) + }) + } + streams.Wait() + + for i, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - gotNumRowEvents, gotNumFieldEvents := doVStream(t, vc, tc.flags) + require.NoError(t, streamErrs[i]) for k := range expectedNumRowEvents { - require.Equalf(t, 1, gotNumFieldEvents[k], "incorrect number of field events for table %s, got %d", k, gotNumFieldEvents[k]) + require.Equalf(t, 1, gotNumFieldEvents[i][k], "incorrect number of field events for table %s, got %d", k, gotNumFieldEvents[i][k]) + } + if tc.expectedHeartbeats == 0 { + require.Zero(t, gotNumRowEvents[i]["heartbeat"], "received heartbeat events with keyspace heartbeats off") + } else { + require.GreaterOrEqual(t, gotNumRowEvents[i]["heartbeat"], tc.expectedHeartbeats, "incorrect number of heartbeat events received") } - require.GreaterOrEqual(t, gotNumRowEvents["heartbeat"], tc.expectedHeartbeats, "incorrect number of heartbeat events received") - log.Infof("Total number of heartbeat events received: %v", gotNumRowEvents["heartbeat"]) - delete(gotNumRowEvents, "heartbeat") - require.Equal(t, expectedNumRowEvents, gotNumRowEvents) + log.Infof("Total number of heartbeat events received: %v", gotNumRowEvents[i]["heartbeat"]) + delete(gotNumRowEvents[i], "heartbeat") + require.Equal(t, expectedNumRowEvents, gotNumRowEvents[i]) }) } } @@ -1295,21 +1457,22 @@ func TestVStreamPushdownFilters(t *testing.T) { // Coordinate go-routines. streamCtx, streamCancel := context.WithTimeout(context.Background(), 10*time.Second) defer streamCancel() - done := make(chan struct{}) + insertsDone := make(chan struct{}) // First goroutine that keeps inserting rows into the table being streamed until the - // stream context is cancelled. + // stream context is cancelled, and then inserts the sentinel row, which is a paul too. createdPauls := startingPauls createdNonPauls := 0 go func() { + defer close(insertsDone) id := 1 for { select { case <-streamCtx.Done(): - // Give the VStream a little catch-up time before telling it to stop - // via the done channel. - time.Sleep(10 * time.Second) - close(done) + _, err := vtgateConn.ExecuteFetch(fmt.Sprintf("insert into `%s`.customer (cid, name) values (%d, 'paÜl')", ks, sentinelCID), 1, false) + if assert.NoError(t, err) { + createdPauls++ + } return default: if id%10 == 0 { @@ -1345,7 +1508,8 @@ func TestVStreamPushdownFilters(t *testing.T) { // So we should have at least one paul row event in the copy phase, and // we should have many paul row events in the running phase. - copyPhaseRowEvents, runningPhaseRowEvents := runVStreamAndGetNumOfRowEvents(t, ctx, vstreamConn, vgtid, filter, done) + copyPhaseRowEvents, runningPhaseRowEvents := runVStreamAndGetNumOfRowEvents(t, ctx, vstreamConn, vgtid, filter) + <-insertsDone require.NotZero(t, createdPauls) require.NotZero(t, createdNonPauls) @@ -1357,12 +1521,15 @@ func TestVStreamPushdownFilters(t *testing.T) { require.Equal(t, createdPauls, copyPhaseRowEvents+runningPhaseRowEvents) } -// runVStreamAndGetNumOfRowEvents runs VStream with the specified filter and -// returns number of copy phase and running phase row events. +// runVStreamAndGetNumOfRowEvents runs VStream with the specified filter until +// it receives the sentinel row, and returns number of copy phase and running +// phase row events. func runVStreamAndGetNumOfRowEvents(t *testing.T, ctx context.Context, vstreamConn *vtgateconn.VTGateConn, - vgtid *binlogdatapb.VGtid, filter *binlogdatapb.Filter, done chan struct{}) (copyPhaseRowEvents int, runningPhaseRowEvents int) { + vgtid *binlogdatapb.VGtid, filter *binlogdatapb.Filter) (copyPhaseRowEvents int, runningPhaseRowEvents int) { copyPhase := true func() { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() reader, err := vstreamConn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, &vtgatepb.VStreamFlags{}) require.NoError(t, err) for { @@ -1370,6 +1537,7 @@ func runVStreamAndGetNumOfRowEvents(t *testing.T, ctx context.Context, vstreamCo switch err { case nil: + sentinelSeen := false for _, ev := range evs { switch ev.Type { case binlogdatapb.VEventType_COPY_COMPLETED: @@ -1380,15 +1548,14 @@ func runVStreamAndGetNumOfRowEvents(t *testing.T, ctx context.Context, vstreamCo } else { runningPhaseRowEvents++ } + sentinelSeen = sentinelSeen || isSentinelRow(ev.RowEvent) } } + if sentinelSeen { + return + } default: - require.FailNow(t, fmt.Sprintf("VStream returned unexpected error: %v", err)) - } - select { - case <-done: - return - default: + require.FailNow(t, fmt.Sprintf("VStream returned unexpected error before the sentinel row: %v", err)) } } }() diff --git a/go/test/endtoend/vtgate/foreignkey/stress/fk_stress_test.go b/go/test/endtoend/vtgate/foreignkey/stress/fk_stress_test.go index c51dba7d0bd..f0b337e4fb5 100644 --- a/go/test/endtoend/vtgate/foreignkey/stress/fk_stress_test.go +++ b/go/test/endtoend/vtgate/foreignkey/stress/fk_stress_test.go @@ -124,6 +124,13 @@ func (w *WriteMetrics) Clear() { w.deletesFKErrors = 0 } +func (w *WriteMetrics) Deletes() int64 { + w.mu.Lock() + defer w.mu.Unlock() + + return w.deletes +} + func (w *WriteMetrics) String() string { return fmt.Sprintf(`WriteMetrics: inserts-deletes=%d, updates-deletes=%d, insertsAttempts=%d, insertsFailures=%d, insertsNoops=%d, inserts=%d, @@ -159,7 +166,8 @@ var ( tableNames = []string{parentTableName, childTableName, child2TableName, grandchildTableName, selfTableName, nofkTableName} reverseTableNames []string - seedOnce sync.Once + seedOnce sync.Once + initGlobalsOnce sync.Once referenceActionMap = map[sqlparser.ReferenceAction]string{ sqlparser.NoAction: "NO ACTION", @@ -415,6 +423,10 @@ func queryTablet(t *testing.T, tablet *cluster.Vttablet, query string, expectErr } func tabletTestName(t *testing.T, tablet *cluster.Vttablet) string { + // A nil tablet must fail here: when the tablet globals are unset, a + // nil argument would otherwise match the first case below and + // mislabel the subtest. + require.NotNil(t, tablet) switch tablet { case primary: return "primary" @@ -507,27 +519,38 @@ func validateMetrics(t *testing.T, tcase *testCase) { } } +// initTestGlobals wires the package globals (tablets, table orderings, +// write metrics). Every root test must call it first: the test runner +// reruns a failed root test with an exact -run filter, so a root test +// cannot rely on another root test having run before it in the same +// process. +func initTestGlobals(t *testing.T) { + initGlobalsOnce.Do(func() { + shards = clusterInstance.Keyspaces[0].Shards + require.Len(t, shards, 1) + require.Len(t, shards[0].Vttablets, 3) // primary, no-fk replica, fk replica + primary = shards[0].Vttablets[0] + require.NotNil(t, primary) + replicaNoFK = shards[0].Vttablets[1] + require.NotNil(t, replicaNoFK) + require.NotEqual(t, primary.Alias, replicaNoFK.Alias) + replicaFK = shards[0].Vttablets[2] + require.NotNil(t, replicaFK) + require.NotEqual(t, primary.Alias, replicaFK.Alias) + require.NotEqual(t, replicaNoFK.Alias, replicaFK.Alias) + + reverseTableNames = slices.Clone(tableNames) + slices.Reverse(reverseTableNames) + require.ElementsMatch(t, tableNames, reverseTableNames) + + for _, tableName := range tableNames { + writeMetrics[tableName] = &WriteMetrics{} + } + }) +} + func TestInitialSetup(t *testing.T) { - shards = clusterInstance.Keyspaces[0].Shards - require.Equal(t, 1, len(shards)) - require.Equal(t, 3, len(shards[0].Vttablets)) // primary, no-fk replica, fk replica - primary = shards[0].Vttablets[0] - require.NotNil(t, primary) - replicaNoFK = shards[0].Vttablets[1] - require.NotNil(t, replicaNoFK) - require.NotEqual(t, primary.Alias, replicaNoFK.Alias) - replicaFK = shards[0].Vttablets[2] - require.NotNil(t, replicaFK) - require.NotEqual(t, primary.Alias, replicaFK.Alias) - require.NotEqual(t, replicaNoFK.Alias, replicaFK.Alias) - - reverseTableNames = slices.Clone(tableNames) - slices.Reverse(reverseTableNames) - require.ElementsMatch(t, tableNames, reverseTableNames) - - for _, tableName := range tableNames { - writeMetrics[tableName] = &WriteMetrics{} - } + initTestGlobals(t) } type testCase struct { @@ -653,6 +676,7 @@ func ExecuteFKTest(t *testing.T, tcase *testCase) { } func TestStressFK(t *testing.T) { + initTestGlobals(t) t.Run("validate replication health", func(t *testing.T) { validateReplicationIsHealthy(t, replicaNoFK) @@ -1240,9 +1264,22 @@ func populateTables(t *testing.T, tcase *testCase) { for i := 0; i < maxTableRows/4; i++ { generateUpdate(t, tableName, conn) } - for i := 0; i < maxTableRows/4; i++ { + // A delete only succeeds when the random id hits a row + // whose updates column is 1 and which no child row (or, + // for stress_self, the row itself) references. On + // stress_self most rows are self-referencing, so only a + // handful of the random attempts succeed, and a fixed + // attempt count can end the loop with zero successful + // deletes; testSelectTableMetrics requires at least one. + // The loop exits after the base attempt count once a + // delete has succeeded, or after maxTableRows attempts. + for i := range maxTableRows { + if i >= maxTableRows/4 && writeMetrics[tableName].Deletes() > 0 { + break + } generateDelete(t, tableName, conn) } + require.NotZerof(t, writeMetrics[tableName].Deletes(), "no successful delete on %s while seeding", tableName) }) t.Run("creating seed", func(t *testing.T) { // We create the seed table in the likeness of stress_parent, because that's the only table diff --git a/go/test/endtoend/vtgate/queries/subquery/subquery_test.go b/go/test/endtoend/vtgate/queries/subquery/subquery_test.go index 135b86195a5..8db6bce74e6 100644 --- a/go/test/endtoend/vtgate/queries/subquery/subquery_test.go +++ b/go/test/endtoend/vtgate/queries/subquery/subquery_test.go @@ -56,6 +56,15 @@ func TestSubqueriesHasValues(t *testing.T) { mcmp.Exec("insert into t1(id1, id2) values (0,1),(1,2),(2,3),(3,4),(4,5),(5,6)") mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE id1 IN (SELECT id1 FROM t1 WHERE id1 > 10)`, `[]`) mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE id1 NOT IN (SELECT id1 FROM t1 WHERE id1 > 10) ORDER BY id2`, `[[INT64(1)] [INT64(2)] [INT64(3)] [INT64(4)] [INT64(5)] [INT64(6)]]`) + + // Row IN and NOT IN subqueries that can't be merged bind each row as a tuple, also when they return no rows. + if utils.BinaryIsAtLeastAtVersion(23, "vtgate") { + mcmp.Exec("insert into t2(id3, id4) values (1, 2), (3, 4), (7, 8)") + mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE (id1, id2) IN (SELECT id3, id4 FROM t2) ORDER BY id2`, `[[INT64(2)] [INT64(4)]]`) + mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE (id1, id2) NOT IN (SELECT id3, id4 FROM t2) ORDER BY id2`, `[[INT64(1)] [INT64(3)] [INT64(5)] [INT64(6)]]`) + mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE (id1, id2) IN (SELECT id3, id4 FROM t2 WHERE id3 > 10)`, `[]`) + mcmp.AssertMatches(`SELECT id2 FROM t1 WHERE (id1, id2) NOT IN (SELECT id3, id4 FROM t2 WHERE id3 > 10) ORDER BY id2`, `[[INT64(1)] [INT64(2)] [INT64(3)] [INT64(4)] [INT64(5)] [INT64(6)]]`) + } } func TestNotINQueries(t *testing.T) { @@ -221,6 +230,17 @@ func TestSubqueries(t *testing.T) { `SELECT id, name, (SELECT COUNT(*) FROM user_extra) + id AS total_extra_count_plus_id, AVG(id) AS avg_ids FROM user WHERE id < (SELECT MAX(user_id) FROM user_extra) GROUP BY id, name`, } + // Row comparisons with value subqueries that can't be merged, including one that returns no rows, which + // compares as a row of NULLs. + if utils.BinaryIsAtLeastAtVersion(23, "vtgate") { + queries = append(queries, + `SELECT id FROM user WHERE (id, name) < (SELECT user_id, extra_info FROM user_extra WHERE user_id = 3 ORDER BY extra_info LIMIT 1) ORDER BY id`, + `SELECT id FROM user WHERE (id, 'info1') = (SELECT user_id, extra_info FROM user_extra WHERE user_id = 7)`, + `SELECT id FROM user WHERE (id, name) <=> (SELECT user_id, extra_info FROM user_extra WHERE user_id = 42)`, + `SELECT id, (id, name) >= (SELECT user_id, extra_info FROM user_extra WHERE user_id = 5) FROM user ORDER BY id`, + ) + } + for idx, query := range queries { mcmp.Run(fmt.Sprintf("%d %s", idx, query), func(mcmp *utils.MySQLCompare) { mcmp.Exec(query) diff --git a/go/test/endtoend/vtgate/queries/union/union_test.go b/go/test/endtoend/vtgate/queries/union/union_test.go index 36b7204bdad..2180cae37e1 100644 --- a/go/test/endtoend/vtgate/queries/union/union_test.go +++ b/go/test/endtoend/vtgate/queries/union/union_test.go @@ -67,6 +67,8 @@ func TestUnionDistinct(t *testing.T) { mcmp.AssertMatchesNoOrder("select id1, id2 from t1 union select 827, 452 union select id3,id4 from t2", "[[INT64(4) INT64(4)] [INT64(1) INT64(1)] [INT64(2) INT64(2)] [INT64(3) INT64(3)] [INT64(827) INT64(452)] [INT64(2) INT64(3)] [INT64(3) INT64(4)] [INT64(5) INT64(5)]]") mcmp.AssertMatches("select 1 from dual where 1 IN (select 1 as col union select 2)", "[[INT64(1)]]") + mcmp.AssertMatches("select 1 from dual union select id1 from t1 where id1 in (null)", "[[INT64(1)]]") + mcmp.AssertMatches("select id1 from t1 where id1 in (null) union select 1 from dual", "[[INT64(1)]]") if utils.BinaryIsAtLeastAtVersion(19, "vtgate") { mcmp.AssertMatches(`SELECT 1 from t1 UNION SELECT 2 from t1`, `[[INT64(1)] [INT64(2)]]`) mcmp.AssertMatches(`SELECT 5 from t1 UNION SELECT 6 from t1`, `[[INT64(5)] [INT64(6)]]`) diff --git a/go/vt/discovery/replicationlag.go b/go/vt/discovery/replicationlag.go index f9475ccddfc..c0ce31d13c7 100644 --- a/go/vt/discovery/replicationlag.go +++ b/go/vt/discovery/replicationlag.go @@ -68,8 +68,8 @@ func init() { func registerReplicationFlags(fs *pflag.FlagSet) { fs.Duration("discovery-low-replication-lag", lowReplicationLag.Default(), "Threshold below which replication lag is considered low enough to be healthy.") - fs.Duration("discovery-high-replication-lag-minimum-serving", highReplicationLagMinServing.Default(), "Threshold above which replication lag is considered too high when applying the min_number_serving_vttablets flag.") - fs.Int("min-number-serving-vttablets", minNumTablets.Default(), "The minimum number of vttablets for each replicating tablet_type (e.g. replica, rdonly) that will be continue to be used even with replication lag above discovery_low_replication_lag, but still below discovery_high_replication_lag_minimum_serving.") + fs.Duration("discovery-high-replication-lag-minimum-serving", highReplicationLagMinServing.Default(), "Threshold above which replication lag is considered too high when applying the --min-number-serving-vttablets flag.") + fs.Int("min-number-serving-vttablets", minNumTablets.Default(), "The minimum number of vttablets for each replicating tablet_type (e.g. replica, rdonly) that will be continue to be used even with replication lag above --discovery-low-replication-lag, but still below --discovery-high-replication-lag-minimum-serving.") fs.Bool("legacy-replication-lag-algorithm", legacyReplicationLagAlgorithm.Default(), "Use the legacy algorithm when selecting vttablets for serving.") viperutil.BindFlags(fs, diff --git a/go/vt/grpcclient/client.go b/go/vt/grpcclient/client.go index ffab53a17a8..71b5d33a3b8 100644 --- a/go/vt/grpcclient/client.go +++ b/go/vt/grpcclient/client.go @@ -79,7 +79,7 @@ func RegisterFlags(fs *pflag.FlagSet) { utils.SetFlagIntVar(fs, &initialWindowSize, "grpc-initial-window-size", initialWindowSize, "gRPC initial window size") utils.SetFlagStringVar(fs, &compression, "grpc-compression", compression, "Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy") - utils.SetFlagStringVar(fs, &credsFile, "grpc-auth-static-client-creds", credsFile, "When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server.") + utils.SetFlagStringVar(fs, &credsFile, "grpc-auth-static-client-creds", credsFile, "When using --grpc-auth-mode=static in the server, this file provides the credentials to use to authenticate with server.") } func RegisterDialConcurrencyFlags(fs *pflag.FlagSet) { diff --git a/go/vt/proto/binlogdata/binlogdata.pb.go b/go/vt/proto/binlogdata/binlogdata.pb.go index a35f5439698..4995f032824 100644 --- a/go/vt/proto/binlogdata/binlogdata.pb.go +++ b/go/vt/proto/binlogdata/binlogdata.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: binlogdata.proto diff --git a/go/vt/proto/binlogservice/binlogservice.pb.go b/go/vt/proto/binlogservice/binlogservice.pb.go index 363f8123c0e..d9669eab454 100644 --- a/go/vt/proto/binlogservice/binlogservice.pb.go +++ b/go/vt/proto/binlogservice/binlogservice.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: binlogservice.proto diff --git a/go/vt/proto/logutil/logutil.pb.go b/go/vt/proto/logutil/logutil.pb.go index 23c1f6472b7..d0ee4088017 100644 --- a/go/vt/proto/logutil/logutil.pb.go +++ b/go/vt/proto/logutil/logutil.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: logutil.proto diff --git a/go/vt/proto/mysqlctl/mysqlctl.pb.go b/go/vt/proto/mysqlctl/mysqlctl.pb.go index ccbefc9af64..c97c75df867 100644 --- a/go/vt/proto/mysqlctl/mysqlctl.pb.go +++ b/go/vt/proto/mysqlctl/mysqlctl.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: mysqlctl.proto diff --git a/go/vt/proto/query/query.pb.go b/go/vt/proto/query/query.pb.go index 6d225ffe225..9799f9f0776 100644 --- a/go/vt/proto/query/query.pb.go +++ b/go/vt/proto/query/query.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: query.proto diff --git a/go/vt/proto/queryservice/queryservice.pb.go b/go/vt/proto/queryservice/queryservice.pb.go index fc19fd9e66b..af1c5bbec2e 100644 --- a/go/vt/proto/queryservice/queryservice.pb.go +++ b/go/vt/proto/queryservice/queryservice.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: queryservice.proto diff --git a/go/vt/proto/replicationdata/replicationdata.pb.go b/go/vt/proto/replicationdata/replicationdata.pb.go index 5079a71b4eb..5e9055ee6a9 100644 --- a/go/vt/proto/replicationdata/replicationdata.pb.go +++ b/go/vt/proto/replicationdata/replicationdata.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: replicationdata.proto diff --git a/go/vt/proto/tableacl/tableacl.pb.go b/go/vt/proto/tableacl/tableacl.pb.go index 2064a1baaf0..76d3ccb04ff 100644 --- a/go/vt/proto/tableacl/tableacl.pb.go +++ b/go/vt/proto/tableacl/tableacl.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: tableacl.proto diff --git a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go index 6cad59cd6fe..30e07f35e37 100644 --- a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go +++ b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: tabletmanagerdata.proto @@ -6267,7 +6267,7 @@ func (*ValidateVReplicationPermissionsRequest) Descriptor() ([]byte, []int) { type ValidateVReplicationPermissionsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // The --db_filtered_user on the tablet. + // The --db-filtered-user on the tablet. User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` // Does the user have the minimum privileges needed to manage // vreplication metadata. diff --git a/go/vt/proto/tabletmanagerservice/tabletmanagerservice.pb.go b/go/vt/proto/tabletmanagerservice/tabletmanagerservice.pb.go index 17cb23c3761..c472dc673c5 100644 --- a/go/vt/proto/tabletmanagerservice/tabletmanagerservice.pb.go +++ b/go/vt/proto/tabletmanagerservice/tabletmanagerservice.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: tabletmanagerservice.proto diff --git a/go/vt/proto/throttlerdata/throttlerdata.pb.go b/go/vt/proto/throttlerdata/throttlerdata.pb.go index 0e1065c95cd..d6efc282772 100644 --- a/go/vt/proto/throttlerdata/throttlerdata.pb.go +++ b/go/vt/proto/throttlerdata/throttlerdata.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: throttlerdata.proto diff --git a/go/vt/proto/throttlerservice/throttlerservice.pb.go b/go/vt/proto/throttlerservice/throttlerservice.pb.go index f88e889c6b8..8d1fa04006e 100644 --- a/go/vt/proto/throttlerservice/throttlerservice.pb.go +++ b/go/vt/proto/throttlerservice/throttlerservice.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: throttlerservice.proto diff --git a/go/vt/proto/topodata/topodata.pb.go b/go/vt/proto/topodata/topodata.pb.go index cdbc9c31e15..c0423ad6572 100644 --- a/go/vt/proto/topodata/topodata.pb.go +++ b/go/vt/proto/topodata/topodata.pb.go @@ -20,7 +20,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: topodata.proto diff --git a/go/vt/proto/vschema/vschema.pb.go b/go/vt/proto/vschema/vschema.pb.go index af63a3f8a12..3c90ad91132 100644 --- a/go/vt/proto/vschema/vschema.pb.go +++ b/go/vt/proto/vschema/vschema.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vschema.proto diff --git a/go/vt/proto/vtadmin/vtadmin.pb.go b/go/vt/proto/vtadmin/vtadmin.pb.go index cf94afba3f0..4232dc95256 100644 --- a/go/vt/proto/vtadmin/vtadmin.pb.go +++ b/go/vt/proto/vtadmin/vtadmin.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtadmin.proto diff --git a/go/vt/proto/vtctldata/vtctldata.pb.go b/go/vt/proto/vtctldata/vtctldata.pb.go index 1508392963f..54b50947508 100644 --- a/go/vt/proto/vtctldata/vtctldata.pb.go +++ b/go/vt/proto/vtctldata/vtctldata.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtctldata.proto diff --git a/go/vt/proto/vtctlservice/vtctlservice.pb.go b/go/vt/proto/vtctlservice/vtctlservice.pb.go index 778059ae294..0122b939cba 100644 --- a/go/vt/proto/vtctlservice/vtctlservice.pb.go +++ b/go/vt/proto/vtctlservice/vtctlservice.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtctlservice.proto diff --git a/go/vt/proto/vtgate/vtgate.pb.go b/go/vt/proto/vtgate/vtgate.pb.go index b616d0aa6ad..fd6cdc301d9 100644 --- a/go/vt/proto/vtgate/vtgate.pb.go +++ b/go/vt/proto/vtgate/vtgate.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtgate.proto diff --git a/go/vt/proto/vtgateservice/vtgateservice.pb.go b/go/vt/proto/vtgateservice/vtgateservice.pb.go index 8fc5de70c7d..c2db636e9ce 100644 --- a/go/vt/proto/vtgateservice/vtgateservice.pb.go +++ b/go/vt/proto/vtgateservice/vtgateservice.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtgateservice.proto diff --git a/go/vt/proto/vtorcdata/vtorcdata.pb.go b/go/vt/proto/vtorcdata/vtorcdata.pb.go index e5cb87a455b..9707f2c7cf9 100644 --- a/go/vt/proto/vtorcdata/vtorcdata.pb.go +++ b/go/vt/proto/vtorcdata/vtorcdata.pb.go @@ -20,7 +20,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtorcdata.proto diff --git a/go/vt/proto/vtrpc/vtrpc.pb.go b/go/vt/proto/vtrpc/vtrpc.pb.go index ebc091ea4e8..a4a39ec3867 100644 --- a/go/vt/proto/vtrpc/vtrpc.pb.go +++ b/go/vt/proto/vtrpc/vtrpc.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vtrpc.proto diff --git a/go/vt/proto/vttest/vttest.pb.go b/go/vt/proto/vttest/vttest.pb.go index ffb7c519e9f..0bf3f6355e4 100644 --- a/go/vt/proto/vttest/vttest.pb.go +++ b/go/vt/proto/vttest/vttest.pb.go @@ -41,7 +41,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vttest.proto diff --git a/go/vt/proto/vttime/vttime.pb.go b/go/vt/proto/vttime/vttime.pb.go index 3672062d102..048f1c61d62 100644 --- a/go/vt/proto/vttime/vttime.pb.go +++ b/go/vt/proto/vttime/vttime.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 +// protoc-gen-go v1.36.11 // protoc v3.21.3 // source: vttime.proto diff --git a/go/vt/servenv/version.go b/go/vt/servenv/version.go index 968147e8c92..e9ebf78d81e 100644 --- a/go/vt/servenv/version.go +++ b/go/vt/servenv/version.go @@ -19,4 +19,4 @@ package servenv // DO NOT EDIT // THIS FILE IS AUTO-GENERATED DURING NEW RELEASES BY THE VITESS-RELEASER -const versionName = "23.0.8" +const versionName = "23.0.9-SNAPSHOT" diff --git a/go/vt/topotools/keyspace.go b/go/vt/topotools/keyspace.go index d197345c3f6..c42ca86b270 100644 --- a/go/vt/topotools/keyspace.go +++ b/go/vt/topotools/keyspace.go @@ -114,6 +114,7 @@ func UpdateShardRecords( servedType topodatapb.TabletType, isFrom bool, clearSourceShards bool, + refreshTimeout time.Duration, logger logutil.Logger, ) error { disableQueryService := isFrom @@ -132,13 +133,24 @@ func UpdateShardRecords( return err } shards[i] = updatedShard - // For 'to' shards, refresh to make them serve. The 'from' shards will - // be refreshed after traffic has migrated. - if !isFrom { - if _, _, err := RefreshTabletsByShard(ctx, ts, tmc, si, cells, logger); err != nil { - logger.Warningf("RefreshTabletsByShard(%v/%v, cells=%v) failed with %v; continuing ...", si.Keyspace(), si.ShardName(), cells, err) - } + } + // For 'to' shards, refresh to make them serve. The 'from' shards will + // be refreshed after traffic has migrated. The shards are refreshed + // concurrently because the caller holds the keyspace lock, and refreshing + // many shards one at a time can outlast the lock's lease. For the same + // reason one slow tablet must not stall the refresh past refreshTimeout. + if !isFrom { + refreshCtx, cancel := context.WithTimeout(ctx, refreshTimeout) + defer cancel() + var wg sync.WaitGroup + for _, si := range shards { + wg.Go(func() { + if _, _, err := RefreshTabletsByShard(refreshCtx, ts, tmc, si, cells, logger); err != nil { + logger.Warningf("RefreshTabletsByShard(%v/%v, cells=%v) failed with %v; continuing ...", si.Keyspace(), si.ShardName(), cells, err) + } + }) } + wg.Wait() } return nil } diff --git a/go/vt/topotools/keyspace_test.go b/go/vt/topotools/keyspace_test.go new file mode 100644 index 00000000000..bfc12144ee8 --- /dev/null +++ b/go/vt/topotools/keyspace_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2026 The Vitess 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 topotools + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/key" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/topo/memorytopo" + "vitess.io/vitess/go/vt/vttablet/tmclient" + + topodatapb "vitess.io/vitess/go/vt/proto/topodata" +) + +// barrierRefreshTMC holds every RefreshState call until all of the expected +// calls have started, or until the call's context is done. It records the +// longest time any call had left before its context deadline. +type barrierRefreshTMC struct { + tmclient.TabletManagerClient + + expected int + mu sync.Mutex + started int + maxLeft time.Duration + allIn chan struct{} +} + +func (c *barrierRefreshTMC) RefreshState(ctx context.Context, tablet *topodatapb.Tablet) error { + c.mu.Lock() + c.started++ + if c.started == c.expected { + close(c.allIn) + } + if deadline, ok := ctx.Deadline(); ok { + c.maxLeft = max(c.maxLeft, time.Until(deadline)) + } + c.mu.Unlock() + + select { + case <-c.allIn: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// TestUpdateShardRecordsRefreshesShardsConcurrently checks that UpdateShardRecords +// refreshes the tablets of all the 'to' shards concurrently, within refreshTimeout. +// Each shard has one tablet whose RefreshState returns only once every shard's +// RefreshState has started, so refreshing the shards one at a time runs into the +// refresh deadline. +func TestUpdateShardRecordsRefreshesShardsConcurrently(t *testing.T) { + const ( + cell = "cell1" + keyspace = "ks" + numShards = 16 + refreshTimeout = 30 * time.Second + ) + ctx, cancel := context.WithTimeout(t.Context(), 2*refreshTimeout) + t.Cleanup(cancel) + ts := memorytopo.NewServer(ctx, cell) + t.Cleanup(ts.Close) + + require.NoError(t, ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{})) + shardNames, err := key.GenerateShardRanges(numShards, 2) + require.NoError(t, err) + shards := make([]*topo.ShardInfo, 0, numShards) + for i, shardName := range shardNames { + require.NoError(t, ts.CreateShard(ctx, keyspace, shardName)) + require.NoError(t, ts.CreateTablet(ctx, &topodatapb.Tablet{ + Alias: &topodatapb.TabletAlias{Cell: cell, Uid: uint32(i + 1)}, + Hostname: "host", + Keyspace: keyspace, + Shard: shardName, + Type: topodatapb.TabletType_REPLICA, + })) + si, err := ts.GetShard(ctx, keyspace, shardName) + require.NoError(t, err) + shards = append(shards, si) + } + + lockCtx, unlock, err := ts.LockKeyspace(ctx, keyspace, "TestUpdateShardRecordsRefreshesShardsConcurrently") + require.NoError(t, err) + t.Cleanup(func() { + var unlockErr error + unlock(&unlockErr) + require.NoError(t, unlockErr) + }) + + tmc := &barrierRefreshTMC{expected: numShards, allIn: make(chan struct{})} + err = UpdateShardRecords(lockCtx, ts, tmc, keyspace, shards, []string{cell}, topodatapb.TabletType_REPLICA, + false /* isFrom */, false /* clearSourceShards */, refreshTimeout, logutil.NewMemoryLogger()) + require.NoError(t, err) + + tmc.mu.Lock() + defer tmc.mu.Unlock() + require.Equal(t, numShards, tmc.started) + require.LessOrEqual(t, tmc.maxLeft, refreshTimeout) +} diff --git a/go/vt/vtctl/reparentutil/replication.go b/go/vt/vtctl/reparentutil/replication.go index a201be19bc8..e1080593cc8 100644 --- a/go/vt/vtctl/reparentutil/replication.go +++ b/go/vt/vtctl/reparentutil/replication.go @@ -370,7 +370,10 @@ func stopReplicationAndBuildStatusMaps( for alias, tabletInfo := range tabletMap { allTablets = append(allTablets, tabletInfo.Tablet) if !ignoredTablets.Has(alias) { - mustWaitFor := tabletAliasToWaitFor == alias + // Only the primary may be left behind: any other tablet may be the most + // advanced one, and if it's cancelled, its position is lost. + isPrimary := primaryAlias != nil && topoproto.TabletAliasEqual(primaryAlias, tabletInfo.Alias) + mustWaitFor := tabletAliasToWaitFor == alias || !isPrimary // If this is a tablet that we must wait for // we increment numErrorsToWaitFor and pass in this to the // fillStatus function to indicate we must send this with the boolean diff --git a/go/vt/vtctl/reparentutil/replication_test.go b/go/vt/vtctl/reparentutil/replication_test.go index 44f2142126f..da8c5783d0e 100644 --- a/go/vt/vtctl/reparentutil/replication_test.go +++ b/go/vt/vtctl/reparentutil/replication_test.go @@ -18,7 +18,9 @@ package reparentutil import ( "context" + "maps" "os" + "slices" "testing" "time" @@ -1395,6 +1397,188 @@ func Test_stopReplicationAndBuildStatusMaps(t *testing.T) { } } +// Test_stopReplicationAndBuildStatusMaps_waitsForAllButThePrimary checks that +// stopReplicationAndBuildStatusMaps goes on without a tablet that hasn't +// answered only when that tablet is the shard's primary. Any other tablet that +// is cancelled may be the most advanced one. +func Test_stopReplicationAndBuildStatusMaps_waitsForAllButThePrimary(t *testing.T) { + type stopResult = struct { + StopStatus *replicationdatapb.StopReplicationStatus + Err error + } + type demoteResult = struct { + PrimaryStatus *replicationdatapb.PrimaryStatus + Err error + } + stopStatus := func(pos string) *stopResult { + return &stopResult{ + StopStatus: &replicationdatapb.StopReplicationStatus{ + Before: &replicationdatapb.Status{Position: pos, IoState: int32(replication.ReplicationStateRunning), SqlState: int32(replication.ReplicationStateRunning)}, + After: &replicationdatapb.Status{Position: pos}, + }, + } + } + // notReplica is what a tablet that thinks it's the primary returns, so that + // it gets demoted. + notReplica := &stopResult{Err: vterrors.ToGRPC(vterrors.Wrap(mysql.ErrNotReplica, "before status failed"))} + demoted := func(pos string) *demoteResult { + return &demoteResult{PrimaryStatus: &replicationdatapb.PrimaryStatus{Position: pos}} + } + tabletInfo := func(uid uint32, tabletType topodatapb.TabletType) *topo.TabletInfo { + return &topo.TabletInfo{ + Tablet: &topodatapb.Tablet{ + Type: tabletType, + Alias: &topodatapb.TabletAlias{Cell: "zone1", Uid: uid}, + }, + } + } + replicas := map[string]*topo.TabletInfo{ + "zone1-0000000101": tabletInfo(101, topodatapb.TabletType_REPLICA), + "zone1-0000000102": tabletInfo(102, topodatapb.TabletType_REPLICA), + "zone1-0000000103": tabletInfo(103, topodatapb.TabletType_REPLICA), + } + withTablets := func(tablets map[string]*topo.TabletInfo, extra ...*topo.TabletInfo) map[string]*topo.TabletInfo { + tabletMap := maps.Clone(tablets) + for _, tablet := range extra { + tabletMap[topoproto.TabletAliasString(tablet.Alias)] = tablet + } + return tabletMap + } + withResults := func(results map[string]*stopResult, alias string, result *stopResult) map[string]*stopResult { + results = maps.Clone(results) + results[alias] = result + return results + } + // zone1-0000000103 is the most advanced replica and the last one to answer. + slowestReplicaResults := map[string]*stopResult{ + "zone1-0000000101": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"), + "zone1-0000000102": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"), + "zone1-0000000103": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-10"), + } + slowestReplicaDelays := map[string]time.Duration{ + "zone1-0000000103": 100 * time.Millisecond, + } + primaryAlias := &topodatapb.TabletAlias{Cell: "zone1", Uid: 100} + + tests := []struct { + name string + tmc *stopReplicationAndBuildStatusMapsTestTMClient + tabletMap map[string]*topo.TabletInfo + primaryAlias *topodatapb.TabletAlias + ignoredTablets sets.Set[string] + expectedStatusMap []string + expectedPrimaryStatusMap []string + }{ + { + name: "primary ignored", + tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ + stopReplicationAndGetStatusResults: slowestReplicaResults, + stopReplicationAndGetStatusDelays: slowestReplicaDelays, + }, + tabletMap: withTablets(replicas, tabletInfo(100, topodatapb.TabletType_PRIMARY)), + primaryAlias: primaryAlias, + ignoredTablets: sets.New("zone1-0000000100"), + expectedStatusMap: []string{"zone1-0000000101", "zone1-0000000102", "zone1-0000000103"}, + }, + { + name: "primary not in tablet map", + tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ + stopReplicationAndGetStatusResults: slowestReplicaResults, + stopReplicationAndGetStatusDelays: slowestReplicaDelays, + }, + tabletMap: replicas, + primaryAlias: primaryAlias, + ignoredTablets: sets.New[string](), + expectedStatusMap: []string{"zone1-0000000101", "zone1-0000000102", "zone1-0000000103"}, + }, + { + name: "no primary in the shard record", + tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ + stopReplicationAndGetStatusResults: slowestReplicaResults, + stopReplicationAndGetStatusDelays: slowestReplicaDelays, + }, + tabletMap: replicas, + ignoredTablets: sets.New[string](), + expectedStatusMap: []string{"zone1-0000000101", "zone1-0000000102", "zone1-0000000103"}, + }, + { + name: "primary answers before the slowest replica", + tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ + demotePrimaryResults: map[string]*demoteResult{ + "zone1-0000000100": demoted("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-10"), + }, + stopReplicationAndGetStatusResults: withResults(slowestReplicaResults, "zone1-0000000100", notReplica), + stopReplicationAndGetStatusDelays: slowestReplicaDelays, + }, + tabletMap: withTablets(replicas, tabletInfo(100, topodatapb.TabletType_PRIMARY)), + primaryAlias: primaryAlias, + ignoredTablets: sets.New[string](), + expectedStatusMap: []string{"zone1-0000000101", "zone1-0000000102", "zone1-0000000103"}, + expectedPrimaryStatusMap: []string{"zone1-0000000100"}, + }, + { + // The shard record still names zone1-0000000100, which is a replica by + // now. zone1-0000000103 is the actual primary and the most advanced + // tablet; its demotion must not be cancelled. + name: "stale primary in the shard record", + tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ + demotePrimaryResults: map[string]*demoteResult{ + "zone1-0000000103": demoted("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-12"), + }, + demotePrimaryDelays: map[string]time.Duration{ + "zone1-0000000103": 100 * time.Millisecond, + }, + stopReplicationAndGetStatusResults: map[string]*stopResult{ + "zone1-0000000100": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"), + "zone1-0000000101": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"), + "zone1-0000000102": stopStatus("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"), + "zone1-0000000103": notReplica, + }, + }, + tabletMap: withTablets(map[string]*topo.TabletInfo{ + "zone1-0000000101": replicas["zone1-0000000101"], + "zone1-0000000102": replicas["zone1-0000000102"], + }, tabletInfo(100, topodatapb.TabletType_REPLICA), tabletInfo(103, topodatapb.TabletType_PRIMARY)), + primaryAlias: primaryAlias, + ignoredTablets: sets.New[string](), + expectedStatusMap: []string{"zone1-0000000100", "zone1-0000000101", "zone1-0000000102"}, + expectedPrimaryStatusMap: []string{"zone1-0000000103"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + durability, err := policy.GetDurabilityPolicy(policy.DurabilitySemiSync) + require.NoError(t, err) + + res, err := stopReplicationAndBuildStatusMaps(t.Context(), tt.tmc, &events.Reparent{}, tt.tabletMap, tt.primaryAlias, time.Minute, tt.ignoredTablets, nil, durability, false, logutil.NewMemoryLogger()) + require.NoError(t, err) + assert.ElementsMatch(t, tt.expectedStatusMap, slices.Collect(maps.Keys(res.statusMap))) + assert.ElementsMatch(t, tt.expectedPrimaryStatusMap, slices.Collect(maps.Keys(res.primaryStatusMap))) + }) + } + + t.Run("unreachable primary is not waited for", func(t *testing.T) { + tmc := &stopReplicationAndBuildStatusMapsTestTMClient{ + demotePrimaryResults: map[string]*demoteResult{ + "zone1-0000000100": demoted("MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-10"), + }, + demotePrimaryDelays: map[string]time.Duration{ + "zone1-0000000100": time.Minute, + }, + stopReplicationAndGetStatusResults: withResults(slowestReplicaResults, "zone1-0000000100", notReplica), + } + durability, err := policy.GetDurabilityPolicy(policy.DurabilitySemiSync) + require.NoError(t, err) + + start := time.Now() + res, err := stopReplicationAndBuildStatusMaps(t.Context(), tmc, &events.Reparent{}, withTablets(replicas, tabletInfo(100, topodatapb.TabletType_PRIMARY)), primaryAlias, time.Minute, sets.New[string](), nil, durability, false, logutil.NewMemoryLogger()) + require.NoError(t, err) + assert.Less(t, time.Since(start), 30*time.Second) + assert.Len(t, res.statusMap, 3) + assert.Empty(t, res.primaryStatusMap) + }) +} + func TestReplicaWasRunning(t *testing.T) { t.Parallel() diff --git a/go/vt/vtctl/workflow/framework_test.go b/go/vt/vtctl/workflow/framework_test.go index f6eea36cd3e..2533c9a0caf 100644 --- a/go/vt/vtctl/workflow/framework_test.go +++ b/go/vt/vtctl/workflow/framework_test.go @@ -280,8 +280,11 @@ type testTMClient struct { updateVReplicationWorklowRequests map[uint32][]*updateVReplicationWorkflowRequestResponse applySchemaRequests map[uint32][]*applySchemaRequestResponse primaryPositions map[uint32]string + primaryPositionErrors map[uint32]error vdiffRequests map[uint32]*vdiffRequestResponse refreshStateErrors map[uint32]error + // How long each RefreshState call takes. Set it before use. + refreshStateDelay time.Duration // Stack of ReadVReplicationWorkflowsResponse to return, in order, for each shard readVReplicationWorkflowsResponses map[string][]*tabletmanagerdatapb.ReadVReplicationWorkflowsResponse @@ -818,9 +821,21 @@ func (tmc *testTMClient) setPrimaryPosition(tablet *topodatapb.Tablet, position tmc.primaryPositions[tablet.Alias.Uid] = position } +func (tmc *testTMClient) setPrimaryPositionError(tablet *topodatapb.Tablet, err error) { + tmc.mu.Lock() + defer tmc.mu.Unlock() + if tmc.primaryPositionErrors == nil { + tmc.primaryPositionErrors = make(map[uint32]error) + } + tmc.primaryPositionErrors[tablet.Alias.Uid] = err +} + func (tmc *testTMClient) PrimaryPosition(ctx context.Context, tablet *topodatapb.Tablet) (string, error) { tmc.mu.Lock() defer tmc.mu.Unlock() + if err := tmc.primaryPositionErrors[tablet.Alias.Uid]; err != nil { + return "", err + } if tmc.primaryPositions != nil && tmc.primaryPositions[tablet.Alias.Uid] != "" { return tmc.primaryPositions[tablet.Alias.Uid], nil } @@ -846,6 +861,13 @@ func (tmc *testTMClient) SetRefreshStateError(tablet *topodatapb.Tablet, err err } func (tmc *testTMClient) RefreshState(ctx context.Context, tablet *topodatapb.Tablet) error { + if tmc.refreshStateDelay > 0 { + select { + case <-time.After(tmc.refreshStateDelay): + case <-ctx.Done(): + return ctx.Err() + } + } tmc.mu.Lock() defer tmc.mu.Unlock() diff --git a/go/vt/vtctl/workflow/server.go b/go/vt/vtctl/workflow/server.go index 0a7f169b9b4..51df33558cf 100644 --- a/go/vt/vtctl/workflow/server.go +++ b/go/vt/vtctl/workflow/server.go @@ -2619,7 +2619,7 @@ func (s *Server) DeleteShard(ctx context.Context, keyspace, shard string, recurs func (s *Server) updateShardRecords(ctx context.Context, keyspace string, shards []*topo.ShardInfo, cells []string, servedType topodatapb.TabletType, isFrom bool, clearSourceShards bool, logger logutil.Logger, ) (err error) { - return topotools.UpdateShardRecords(ctx, s.ts, s.tmc, keyspace, shards, cells, servedType, isFrom, clearSourceShards, logger) + return topotools.UpdateShardRecords(ctx, s.ts, s.tmc, keyspace, shards, cells, servedType, isFrom, clearSourceShards, shardTabletRefreshTimeout, logger) } // refreshPrimaryTablets will just RPC-ping all the primary tablets with RefreshState @@ -3218,6 +3218,9 @@ func (s *Server) switchWrites(ctx context.Context, req *vtctldatapb.WorkflowSwit // intra-keyspace materializations that write on the source), and we know for certain // that any in progress writes are done. if err := ts.gatherSourcePositions(ctx); err != nil { + if cerr := sw.cancelMigration(ctx, sm); cerr != nil { + err = vterrors.Errorf(vtrpcpb.Code_CANCELED, "%v\n\n%v", err, cerr) + } return handleError("failed to gather replication positions on migration sources", err) } @@ -3375,32 +3378,38 @@ func (s *Server) canSwitch(ctx context.Context, ts *trafficSwitcher, maxAllowedR } // Ensure that the tablets on both sides are in good shape as we make this same call in the - // process and an error will cause us to backout. + // process and an error will cause us to backout. All of the shards are refreshed + // concurrently, so the shared timeout only has to cover the slowest shard. refreshErrors := strings.Builder{} var m sync.Mutex - var wg sync.WaitGroup rtbsCtx, cancel := context.WithTimeout(ctx, shardTabletRefreshTimeout) defer cancel() - refreshTablets := func(shards []*topo.ShardInfo, stype string) { - defer wg.Done() - for _, si := range shards { - if partial, partialDetails, err := topotools.RefreshTabletsByShard(rtbsCtx, s.ts, s.tmc, si, nil, ts.Logger()); err != nil || partial { - msg := fmt.Sprintf("failed to successfully refresh all tablets in the %s/%s %s shard (%v):\n %v\n", - si.Keyspace(), si.ShardName(), stype, err, partialDetails) - if partial && ts.force { - log.Warning(msg) - } else { - m.Lock() - refreshErrors.WriteString(msg) - m.Unlock() - } + refreshTablets := func(si *topo.ShardInfo, stype string) { + if partial, partialDetails, err := topotools.RefreshTabletsByShard(rtbsCtx, s.ts, s.tmc, si, nil, ts.Logger()); err != nil || partial { + msg := fmt.Sprintf("failed to successfully refresh all tablets in the %s/%s %s shard (%v):\n %v\n", + si.Keyspace(), si.ShardName(), stype, err, partialDetails) + if partial && ts.force { + log.Warning(msg) + } else { + m.Lock() + refreshErrors.WriteString(msg) + m.Unlock() } } } - wg.Add(1) - go refreshTablets(ts.SourceShards(), "source") - wg.Add(1) - go refreshTablets(ts.TargetShards(), "target") + var wg sync.WaitGroup + wg.Go(func() { + _ = ts.ForAllSources(func(source *MigrationSource) error { + refreshTablets(source.GetShard(), "source") + return nil + }) + }) + wg.Go(func() { + _ = ts.ForAllTargets(func(target *MigrationTarget) error { + refreshTablets(target.GetShard(), "target") + return nil + }) + }) wg.Wait() if refreshErrors.Len() > 0 { return fmt.Sprintf(cannotSwitchFailedTabletRefresh, refreshErrors.String()), nil diff --git a/go/vt/vtctl/workflow/server_test.go b/go/vt/vtctl/workflow/server_test.go index c832bdb6a92..07f8fa0ff13 100644 --- a/go/vt/vtctl/workflow/server_test.go +++ b/go/vt/vtctl/workflow/server_test.go @@ -37,6 +37,7 @@ import ( "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/test/utils" "vitess.io/vitess/go/textutil" + "vitess.io/vitess/go/vt/key" "vitess.io/vitess/go/vt/logutil" "vitess.io/vitess/go/vt/mysqlctl/tmutils" "vitess.io/vitess/go/vt/topo" @@ -1254,6 +1255,12 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { query: fmt.Sprintf("update _vt.vreplication set message = 'FROZEN' where db_name='vt_%s' and workflow='%s'", sourceKeyspaceName, ReverseWorkflowName(workflowName)), result: &querypb.QueryResult{}, } + // Canceling a forward switch restarts the workflow and deletes the + // reverse one. + restartWFQR := &queryResult{ + query: fmt.Sprintf("update _vt.vreplication set state='Running', message='' where db_name='vt_%s' and workflow='%s'", targetKeyspaceName, workflowName), + result: &querypb.QueryResult{}, + } hasDeniedTableEntry := func(si *topo.ShardInfo) bool { if si == nil || len(si.TabletControls) == 0 { @@ -1271,8 +1278,13 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { req *vtctldatapb.WorkflowSwitchTrafficRequest multiTenant bool preFunc func(env *testEnv) - want *vtctldatapb.WorkflowSwitchTrafficResponse - wantErr bool + // expectQueries, when set, replaces the default expected queries. + expectQueries func(env *testEnv, source, target *testKeyspace) + want *vtctldatapb.WorkflowSwitchTrafficResponse + wantErr bool + // wantCanceled means that the failed switch must have been canceled, + // leaving writes on the source. + wantCanceled bool }{ { name: "basic forward", @@ -1431,6 +1443,39 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { CurrentState: "All Reads Switched. Writes Switched", }, }, + { + name: "forward with an error reading the source positions", + sourceKeyspace: &testKeyspace{ + KeyspaceName: sourceKeyspaceName, + ShardNames: []string{"0"}, + }, + targetKeyspace: &testKeyspace{ + KeyspaceName: targetKeyspaceName, + ShardNames: []string{"-80", "80-"}, + }, + req: &vtctldatapb.WorkflowSwitchTrafficRequest{ + Keyspace: targetKeyspaceName, + Workflow: workflowName, + Direction: int32(DirectionForward), + TabletTypes: allTabletTypes, + }, + preFunc: func(env *testEnv) { + env.tmc.setPrimaryPositionError(env.tablets[sourceKeyspaceName][startingSourceTabletUID], errors.New("position read failed")) + }, + expectQueries: func(env *testEnv, source, target *testKeyspace) { + env.tmc.expectVRQueryResultOnKeyspaceTablets(target.KeyspaceName, copyTableQR) + for i := 0; i < len(target.ShardNames); i++ { // Per stream + env.tmc.expectVRQueryResultOnKeyspaceTablets(source.KeyspaceName, journalQR) + } + for i := 0; i < len(target.ShardNames); i++ { // Per stream + env.tmc.expectVRQueryResultOnKeyspaceTablets(source.KeyspaceName, lockTableQR) + } + env.tmc.expectVRQueryResultOnKeyspaceTablets(target.KeyspaceName, restartWFQR) + env.tmc.expectVRQueryResultOnKeyspaceTablets(source.KeyspaceName, deleteReverseWFQR) + }, + wantErr: true, + wantCanceled: true, + }, } for _, tc := range testcases { @@ -1442,7 +1487,9 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { defer env.close() env.tmc.schema = schema - if tc.req.Direction == int32(DirectionForward) { + if tc.expectQueries != nil { + tc.expectQueries(env, tc.sourceKeyspace, tc.targetKeyspace) + } else if tc.req.Direction == int32(DirectionForward) { env.tmc.expectVRQueryResultOnKeyspaceTablets(tc.targetKeyspace.KeyspaceName, copyTableQR) env.tmc.expectVRQueryResultOnKeyspaceTablets(tc.targetKeyspace.KeyspaceName, cutoverQR) for i := 0; i < len(tc.targetKeyspace.ShardNames); i++ { // Per stream @@ -1527,6 +1574,22 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { got, err := env.ws.WorkflowSwitchTraffic(ctx, tc.req) if tc.wantErr { require.Error(t, err) + if tc.wantCanceled { + require.ErrorContains(t, err, "failed to gather replication positions") + require.NotContains(t, err.Error(), "cancel migration failed") + // Writes are back on the source, and every expected + // query, including the cancel's, was run. + for _, keyspace := range []*testKeyspace{tc.sourceKeyspace, tc.targetKeyspace} { + for _, shardName := range keyspace.ShardNames { + si, err := env.ts.GetShard(ctx, keyspace.KeyspaceName, shardName) + require.NoError(t, err) + require.Equal(t, keyspace == tc.targetKeyspace, hasDeniedTableEntry(si), "denied tables in %s/%s", keyspace.KeyspaceName, shardName) + } + } + for tabletID, queries := range env.tmc.vrQueries { + require.Empty(t, queries, "queries not run on tablet %d", tabletID) + } + } return } require.NoError(t, err) @@ -1610,6 +1673,52 @@ func TestMoveTablesTrafficSwitching(t *testing.T) { } } +// TestCanSwitchRefreshesShardsConcurrently confirms that the tablet refresh done before +// switching traffic refreshes the shards concurrently, so it does not fail when refreshing +// the shards one at a time would take longer than shardTabletRefreshTimeout. +func TestCanSwitchRefreshesShardsConcurrently(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 2*shardTabletRefreshTimeout) + defer cancel() + + workflowName := "wf1" + tableName := "t1" + targetShards, err := key.GenerateShardRanges(16, 0) + require.NoError(t, err) + sourceKeyspace := &testKeyspace{ + KeyspaceName: "sourceks", + ShardNames: []string{"0"}, + } + targetKeyspace := &testKeyspace{ + KeyspaceName: "targetks", + ShardNames: targetShards, + } + + env := newTestEnv(t, ctx, defaultCellName, sourceKeyspace, targetKeyspace) + defer env.close() + env.tmc.schema = map[string]*tabletmanagerdatapb.SchemaDefinition{ + tableName: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: tableName, + Schema: fmt.Sprintf("CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))", tableName), + }, + }, + }, + } + env.tmc.expectVRQueryResultOnKeyspaceTablets(targetKeyspace.KeyspaceName, &queryResult{ + query: "/select vrepl_id, table_name, lastpk from _vt.copy_state.*", + result: &querypb.QueryResult{}, + }) + // Refreshing the target shards one at a time takes 1.6 times the timeout. + env.tmc.refreshStateDelay = shardTabletRefreshTimeout / 10 + + ts, err := env.ws.buildTrafficSwitcher(ctx, targetKeyspace.KeyspaceName, workflowName) + require.NoError(t, err) + reason, err := env.ws.canSwitch(ctx, ts, 30, nil) + require.NoError(t, err) + require.Empty(t, reason) +} + func TestMoveTablesSwitchWritesCompletesAfterCancelOnFreeze(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/go/vt/vtgate/buffer/flags.go b/go/vt/vtgate/buffer/flags.go index 9290132f411..d5dcafacd0c 100644 --- a/go/vt/vtgate/buffer/flags.go +++ b/go/vt/vtgate/buffer/flags.go @@ -53,7 +53,7 @@ func registerFlags(fs *pflag.FlagSet) { utils.SetFlagDurationVar(fs, &bufferMinTimeBetweenFailovers, "buffer-min-time-between-failovers", 1*time.Minute, "Minimum time between the end of a failover and the start of the next one (tracked per shard). Faster consecutive failovers will not trigger buffering.") utils.SetFlagIntVar(fs, &bufferDrainConcurrency, "buffer-drain-concurrency", 1, "Maximum number of requests retried simultaneously. More concurrency will increase the load on the PRIMARY vttablet when draining the buffer.") - utils.SetFlagStringVar(fs, &bufferKeyspaceShards, "buffer-keyspace-shards", "", "If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable_buffer=true.") + utils.SetFlagStringVar(fs, &bufferKeyspaceShards, "buffer-keyspace-shards", "", "If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable-buffer=true.") } func init() { @@ -80,7 +80,7 @@ func verifyFlags() error { } if bufferKeyspaceShards != "" && !bufferEnabled { - return fmt.Errorf("--buffer-keyspace-shards=%v also requires that --enable_buffer is set", bufferKeyspaceShards) + return fmt.Errorf("--buffer-keyspace-shards=%v also requires that --enable-buffer is set", bufferKeyspaceShards) } if bufferEnabled && bufferEnabledDryRun && bufferKeyspaceShards == "" { return errors.New("both the dry-run mode and actual buffering is enabled. To avoid ambiguity, keyspaces and shards for actual buffering must be explicitly listed in --buffer-keyspace-shards") diff --git a/go/vt/vtgate/engine/cached_size.go b/go/vt/vtgate/engine/cached_size.go index f3a4e884370..9de2eda380b 100644 --- a/go/vt/vtgate/engine/cached_size.go +++ b/go/vt/vtgate/engine/cached_size.go @@ -1346,12 +1346,19 @@ func (cached *UncorrelatedSubquery) CachedSize(alloc bool) int64 { } size := int64(0) if alloc { - size += int64(80) + size += int64(112) } // field SubqueryResult string size += hack.RuntimeAllocSize(int64(len(cached.SubqueryResult))) // field HasValues string size += hack.RuntimeAllocSize(int64(len(cached.HasValues))) + // field RowResults []string + { + size += hack.RuntimeAllocSize(int64(cap(cached.RowResults)) * int64(16)) + for _, elem := range cached.RowResults { + size += hack.RuntimeAllocSize(int64(len(elem))) + } + } // field Subquery vitess.io/vitess/go/vt/vtgate/engine.Primitive if cc, ok := cached.Subquery.(cachedObject); ok { size += cc.CachedSize(true) diff --git a/go/vt/vtgate/engine/uncorrelated_subquery.go b/go/vt/vtgate/engine/uncorrelated_subquery.go index aaa36c06f2b..9eb6a308411 100644 --- a/go/vt/vtgate/engine/uncorrelated_subquery.go +++ b/go/vt/vtgate/engine/uncorrelated_subquery.go @@ -18,6 +18,7 @@ package engine import ( "context" + "fmt" "vitess.io/vitess/go/sqltypes" querypb "vitess.io/vitess/go/vt/proto/query" @@ -36,6 +37,13 @@ type UncorrelatedSubquery struct { // SubqueryResult and HasValues are used to send in the bindvar used in the query to the underlying primitive SubqueryResult string HasValues string + // RowColumns is how many columns each row of a PulloutIn or PulloutNotIn subquery has, when it is more than + // one, as in `(a, b) in (select x, y ...)`. SubqueryResult is then bound to a list of tuples rather than of values. + RowColumns int + // RowResults holds one bind variable per column for a PulloutValue subquery whose row has more than one column, + // as in `(a, b) < (select x, y ...)`. Each column is bound to its own variable, and SubqueryResult is not bound, + // because outside of an IN list a bind variable can only hold a single value. + RowResults []string Subquery Primitive Outer Primitive @@ -76,13 +84,10 @@ func (ps *UncorrelatedSubquery) GetFields(ctx context.Context, vcursor VCursor, } switch ps.Opcode { case opcode.PulloutValue: - combinedVars[ps.SubqueryResult] = sqltypes.NullBindVariable + ps.bindNullValue(combinedVars) case opcode.PulloutIn, opcode.PulloutNotIn: combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) - combinedVars[ps.SubqueryResult] = &querypb.BindVariable{ - Type: querypb.Type_TUPLE, - Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.NewInt64(0))}, - } + combinedVars[ps.SubqueryResult] = ps.placeholderList() case opcode.PulloutExists: combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) } @@ -111,13 +116,25 @@ func (ps *UncorrelatedSubquery) execSubquery(ctx context.Context, vcursor VCurso for k, v := range bindVars { combinedVars[k] = v } + width := max(ps.RowColumns, len(ps.RowResults)) + for _, row := range result.Rows { + if len(row) < width { + return nil, vterrors.VT13001(fmt.Sprintf("subquery returned %d columns for a row of %d", len(row), width)) + } + } switch ps.Opcode { case opcode.PulloutValue: switch len(result.Rows) { case 0: - combinedVars[ps.SubqueryResult] = sqltypes.NullBindVariable + ps.bindNullValue(combinedVars) case 1: - combinedVars[ps.SubqueryResult] = sqltypes.ValueBindVariable(result.Rows[0][0]) + if len(ps.RowResults) == 0 { + combinedVars[ps.SubqueryResult] = sqltypes.ValueBindVariable(result.Rows[0][0]) + break + } + for i, name := range ps.RowResults { + combinedVars[name] = sqltypes.ValueBindVariable(result.Rows[0][i]) + } default: return nil, errSqRow } @@ -126,10 +143,7 @@ func (ps *UncorrelatedSubquery) execSubquery(ctx context.Context, vcursor VCurso case 0: combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(0) // Add a bogus value. It will not be checked. - combinedVars[ps.SubqueryResult] = &querypb.BindVariable{ - Type: querypb.Type_TUPLE, - Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.NewInt64(0))}, - } + combinedVars[ps.SubqueryResult] = ps.placeholderList() default: combinedVars[ps.HasValues] = sqltypes.Int64BindVariable(1) values := &querypb.BindVariable{ @@ -137,7 +151,12 @@ func (ps *UncorrelatedSubquery) execSubquery(ctx context.Context, vcursor VCurso Values: make([]*querypb.Value, len(result.Rows)), } for i, v := range result.Rows { - values.Values[i] = sqltypes.ValueToProto(v[0]) + if ps.RowColumns > 1 { + // The subquery can return more columns than the row, such as a weight_string for its ordering. + values.Values[i] = sqltypes.TupleToProto(v[:ps.RowColumns]) + } else { + values.Values[i] = sqltypes.ValueToProto(v[0]) + } } combinedVars[ps.SubqueryResult] = values } @@ -152,18 +171,54 @@ func (ps *UncorrelatedSubquery) execSubquery(ctx context.Context, vcursor VCurso return combinedVars, nil } +// bindNullValue binds the result of a PulloutValue subquery that returned no rows: NULL, or a NULL for each +// column of its row, which is how MySQL compares a row with an empty row subquery. +func (ps *UncorrelatedSubquery) bindNullValue(bindVars map[string]*querypb.BindVariable) { + if len(ps.RowResults) == 0 { + bindVars[ps.SubqueryResult] = sqltypes.NullBindVariable + return + } + for _, name := range ps.RowResults { + bindVars[name] = sqltypes.NullBindVariable + } +} + +// placeholderList returns the list bound for a PulloutIn or PulloutNotIn subquery that returned no rows. The +// has-values guard keeps it from being compared, but MySQL still rejects it unless it is as wide as the row. +func (ps *UncorrelatedSubquery) placeholderList() *querypb.BindVariable { + if ps.RowColumns <= 1 { + return &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + Values: []*querypb.Value{sqltypes.ValueToProto(sqltypes.NewInt64(0))}, + } + } + row := make([]sqltypes.Value, ps.RowColumns) + for i := range row { + row[i] = sqltypes.NewInt64(0) + } + return &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + Values: []*querypb.Value{sqltypes.TupleToProto(row)}, + } +} + func (ps *UncorrelatedSubquery) description() PrimitiveDescription { other := map[string]any{} var pulloutVars []string if ps.HasValues != "" { pulloutVars = append(pulloutVars, ps.HasValues) } - if ps.SubqueryResult != "" { + if len(ps.RowResults) > 0 { + pulloutVars = append(pulloutVars, ps.RowResults...) + } else if ps.SubqueryResult != "" { pulloutVars = append(pulloutVars, ps.SubqueryResult) } if len(pulloutVars) > 0 { other["PulloutVars"] = pulloutVars } + if ps.RowColumns > 1 { + other["RowColumns"] = ps.RowColumns + } return PrimitiveDescription{ OperatorType: "UncorrelatedSubquery", Variant: ps.Opcode.String(), diff --git a/go/vt/vtgate/engine/uncorrelated_subquery_test.go b/go/vt/vtgate/engine/uncorrelated_subquery_test.go index 668b071d527..23c71bbf6c8 100644 --- a/go/vt/vtgate/engine/uncorrelated_subquery_test.go +++ b/go/vt/vtgate/engine/uncorrelated_subquery_test.go @@ -67,6 +67,26 @@ func TestPulloutSubqueryValueGood(t *testing.T) { sfp.ExpectLog(t, []string{fmt.Sprintf(`Execute aa: %v false`, sqltypes.Int64BindVariable(1))}) ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute aa: %v sq: %v false`, sqltypes.Int64BindVariable(1), sqltypes.Int64BindVariable(1))}) expectResult(t, result, underlyingResult) + + // A row of more than one column binds each column to its own variable, leaving out any extra columns the + // subquery returns, such as a weight_string for its ordering. + rowResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1|col2|weight_string(col2)", + "int64|varchar|varbinary", + ), + "1|a|A", + ) + sfp = &fakePrimitive{ + results: []*sqltypes.Result{rowResult}, + } + ufp.rewind() + ps.Subquery = sfp + ps.RowResults = []string{"sq_0", "sq_1"} + + _, err = ps.TryExecute(t.Context(), &noopVCursor{}, bindVars, false) + require.NoError(t, err) + ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute aa: %v sq_0: %v sq_1: %v false`, sqltypes.Int64BindVariable(1), sqltypes.Int64BindVariable(1), sqltypes.StringBindVariable("a"))}) } func TestPulloutSubqueryValueNone(t *testing.T) { @@ -92,6 +112,15 @@ func TestPulloutSubqueryValueNone(t *testing.T) { } sfp.ExpectLog(t, []string{`Execute false`}) ufp.ExpectLog(t, []string{`Execute sq: false`}) + + // For a row of more than one column, each column is NULL, which is how MySQL compares a row with an empty + // row subquery. + sfp.rewind() + ufp.rewind() + ps.RowResults = []string{"sq_0", "sq_1"} + _, err := ps.TryExecute(t.Context(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) + require.NoError(t, err) + ufp.ExpectLog(t, []string{`Execute sq_0: sq_1: false`}) } func TestPulloutSubqueryValueBadRows(t *testing.T) { @@ -114,6 +143,28 @@ func TestPulloutSubqueryValueBadRows(t *testing.T) { _, err := ps.TryExecute(context.Background(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) require.EqualError(t, err, "subquery returned more than one row") + + // A row that is narrower than the comparison it is bound for is a planner bug, for a value comparison and for + // IN alike. + narrowResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1", + "int64", + ), + "1", + ) + ps.Subquery = &fakePrimitive{results: []*sqltypes.Result{narrowResult}} + ps.RowResults = []string{"sq_0", "sq_1"} + _, err = ps.TryExecute(t.Context(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) + require.EqualError(t, err, "VT13001: [BUG] subquery returned 1 columns for a row of 2") + + ps.Subquery = &fakePrimitive{results: []*sqltypes.Result{narrowResult}} + ps.Opcode = PulloutIn + ps.HasValues = "has_values" + ps.RowResults = nil + ps.RowColumns = 2 + _, err = ps.TryExecute(t.Context(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) + require.EqualError(t, err, "VT13001: [BUG] subquery returned 1 columns for a row of 2") } func TestPulloutSubqueryInNotinGood(t *testing.T) { @@ -152,6 +203,30 @@ func TestPulloutSubqueryInNotinGood(t *testing.T) { } sfp.ExpectLog(t, []string{`Execute false`}) ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute has_values: %v sq: %v false`, sqltypes.Int64BindVariable(1), &querypb.BindVariable{Type: querypb.Type_TUPLE, Values: []*querypb.Value{{Type: querypb.Type_INT64, Value: []byte("1")}, {Type: querypb.Type_INT64, Value: []byte("2")}}})}) + + // Rows of more than one column are bound as a list of tuples, leaving out any extra columns the subquery + // returns. + rowResult := sqltypes.MakeTestResult( + sqltypes.MakeTestFields( + "col1|col2|weight_string(col2)", + "int64|varchar|varbinary", + ), + "1|a|A", + "2|b|B", + ) + sfp = &fakePrimitive{ + results: []*sqltypes.Result{rowResult}, + } + ufp.rewind() + ps.Subquery = sfp + ps.RowColumns = 2 + _, err := ps.TryExecute(t.Context(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) + require.NoError(t, err) + rows := &querypb.BindVariable{Type: querypb.Type_TUPLE, Values: []*querypb.Value{ + sqltypes.TupleToProto([]sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewVarChar("a")}), + sqltypes.TupleToProto([]sqltypes.Value{sqltypes.NewInt64(2), sqltypes.NewVarChar("b")}), + }} + ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute has_values: %v sq: %v false`, sqltypes.Int64BindVariable(1), rows)}) } func TestPulloutSubqueryInNone(t *testing.T) { @@ -178,6 +253,18 @@ func TestPulloutSubqueryInNone(t *testing.T) { } sfp.ExpectLog(t, []string{`Execute false`}) ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute has_values: %v sq: %v false`, sqltypes.Int64BindVariable(0), &querypb.BindVariable{Type: querypb.Type_TUPLE, Values: []*querypb.Value{{Type: querypb.Type_INT64, Value: []byte("0")}}})}) + + // For rows of more than one column, the placeholder is a tuple as wide as the row: MySQL rejects anything + // else, even when the has-values guard is false. + sfp.rewind() + ufp.rewind() + ps.RowColumns = 2 + _, err := ps.TryExecute(t.Context(), &noopVCursor{}, make(map[string]*querypb.BindVariable), false) + require.NoError(t, err) + placeholder := &querypb.BindVariable{Type: querypb.Type_TUPLE, Values: []*querypb.Value{ + sqltypes.TupleToProto([]sqltypes.Value{sqltypes.NewInt64(0), sqltypes.NewInt64(0)}), + }} + ufp.ExpectLog(t, []string{fmt.Sprintf(`Execute has_values: %v sq: %v false`, sqltypes.Int64BindVariable(0), placeholder)}) } func TestPulloutSubqueryExists(t *testing.T) { diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 7513637e8ab..e1d245193be 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3040,7 +3040,7 @@ func (asm *assembler) Fn_LTRIM2(col collations.TypedCollation) { str := env.vm.stack[env.vm.sp-2].(*evalBytes) pat := env.vm.stack[env.vm.sp-1].(*evalBytes) str.tt = int16(sqltypes.VarChar) - str.bytes = bytes.TrimPrefix(str.bytes, pat.bytes) + str.bytes = trimLeftRepeated(str.bytes, pat.bytes) str.col = col env.vm.sp-- return 1 @@ -3053,7 +3053,7 @@ func (asm *assembler) Fn_RTRIM2(col collations.TypedCollation) { str := env.vm.stack[env.vm.sp-2].(*evalBytes) pat := env.vm.stack[env.vm.sp-1].(*evalBytes) str.tt = int16(sqltypes.VarChar) - str.bytes = bytes.TrimSuffix(str.bytes, pat.bytes) + str.bytes = trimRightRepeated(str.bytes, pat.bytes) str.col = col env.vm.sp-- return 1 @@ -3066,7 +3066,7 @@ func (asm *assembler) Fn_TRIM2(col collations.TypedCollation) { str := env.vm.stack[env.vm.sp-2].(*evalBytes) pat := env.vm.stack[env.vm.sp-1].(*evalBytes) str.tt = int16(sqltypes.VarChar) - str.bytes = bytes.TrimPrefix(bytes.TrimSuffix(str.bytes, pat.bytes), pat.bytes) + str.bytes = trimRightRepeated(trimLeftRepeated(str.bytes, pat.bytes), pat.bytes) str.col = col env.vm.sp-- return 1 diff --git a/go/vt/vtgate/evalengine/compiler_test.go b/go/vt/vtgate/evalengine/compiler_test.go index af7cc144c83..8cf1390490a 100644 --- a/go/vt/vtgate/evalengine/compiler_test.go +++ b/go/vt/vtgate/evalengine/compiler_test.go @@ -871,6 +871,23 @@ func TestCompilerSingle(t *testing.T) { expression: `GREATEST(JSON_OBJECT(), JSON_ARRAY())`, result: `VARCHAR("{}")`, }, + { + // TRIM with a remstr removes every repeated occurrence, not only one. + expression: `TRIM(BOTH 'x' FROM 'xxxbarxxx')`, + result: `VARCHAR("bar")`, + }, + { + expression: `TRIM('x' FROM 'xxxbarxxx')`, + result: `VARCHAR("bar")`, + }, + { + expression: `TRIM(LEADING 'ab' FROM 'ababcab')`, + result: `VARCHAR("cab")`, + }, + { + expression: `TRIM(TRAILING '0' FROM '1.500')`, + result: `VARCHAR("1.5")`, + }, } tz, _ := time.LoadLocation("Europe/Madrid") diff --git a/go/vt/vtgate/evalengine/fn_string.go b/go/vt/vtgate/evalengine/fn_string.go index 34d2bdaac9e..2ad5e9c63f3 100644 --- a/go/vt/vtgate/evalengine/fn_string.go +++ b/go/vt/vtgate/evalengine/fn_string.go @@ -1469,14 +1469,38 @@ func (call *builtinTrim) eval(env *ExpressionEnv) (eval, error) { switch call.trim { case sqlparser.LeadingTrimType: - return newEvalText(bytes.TrimPrefix(text.bytes, pat.bytes), text.col), nil + return newEvalText(trimLeftRepeated(text.bytes, pat.bytes), text.col), nil case sqlparser.TrailingTrimType: - return newEvalText(bytes.TrimSuffix(text.bytes, pat.bytes), text.col), nil + return newEvalText(trimRightRepeated(text.bytes, pat.bytes), text.col), nil default: - return newEvalText(bytes.TrimPrefix(bytes.TrimSuffix(text.bytes, pat.bytes), pat.bytes), text.col), nil + return newEvalText(trimRightRepeated(trimLeftRepeated(text.bytes, pat.bytes), pat.bytes), text.col), nil } } +// trimLeftRepeated removes every leading occurrence of pat from str, the way +// MySQL's TRIM(LEADING pat FROM str) does: TRIM(LEADING 'x' FROM 'xxxa') is 'a'. +func trimLeftRepeated(str, pat []byte) []byte { + if len(pat) == 0 { + return str + } + for bytes.HasPrefix(str, pat) { + str = str[len(pat):] + } + return str +} + +// trimRightRepeated removes every trailing occurrence of pat from str, the way +// MySQL's TRIM(TRAILING pat FROM str) does: TRIM(TRAILING '0' FROM '1.500') is '1.5'. +func trimRightRepeated(str, pat []byte) []byte { + if len(pat) == 0 { + return str + } + for bytes.HasSuffix(str, pat) { + str = str[:len(str)-len(pat)] + } + return str +} + func (call *builtinTrim) compile(c *compiler) (ctype, error) { str, err := call.Arguments[0].compile(c) if err != nil { diff --git a/go/vt/vtgate/evalengine/testcases/inputs.go b/go/vt/vtgate/evalengine/testcases/inputs.go index c6aa41d3f79..f4a82c18ccc 100644 --- a/go/vt/vtgate/evalengine/testcases/inputs.go +++ b/go/vt/vtgate/evalengine/testcases/inputs.go @@ -339,6 +339,7 @@ var inputTrimStrings = []string{ "\"a\"", "\"abc\"", "'abca'", + "'aabcaa'", "1", "-1", "0123", diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index c9246f9ec7b..0fd437f5ba6 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -277,13 +277,27 @@ func transformSubQuery(ctx *plancontext.PlanningContext, op *operators.SubQuery) } if len(cols) == 0 { // no correlation, so uncorrelated it is - return &engine.UncorrelatedSubquery{ + sq := &engine.UncorrelatedSubquery{ Opcode: op.FilterType, SubqueryResult: op.SubqueryValueName, HasValues: op.HasValuesName, Subquery: inner, Outer: outer, - }, nil + } + if rowColumns := op.RowColumns(); rowColumns > 1 { + switch op.FilterType { + case opcode.PulloutValue: + sq.RowResults = ctx.RowSubqueryArgs[op.ArgName] + case opcode.PulloutIn, opcode.PulloutNotIn: + // Only a comparison guarded by HasValues is safe to bind as a list of rows: without the guard, an + // empty result would be compared with the placeholder row. In select expressions and UPDATE ... SET + // there is no guard, so leave those as they were, which MySQL rejects for a row. + if op.HasValuesName != "" { + sq.RowColumns = rowColumns + } + } + } + return sq, nil } return &engine.SemiJoin{ diff --git a/go/vt/vtgate/planbuilder/operators/route.go b/go/vt/vtgate/planbuilder/operators/route.go index 9fcd58b80bc..a47cd2a87c7 100644 --- a/go/vt/vtgate/planbuilder/operators/route.go +++ b/go/vt/vtgate/planbuilder/operators/route.go @@ -116,6 +116,11 @@ type ( // UpdateRoutingLogic first checks if we are dealing with a predicate that func UpdateRoutingLogic(ctx *plancontext.PlanningContext, in sqlparser.Expr, r Routing) Routing { + if nr, ok := r.(*NoneRouting); ok { + // a none routing stays none no matter what further predicates say. + return nr + } + ks := r.Keyspace() if ks == nil { var err error diff --git a/go/vt/vtgate/planbuilder/operators/route_planning.go b/go/vt/vtgate/planbuilder/operators/route_planning.go index 7e7307b097c..0c16036bdcb 100644 --- a/go/vt/vtgate/planbuilder/operators/route_planning.go +++ b/go/vt/vtgate/planbuilder/operators/route_planning.go @@ -19,6 +19,7 @@ package operators import ( "bytes" "io" + "slices" querypb "vitess.io/vitess/go/vt/proto/query" topodatapb "vitess.io/vitess/go/vt/proto/topodata" @@ -322,6 +323,63 @@ func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPredic return join, Rewrote("logical join to applyJoin ") } +// realTableKeyspaces collects the distinct keyspaces of the real tables under +// op. Virtual tables (recursive CTE references) have no vschema table behind +// them, dual is only a synthetic reference table that exists on every shard, +// and information_schema tables only get a synthetic VTable in an arbitrary +// keyspace (createInfSchemaRoute), so none of them contributes anything. +// This reflects where the referenced tables actually live rather than where a +// routing points. +func realTableKeyspaces(op Operator) []*vindexes.Keyspace { + if op == nil { + return nil + } + var result []*vindexes.Keyspace + _ = Visit(op, func(this Operator) error { + tbl, ok := this.(*Table) + if !ok || tbl.VTable == nil { + return nil + } + if tbl.QTable != nil && tbl.QTable.IsInfSchema { + return nil + } + if tbl.VTable.Type == vindexes.TypeReference && tbl.VTable.Name.String() == "dual" { + return nil + } + addOperatorKeyspace(&result, tbl.VTable.Keyspace) + return nil + }) + return result +} + +// addOperatorKeyspace appends ks to result unless it is nil or already present. +// Uses slice-based dedup with pointer equality (Vitess shares keyspace objects by +// pointer within a planning context) since the number of distinct keyspaces is +// very small. +func addOperatorKeyspace(result *[]*vindexes.Keyspace, ks *vindexes.Keyspace) { + if ks == nil { + return + } + if slices.Contains(*result, ks) { + return + } + *result = append(*result, ks) +} + +// hasInfoSchemaTables reports whether any table under op reads from +// information_schema. +func hasInfoSchemaTables(op Operator) bool { + var found bool + _ = Visit(op, func(this Operator) error { + if tbl, ok := this.(*Table); ok && tbl.QTable != nil && tbl.QTable.IsInfSchema { + found = true + return io.EOF + } + return nil + }) + return found +} + func operatorsToRoutes(a, b Operator) (*Route, *Route) { aRoute, ok := a.(*Route) if !ok { diff --git a/go/vt/vtgate/planbuilder/operators/route_planning_test.go b/go/vt/vtgate/planbuilder/operators/route_planning_test.go new file mode 100644 index 00000000000..d464d8c4878 --- /dev/null +++ b/go/vt/vtgate/planbuilder/operators/route_planning_test.go @@ -0,0 +1,179 @@ +/* +Copyright 2026 The Vitess 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 operators + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" + "vitess.io/vitess/go/vt/vtgate/semantics" + "vitess.io/vitess/go/vt/vtgate/vindexes" +) + +func TestRealTableKeyspaces(t *testing.T) { + ks1 := &vindexes.Keyspace{Name: "ks1"} + ks2 := &vindexes.Keyspace{Name: "ks2"} + + makeTable := func(ks *vindexes.Keyspace) *Table { + return &Table{ + QTable: &QueryTable{Table: sqlparser.NewTableName("t")}, + VTable: &vindexes.BaseTable{Name: sqlparser.NewIdentifierCS("t"), Keyspace: ks}, + } + } + makeInfSchemaTable := func(ks *vindexes.Keyspace) *Table { + return &Table{ + QTable: &QueryTable{Table: sqlparser.NewTableName("tables"), IsInfSchema: true}, + VTable: &vindexes.BaseTable{Name: sqlparser.NewIdentifierCS("tables"), Keyspace: ks}, + } + } + + tests := []struct { + name string + op Operator + expected []*vindexes.Keyspace + }{ + { + name: "real table", + op: makeTable(ks1), + expected: []*vindexes.Keyspace{ks1}, + }, + { + name: "virtual dual table has no keyspace", + op: &Table{}, + expected: nil, + }, + { + name: "synthetic dual reference table has no keyspace", + op: &Table{ + QTable: &QueryTable{Table: sqlparser.NewTableName("dual")}, + VTable: &vindexes.BaseTable{Name: sqlparser.NewIdentifierCS("dual"), Keyspace: ks1, Type: vindexes.TypeReference}, + }, + expected: nil, + }, + { + name: "table nested under a projection", + op: &Projection{unaryOperator: newUnaryOp(makeTable(ks1))}, + expected: []*vindexes.Keyspace{ks1}, + }, + { + name: "same keyspace tables are deduplicated", + op: &Join{binaryOperator: newBinaryOp(makeTable(ks1), makeTable(ks1))}, + expected: []*vindexes.Keyspace{ks1}, + }, + { + name: "tables from different keyspaces", + op: &Join{binaryOperator: newBinaryOp(makeTable(ks1), makeTable(ks2))}, + expected: []*vindexes.Keyspace{ks1, ks2}, + }, + { + name: "real table joined with a virtual dual", + op: &Join{binaryOperator: newBinaryOp(makeTable(ks1), &Table{})}, + expected: []*vindexes.Keyspace{ks1}, + }, + { + name: "information_schema table with a synthetic vtable contributes nothing", + op: makeInfSchemaTable(ks1), + expected: nil, + }, + { + name: "information_schema table joined with a real table", + op: &Join{binaryOperator: newBinaryOp(makeInfSchemaTable(ks1), makeTable(ks2))}, + expected: []*vindexes.Keyspace{ks2}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, realTableKeyspaces(tt.op)) + }) + } +} + +func TestUpdateRoutingLogicKeepsNoneRouting(t *testing.T) { + ks := &vindexes.Keyspace{Name: "ks1"} + ctx := &plancontext.PlanningContext{ + SemTable: &semantics.SemTable{}, + } + orig := &NoneRouting{keyspace: ks} + falseCmp := &sqlparser.ComparisonExpr{ + Operator: sqlparser.EqualOp, + Left: sqlparser.NewIntLiteral("1"), + Right: sqlparser.NewIntLiteral("2"), + } + + got := UpdateRoutingLogic(ctx, falseCmp, orig) + + assert.Same(t, orig, got) +} + +func TestHasInfoSchemaTables(t *testing.T) { + ks1 := &vindexes.Keyspace{Name: "ks1"} + + makeTable := func() *Table { + return &Table{ + QTable: &QueryTable{Table: sqlparser.NewTableName("t")}, + VTable: &vindexes.BaseTable{Name: sqlparser.NewIdentifierCS("t"), Keyspace: ks1}, + } + } + makeInfSchemaTable := func() *Table { + return &Table{ + QTable: &QueryTable{Table: sqlparser.NewTableName("tables"), IsInfSchema: true}, + VTable: &vindexes.BaseTable{Name: sqlparser.NewIdentifierCS("tables"), Keyspace: ks1}, + } + } + + tests := []struct { + name string + op Operator + expected bool + }{ + { + name: "real table", + op: makeTable(), + expected: false, + }, + { + name: "information_schema table", + op: makeInfSchemaTable(), + expected: true, + }, + { + name: "virtual dual table", + op: &Table{}, + expected: false, + }, + { + name: "information_schema table nested under a projection", + op: &Projection{unaryOperator: newUnaryOp(makeInfSchemaTable())}, + expected: true, + }, + { + name: "information_schema table joined with a real table", + op: &Join{binaryOperator: newBinaryOp(makeTable(), makeInfSchemaTable())}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, hasInfoSchemaTables(tt.op)) + }) + } +} diff --git a/go/vt/vtgate/planbuilder/operators/subquery.go b/go/vt/vtgate/planbuilder/operators/subquery.go index 62b6e7a725e..f06efe8ce7c 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery.go +++ b/go/vt/vtgate/planbuilder/operators/subquery.go @@ -230,6 +230,8 @@ func (sq *SubQuery) settle(ctx *plancontext.PlanningContext, outer Operator) Ope var correlatedSubqueryErr = vterrors.VT12001("correlated subquery is only supported for EXISTS") var subqueryNotAtTopErr = vterrors.VT12001("unmergable subquery can not be inside complex expression") +var rowSubqueryAmongOthersErr = vterrors.VT12001("row comparison with an unmergable subquery in a predicate with other subqueries") + func (sq *SubQuery) addLimit() { // for a correlated subquery, we can add a limit 1 to the subquery sq.Subquery = newLimit(sq.Subquery, &sqlparser.Limit{Rowcount: sqlparser.NewIntLiteral("1")}, true) @@ -243,6 +245,12 @@ func (sq *SubQuery) settleFilter(ctx *plancontext.PlanningContext, outer Operato sq.addLimit() return outer } + // Every subquery in the predicate is replaced with this subquery's argument below, which is only right when it + // is the only one (#21638). Refuse a row comparison in such a predicate, instead of comparing a row with the + // result of another subquery. + if hasRowSubqueryAmongOthers(sq.Original) { + panic(rowSubqueryAmongOthersErr) + } hasValuesArg := func() string { s := ctx.ReservedVars.ReserveVariable(string(sqlparser.HasValueSubQueryBaseName)) @@ -270,9 +278,12 @@ func (sq *SubQuery) settleFilter(ctx *plancontext.PlanningContext, outer Operato } var arg sqlparser.Expr - if sq.FilterType.NeedsListArg() { + switch { + case sq.FilterType.NeedsListArg(): arg = sqlparser.NewListArg(sq.ArgName) - } else { + case sq.FilterType == opcode.PulloutValue && sq.RowColumns() > 1: + arg = sq.rowArguments(ctx) + default: arg = sqlparser.NewArgument(sq.ArgName) } cursor.Replace(arg) @@ -309,6 +320,62 @@ func (sq *SubQuery) settleFilter(ctx *plancontext.PlanningContext, outer Operato return newFilter(outer, predicates...) } +// RowColumns returns how many columns each row of the subquery has. It returns 1 when that is not known, which +// is the case for a subquery selecting `*`. +func (sq *SubQuery) RowColumns() int { + return subqueryRowColumns(sq.originalSubquery) +} + +func subqueryRowColumns(subq *sqlparser.Subquery) int { + columns := subq.Select.GetColumns() + for _, column := range columns { + if _, isStar := column.(*sqlparser.StarExpr); isStar { + return 1 + } + } + return max(len(columns), 1) +} + +// hasRowSubqueryAmongOthers reports whether expr contains more than one subquery, not counting those nested in +// another, and one of them, other than an EXISTS, returns rows of more than one column. +func hasRowSubqueryAmongOthers(expr sqlparser.Expr) bool { + count, hasRow := 0, false + _ = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { + switch node := node.(type) { + case *sqlparser.ExistsExpr: + count++ + return false, nil + case *sqlparser.Subquery: + count++ + hasRow = hasRow || subqueryRowColumns(node) > 1 + return false, nil + } + return true, nil + }, expr) + return count > 1 && hasRow +} + +// rowArguments returns the row of arguments that stands in for a value subquery whose row has more than one +// column, as in `(a, b) < (select x, y ...)`. A single argument can't hold a row, so each column gets its own. +// The arguments are reserved the first time they're needed, and reused after that. They are left untyped: the +// select expressions of a UNION's first arm don't give the types of the row it returns. +func (sq *SubQuery) rowArguments(ctx *plancontext.PlanningContext) sqlparser.ValTuple { + names, ok := ctx.RowSubqueryArgs[sq.ArgName] + if !ok { + names = make([]string, sq.RowColumns()) + for i := range names { + names[i] = ctx.ReservedVars.ReserveVariable(fmt.Sprintf("%s_%d", sq.ArgName, i)) + } + ctx.RowSubqueryArgs[sq.ArgName] = names + } + + row := make(sqlparser.ValTuple, len(names)) + for i, name := range names { + row[i] = sqlparser.NewArgument(name) + } + return row +} + func dontEnterSubqueries(node, _ sqlparser.SQLNode) bool { if _, ok := node.(*sqlparser.Subquery); ok { return false diff --git a/go/vt/vtgate/planbuilder/operators/subquery_planning.go b/go/vt/vtgate/planbuilder/operators/subquery_planning.go index 95a45440578..908b7af2a29 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery_planning.go +++ b/go/vt/vtgate/planbuilder/operators/subquery_planning.go @@ -98,7 +98,7 @@ func settleSubqueries(ctx *plancontext.PlanningContext, op Operator) Operator { } case *Aggregator: for _, aggr := range op.Aggregations { - newExpr, rewritten := rewriteMergedSubqueryExpr(ctx, aggr.SubQueryExpression, aggr.Original.Expr) + newExpr, rewritten := settleSubqueryExpr(ctx, aggr.SubQueryExpression, aggr.Original.Expr) if rewritten { aggr.Original.Expr = newExpr op.Columns[aggr.ColOffset].Expr = newExpr @@ -141,12 +141,50 @@ func mergeSubqueryExpr(ctx *plancontext.PlanningContext, pe *ProjExpr) { if !ok { return } - newExpr, rewritten := rewriteMergedSubqueryExpr(ctx, se, pe.EvalExpr) + newExpr, rewritten := settleSubqueryExpr(ctx, se, pe.EvalExpr) if rewritten { pe.EvalExpr = newExpr } } +// settleSubqueryExpr rewrites the subqueries that an expression uses: back into the expression for those that +// were merged, and into a row of arguments for value subqueries that were pulled out with rows of more than one +// column. +func settleSubqueryExpr(ctx *plancontext.PlanningContext, se SubQueryExpression, expr sqlparser.Expr) (sqlparser.Expr, bool) { + expr, merged := rewriteMergedSubqueryExpr(ctx, se, expr) + expr, expanded := expandRowSubqueryArgs(ctx, se, expr) + return expr, merged || expanded +} + +// expandRowSubqueryArgs replaces the placeholder of each pulled-out value subquery whose row has more than one +// column with a row of arguments, one per column, since a single argument can't hold a row. +func expandRowSubqueryArgs(ctx *plancontext.PlanningContext, se SubQueryExpression, expr sqlparser.Expr) (sqlparser.Expr, bool) { + rewritten := false + for _, sq := range se { + if sq.FilterType != opcode.PulloutValue || sq.isMerged(ctx) || sq.RowColumns() <= 1 { + continue + } + expr = sqlparser.Rewrite(expr, nil, func(cursor *sqlparser.Cursor) bool { + switch node := cursor.Node().(type) { + case *sqlparser.ColName: + if !node.Qualifier.IsEmpty() || node.Name.String() != sq.ArgName { + return true + } + case *sqlparser.Argument: + if node.Name != sq.ArgName { + return true + } + default: + return true + } + cursor.Replace(sq.rowArguments(ctx)) + rewritten = true + return false + }).(sqlparser.Expr) + } + return expr, rewritten +} + func rewriteMergedSubqueryExpr(ctx *plancontext.PlanningContext, se SubQueryExpression, expr sqlparser.Expr) (sqlparser.Expr, bool) { rewritten := false diff --git a/go/vt/vtgate/planbuilder/operators/union_merging.go b/go/vt/vtgate/planbuilder/operators/union_merging.go index 6fafdb4fe4d..cf8a582320e 100644 --- a/go/vt/vtgate/planbuilder/operators/union_merging.go +++ b/go/vt/vtgate/planbuilder/operators/union_merging.go @@ -17,6 +17,8 @@ limitations under the License. package operators import ( + "slices" + "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vtgate/engine" "vitess.io/vitess/go/vt/vtgate/evalengine" @@ -113,6 +115,17 @@ func mergeUnionInputs( return nil, nil } + // a none routing resolves to no shards at execution time, so its side of the + // union contributes no rows. None pairings must be decided before the dual + // and anyShard cases below: those would retain the none routing and + // incorrectly discard the other side's rows. + if a == none || b == none { + if op, exprs, merged := tryMergeNoneUnion(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct, routingA, routingB, a, b); merged { + return op, exprs + } + return nil, nil + } + switch { // if either side is a dual query, we can always merge them together // an unsharded/reference route can be merged with anything going to that keyspace @@ -121,11 +134,6 @@ func mergeUnionInputs( case a == dual || (a == anyShard && sameKeyspace): return createMergedUnion(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct, routingB, nil) - case a == none: - return createMergedUnion(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct, routingB, nil) - case b == none: - return createMergedUnion(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct, routingA, nil) - case a == sharded && b == sharded && sameKeyspace: res, exprs := tryMergeUnionShardedRouting(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct) if res != nil { @@ -135,6 +143,75 @@ func mergeUnionInputs( return nil, nil } +// tryMergeNoneUnion merges a union pairing in which at least one side has a +// none routing. The none side contributes no rows, but its query text (and the +// field query derived from it) still references its tables, so a merge may only +// adopt a routing whose keyspace holds every real table of the none side. +func tryMergeNoneUnion( + ctx *plancontext.PlanningContext, + lhsRoute, rhsRoute *Route, + lhsExprs, rhsExprs []sqlparser.SelectExpr, + distinct bool, + routingA, routingB Routing, + a, b routingType, +) (Operator, []sqlparser.SelectExpr, bool) { + otherRouting, otherType := routingB, b + noneRoute, otherRoute := lhsRoute, rhsRoute + if a != none { + otherRouting, otherType = routingA, a + noneRoute, otherRoute = rhsRoute, lhsRoute + } + noneKeyspaces := realTableKeyspaces(noneRoute.Source) + + var routing Routing + switch { + case otherType == none: + // both sides are empty. They can collapse into a single none route as + // long as their combined real tables live in one keyspace: the merged + // route's field query must be executable on a shard of that keyspace. + for _, ks := range realTableKeyspaces(otherRoute.Source) { + if !slices.Contains(noneKeyspaces, ks) { + noneKeyspaces = append(noneKeyspaces, ks) + } + } + switch len(noneKeyspaces) { + case 0: + routing = noneRoute.Routing + case 1: + routing = &NoneRouting{keyspace: noneKeyspaces[0]} + default: + return nil, nil, false + } + case hasInfoSchemaTables(noneRoute.Source): + // an information_schema comparison may have been rewritten to a + // planner-generated argument (e.g. :__vtschemaname) that only a DBA + // route binds. Adopting an executable routing would ship that argument + // to a shard with no binding for it, so this branch must stay separate. + return nil, nil, false + case len(noneKeyspaces) == 0: + // the none side references no real tables, so its keyspace is only a + // placeholder: adopt the other side's routing unconditionally. + routing = otherRouting + case otherType == dual: + // a dual side has no keyspace and no tables of its own, so any shard + // in the none side's single keyspace can produce its rows. + if len(noneKeyspaces) != 1 { + return nil, nil, false + } + routing = &AnyShardRouting{keyspace: noneKeyspaces[0]} + default: + // the other side's routing is retained, but only when it targets the + // keyspace holding the none side's tables: they exist nowhere else. + if len(noneKeyspaces) != 1 || noneKeyspaces[0] != otherRouting.Keyspace() { + return nil, nil, false + } + routing = otherRouting + } + + op, exprs := createMergedUnion(ctx, lhsRoute, rhsRoute, lhsExprs, rhsExprs, distinct, routing, nil) + return op, exprs, true +} + func tryMergeUnionShardedRouting( ctx *plancontext.PlanningContext, routeA, routeB *Route, diff --git a/go/vt/vtgate/planbuilder/plancontext/planning_context.go b/go/vt/vtgate/planbuilder/plancontext/planning_context.go index 17b12718349..7ffe977706c 100644 --- a/go/vt/vtgate/planbuilder/plancontext/planning_context.go +++ b/go/vt/vtgate/planbuilder/plancontext/planning_context.go @@ -48,6 +48,10 @@ type PlanningContext struct { // Projected subqueries that have been merged MergedSubqueries []*sqlparser.Subquery + // RowSubqueryArgs holds, keyed by a subquery's arg name, the arguments that stand in for each column of a + // value subquery whose row has more than one column, once the subquery has been pulled out. + RowSubqueryArgs map[string][]string + // CurrentPhase keeps track of how far we've gone in the planning process // The type should be operators.Phase, but depending on that would lead to circular dependencies CurrentPhase int @@ -105,6 +109,7 @@ func CreatePlanningContext(stmt sqlparser.Statement, VSchema: vschema, PlannerVersion: version, ReservedArguments: map[sqlparser.Expr]string{}, + RowSubqueryArgs: map[string][]string{}, Statement: stmt, PredTracker: predicates.NewTracker(), }, nil @@ -359,6 +364,7 @@ func (ctx *PlanningContext) UseMirror() *PlanningContext { ReservedArguments: map[sqlparser.Expr]string{}, VerifyAllFKs: ctx.VerifyAllFKs, MergedSubqueries: ctx.MergedSubqueries, + RowSubqueryArgs: ctx.RowSubqueryArgs, CurrentPhase: ctx.CurrentPhase, Statement: ctx.Statement, OuterTables: ctx.OuterTables, diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.json b/go/vt/vtgate/planbuilder/testdata/dml_cases.json index f5d9d9ea965..188950fcd83 100644 --- a/go/vt/vtgate/planbuilder/testdata/dml_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.json @@ -7177,5 +7177,281 @@ "user.user" ] } + }, + { + "comment": "a destination-targeted union subquery with an empty same-keyspace branch merges into the destination route", + "query": "update `user[-80]`.user_extra set val = 1 where user_id in (select id from `user[-80]`.`user` where id in (null) union select id from `user[-80]`.`user`)", + "plan": { + "Type": "Complex", + "QueryType": "UPDATE", + "Original": "update `user[-80]`.user_extra set val = 1 where user_id in (select id from `user[-80]`.`user` where id in (null) union select id from `user[-80]`.`user`)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values", + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetDestination": "ExactKeyRange(-80)", + "FieldQuery": "select id, weight_string(id) from `user` where 1 != 1 union select id, weight_string(id) from `user` where 1 != 1", + "Query": "select id, weight_string(id) from `user` where id in (null) union select id, weight_string(id) from `user`" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Update", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "update user_extra set val = 1 where :__sq_has_values and user_id in ::__sq1" + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + }, + "skip_e2e": true + }, + { + "comment": "a cross-keyspace empty branch stays separate from a destination-targeted branch", + "query": "update `user[-80]`.user_extra set val = 1 where user_id in (select oid from ordering.order_event where oid in (null) union select id from `user[-80]`.`user`)", + "plan": { + "Type": "Complex", + "QueryType": "UPDATE", + "Original": "update `user[-80]`.user_extra set val = 1 where user_id in (select oid from ordering.order_event where oid in (null) union select id from `user[-80]`.`user`)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values", + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select oid, weight_string(oid) from order_event where 1 != 1", + "Query": "select distinct oid, weight_string(oid) from order_event where oid in (null) lock in share mode" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetDestination": "ExactKeyRange(-80)", + "FieldQuery": "select id, weight_string(id) from `user` where 1 != 1", + "Query": "select distinct id, weight_string(id) from `user` lock in share mode" + } + ] + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Update", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "update user_extra set val = 1 where :__sq_has_values and user_id in ::__sq1" + } + ] + }, + "TablesUsed": [ + "ordering.order_event", + "user.user", + "user.user_extra" + ] + }, + "skip_e2e": true + }, + { + "comment": "update with a row comparison against a value subquery binds each column of the row", + "query": "update user set col = 1 where (col, name) < (select user_id, col from music where user_id = 5)", + "plan": { + "Type": "Complex", + "QueryType": "UPDATE", + "Original": "update user set col = 1 where (col, name) < (select user_id, col from music where user_id = 5)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id, col from music where 1 != 1", + "Query": "select user_id, col from music where user_id = 5 lock in share mode", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Update", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "update `user` set col = 1 where (col, `name`) < (:__sq1_0, :__sq1_1)" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } + }, + { + "comment": "update set with a row comparison against a value subquery binds each column of the row", + "query": "update user set col = ((col, name) = (select user_id, col from music where user_id = 5)) where id = 1", + "plan": { + "Type": "Complex", + "QueryType": "UPDATE", + "Original": "update user set col = ((col, name) = (select user_id, col from music where user_id = 5)) where id = 1", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id, col from music where 1 != 1", + "Query": "select user_id, col from music where user_id = 5 lock in share mode", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Update", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Query": "update `user` set col = (col, `name`) = (:__sq1_0, :__sq1_1) where id = 1", + "Values": [ + "1" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } + }, + { + "comment": "delete with a row NOT IN subquery binds each row as a tuple", + "query": "delete from user where (col, name) not in (select user_id, col from music where user_id = 5)", + "plan": { + "Type": "Complex", + "QueryType": "DELETE", + "Original": "delete from user where (col, name) not in (select user_id, col from music where user_id = 5)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutNotIn", + "PulloutVars": [ + "__sq_has_values", + "__sq1" + ], + "RowColumns": 2, + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id, col from music where 1 != 1", + "Query": "select user_id, col from music where user_id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` where not :__sq_has_values or (col, `name`) not in ::__sq1 for update", + "Query": "delete from `user` where not :__sq_has_values or (col, `name`) not in ::__sq1" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.json b/go/vt/vtgate/planbuilder/testdata/filter_cases.json index ea78afe8451..0226a94ec49 100644 --- a/go/vt/vtgate/planbuilder/testdata/filter_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.json @@ -5042,5 +5042,359 @@ "user.sales_extra" ] } + }, + { + "comment": "row comparison with a value subquery that can't be merged binds each column of the row", + "query": "select id from user where id = 1 and (col, name) < (select col, name from user where id in (2, 3) limit 1)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where id = 1 and (col, name) < (select col, name from user where id in (2, 3) limit 1)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Limit", + "Count": "1", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, `name` from `user` where 1 != 1", + "Query": "select col, `name` from `user` where id in ::__vals limit 1", + "Values": [ + "(2, 3)" + ], + "Vindex": "user_index" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user` where id = 1 and (col, `name`) < (:__sq1_0, :__sq1_1)", + "Values": [ + "1" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "row IN subquery that can't be merged routes on the tuple column and binds each row as a tuple", + "query": "select id from user where (id, col) in (select id, col from user where name = 'x')", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where (id, col) in (select id, col from user where name = 'x')", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq_has_values", + "__sq1" + ], + "RowColumns": 2, + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "VindexLookup", + "Variant": "Equal", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "Values": [ + "'x'" + ], + "Vindex": "name_user_map", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "IN", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `name`, keyspace_id from name_user_vdx where 1 != 1", + "Query": "select `name`, keyspace_id from name_user_vdx where `name` in ::__vals", + "Values": [ + "::name" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "ByDestination", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, col from `user` where 1 != 1", + "Query": "select id, col from `user` where `name` = 'x'" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "MultiEqual", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user` where :__sq_has_values and (id, col) in ::__sq1", + "Values": [ + "__sq1:0" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "row comparison with a value subquery in HAVING is evaluated with an argument per column", + "query": "select col, count(*) from user group by col having (col, count(*)) <=> (select col, id from user where id = 5)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select col, count(*) from user group by col having (col, count(*)) <=> (select col, id from user where id = 5)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, id from `user` where 1 != 1", + "Query": "select col, id from `user` where id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Filter", + "Predicate": "(`user`.col, count(*)) <=> (:__sq1_0, :__sq1_1)", + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(1) AS count(*)", + "GroupBy": "0", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, count(*) from `user` where 1 != 1 group by col", + "OrderBy": "0 ASC", + "Query": "select col, count(*) from `user` group by col order by col asc" + } + ] + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "row arguments of a UNION subquery are untyped, since its first arm doesn't give the types of the row it returns", + "query": "select col, count(*) from user group by col having (col, count(*)) <=> (select null, null from user where id = 99 union all select col, id from user where id = 5)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select col, count(*) from user group by col having (col, count(*)) <=> (select null, null from user where id = 99 union all select col, id from user where id = 5)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select null, null from `user` where 1 != 1", + "Query": "select null, null from `user` where id = 99", + "Values": [ + "99" + ], + "Vindex": "user_index" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, id from `user` where 1 != 1", + "Query": "select col, id from `user` where id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Filter", + "Predicate": "(`user`.col, count(*)) <=> (:__sq1_0, :__sq1_1)", + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(1) AS count(*)", + "GroupBy": "0", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, count(*) from `user` where 1 != 1 group by col", + "OrderBy": "0 ASC", + "Query": "select col, count(*) from `user` group by col order by col asc" + } + ] + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "a row comparison and a scalar comparison with subqueries that can't be merged, in separate AND-ed predicates, each get their own arguments", + "query": "select id from user where (col, name) < (select user_id, col from music where user_id = 3) and col > (select user_id from music where user_id = 8)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where (col, name) < (select user_id, col from music where user_id = 3) and col > (select user_id from music where user_id = 8)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq2" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id from music where 1 != 1", + "Query": "select user_id from music where user_id = 8", + "Values": [ + "8" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id, col from music where 1 != 1", + "Query": "select user_id, col from music where user_id = 3", + "Values": [ + "3" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user` where (col, `name`) < (:__sq1_0, :__sq1_1) and col > :__sq2" + } + ] + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index 33d6fc95992..a92cb0597f1 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -6238,5 +6238,106 @@ "user.user" ] } + }, + { + "comment": "row comparison with a value subquery in the select list binds each column of the row", + "query": "select id, (col, name) = (select col, name from user where id = 5) from user where id = 1", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id, (col, name) = (select col, name from user where id = 5) from user where id = 1", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1_0", + "__sq1_1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select col, `name` from `user` where 1 != 1", + "Query": "select col, `name` from `user` where id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, (col, `name`) = (:__sq1_0, :__sq1_1) as `(col, ``name``) = (select col, ``name`` from ``user`` where id = 5)` from `user` where 1 != 1", + "Query": "select id, (col, `name`) = (:__sq1_0, :__sq1_1) as `(col, ``name``) = (select col, ``name`` from ``user`` where id = 5)` from `user` where id = 1", + "Values": [ + "1" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "row IN subquery in the select list has no has-values guard, so its rows are not bound as tuples", + "query": "select id, (col, name) in (select user_id, col from music where user_id = 5) from user", + "skip_e2e": true, + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id, (col, name) in (select user_id, col from music where user_id = 5) from user", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutIn", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_id, col from music where 1 != 1", + "Query": "select user_id, col from music where user_id = 5", + "Values": [ + "5" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, (col, `name`) in ::__sq1 as `(col, ``name``) in (select user_id, col from music where user_id = 5)` from `user` where 1 != 1", + "Query": "select id, (col, `name`) in ::__sq1 as `(col, ``name``) in (select user_id, col from music where user_id = 5)` from `user`" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/union_cases.json b/go/vt/vtgate/planbuilder/testdata/union_cases.json index 0c953187990..151522bd119 100644 --- a/go/vt/vtgate/planbuilder/testdata/union_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/union_cases.json @@ -454,6 +454,687 @@ ] } }, + { + "comment": "none routing merged with dual: any shard in the none side's keyspace serves the dual rows", + "query": "select id from user where id in (null) union select 1 from dual", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select id from user where id in (null) union select 1 from dual", + "Instructions": { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1 union select 1 from dual where 1 != 1", + "Query": "select id from `user` where id in (null) union select 1 from dual" + }, + "TablesUsed": [ + "main.dual", + "user.user" + ] + } + }, + { + "comment": "dual merged with a none routing on the right: any shard in the none side's keyspace serves the dual rows", + "query": "select 1 from dual union select id from user where id in (null)", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select 1 from dual union select id from user where id in (null)", + "Instructions": { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from dual where 1 != 1 union select id from `user` where 1 != 1", + "Query": "select 1 from dual union select id from `user` where id in (null)" + }, + "TablesUsed": [ + "main.dual", + "user.user" + ] + } + }, + { + "comment": "none routings from different keyspaces stay unmerged; the dual leg merges into one none side's keyspace", + "query": "select oid from ordering.order_event where oid in (null) union select id from user where id in (null) union select 1 from dual", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select oid from ordering.order_event where oid in (null) union select id from user where id in (null) union select 1 from dual", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select oid, weight_string(oid) from order_event where 1 != 1 union select 1, weight_string(1) from dual where 1 != 1", + "Query": "select oid, weight_string(oid) from order_event where oid in (null) union select 1, weight_string(1) from dual" + }, + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user` where id in (null)) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "main.dual", + "ordering.order_event", + "user.user" + ] + }, + "skip_e2e": true + }, + { + "comment": "a none routing only merges with a sharded routing in its own keyspace", + "query": "select oid from ordering.order_event where oid in (null) union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select oid from ordering.order_event where oid in (null) union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as oid, weight_string(dt.c0) from (select oid from order_event where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as oid, weight_string(dt.c0) from (select distinct oid from order_event where oid in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user`) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "ordering.order_event", + "user.user" + ] + }, + "skip_e2e": true + }, + { + "comment": "a none routing only merges with an unsharded routing in its own keyspace", + "query": "select oid from ordering.order_event where oid in (null) union select col from unsharded", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select oid from ordering.order_event where oid in (null) union select col from unsharded", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as oid, weight_string(dt.c0) from (select oid from order_event where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as oid, weight_string(dt.c0) from (select distinct oid from order_event where oid in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as col, weight_string(dt.c0) from (select col from unsharded where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as col, weight_string(dt.c0) from (select distinct col from unsharded) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "main.unsharded", + "ordering.order_event" + ] + }, + "skip_e2e": true + }, + { + "comment": "a none routing merges with a sharded routing in the same keyspace", + "query": "select id from user where id in (null) union select id from user_extra", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where id in (null) union select id from user_extra", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, weight_string(id) from `user` where 1 != 1 union select id, weight_string(id) from user_extra where 1 != 1", + "Query": "select id, weight_string(id) from `user` where id in (null) union select id, weight_string(id) from user_extra" + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } + }, + { + "comment": "union all keeps cross-keyspace none legs unmerged while the dual leg merges in order", + "query": "select oid from ordering.order_event where oid in (null) union all select id from user where id in (null) union all select 1 from dual", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select oid from ordering.order_event where oid in (null) union all select id from user where id in (null) union all select 1 from dual", + "Instructions": { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select oid from order_event where 1 != 1", + "Query": "select oid from order_event where oid in (null)" + }, + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1 union all select 1 from dual where 1 != 1", + "Query": "select id from `user` where id in (null) union all select 1 from dual" + } + ] + }, + "TablesUsed": [ + "main.dual", + "ordering.order_event", + "user.user" + ] + }, + "skip_e2e": true + }, + { + "comment": "a dual-derived none routing that absorbed a real table only merges into that table's keyspace", + "query": "select u.col from unsharded as u, (select 1 from dual where 1 in (null)) as x union all select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select u.col from unsharded as u, (select 1 from dual where 1 in (null)) as x union all select id from user", + "Instructions": { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select u.col from (select 1 from dual where 1 != 1) as x, unsharded as u where 1 != 1", + "Query": "select u.col from (select 1 from dual where 1 in (null)) as x, unsharded as u" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user`" + } + ] + }, + "TablesUsed": [ + "main.dual", + "main.unsharded", + "user.user" + ] + } + }, + { + "comment": "a dual-derived none routing that absorbed a real table merges with a branch in that table's keyspace", + "query": "select u.col from unsharded as u, (select 1 from dual where 1 in (null)) as x union select col2 from unsharded_auto", + "plan": { + "Type": "Passthrough", + "QueryType": "SELECT", + "Original": "select u.col from unsharded as u, (select 1 from dual where 1 in (null)) as x union select col2 from unsharded_auto", + "Instructions": { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select u.col from unsharded as u, (select 1 from dual where 1 != 1) as x where 1 != 1 union select col2 from unsharded_auto where 1 != 1", + "Query": "select u.col from unsharded as u, (select 1 from dual where 1 in (null)) as x union select col2 from unsharded_auto" + }, + "TablesUsed": [ + "main.dual", + "main.unsharded", + "main.unsharded_auto" + ] + } + }, + { + "comment": "none routings from different keyspaces stay unmerged: no single keyspace could resolve the merged field query", + "query": "select id from user where id in (null) union select oid from ordering.order_event where oid in (null)", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where id in (null) union select oid from ordering.order_event where oid in (null)", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user` where id in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "ordering", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as oid, weight_string(dt.c0) from (select oid from order_event where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as oid, weight_string(dt.c0) from (select distinct oid from order_event where oid in (null)) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "ordering.order_event", + "user.user" + ] + }, + "skip_e2e": true + }, + { + "comment": "a none routing over a rewritten information_schema branch must not adopt the dual side: only a DBA route binds :__vtschemaname", + "query": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select 1 from dual", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select 1 from dual", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "0: utf8mb3_general_ci" + ], + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select `table_name` from information_schema.`tables` where 1 != 1", + "Query": "select distinct `table_name` from information_schema.`tables` where table_schema = :__vtschemaname /* VARCHAR */ and 0" + }, + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select 1 from dual where 1 != 1", + "Query": "select distinct 1 from dual" + } + ] + } + ] + }, + "TablesUsed": [ + "main.dual" + ] + } + }, + { + "comment": "the information_schema guard also applies when the other side lands on the inferred keyspace", + "query": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select col from unsharded", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select col from unsharded", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select `table_name` from information_schema.`tables` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select distinct `table_name` from information_schema.`tables` where table_schema = :__vtschemaname /* VARCHAR */ and 0) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as col, weight_string(dt.c0) from (select col from unsharded where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as col, weight_string(dt.c0) from (select distinct col from unsharded) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "main.unsharded" + ] + } + }, + { + "comment": "a degraded information_schema branch stays separate from a sharded side", + "query": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select `table_name` from information_schema.`tables` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select distinct `table_name` from information_schema.`tables` where table_schema = :__vtschemaname /* VARCHAR */ and 0) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user`) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, + { + "comment": "a degraded information_schema branch stays separate from a live DBA route", + "query": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select table_name from information_schema.columns", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select table_name from information_schema.tables where table_schema = 'user' and 1 = 2 union select table_name from information_schema.columns", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "0: utf8mb3_general_ci" + ], + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select `table_name` from information_schema.`tables` where 1 != 1", + "Query": "select distinct `table_name` from information_schema.`tables` where table_schema = :__vtschemaname /* VARCHAR */ and 0" + }, + { + "OperatorType": "Route", + "Variant": "DBA", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select `table_name` from information_schema.`columns` where 1 != 1", + "Query": "select distinct `table_name` from information_schema.`columns`" + } + ] + } + ] + } + }, + "skip_e2e": true + }, + { + "comment": "a reference-degraded none routing keeps its canonical keyspace and declines a cross-keyspace merge", + "query": "select id from ambiguous_ref_with_source where id in (null) union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from ambiguous_ref_with_source where id in (null) union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from ambiguous_ref_with_source where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from ambiguous_ref_with_source where id in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user`) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "main.ambiguous_ref_with_source", + "user.user" + ] + } + }, + { + "comment": "differing physical reference names must not merge: the none side's table does not exist in the other keyspace", + "query": "select id from source_of_ref where id in (null) union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from source_of_ref where id in (null) union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from source_of_ref where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from source_of_ref where id in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user`) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "main.source_of_ref", + "user.user" + ] + } + }, + { + "comment": "a reference-degraded none routing whose keyspace matches the other side still merges", + "query": "select id from ref_with_source where id in (null) union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from ref_with_source where id in (null) union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id, weight_string(id) from ref_with_source where 1 != 1 union select id, weight_string(id) from `user` where 1 != 1", + "Query": "select id, weight_string(id) from ref_with_source where id in (null) union select id, weight_string(id) from `user`" + } + ] + }, + "TablesUsed": [ + "user.ref_with_source", + "user.user" + ] + }, + "skip_e2e": true + }, { "comment": "union with the same target shard because of vindex", "query": "select * from music where user_id = 1 union select * from user where id = 1", @@ -2125,5 +2806,114 @@ "user.user" ] } + }, + { + "comment": "statically false dual branch merges into the other side's keyspace", + "query": "select 42 from dual where false union select id from user", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select 42 from dual where false union select id from user", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "0" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 42 from dual where 1 != 1 union select id from `user` where 1 != 1", + "Query": "select 42 from dual where 0 union select id from `user`" + } + ] + }, + "TablesUsed": [ + "main.dual", + "user.user" + ] + } + }, + { + "comment": "statically false dual branch on the right merges into the other side's keyspace", + "query": "select id from user union select 42 from dual where false", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user union select 42 from dual where false", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "0" + ], + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1 union select 42 from dual where 1 != 1", + "Query": "select id from `user` union select 42 from dual where 0" + } + ] + }, + "TablesUsed": [ + "main.dual", + "user.user" + ] + } + }, + { + "comment": "a none routing over a sharded table stays separate from a live DBA route", + "query": "select id from user where id in (null) union select table_name from information_schema.tables", + "plan": { + "Type": "Complex", + "QueryType": "SELECT", + "Original": "select id from user where id in (null) union select table_name from information_schema.tables", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "(0:1)" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Concatenate", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "None", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select dt.c0 as id, weight_string(dt.c0) from (select id from `user` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as id, weight_string(dt.c0) from (select distinct id from `user` where id in (null)) as dt(c0)" + }, + { + "OperatorType": "Route", + "Variant": "DBA", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select `table_name` from information_schema.`tables` where 1 != 1) as dt(c0) where 1 != 1", + "Query": "select dt.c0 as `table_name`, weight_string(dt.c0) from (select distinct `table_name` from information_schema.`tables`) as dt(c0)" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" + ] + }, + "skip_e2e": true } ] diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 95994b678a9..5a6d70e8850 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -423,5 +423,20 @@ "comment": "unknown sql_mode names fail with MySQL's error", "query": "set sql_mode = 'BOGUS'", "plan": "Variable 'sql_mode' can't be set to the value of 'BOGUS'" + }, + { + "comment": "row comparisons with two subqueries that can't be merged in one predicate are refused, since the second subquery's result would be lost", + "query": "select id from user where (col, name) < (select user_id, col from music where user_id = 3) or (col, name) > (select user_id, col from music where user_id = 8)", + "plan": "VT12001: unsupported: row comparison with an unmergable subquery in a predicate with other subqueries" + }, + { + "comment": "a row comparison and a scalar comparison with subqueries that can't be merged in one predicate are refused", + "query": "select id from user where col < (select user_id from music where user_id = 3) or (col, name) > (select user_id, col from music where user_id = 8)", + "plan": "VT12001: unsupported: row comparison with an unmergable subquery in a predicate with other subqueries" + }, + { + "comment": "a DELETE with row comparisons against two subqueries that can't be merged in one predicate is refused", + "query": "delete from user where (col, name) in (select user_id, col from music where user_id = 3) or (col, name) = (select user_id, col from music where user_id = 8)", + "plan": "VT12001: unsupported: row comparison with an unmergable subquery in a predicate with other subqueries" } ] diff --git a/go/vt/vtgate/tabletgateway_flaky_test.go b/go/vt/vtgate/tabletgateway_flaky_test.go index 124997bea9e..8c691eee3df 100644 --- a/go/vt/vtgate/tabletgateway_flaky_test.go +++ b/go/vt/vtgate/tabletgateway_flaky_test.go @@ -112,10 +112,10 @@ func TestGatewayBufferingWhenPrimarySwitchesServingState(t *testing.T) { waitForBuffering(true) // execute the query in a go routine since it should be buffered, and check that it eventually succeed - queryChan := make(chan struct{}) + done := make(chan struct{}, 1) go func() { res, err = tg.Execute(ctx, target, "query", nil, 0, 0, nil) - queryChan <- struct{}{} + done <- struct{}{} }() // set the serving type for the primary tablet true and broadcast it so that the buffering code registers this change @@ -127,7 +127,7 @@ func TestGatewayBufferingWhenPrimarySwitchesServingState(t *testing.T) { // wait for the query to execute before checking for results select { - case <-queryChan: + case <-done: require.NoError(t, err) require.Equal(t, sqlResult1, res) case <-time.After(15 * time.Second): @@ -222,10 +222,10 @@ func TestGatewayBufferingWhileReparenting(t *testing.T) { sbcReplica.SetResults([]*sqltypes.Result{sqlResult1}) // execute the query in a go routine since it should be buffered, and check that it eventually succeed - queryChan := make(chan struct{}) + done := make(chan struct{}, 1) go func() { res, err = tg.Execute(ctx, target, "query", nil, 0, 0, nil) - queryChan <- struct{}{} + done <- struct{}{} }() // set the serving type for the new primary tablet true and broadcast it so that the buffering code registers this change @@ -256,7 +256,7 @@ outer: // wait for the query to execute before checking for results select { - case <-queryChan: + case <-done: require.NoError(t, err) require.Equal(t, sqlResult1, res) case <-time.After(15 * time.Second): @@ -330,14 +330,14 @@ func TestInconsistentStateDetectedBuffering(t *testing.T) { var res *sqltypes.Result var err error - queryChan := make(chan struct{}) + done := make(chan struct{}, 1) go func() { res, err = tg.Execute(ctx, target, "query", nil, 0, 0, nil) - queryChan <- struct{}{} + done <- struct{}{} }() select { - case <-queryChan: + case <-done: require.Nil(t, res) require.Error(t, err) // depending on whether the health check ticks before or after the buffering code, we might get different errors diff --git a/go/vt/vtgate/vstream_manager.go b/go/vt/vtgate/vstream_manager.go index 2b9fc80a6aa..55e30900fd3 100644 --- a/go/vt/vtgate/vstream_manager.go +++ b/go/vt/vtgate/vstream_manager.go @@ -637,7 +637,7 @@ func (vs *vstream) streamFromTablet(ctx context.Context, sgtid *binlogdatapb.Sha err = fmt.Errorf("tablet %s is no longer healthy: %s, restarting vstream", topoproto.TabletAliasString(tablet.Alias), shr.RealtimeStats.HealthError) case shr.RealtimeStats.ReplicationLagSeconds > uint32(discovery.GetLowReplicationLag().Seconds()): - err = fmt.Errorf("tablet %s has a replication lag of %d seconds which is beyond the value provided in --discovery_low_replication_lag of %s so the tablet is no longer considered healthy, restarting vstream", + err = fmt.Errorf("tablet %s has a replication lag of %d seconds which is beyond the value provided in --discovery-low-replication-lag of %s so the tablet is no longer considered healthy, restarting vstream", topoproto.TabletAliasString(tablet.Alias), shr.RealtimeStats.ReplicationLagSeconds, discovery.GetLowReplicationLag()) } if err != nil { diff --git a/go/vt/vtgate/vtgate.go b/go/vt/vtgate/vtgate.go index c135375c510..8b179e6ee8e 100644 --- a/go/vt/vtgate/vtgate.go +++ b/go/vt/vtgate/vtgate.go @@ -138,7 +138,7 @@ var ( return vtgatepb.TransactionMode_TWOPC default: fmt.Printf("Invalid option: %v\n", txMode) - fmt.Println("Usage: -transaction_mode {SINGLE | MULTI | TWOPC}") + fmt.Println("Usage: --transaction-mode {SINGLE | MULTI | TWOPC}") os.Exit(1) return -1 } diff --git a/go/vt/vtorc/logic/tablet_discovery.go b/go/vt/vtorc/logic/tablet_discovery.go index d381a4ea78d..b8a77cbe292 100644 --- a/go/vt/vtorc/logic/tablet_discovery.go +++ b/go/vt/vtorc/logic/tablet_discovery.go @@ -51,7 +51,7 @@ var ( shutdownWaitTime = 30 * time.Second // shardsToWatch is a map storing the shards for a given keyspace that need to be watched. // We store the key range for all the shards that we want to watch. - // This is populated by parsing `--clusters_to_watch` flag. + // This is populated by parsing `--clusters-to-watch` flag. shardsToWatch map[string][]*topodatapb.KeyRange // ErrNoPrimaryTablet is a fixed error message. @@ -122,7 +122,7 @@ func RegisterFlags(fs *pflag.FlagSet) { utils.SetFlagDurationVar(fs, &shutdownWaitTime, "shutdown-wait-time", shutdownWaitTime, "Maximum time to wait for VTOrc to release all the locks that it is holding before shutting down on SIGTERM") } -// initializeShardsToWatch parses the --clusters_to_watch flag-value +// initializeShardsToWatch parses the --clusters-to-watch flag-value // into a map of keyspace/shards. func initializeShardsToWatch() error { shardsToWatch = make(map[string][]*topodatapb.KeyRange) @@ -193,7 +193,7 @@ func OpenTabletDiscovery() <-chan time.Time { if _, err := db.ExecVTOrc("DELETE FROM vitess_tablet"); err != nil { log.Error(err) } - // Parse --clusters_to_watch into a filter. + // Parse --clusters-to-watch into a filter. err := initializeShardsToWatch() if err != nil { log.Fatalf("Error parsing --clusters-to-watch: %v", err) diff --git a/go/vt/vttablet/onlineddl/executor.go b/go/vt/vttablet/onlineddl/executor.go index a564b9ee793..09b3eccde32 100644 --- a/go/vt/vttablet/onlineddl/executor.go +++ b/go/vt/vttablet/onlineddl/executor.go @@ -1119,6 +1119,7 @@ func (e *Executor) cutOverVReplMigration(ctx context.Context, s *VReplStream, sh } } + var postWritesPos replication.Position if isVreplicationTestSuite { // The testing suite may inject queries internally from the server via a recurring EVENT. // Those queries are unaffected by query rules (ACLs) because they don't go through Vitess. @@ -1129,6 +1130,14 @@ func (e *Executor) cutOverVReplMigration(ctx context.Context, s *VReplStream, sh if _, err := e.execQuery(ctx, renameBeforeQuery); err != nil { return err } + // No write to the table can follow the RENAME, so the position right after it covers + // all of them. Reading it before the stage writes below makes the position end in the + // RENAME itself, which VReplication saves at once, rather than in the stage writes, + // which reach the stream as empty transactions that it saves only after its idle + // timeout. + if postWritesPos, err = e.primaryPosition(ctx); err != nil { + return vterrors.Wrapf(err, "failed reading pos after renaming") + } e.updateMigrationStage(ctx, onlineDDL.UUID, "test suite 'before' table renamed") } else { // real production @@ -1164,12 +1173,11 @@ func (e *Executor) cutOverVReplMigration(ctx context.Context, s *VReplStream, sh return vterrors.Wrapf(err, "failed killing table lock holders and accessors") } } - } - e.updateMigrationStage(ctx, onlineDDL.UUID, "reading post-lock pos") - postWritesPos, err := e.primaryPosition(ctx) - if err != nil { - return vterrors.Wrapf(err, "failed reading pos after locking") + e.updateMigrationStage(ctx, onlineDDL.UUID, "reading post-lock pos") + if postWritesPos, err = e.primaryPosition(ctx); err != nil { + return vterrors.Wrapf(err, "failed reading pos after locking") + } } // Right now: new queries are buffered, any existing query will have executed, and worst case scenario is diff --git a/go/vt/vttablet/tabletmanager/rpc_vreplication.go b/go/vt/vttablet/tabletmanager/rpc_vreplication.go index 35928602df7..40b1cb1bed5 100644 --- a/go/vt/vttablet/tabletmanager/rpc_vreplication.go +++ b/go/vt/vttablet/tabletmanager/rpc_vreplication.go @@ -874,7 +874,7 @@ func (tm *TabletManager) createSequenceTable(ctx context.Context, tableName stri return err } -// ValidateVReplicationPermissionsOld validates that the --db_filtered_user has +// ValidateVReplicationPermissionsOld validates that the --db-filtered-user has // the minimum permissions required on the sidecardb vreplication table // needed in order to manage vreplication metadata. // Switching to use a functional test approach in ValidateVReplicationPermissions below @@ -921,7 +921,7 @@ func (tm *TabletManager) ValidateVReplicationPermissionsOld(ctx context.Context, }, nil } -// ValidateVReplicationPermissions validates that the --db_filtered_user has +// ValidateVReplicationPermissions validates that the --db-filtered-user has // the minimum permissions required on the sidecardb vreplication table // using a functional testing approach that doesn't require access to mysql.user table. func (tm *TabletManager) ValidateVReplicationPermissions(ctx context.Context, req *tabletmanagerdatapb.ValidateVReplicationPermissionsRequest) (*tabletmanagerdatapb.ValidateVReplicationPermissionsResponse, error) { diff --git a/go/vt/vttablet/tabletmanager/vreplication/engine.go b/go/vt/vttablet/tabletmanager/vreplication/engine.go index 2607f1683a7..a1c31c25e77 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/engine.go +++ b/go/vt/vttablet/tabletmanager/vreplication/engine.go @@ -71,6 +71,12 @@ const ( // stop replicating. var waitRetryTime = 1 * time.Second +// waitForPosRetryTime is how long WaitForPos waits before it first checks the +// stream's position again. The wait doubles after each check, up to +// waitRetryTime, so a short wait returns soon after the stream saves the +// position, while a long one still checks only once per waitRetryTime. +var waitForPosRetryTime = 100 * time.Millisecond + // How frequently vcopier will update _vt.vreplication rows_copied var rowsCopiedUpdateInterval = 30 * time.Second @@ -795,8 +801,9 @@ func (vre *Engine) WaitForPos(ctx context.Context, id int32, pos string) error { } defer dbClient.Close() - tkr := time.NewTicker(waitRetryTime) - defer tkr.Stop() + retryTime := waitForPosRetryTime + retry := time.NewTimer(retryTime) + defer retry.Stop() for { qr, err := dbClient.ExecuteFetch(binlogplayer.ReadVReplicationStatus(id), 10) switch { @@ -851,8 +858,10 @@ func (vre *Engine) WaitForPos(ctx context.Context, id int32, pos string) error { return doneErr case <-vre.ctx.Done(): return fmt.Errorf("vreplication is closing: %v", vre.ctx.Err()) - case <-tkr.C: + case <-retry.C: } + retryTime = min(2*retryTime, max(waitRetryTime, waitForPosRetryTime)) + retry.Reset(retryTime) } } diff --git a/go/vt/vttablet/tabletmanager/vreplication/engine_test.go b/go/vt/vttablet/tabletmanager/vreplication/engine_test.go index af054932c89..f9ee8d97e13 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/engine_test.go +++ b/go/vt/vttablet/tabletmanager/vreplication/engine_test.go @@ -369,9 +369,10 @@ func TestEngineSelect(t *testing.T) { } func TestWaitForPos(t *testing.T) { - savedRetryTime := waitRetryTime - defer func() { waitRetryTime = savedRetryTime }() + savedRetryTime, savedWaitForPosRetryTime := waitRetryTime, waitForPosRetryTime + defer func() { waitRetryTime, waitForPosRetryTime = savedRetryTime, savedWaitForPosRetryTime }() waitRetryTime = 10 * time.Millisecond + waitForPosRetryTime = 10 * time.Millisecond dbClient := binlogplayer.NewMockDBClient(t) mysqld := &mysqlctl.FakeMysqlDaemon{} @@ -402,6 +403,35 @@ func TestWaitForPos(t *testing.T) { } } +// TestWaitForPosRetriesQuickly checks that WaitForPos notices a position that +// the stream saves just after the first check within its first, short retry +// instead of a full waitRetryTime later. +func TestWaitForPosRetriesQuickly(t *testing.T) { + dbClient := binlogplayer.NewMockDBClient(t) + mysqld := &mysqlctl.FakeMysqlDaemon{} + mysqld.MysqlPort.Store(3306) + + dbClientFactory := func() binlogplayer.DBClient { return dbClient } + vre := NewTestEngine(env.TopoServ, env.Cells[0], mysqld, dbClientFactory, dbClientFactory, dbClient.DBName(), nil) + + dbClient.ExpectRequest("select * from _vt.vreplication where db_name='db'", &sqltypes.Result{}, nil) + vre.Open(t.Context()) + + dbClient.ExpectRequest("select pos, state, message from _vt.vreplication where id=1", &sqltypes.Result{Rows: [][]sqltypes.Value{{ + sqltypes.NewVarBinary("MariaDB/0-1-1083"), + sqltypes.NewVarBinary(binlogdatapb.VReplicationWorkflowState_Running.String()), + sqltypes.NewVarBinary(""), + }}}, nil) + dbClient.ExpectRequest("select pos, state, message from _vt.vreplication where id=1", &sqltypes.Result{Rows: [][]sqltypes.Value{{ + sqltypes.NewVarBinary("MariaDB/0-1-1084"), + sqltypes.NewVarBinary(binlogdatapb.VReplicationWorkflowState_Running.String()), + sqltypes.NewVarBinary(""), + }}}, nil) + start := time.Now() + require.NoError(t, vre.WaitForPos(t.Context(), 1, "MariaDB/0-1-1084")) + assert.Less(t, time.Since(start), waitRetryTime/2) +} + func TestWaitForPosError(t *testing.T) { dbClient := binlogplayer.NewMockDBClient(t) mysqld := &mysqlctl.FakeMysqlDaemon{} diff --git a/go/vt/vttablet/tabletserver/query_executor.go b/go/vt/vttablet/tabletserver/query_executor.go index 039e8499a15..4f133d11db3 100644 --- a/go/vt/vttablet/tabletserver/query_executor.go +++ b/go/vt/vttablet/tabletserver/query_executor.go @@ -821,6 +821,9 @@ func (qre *QueryExecutor) execNextval() (*sqltypes.Result, error) { t.SequenceInfo.Lock() defer t.SequenceInfo.Unlock() if t.SequenceInfo.NextVal == 0 || t.SequenceInfo.NextVal+inc > t.SequenceInfo.LastVal { + // Stage the new cache range and write it to SequenceInfo after the COMMIT succeeds. + // A failed COMMIT can leave the old next_id in the sequence table. + nextVal, lastVal := t.SequenceInfo.NextVal, t.SequenceInfo.LastVal _, err := qre.execAsTransaction(func(conn *StatefulConnection) (*sqltypes.Result, error) { query := fmt.Sprintf("select next_id, cache from %s where id = 0 for update", sqlparser.String(tableName)) qr, err := qre.execStatefulConn(conn, query, false) @@ -834,16 +837,15 @@ func (qre *QueryExecutor) execNextval() (*sqltypes.Result, error) { if err != nil { return nil, vterrors.Wrapf(err, "error loading sequence %s", tableName) } - // If LastVal does not match next ID, then either: - // VTTablet just started, and we're initializing the cache, or - // Someone reset the id underneath us. - if t.SequenceInfo.LastVal != nextID { - if nextID < t.SequenceInfo.LastVal { - log.Warningf("Sequence next ID value %v is below the currently cached max %v, updating it to max", nextID, t.SequenceInfo.LastVal) - nextID = t.SequenceInfo.LastVal + // Start the staged range at next_id when next_id differs from the cached last value. + // The two differ when the cache is empty, when a write outside this cache changed next_id, + // or when an earlier refill's COMMIT applied but returned an error. + if lastVal != nextID { + if nextID < lastVal { + log.Warningf("Sequence next ID value %v is below the currently cached max %v, updating it to max", nextID, lastVal) + nextID = lastVal } - t.SequenceInfo.NextVal = nextID - t.SequenceInfo.LastVal = nextID + nextVal = nextID } cache, err := qr.Rows[0][1].ToCastInt64() if err != nil { @@ -853,7 +855,7 @@ func (qre *QueryExecutor) execNextval() (*sqltypes.Result, error) { return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "invalid cache value for sequence %s: %d", tableName, cache) } newLast := nextID + cache - for newLast < t.SequenceInfo.NextVal+inc { + for newLast < nextVal+inc { newLast += cache } query = fmt.Sprintf("update %s set next_id = %d where id = 0", sqlparser.String(tableName), newLast) @@ -861,12 +863,15 @@ func (qre *QueryExecutor) execNextval() (*sqltypes.Result, error) { if err != nil { return nil, err } - t.SequenceInfo.LastVal = newLast + lastVal = newLast return nil, nil }) if err != nil { return nil, err } + + t.SequenceInfo.NextVal = nextVal + t.SequenceInfo.LastVal = lastVal } ret := t.SequenceInfo.NextVal t.SequenceInfo.NextVal += inc diff --git a/go/vt/vttablet/tabletserver/query_executor_test.go b/go/vt/vttablet/tabletserver/query_executor_test.go index 31d964927e3..c8cef06bf3b 100644 --- a/go/vt/vttablet/tabletserver/query_executor_test.go +++ b/go/vt/vttablet/tabletserver/query_executor_test.go @@ -856,6 +856,99 @@ func TestQueryExecutorPlanNextval(t *testing.T) { } } +// TestQueryExecutorPlanNextvalCommitFailureKeepsCache verifies that a failed +// refill COMMIT leaves the sequence cache unchanged and that the next call +// refills the cache from the sequence table. +func TestQueryExecutorPlanNextvalCommitFailureKeepsCache(t *testing.T) { + db := setUpQueryExecutorTest(t) + t.Cleanup(db.Close) + + db.AddQuery("select next_id, cache from seq where id = 0 for update", &sqltypes.Result{ + Fields: []*querypb.Field{{Type: sqltypes.Int64}, {Type: sqltypes.Int64}}, + Rows: [][]sqltypes.Value{{sqltypes.NewInt64(1), sqltypes.NewInt64(3)}}, + }) + db.AddQuery("update seq set next_id = 4 where id = 0", &sqltypes.Result{}) + + ctx := t.Context() + tsv := newTestTabletServer(ctx, noFlags, db) + t.Cleanup(tsv.StopService) + + db.AddRejectedQuery("commit", errors.New("commit failed")) + qre := newTestQueryExecutor(ctx, tsv, "select next value from seq", 0) + _, err := qre.Execute() + require.ErrorContains(t, err, "commit failed") + + // Check that the cache stays empty. The table still holds next_id = 1. + seq := qre.plan.Table.SequenceInfo + require.Zero(t, seq.NextVal) + require.Zero(t, seq.LastVal) + + // Make the retry read next_id = 4 from the table. A stale cache returns 1. + db.AddQuery("select next_id, cache from seq where id = 0 for update", &sqltypes.Result{ + Fields: []*querypb.Field{{Type: sqltypes.Int64}, {Type: sqltypes.Int64}}, + Rows: [][]sqltypes.Value{{sqltypes.NewInt64(4), sqltypes.NewInt64(3)}}, + }) + db.AddQuery("update seq set next_id = 7 where id = 0", &sqltypes.Result{}) + db.DeleteRejectedQuery("commit") + + qre = newTestQueryExecutor(ctx, tsv, "select next value from seq", 0) + got, err := qre.Execute() + require.NoError(t, err) + + want := &sqltypes.Result{ + Fields: []*querypb.Field{{ + Name: "nextval", + Type: sqltypes.Int64, + }}, + Rows: [][]sqltypes.Value{{ + sqltypes.NewInt64(4), + }}, + } + require.Truef(t, got.Equal(want), "qre.Execute() =\n%#v, want:\n%#v", got, want) +} + +// TestQueryExecutorPlanNextvalPartialRefillCommitFailureKeepsCache verifies +// that a failed refill COMMIT on a non-empty cache leaves the cached range +// unchanged. The refill starts from the cached last value, which equals the +// next_id in the sequence table. +func TestQueryExecutorPlanNextvalPartialRefillCommitFailureKeepsCache(t *testing.T) { + db := setUpQueryExecutorTest(t) + t.Cleanup(db.Close) + + db.AddQuery("select next_id, cache from seq where id = 0 for update", &sqltypes.Result{ + Fields: []*querypb.Field{{Type: sqltypes.Int64}, {Type: sqltypes.Int64}}, + Rows: [][]sqltypes.Value{{sqltypes.NewInt64(1), sqltypes.NewInt64(3)}}, + }) + db.AddQuery("update seq set next_id = 4 where id = 0", &sqltypes.Result{}) + + ctx := t.Context() + tsv := newTestTabletServer(ctx, noFlags, db) + t.Cleanup(tsv.StopService) + + // Use up the cached range [1, 4). The table now holds next_id = 4. + for range 3 { + qre := newTestQueryExecutor(ctx, tsv, "select next value from seq", 0) + _, err := qre.Execute() + require.NoError(t, err) + } + + db.AddQuery("select next_id, cache from seq where id = 0 for update", &sqltypes.Result{ + Fields: []*querypb.Field{{Type: sqltypes.Int64}, {Type: sqltypes.Int64}}, + Rows: [][]sqltypes.Value{{sqltypes.NewInt64(4), sqltypes.NewInt64(3)}}, + }) + db.AddQuery("update seq set next_id = 7 where id = 0", &sqltypes.Result{}) + db.AddRejectedQuery("commit", errors.New("commit failed")) + + qre := newTestQueryExecutor(ctx, tsv, "select next value from seq", 0) + _, err := qre.Execute() + require.ErrorContains(t, err, "commit failed") + + // Check that the cache keeps the used up range. A stale cache holds LastVal = 7. + seq := qre.plan.Table.SequenceInfo + require.EqualValues(t, 4, seq.NextVal) + require.EqualValues(t, 4, seq.LastVal) +} + func TestQueryExecutorMessageStreamACL(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/go/vt/vttablet/tabletserver/tabletenv/config.go b/go/vt/vttablet/tabletserver/tabletenv/config.go index 91f43ad3b1b..5077edd8f1e 100644 --- a/go/vt/vttablet/tabletserver/tabletenv/config.go +++ b/go/vt/vttablet/tabletserver/tabletenv/config.go @@ -184,7 +184,7 @@ func registerTabletEnvFlags(fs *pflag.FlagSet) { utils.SetFlagBoolVar(fs, ¤tConfig.EnableTransactionLimit, "enable-transaction-limit", defaultConfig.EnableTransactionLimit, "If true, limit on number of transactions open at the same time will be enforced for all users. User trying to open a new transaction after exhausting their limit will receive an error immediately, regardless of whether there are available slots or not.") utils.SetFlagBoolVar(fs, ¤tConfig.EnableTransactionLimitDryRun, "enable-transaction-limit-dry-run", defaultConfig.EnableTransactionLimitDryRun, "If true, limit on number of transactions open at the same time will be tracked for all users, but not enforced.") - utils.SetFlagFloat64Var(fs, ¤tConfig.TransactionLimitPerUser, "transaction-limit-per-user", defaultConfig.TransactionLimitPerUser, "Maximum number of transactions a single user is allowed to use at any time, represented as fraction of -transaction_cap.") + utils.SetFlagFloat64Var(fs, ¤tConfig.TransactionLimitPerUser, "transaction-limit-per-user", defaultConfig.TransactionLimitPerUser, "Maximum number of transactions a single user is allowed to use at any time, represented as fraction of --queryserver-config-transaction-cap.") utils.SetFlagBoolVar(fs, ¤tConfig.TransactionLimitByUsername, "transaction-limit-by-username", defaultConfig.TransactionLimitByUsername, "Include VTGateCallerID.username when considering who the user is for the purpose of transaction limit.") utils.SetFlagBoolVar(fs, ¤tConfig.TransactionLimitByPrincipal, "transaction-limit-by-principal", defaultConfig.TransactionLimitByPrincipal, "Include CallerID.principal when considering who the user is for the purpose of transaction limit.") utils.SetFlagBoolVar(fs, ¤tConfig.TransactionLimitByComponent, "transaction-limit-by-component", defaultConfig.TransactionLimitByComponent, "Include CallerID.component when considering who the user is for the purpose of transaction limit.") @@ -1001,7 +1001,7 @@ func (c *TabletConfig) verifyTransactionLimitConfig() error { bySubcomp = c.TransactionLimitBySubcomponent ) if byAny := byUser || byPrincipal || byComp || bySubcomp; !byAny { - return errors.New("no user discriminating fields selected for transaction limiter, everyone would share single chunk of transaction pool. Override with at least one of --transaction_limit_by flags set to true") + return errors.New("no user discriminating fields selected for transaction limiter, everyone would share single chunk of transaction pool. Override with at least one of the --transaction-limit-by-* flags set to true") } if v := c.TransactionLimitPerUser; v <= 0 || v >= 1 { return fmt.Errorf("--transaction-limit-per-user should be a fraction within range (0, 1) (specified value: %v)", v) diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go index a6fd2523433..10ce00d7d99 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go +++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go @@ -1935,7 +1935,6 @@ func TestMinimalMode(t *testing.T) { engine = nil oldEnv := env env = nil - newEngine(t, ctx, "minimal") defer func() { if engine != nil { engine.Close() @@ -1946,6 +1945,7 @@ func TestMinimalMode(t *testing.T) { engine = oldEngine env = oldEnv }() + newEngine(t, ctx, "minimal") err := engine.Stream(context.Background(), "current", nil, nil, throttlerapp.VStreamerName, func(evs []*binlogdatapb.VEvent) error { return nil }, nil) require.Error(t, err, "minimal binlog_row_image is not supported by Vitess VReplication") } diff --git a/go/vt/wrangler/keyspace.go b/go/vt/wrangler/keyspace.go index 6fce73b1f0f..d8ae469c02e 100644 --- a/go/vt/wrangler/keyspace.go +++ b/go/vt/wrangler/keyspace.go @@ -120,7 +120,7 @@ func (wr *Wrangler) refreshPrimaryTablets(ctx context.Context, shards []*topo.Sh // updateShardRecords updates the shard records based on 'from' or 'to' direction. func (wr *Wrangler) updateShardRecords(ctx context.Context, keyspace string, shards []*topo.ShardInfo, cells []string, servedType topodatapb.TabletType, isFrom bool, clearSourceShards bool) (err error) { - return topotools.UpdateShardRecords(ctx, wr.ts, wr.tmc, keyspace, shards, cells, servedType, isFrom, clearSourceShards, wr.Logger()) + return topotools.UpdateShardRecords(ctx, wr.ts, wr.tmc, keyspace, shards, cells, servedType, isFrom, clearSourceShards, shardTabletRefreshTimeout, wr.Logger()) } func encodeString(in string) string { diff --git a/java/client/pom.xml b/java/client/pom.xml index 600efaea585..c16882174fe 100644 --- a/java/client/pom.xml +++ b/java/client/pom.xml @@ -5,7 +5,7 @@ io.vitess vitess-parent - 23.0.8 + 23.0.9-SNAPSHOT vitess-client Vitess Java Client diff --git a/java/example/pom.xml b/java/example/pom.xml index 9dd76034c65..9919b59f3e0 100644 --- a/java/example/pom.xml +++ b/java/example/pom.xml @@ -5,7 +5,7 @@ io.vitess vitess-parent - 23.0.8 + 23.0.9-SNAPSHOT vitess-example Vitess Java Client Example diff --git a/java/grpc-client/pom.xml b/java/grpc-client/pom.xml index d8fe7b45045..161ecd069ad 100644 --- a/java/grpc-client/pom.xml +++ b/java/grpc-client/pom.xml @@ -5,7 +5,7 @@ io.vitess vitess-parent - 23.0.8 + 23.0.9-SNAPSHOT vitess-grpc-client Vitess gRPC Client diff --git a/java/jdbc/pom.xml b/java/jdbc/pom.xml index bfe5d2a0f97..e7e596ed903 100644 --- a/java/jdbc/pom.xml +++ b/java/jdbc/pom.xml @@ -5,7 +5,7 @@ io.vitess vitess-parent - 23.0.8 + 23.0.9-SNAPSHOT vitess-jdbc Vitess JDBC Driver diff --git a/java/pom.xml b/java/pom.xml index 65fb73e2d97..14f23b87e47 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -11,7 +11,7 @@ io.vitess vitess-parent - 23.0.8 + 23.0.9-SNAPSHOT pom Vitess Java Client libraries [Parent] @@ -293,7 +293,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.6.0 + 0.12.0 true central diff --git a/proto/tabletmanagerdata.proto b/proto/tabletmanagerdata.proto index e46bc63dbd4..94eeceab516 100644 --- a/proto/tabletmanagerdata.proto +++ b/proto/tabletmanagerdata.proto @@ -714,7 +714,7 @@ message ValidateVReplicationPermissionsRequest { } message ValidateVReplicationPermissionsResponse { - // The --db_filtered_user on the tablet. + // The --db-filtered-user on the tablet. string user = 1; // Does the user have the minimum privileges needed to manage // vreplication metadata. diff --git a/tools/e2e_go_test.sh b/tools/e2e_go_test.sh index baa77b9ba98..2b35e7c928e 100755 --- a/tools/e2e_go_test.sh +++ b/tools/e2e_go_test.sh @@ -23,6 +23,11 @@ if [[ -n "$JUNIT_OUTPUT" ]]; then GOTESTSUM_ARGS+=("--junitfile" "$JUNIT_OUTPUT") fi +# Compile the tests with the same CGO_ENABLED=0 and -trimpath that "make build" +# uses for the binaries. Both are part of Go's build cache key, so the tests +# then reuse the packages "make build" already compiled instead of compiling +# the whole dependency graph a second time. +# # The test packages must come before "$@" on the go test command line: "$@" # can contain test-binary flags such as --topo-flavor or -keep-data, which # go test only forwards to the test binary when they appear after the package @@ -33,4 +38,4 @@ fi # this command; go test applies the -run flag (the last occurrence wins) and # hands the trailing package string to the test binary, which ignores it. # shellcheck disable=SC2086 -go tool -modfile=tools/gotestsum/go.mod gotestsum "${GOTESTSUM_ARGS[@]}" --raw-command -- go test -json -v -count=1 $PACKAGES "$@" -args -alsologtostderr +CGO_ENABLED=0 go tool -modfile=tools/gotestsum/go.mod gotestsum "${GOTESTSUM_ARGS[@]}" --raw-command -- go test -trimpath -json -v -count=1 $PACKAGES "$@" -args -alsologtostderr