diff --git a/.github/labeler.yml b/.github/labeler.yml index 037133b9c7385..50db226ce100b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,21 +1,27 @@ area/k8s: -- src/go/k8s/**/* +- changed-files: + - any-glob-to-any-file: ['src/go/k8s/**/*'] k8s/tests: -- src/go/k8s/**/* +- changed-files: + - any-glob-to-any-file: ['src/go/k8s/**/*'] area/build: -- cmake/**/* -- .github/**/* +- changed-files: + - any-glob-to-any-file: ['cmake/**/*', '.github/**/*'] area/docs: -- docs/**/* +- changed-files: + - any-glob-to-any-file: ['docs/**/*'] area/rpk: -- src/go/rpk/**/* +- changed-files: + - any-glob-to-any-file: ['src/go/rpk/**/*'] area/redpanda: -- src/v/**/* +- changed-files: + - any-glob-to-any-file: ['src/v/**/*'] area/wasm: -- src/transform-sdk/**/* +- changed-files: + - any-glob-to-any-file: ['src/transform-sdk/**/*'] diff --git a/.github/workflows/backport-command.yml b/.github/workflows/backport-command.yml index e18a25845595f..cda36d16cfdb8 100644 --- a/.github/workflows/backport-command.yml +++ b/.github/workflows/backport-command.yml @@ -25,11 +25,25 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SM_READONLY_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SM_READONLY_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + + - name: get secrets from aws sm + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/actions_bot_token + parse-json-secrets: true - name: Get type of backport (issue or PR) env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} CLIENT_PAYLOAD: ${{ toJson(github.event.client_payload) }} id: get_backport_type run: $SCRIPT_DIR/get_backport_type.sh @@ -39,7 +53,7 @@ jobs: uses: peter-evans/create-or-update-comment@v1 if: failure() with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ github.event.client_payload.github.payload.comment.id }} reaction-type: "-1" @@ -48,7 +62,7 @@ jobs: if: failure() env: COMMENTED_ON: ${{ steps.get_backport_type.outputs.commented_on }} - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} run: $SCRIPT_DIR/post_error.sh shell: bash @@ -62,11 +76,11 @@ jobs: BACKPORT_BRANCH: ${{ needs.backport-type.outputs.backport_branch }} steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get user env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} id: user run: | username=$(gh api user --jq .login) @@ -82,7 +96,7 @@ jobs: - name: Discover and create milestone env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} TARGET_MILESTONE: ${{ needs.backport-type.outputs.target_milestone }} id: create_milestone run: $SCRIPT_DIR/create_milestone.sh @@ -91,7 +105,7 @@ jobs: - name: Create issue if: needs.backport-type.outputs.commented_on == 'issue' env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} TARGET_MILESTONE: ${{ steps.create_milestone.outputs.milestone }} ORIG_TITLE: ${{ github.event.client_payload.github.payload.issue.title }} ORIG_LABELS: ${{ toJson(github.event.client_payload.github.payload.issue.labels) }} @@ -111,23 +125,23 @@ jobs: if: needs.backport-type.outputs.commented_on == 'pr' env: BACKPORT_PR_NUMBER: ${{ github.event.client_payload.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} id: backport_commits run: | backport_commits=$(gh api "repos/$TARGET_FULL_REPO/pulls/$BACKPORT_PR_NUMBER/commits" --jq .[].sha | paste -s -d ' ' -) echo "backport_commits=$backport_commits" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.backport-type.outputs.commented_on == 'pr' with: repository: ${{ steps.user.outputs.username }}/${{ steps.user.outputs.repo }} - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} path: ./fork - name: Backport commits and get details if: needs.backport-type.outputs.commented_on == 'pr' env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} ORIG_TITLE: ${{ github.event.client_payload.github.payload.issue.title }} BACKPORT_COMMITS: ${{ steps.backport_commits.outputs.backport_commits }} IS_MERGED: ${{ github.event.client_payload.pull_request.merged }} @@ -142,7 +156,7 @@ jobs: - name: Create pull request if: needs.backport-type.outputs.commented_on == 'pr' env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} TARGET_MILESTONE: ${{ steps.create_milestone.outputs.milestone }} ORIG_TITLE: ${{ github.event.client_payload.github.payload.issue.title }} ORIG_REVIEWERS: ${{ steps.reviewers.outputs.reviewers }} @@ -158,7 +172,7 @@ jobs: - name: Add reaction uses: peter-evans/create-or-update-comment@v1 with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ github.event.client_payload.github.payload.comment.id }} reaction-type: hooray @@ -167,7 +181,7 @@ jobs: uses: peter-evans/create-or-update-comment@v1 if: failure() with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ github.event.client_payload.github.payload.comment.id }} reaction-type: "-1" @@ -176,14 +190,14 @@ jobs: if: failure() env: COMMENTED_ON: ${{ needs.backport-type.outputs.commented_on }} - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} run: $SCRIPT_DIR/post_error.sh shell: bash - name: Create Issue On Error if: failure() env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} TARGET_MILESTONE: ${{ steps.create_milestone.outputs.milestone }} ORIG_TITLE: ${{ github.event.client_payload.github.payload.issue.title }} ORIG_LABELS: ${{ toJson(github.event.client_payload.github.payload.issue.labels) }} diff --git a/.github/workflows/backport-on-merge.yml b/.github/workflows/backport-on-merge.yml index 05ead7ab61bb3..c00d35963ce47 100644 --- a/.github/workflows/backport-on-merge.yml +++ b/.github/workflows/backport-on-merge.yml @@ -17,7 +17,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Find the PR associated with this push, if there is one. - uses: jwalton/gh-find-current-pr@v1 @@ -29,10 +29,21 @@ jobs: if: success() && steps.findPr.outputs.number env: PR: ${{ steps.findPr.outputs.pr }} - + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SM_READONLY_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SM_READONLY_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - name: get secrets from aws sm + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/actions_bot_token + parse-json-secrets: true - name: Backport On Merge env: - GITHUB_TOKEN: ${{ secrets.ACTIONS_BOT_TOKEN }} + GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} id: extract_required_backports_from_pr_body run: $SCRIPT_DIR/backport_on_merge.sh shell: bash diff --git a/.github/workflows/buildkite-slash-commands.yml b/.github/workflows/buildkite-slash-commands.yml index e385d0a36be54..380502056b039 100644 --- a/.github/workflows/buildkite-slash-commands.yml +++ b/.github/workflows/buildkite-slash-commands.yml @@ -14,23 +14,38 @@ jobs: run-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SM_READONLY_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SM_READONLY_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + + - name: get secrets from aws sm + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/actions_bot_token + ,sdlc/prod/github/buildkite_token + parse-json-secrets: true + + - uses: actions/checkout@v4 with: repository: redpanda-data/sparse-checkout - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} path: sparse-checkout - uses: ./sparse-checkout with: repository: redpanda-data/vtools - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} patterns: actions path: ghca - name: Buildkite slash command action uses: ./ghca/actions/buildkite-slash-commands with: - buildkite_token: ${{ secrets.BUILDKITE_TOKEN }} + buildkite_token: ${{ env.BUILDKITE_TOKEN }} buildkite_org: redpanda buildkite_pipeline: redpanda command: ${{ github.event.client_payload.slash_command.command }} @@ -38,7 +53,7 @@ jobs: - name: Success reaction uses: peter-evans/create-or-update-comment@v2 with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ github.event.client_payload.github.payload.comment.id }} reaction-type: hooray @@ -47,5 +62,5 @@ jobs: if: failure() uses: ./ghca/actions/slash-command-error with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} body: ${{ env.ERROR_MSG }} diff --git a/.github/workflows/cloud-installpack-bk-trigger.yml b/.github/workflows/cloud-installpack-bk-trigger.yml index 8881338dbdab2..dc2da65f1dc3a 100644 --- a/.github/workflows/cloud-installpack-bk-trigger.yml +++ b/.github/workflows/cloud-installpack-bk-trigger.yml @@ -1,30 +1,40 @@ +--- name: check formatting on: release: types: [published] - jobs: trigger-bump: runs-on: ubuntu-latest - + permissions: + id-token: write + contents: read steps: - - uses: actions/checkout@v3 - with: - repository: redpanda-data/sparse-checkout - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ vars.RP_AWS_CRED_REGION }} + role-to-assume: arn:aws:iam::${{ secrets.RP_AWS_CRED_ACCOUNT_ID }}:role/${{ vars.RP_AWS_CRED_BASE_ROLE_NAME }}${{ github.event.repository.name }} + - uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/actions_bot_token + ,sdlc/prod/github/buildkite_token + parse-json-secrets: true + - uses: actions/checkout@v4 + with: + repository: redpanda-data/sparse-checkout + token: ${{ env.ACTIONS_BOT_TOKEN }} path: sparse-checkout - - - uses: ./sparse-checkout + - uses: ./sparse-checkout with: repository: redpanda-data/vtools - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} patterns: actions - path: ghca - + path: ghca - name: Trigger Versions Bump Buildkite Job uses: ./ghca/actions/buildkite-pipeline-trigger with: - buildkite_token: ${{ secrets.BUILDKITE_TOKEN }} + buildkite_token: ${{ env.BUILDKITE_TOKEN }} buildkite_org: redpanda buildkite_pipeline: ${{ vars.CLOUD_PIPELINE }} commit: HEAD diff --git a/.github/workflows/kics-iac.yml b/.github/workflows/kics-iac.yml deleted file mode 100644 index 55eaabd200c1d..0000000000000 --- a/.github/workflows/kics-iac.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: kics scanning -on: - push: - branches: dev -jobs: - kics: - runs-on: ubuntu-latest - env: - AWS_ACCESS_KEY_ID: ${{ secrets.VULN_REPORTS_AWS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.VULN_REPORTS_AWS_SECRET_ACCESS_KEY }} - VULN_REPORTS_AWS_BUCKET: ${{ secrets.VULN_REPORTS_AWS_BUCKET }} - AWS_EC2_METADATA_DISABLED: true - steps: - - uses: actions/checkout@v2 - - name: run kics Scan - uses: checkmarx/kics-github-action@v1.6.3 - with: - path: . - ignore_on_exit: results - output_path: res/ - exclude_paths: tests/,src/go/k8s/tests/,src/go/rpk/pkg/testfs/,src/go/k8s/config/ - - name: display kics results - run: | - cat res/results.json - - name: upload scan results - run: | - set -eu - KEY="`date +%Y`/`date +%m`/`date +%d`/${GITHUB_REPOSITORY#*/}_${GITHUB_REF#refs/heads/}_kics_`date +%s`.json" - echo "[i] writing to s3 object '$KEY'" - aws s3 cp res/results.json s3://$VULN_REPORTS_AWS_BUCKET/$KEY diff --git a/.github/workflows/lint-cpp.yml b/.github/workflows/lint-cpp.yml index ba96d180d60ba..6a2e5e0731dc4 100644 --- a/.github/workflows/lint-cpp.yml +++ b/.github/workflows/lint-cpp.yml @@ -27,18 +27,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run clang-format run: | docker run \ - -v $PWD:/redpanda ubuntu \ + -v $PWD:/redpanda ubuntu:noble \ bash -c "cd /redpanda && \ apt update && \ - apt install -y wget git lsb-release wget software-properties-common gnupg && \ - wget https://apt.llvm.org/llvm.sh && \ - chmod +x llvm.sh && \ - ./llvm.sh 16 && \ - apt-get install -y clang-format-16 && \ + apt install -y git clang-format-16 && \ find . -type f -regex '.*\.\(cpp\|h\|hpp\|cc\|proto\|java\)' | xargs -n1 clang-format-16 -i -style=file -fallback-style=none" git diff --exit-code diff --git a/.github/workflows/lint-golang.yml b/.github/workflows/lint-golang.yml index 09c1a21ad835c..fcce06801e9b2 100644 --- a/.github/workflows/lint-golang.yml +++ b/.github/workflows/lint-golang.yml @@ -27,10 +27,10 @@ jobs: go-version: stable - name: Run golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: latest - args: --timeout 5m + args: --timeout 10m --verbose working-directory: src/go/rpk/ - name: Install gofumpt @@ -43,7 +43,7 @@ jobs: - name: Run gofumpt run: | - find src/go -type f -not -name '*.pb.go' -not -name 'zz*' -name '*.go' | xargs -n1 "$HOME/.local/bin/gofumpt" -w -lang=1.20 + find src/go -type f -not -name '*.connect.go' -not -name '*.pb.go' -not -name 'zz*' -name '*.go' | xargs -n1 "$HOME/.local/bin/gofumpt" -w -lang=1.20 git diff --exit-code - name: go mod tidy (rpk) diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml index 23628cb08c8c8..9ad08c9484a3d 100644 --- a/.github/workflows/lint-python.yml +++ b/.github/workflows/lint-python.yml @@ -19,10 +19,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install yapf - run: pip install yapf==0.40.1 + run: | + python3 -mvenv /tmp/venv/yapf + source /tmp/venv/yapf/bin/activate + pip install yapf==0.40.1 - name: Run yapf - run: find . -type f -name '*.py' | xargs -n8 yapf -d + run: find . -type f -name '*.py' | xargs -n8 /tmp/venv/yapf/bin/yapf -d diff --git a/.github/workflows/lint-sh.yml b/.github/workflows/lint-sh.yml index 17dc171c879d4..0e210ad2e7ef1 100644 --- a/.github/workflows/lint-sh.yml +++ b/.github/workflows/lint-sh.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install shfmt env: diff --git a/.github/workflows/old-backports.yml b/.github/workflows/old-backports.yml deleted file mode 100644 index f1929f208f291..0000000000000 --- a/.github/workflows/old-backports.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: old-backports -on: - schedule: - - cron: '*/10 * * * *' -jobs: - alert-on-abandoned-backports: - outputs: - OUTPUT: ${{ steps.get-output.outputs.OUTPUT }} - runs-on: ubuntu-latest - - steps: - - name: Get output - id: get-output - run: | - echo OUTPUT=$(gh search issues --label "kind/backport" --state open --repo "redpanda-data/redpanda" --updated "<`date --date="15 days ago" +"%Y"-"%m"-"%d"`" --sort updated --order asc --limit 15 --json "assignees,updatedAt,url" --jq '.[] | "@" + (.assignees[] | {login} | .login), .url, .updatedAt,"----"') >>"$GITHUB_OUTPUT" - shell: bash - - - name: "Post to a test channel (temporary)" - id: send-slack-message - uses: slackapi/slack-github-action@v1.24.0 - with: - channel-id: "C05DHDW4VCL" - slack-message: "Test: ${{ steps.get-output.outputs.OUTPUT }}" - env: - SLACK_BOT_TOKEN: ${{ secrets.VBOTBUILDOVICH_SLACK_BOT_TOKEN }} diff --git a/.github/workflows/packages-created.yml b/.github/workflows/packages-created.yml deleted file mode 100644 index 7d933822722bd..0000000000000 --- a/.github/workflows/packages-created.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: package_creation_handler -on: - repository_dispatch: - types: [packages-created] - -jobs: - package_creation_handler: - runs-on: ubuntu-20.04 - - steps: - - name: Notify PR - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ secrets.VTOOLS_GITHUB_API_TOKEN }} - issue-number: ${{ github.event.client_payload.pr_number }} - body: | - Packages created for ${{ github.event.client_payload.ref }} - Tests requiring them can now be run. Try: - `/chaos-test` or `/ducktape` - if: ${{ github.event.client_payload.pr_number }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 00c36f0af66f1..1b8741fa0816d 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -11,6 +11,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 44e909adcd137..ba05fca467710 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -1,16 +1,28 @@ +--- name: promote on: release: types: [published] - jobs: trigger-promote: runs-on: ubuntu-latest + permissions: + id-token: write + contents: read steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ vars.RP_AWS_CRED_REGION }} + role-to-assume: arn:aws:iam::${{ secrets.RP_AWS_CRED_ACCOUNT_ID }}:role/${{ vars.RP_AWS_CRED_BASE_ROLE_NAME }}${{ github.event.repository.name }} + - uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/buildkite_token + parse-json-secrets: true - name: trigger redpanda promote pipeline uses: "buildkite/trigger-pipeline-action@v2.0.0" with: - buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }} + buildkite_api_access_token: ${{ env.BUILDKITE_TOKEN }} pipeline: "redpanda/redpanda" branch: dev message: ":github: Promote redpanda packages" diff --git a/.github/workflows/release-rp-storage-tool.yml b/.github/workflows/release-rp-storage-tool.yml new file mode 100644 index 0000000000000..ff8f018b705a1 --- /dev/null +++ b/.github/workflows/release-rp-storage-tool.yml @@ -0,0 +1,54 @@ +--- +name: Release rp-storage-tool +on: + push: + tags: ['v*'] +jobs: + release-rp-storage-tool: + if: ${{ github.repository == 'redpanda-data/redpanda' }} + name: Release - ${{ matrix.platform.release_for }} + strategy: + matrix: + platform: + - release_for: linux-amd64 + os: ubuntu-latest + - release_for: linux-arm64 + os: ubuntu-latest-2-arm64 + runs-on: ${{ matrix.platform.os }} + permissions: + id-token: write + contents: read + steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ vars.RP_AWS_CRED_REGION }} + role-to-assume: arn:aws:iam::${{ secrets.RP_AWS_CRED_ACCOUNT_ID }}:role/${{ vars.RP_AWS_CRED_BASE_ROLE_NAME }}${{ github.event.repository.name }} + - uses: actions/checkout@v4 + - name: Build binary + run: | + export DOCKER_BUILDKIT=1 + docker build -f tests/docker/Dockerfile \ + -t rp-storage-tool \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --cache-from redpandadata/redpanda-test-node:cache-amd64 \ + --cache-from redpandadata/redpanda-test-node:cache-arm64 \ + --target rp-storage-tool . \ + --load + - name: Get binary + run: | + id=$(docker create rp-storage-tool) + docker cp $id:/usr/local/bin/rp-storage-tool ./ + ./rp-storage-tool --help + - name: Push to public bucket + run: | + arch=$(uname -m) + os=$(uname | tr '[:upper:]' '[:lower:]') + if [[ $arch == "x86_64" || $arch == "amd64" ]]; then + arch="amd64" + elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then + arch="arm64" + else + echo "unknown arch: $arch" + exit 1 + fi + aws s3 cp ./rp-storage-tool s3://vectorized-public/releases/rp-storage-tool/${{ github.ref_name }}/$os/$arch/rp-storage-tool --acl public-read diff --git a/.github/workflows/render-draft-release-notes.yml b/.github/workflows/render-draft-release-notes.yml deleted file mode 100644 index 4d7ea20148b1d..0000000000000 --- a/.github/workflows/render-draft-release-notes.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Render Draft Release Notes -on: - push: - branches: [ dev, 'v[0-9]+.[0-9]+.x' ] -jobs: - render: - runs-on: ubuntu-latest - steps: - - name: Curl rpchangelog - run: | - mkdir -v -p rpchangelog - curl -s -S -f -L -o rpchangelog/requirements.txt https://vectorized-public.s3.us-west-2.amazonaws.com/rpchangelog/requirements.txt - curl -s -S -f -L -o rpchangelog/rpchangelog.py https://vectorized-public.s3.us-west-2.amazonaws.com/rpchangelog/rpchangelog.py - chmod +x rpchangelog/rpchangelog.py - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - cache: 'pip' - - run: pip3 install -r ./rpchangelog/requirements.txt - - name: Render draft release notes to job summary - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [[ $GITHUB_REF_NAME == 'dev' ]]; then - PREVIOUS_TAG_NAME=$(gh -R "$GITHUB_REPOSITORY_OWNER/redpanda" release list --exclude-drafts -L 1 | tail -1 | cut -f1) - YEAR_VER=$(echo "$PREVIOUS_TAG_NAME" | cut -d. -f1) - FEATURE_VER=$(echo "$PREVIOUS_TAG_NAME" | cut -d. -f2) - TAG_NAME="$YEAR_VER.$((FEATURE_VER+1)).1" - else - # e.g. GITHUB_REF_NAME=v22.3.x - SEARCH_PATTERN=${GITHUB_REF_NAME::-1} - PREVIOUS_TAG_NAME=$(gh -R "$GITHUB_REPOSITORY_OWNER/redpanda" release list --exclude-drafts | grep "$SEARCH_PATTERN" | head -1 | cut -f1) - YEAR_VER=$(echo "$PREVIOUS_TAG_NAME" | cut -d. -f1) - FEATURE_VER=$(echo "$PREVIOUS_TAG_NAME" | cut -d. -f2) - PATCH_VER=$(echo "$PREVIOUS_TAG_NAME" | cut -d. -f3) - TAG_NAME="$YEAR_VER.$FEATURE_VER.$((PATCH_VER+1))" - fi - ./rpchangelog/rpchangelog.py --log-level=DEBUG --github-owner="$GITHUB_REPOSITORY_OWNER" rel "$TAG_NAME" "$GITHUB_REF_NAME" "$PREVIOUS_TAG_NAME" >> "$GITHUB_STEP_SUMMARY" - LINK_TO_SUMMARY="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - FINAL_MSG="View rendered draft release notes in the job summary: $LINK_TO_SUMMARY" - if grep -q '## Unclear' "$GITHUB_STEP_SUMMARY"; then - echo '❌ Release Notes Unclear' - echo $FINAL_MSG - exit 1 - else - echo '✔ Release Notes Clear' - echo $FINAL_MSG - fi diff --git a/.github/workflows/render-pr-body-release-notes.yml b/.github/workflows/render-pr-body-release-notes.yml index 10ad2a4120eaf..f2360f1bd14c5 100644 --- a/.github/workflows/render-pr-body-release-notes.yml +++ b/.github/workflows/render-pr-body-release-notes.yml @@ -26,12 +26,16 @@ jobs: with: python-version: '3.10' cache: 'pip' - - run: pip3 install -r ./rpchangelog/requirements.txt + - run: | + python3 -mvenv /tmp/venv/rpcl + source /tmp/venv/rpcl/bin/activate + pip3 install -r ./rpchangelog/requirements.txt - name: Render PR body release notes to job summary env: PR_NUM: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + source /tmp/venv/rpcl/bin/activate ./rpchangelog/rpchangelog.py --log-level=DEBUG --github-owner="$GITHUB_REPOSITORY_OWNER" pr "$PR_NUM" >> "$GITHUB_STEP_SUMMARY" LINK_TO_SUMMARY="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" FINAL_MSG="View rendered release notes of PR #$PR_NUM in the job summary: $LINK_TO_SUMMARY" diff --git a/.github/workflows/rp-storage-tool-checks.yml b/.github/workflows/rp-storage-tool-checks.yml index 211d46a335116..2c76c5e7dab19 100644 --- a/.github/workflows/rp-storage-tool-checks.yml +++ b/.github/workflows/rp-storage-tool-checks.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install stable toolchain uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/slack-messages.yml b/.github/workflows/slack-messages.yml deleted file mode 100644 index 3a4ed5c20df64..0000000000000 --- a/.github/workflows/slack-messages.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Release Slack Message -on: - release: - types: [published] - -env: - MESSAGE: "Redpanda release ${{ github.event.release.tag_name }} has been published: ${{ github.event.release.html_url }}" - -jobs: - post-slack-message: - name: Post Slack Message - runs-on: ubuntu-latest - steps: - - - name: "Post to internal #releases channel" - id: internal_releases - uses: slackapi/slack-github-action@v1.18.0 - with: - channel-id: ${{ secrets.INTERNAL_RELEASES_SLACK_CHANNEL }} - slack-message: ${{ env.MESSAGE }} - env: - SLACK_BOT_TOKEN: ${{ secrets.VBOTBUILDOVICH_SLACK_BOT_TOKEN }} - - - name: "Post to internal #general channel" - id: internal_general - uses: slackapi/slack-github-action@v1.18.0 - with: - channel-id: ${{ secrets.INTERNAL_GENERAL_SLACK_CHANNEL }} - slack-message: ${{ env.MESSAGE }} - env: - SLACK_BOT_TOKEN: ${{ secrets.VBOTBUILDOVICH_SLACK_BOT_TOKEN }} - - - name: "Post to community #releases channel" - id: community_releases - uses: slackapi/slack-github-action@v1.18.0 - with: - channel-id: C034RDJLC31 - slack-message: ${{ env.MESSAGE }} - env: - SLACK_BOT_TOKEN: ${{ secrets.BUILDERBOT_COMMUNITY_SLACK_BOT_TOKEN }} - diff --git a/.github/workflows/slash-commands.yml b/.github/workflows/slash-commands.yml index a0d1179ed508c..32722f095c23d 100644 --- a/.github/workflows/slash-commands.yml +++ b/.github/workflows/slash-commands.yml @@ -6,10 +6,22 @@ jobs: slashCommandDispatch: runs-on: ubuntu-latest steps: + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_SM_READONLY_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SM_READONLY_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - name: get secrets from aws sm + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + ,sdlc/prod/github/actions_bot_token + parse-json-secrets: true - name: Slash Command Dispatch uses: peter-evans/slash-command-dispatch@v2 with: - token: ${{ secrets.ACTIONS_BOT_TOKEN }} + token: ${{ env.ACTIONS_BOT_TOKEN }} permission: read issue-type: both commands: | diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000000000..f7ab307025a9c --- /dev/null +++ b/.yamllint @@ -0,0 +1,9 @@ +--- +extends: default +rules: + line-length: + max: 900 + indentation: + spaces: 2 + truthy: + allowed-values: ['true', 'false', 'on'] diff --git a/install-dependencies.sh b/install-dependencies.sh index 1d02d78cb0ad2..95c1b7ae3fdd6 100755 --- a/install-dependencies.sh +++ b/install-dependencies.sh @@ -79,6 +79,7 @@ fedora_deps=( lld llvm lz4-devel + lz4-static ninja-build numactl-devel procps diff --git a/src/go/.goreleaser.yaml b/src/go/.goreleaser.yaml index 1b75d9af924f2..f89c9c67c4cba 100644 --- a/src/go/.goreleaser.yaml +++ b/src/go/.goreleaser.yaml @@ -67,39 +67,5 @@ release: name: redpanda draft: true discussion_category_name: Releases -brews: - - name: redpanda - homepage: "https://redpanda.com" - description: "Redpanda CLI & toolbox" - repository: - owner: redpanda-data - name: homebrew-tap - folder: Formula - skip_upload: auto - ids: - - rpk - extra_install: | - generate_completions_from_executable(bin/"rpk", "generate", "shell-completion", base_name: "rpk") - caveats: | - Redpanda Keeper (rpk) is Redpanda's command line interface (CLI) - utility. The rpk commands let you configure, manage, and tune - Redpanda clusters. They also let you manage topics, groups, - and access control lists (ACLs). - Start a three-node docker cluster locally: - - rpk container start -n 3 - - Interact with the cluster using commands like: - - rpk topic list - - When done, stop and delete the docker cluster: - - rpk container purge - - For more examples and guides, visit: https://docs.redpanda.com - commit_author: - name: vbotbuildovich - email: vbot@redpanda.com announce: skip: "true" diff --git a/src/go/rpk/.golangci.yml b/src/go/rpk/.golangci.yml index b21539d0a9ac9..60e95f0622bd2 100644 --- a/src/go/rpk/.golangci.yml +++ b/src/go/rpk/.golangci.yml @@ -1,13 +1,13 @@ run: allow-parallel-runners: true + tests: false # golangci-lint by default ignores some staticcheck and vet raised issues that # are actually important to catch. The following ensures that we do not ignore # those tools ever. issues: exclude-use-default: false - new-from-rev: HEAD - + # We opt out of all suggested linters and manually pick what we want. # Please do not use enable-all. linters: @@ -28,7 +28,6 @@ linters: - durationcheck - errname - errorlint - - exportloopref - godot - gofmt - gofumpt @@ -51,26 +50,16 @@ linters-settings: # If we want to opt out of a lint, we require an explanation. nolintlint: - allow-leading-space: true allow-unused: false require-explanation: true require-specific: true - # If gofumpt is run outside a module, it assumes Go 1.0 rather than the - # latest Go. We always want the latest formatting. - # - # https://github.com/mvdan/gofumpt/issues/137 - gofumpt: - lang-version: "1.20" - # Revive is yet another metalinter with a lot of useful lints. # The below opts in to all the ones we would like to use. revive: ignore-generated-header: true severity: warning confidence: 0.8 - error-code: 0 - warning-code: 0 rules: - name: atomic - name: blank-imports diff --git a/src/go/rpk/Makefile b/src/go/rpk/Makefile deleted file mode 100644 index 27fb16ed6bb4b..0000000000000 --- a/src/go/rpk/Makefile +++ /dev/null @@ -1,76 +0,0 @@ -# Author 2021 Per Buer -# Copyright 2021 Redpanda Data, Inc. -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.md -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0 - -GOCMD=go -GOTEST=$(GOCMD) test -GOBUILD=$(GOCMD) build -GOOS := $(shell go env GOOS) -GOARCH := $(shell go env GOARCH) -OUTDIR := $(GOOS)-$(GOARCH) - -VER_PKG=github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/cmd/version -CONT_PKG=github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/cmd/container/common - -REV := $(shell git rev-parse --short HEAD) -VERSION := $(shell git describe --tags --abbrev=0) -IMG_TAG=$(REV) -LDFLAGS=-X $(VER_PKG).version=$(VERSION) -X $(VER_PKG).rev=$(REV) -X $(CONT_PKG).tag=$(IMG_TAG) - -# Silly color to make the help pretty. -GREEN := $(shell tput -Txterm setaf 2) -YELLOW := $(shell tput -Txterm setaf 3) -WHITE := $(shell tput -Txterm setaf 7) -RESET := $(shell tput -Txterm sgr0) - -all: help - -build: ## Build rpk - $(shell mkdir -p $(OUTDIR)) - $(GOBUILD) -ldflags '$(LDFLAGS)' -o $(OUTDIR) ./... - -test: ## Run the tests - $(GOTEST) ./... - -coverage: ## Run the tests with coverage - $(GOTEST) -coverprofile=coverage.out ./... - -help: ## Show this help. - @echo '' - @echo 'Usage:' - @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' - @echo '' - @echo 'Targets:' - @awk 'BEGIN {FS = ":.*?## "} { \ - if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ - else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ - }' $(MAKEFILE_LIST) - -# Run crlfmt against code -fmt: crlfmt - $(CRLFMT) -w -wrap=80 -ignore '_generated.deepcopy.go$$' . - -# Download crlfmt locally if necessary -CRLFMT = $(shell pwd)/bin/crlfmt -crlfmt: - $(call go-get-tool,$(CRLFMT),github.com/cockroachdb/crlfmt@v0.0.0-20210128092314-b3eff0b87c79) - -# go-get-tool will 'go get' any package $2 and install it to $1. -PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) -define go-get-tool -@[ -f $(1) ] || { \ -set -e ;\ -TMP_DIR=$$(mktemp -d) ;\ -cd $$TMP_DIR ;\ -go mod init tmp ;\ -echo "Downloading $(2)" ;\ -GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ -rm -rf $$TMP_DIR ;\ -} -endef \ No newline at end of file diff --git a/src/go/rpk/buf.gen.yaml b/src/go/rpk/buf.gen.yaml index f9ef32102f335..cb99a7c50d272 100644 --- a/src/go/rpk/buf.gen.yaml +++ b/src/go/rpk/buf.gen.yaml @@ -12,8 +12,6 @@ plugins: - name: go out: proto/gen/go opt: paths=source_relative - - name: go-grpc + - name: connect-go out: proto/gen/go - opt: - - paths=source_relative - - require_unimplemented_servers=false \ No newline at end of file + opt: paths=source_relative \ No newline at end of file diff --git a/src/go/rpk/generate.sh b/src/go/rpk/generate.sh deleted file mode 100755 index 231452bfd83de..0000000000000 --- a/src/go/rpk/generate.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -go install github.com/bufbuild/buf/cmd/buf@latest -go install google.golang.org/protobuf/cmd/protoc-gen-go@latest -go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest -rm -rf proto/gen -buf generate buf.build/redpandadata/common --path redpanda/api/common/v1alpha1/common.proto -buf generate buf.build/redpandadata/cloud --path redpanda/api/controlplane/v1beta1 diff --git a/src/go/rpk/go.mod b/src/go/rpk/go.mod index feb308ddce237..146674b6a27bd 100644 --- a/src/go/rpk/go.mod +++ b/src/go/rpk/go.mod @@ -1,94 +1,97 @@ module github.com/redpanda-data/redpanda/src/go/rpk -go 1.21 +go 1.22.2 require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20231115204500-e097f827e652.1 - cloud.google.com/go/compute/metadata v0.2.3 + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 + cloud.google.com/go/compute/metadata v0.5.0 + connectrpc.com/connect v1.16.2 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/avast/retry-go v3.0.0+incompatible - github.com/aws/aws-sdk-go v1.45.25 - github.com/beevik/ntp v1.3.0 - github.com/bufbuild/protocompile v0.6.0 + github.com/aws/aws-sdk-go v1.55.5 + github.com/beevik/ntp v1.4.3 + github.com/bufbuild/protocompile v0.14.1 github.com/coreos/go-systemd/v22 v22.5.0 - github.com/docker/docker v24.0.6+incompatible - github.com/docker/go-connections v0.4.0 + github.com/docker/docker v27.2.1+incompatible + github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 - github.com/fatih/color v1.15.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 - github.com/hamba/avro/v2 v2.16.0 + github.com/fatih/color v1.17.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 + github.com/hamba/avro/v2 v2.25.2 github.com/hashicorp/go-multierror v1.1.1 - github.com/lestrrat-go/jwx v1.2.26 - github.com/linkedin/goavro/v2 v2.12.0 + github.com/lestrrat-go/jwx v1.2.30 + github.com/linkedin/goavro/v2 v2.13.0 github.com/lorenzosaino/go-sysctl v0.3.1 github.com/mattn/go-isatty v0.0.20 github.com/moby/term v0.5.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc5 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/opencontainers/image-spec v1.1.0 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 - github.com/prometheus/client_model v0.6.0 - github.com/prometheus/common v0.49.0 - github.com/rs/xid v1.5.0 - github.com/safchain/ethtool v0.3.0 - github.com/schollz/progressbar/v3 v3.13.1 + github.com/prometheus/client_model v0.6.1 + github.com/prometheus/common v0.59.1 + github.com/rs/xid v1.6.0 + github.com/safchain/ethtool v0.4.1 + github.com/schollz/progressbar/v3 v3.14.6 github.com/sethgrid/pester v1.2.0 - github.com/spf13/afero v1.10.0 - github.com/spf13/cobra v1.7.0 + github.com/spf13/afero v1.11.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 - github.com/tklauser/go-sysconf v0.3.12 - github.com/twmb/franz-go v1.15.1 - github.com/twmb/franz-go/pkg/kadm v1.10.0 - github.com/twmb/franz-go/pkg/kmsg v1.7.0 - github.com/twmb/franz-go/pkg/sr v0.0.0-20231012204113-ae169a1f35c2 + github.com/stretchr/testify v1.9.0 + github.com/tklauser/go-sysconf v0.3.14 + github.com/twmb/franz-go v1.17.1 + github.com/twmb/franz-go/pkg/kadm v1.13.0 + github.com/twmb/franz-go/pkg/kmsg v1.8.0 + github.com/twmb/franz-go/pkg/sr v1.1.0 github.com/twmb/franz-go/plugin/kzap v1.1.2 github.com/twmb/tlscfg v1.2.1 github.com/twmb/types v1.1.6 - go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d - golang.org/x/sync v0.5.0 - golang.org/x/sys v0.17.0 - golang.org/x/term v0.17.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe - google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe - google.golang.org/grpc v1.61.0 - google.golang.org/protobuf v1.32.0 + go.uber.org/zap v1.27.0 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/sync v0.8.0 + golang.org/x/sys v0.25.0 + golang.org/x/term v0.24.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.2 - k8s.io/apimachinery v0.28.2 - k8s.io/client-go v0.28.2 + k8s.io/api v0.31.0 + k8s.io/apimachinery v0.31.0 + k8s.io/client-go v0.31.0 ) require ( - cloud.google.com/go/compute v1.23.3 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -96,38 +99,43 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pierrec/lz4/v4 v4.1.18 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.25.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/v3 v3.0.3 // indirect - honnef.co/go/tools v0.4.6 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + honnef.co/go/tools v0.5.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect + k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/src/go/rpk/go.sum b/src/go/rpk/go.sum index 15227b5c58bc7..d3ab596683115 100644 --- a/src/go/rpk/go.sum +++ b/src/go/rpk/go.sum @@ -1,220 +1,108 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20231115204500-e097f827e652.1 h1:u0olL4yf2p7Tl5jfsAK5keaFi+JFJuv1CDHrbiXkxkk= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20231115204500-e097f827e652.1/go.mod h1:tiTMKD8j6Pd/D2WzREoweufjzaJKHZg35f/VGcZ2v3I= -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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 h1:SZRVx928rbYZ6hEKUIN+vtGDkl7uotABRWGY4OAg5gM= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= -github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/beevik/ntp v1.3.0 h1:/w5VhpW5BGKS37vFm1p9oVk/t4HnnkKZAZIubHM6F7Q= -github.com/beevik/ntp v1.3.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78= -github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= -github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho= +github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI= +github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -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.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= -github.com/hamba/avro/v2 v2.16.0 h1:0XhyP65Hs8iMLtdSR0v7ZrwRjsbIZdvr7KzYgmx1Mbo= -github.com/hamba/avro/v2 v2.16.0/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hamba/avro/v2 v2.25.2 h1:28dqbOCB7wA/3+J1ZN4GQ40tzsFtbtItkTPWgl97el0= +github.com/hamba/avro/v2 v2.25.2/go.mod h1:I8glyswHnpED3Nlx2ZdUe+4LJnCOOyiCzLMno9i/Uu0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -225,40 +113,32 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.26 h1:4iFo8FPRZGDYe1t19mQP0zTRqA7n8HnJ5lkIiDvJcB0= -github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDuIkx8ftpQ= +github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= +github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/linkedin/goavro/v2 v2.12.0 h1:rIQQSj8jdAUlKQh6DttK8wCRv4t4QO09g1C4aBWXslg= -github.com/linkedin/goavro/v2 v2.12.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= +github.com/linkedin/goavro/v2 v2.13.0 h1:L8eI8GcuciwUkt41Ej62joSZS4kKaYIUdze+6for9NU= +github.com/linkedin/goavro/v2 v2.13.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/lorenzosaino/go-sysctl v0.3.1 h1:3phX80tdITw2fJjZlwbXQnDWs4S30beNcMbw0cn0HtY= github.com/lorenzosaino/go-sysctl v0.3.1/go.mod h1:5grcsBRpspKknNS1qzt1eIeRDLrhpKZAtz8Fcuvs1Rc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -268,12 +148,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -281,6 +157,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -292,466 +170,193 @@ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8d github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 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.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= -github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE= -github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ= +github.com/safchain/ethtool v0.4.1 h1:S6mEleTADqgynileXoiapt/nKnatyR6bmIHoF+h2ADo= +github.com/safchain/ethtool v0.4.1/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48= +github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= +github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= github.com/sethgrid/pester v1.2.0 h1:adC9RS29rRUef3rIKWPOuP1Jm3/MmB6ke+OhE5giENI= github.com/sethgrid/pester v1.2.0/go.mod h1:hEUINb4RqvDxtoCaU0BNT/HV4ig5kfgOasrf1xcvr0A= -github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= -github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/twmb/franz-go v1.15.1 h1:ina2HNm9D5TRn+5fm0VAboOVCqF11XBAJA0JwQJnWyQ= -github.com/twmb/franz-go v1.15.1/go.mod h1:aos+d/UBuigWkOs+6WoqEPto47EvC2jipLAO5qrAu48= -github.com/twmb/franz-go/pkg/kadm v1.10.0 h1:3oYKNP+e3HGo4GYadrDeRxOaAIsOXmX6LBVMz9PxpCU= -github.com/twmb/franz-go/pkg/kadm v1.10.0/go.mod h1:hUMoV4SRho+2ij/S9cL39JaLsr+XINjn0ZkCdBY2DXc= -github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E= -github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw= -github.com/twmb/franz-go/pkg/sr v0.0.0-20231012204113-ae169a1f35c2 h1:f3iFXtwI6ny/UvBgnq7ovIDjuXXkksYHIQrDlYXocS0= -github.com/twmb/franz-go/pkg/sr v0.0.0-20231012204113-ae169a1f35c2/go.mod h1:egX+kicq83hpztv3PRCXKLNO132Ol9JTAJOCRZcqUxI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/twmb/franz-go v1.17.1 h1:0LwPsbbJeJ9R91DPUHSEd4su82WJWcTY1Zzbgbg4CeQ= +github.com/twmb/franz-go v1.17.1/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= +github.com/twmb/franz-go/pkg/kadm v1.13.0 h1:bJq4C2ZikUE2jh/wl9MtMTQ/kpmnBgVFh8XMQBEC+60= +github.com/twmb/franz-go/pkg/kadm v1.13.0/go.mod h1:VMvpfjz/szpH9WB+vGM+rteTzVv0djyHFimci9qm2C0= +github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= +github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= +github.com/twmb/franz-go/pkg/sr v1.1.0 h1:9BpyinApRkdJUC1v81LQD4kC51hKRRPDFmGBSx8LIE8= +github.com/twmb/franz-go/pkg/sr v1.1.0/go.mod h1:aUFRRLI5WYKpKzmWDztzZFecx5eOkCNuuamd91jUV5c= github.com/twmb/franz-go/plugin/kzap v1.1.2 h1:0arX5xJ0soUPX1LlDay6ZZoxuWkWk1lggQ5M/IgRXAE= github.com/twmb/franz-go/plugin/kzap v1.1.2/go.mod h1:53Cl9Uz1pbdOPDvUISIxLrZIWSa2jCuY1bTMauRMBmo= github.com/twmb/tlscfg v1.2.1 h1:IU2efmP9utQEIV2fufpZjPq7xgcZK4qu25viD51BB44= github.com/twmb/tlscfg v1.2.1/go.mod h1:GameEQddljI+8Es373JfQEBvtI4dCTLKWGJbqT2kErs= github.com/twmb/types v1.1.6 h1:PnQYJ8fMHkjPR4mgJkzqX1lYhfamNl5I2zuVBSZwLuE= github.com/twmb/types v1.1.6/go.mod h1:l7Lzw5AFc6JmI+fslBRUXHTG3J9RpvRpCUMXVBnjtJQ= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d h1:NRn/Afz91uVUyEsxMp4lGGxpr5y1qz+Iko60dbkfvLQ= -golang.org/x/exp/typeparams v0.0.0-20231006140011-7918f672742d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= +golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= -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= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= -google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= -google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= -google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -760,33 +365,23 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= -honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= -k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= -k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= -k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= +honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 h1:b2FmK8YH+QEwq/Sy2uAEhmqL5nPfGYbJOcaqjeYYZoA= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/src/go/rpk/pkg/adminapi/admin.go b/src/go/rpk/pkg/adminapi/admin.go index 36f84a6ef8ef2..1f612e2e820f0 100644 --- a/src/go/rpk/pkg/adminapi/admin.go +++ b/src/go/rpk/pkg/adminapi/admin.go @@ -185,7 +185,7 @@ func newAdminAPI(urls []string, auth Auth, tlsConfig *tls.Config, forCloud bool) // Backoff is the default redpanda raft election timeout: this enables us // to cleanly retry on 503s due to leadership changes in progress. - client.Backoff = func(retry int) time.Duration { + client.Backoff = func(_ int) time.Duration { maxJitter := 100 delayMs := retryBackoffMs + rng(maxJitter) return time.Duration(delayMs) * time.Millisecond diff --git a/src/go/rpk/pkg/adminapi/api_debug.go b/src/go/rpk/pkg/adminapi/api_debug.go index 9557229f58f06..402ba08a99877 100644 --- a/src/go/rpk/pkg/adminapi/api_debug.go +++ b/src/go/rpk/pkg/adminapi/api_debug.go @@ -131,11 +131,11 @@ type DebugPartition struct { Replicas []ReplicaState `json:"replicas"` } -func (a *AdminAPI) StartSelfTest(ctx context.Context, nodeIds []int, params []any) (string, error) { +func (a *AdminAPI) StartSelfTest(ctx context.Context, nodeIDs []int, params []any) (string, error) { var testID string body := SelfTestRequest{ Tests: params, - Nodes: nodeIds, + Nodes: nodeIDs, } err := a.sendToLeader(ctx, http.MethodPost, diff --git a/src/go/rpk/pkg/adminapi/api_partition.go b/src/go/rpk/pkg/adminapi/api_partition.go index 3aec0eb027b99..574c1a1e3072e 100644 --- a/src/go/rpk/pkg/adminapi/api_partition.go +++ b/src/go/rpk/pkg/adminapi/api_partition.go @@ -15,6 +15,9 @@ import ( "io" "net/http" "strings" + + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" + "github.com/spf13/afero" ) const partitionsBaseURL = "/v1/cluster/partitions" @@ -188,3 +191,16 @@ func (a *AdminAPI) ForceRecoverFromNode(ctx context.Context, plan []MajorityLost } return a.sendAny(ctx, http.MethodPost, "/v1/partitions/force_recover_from_nodes", body, nil) } + +func (a *AdminAPI) TransferLeadership(ctx context.Context, fs afero.Fs, p *config.RpkProfile, source int, ns, topic string, partition int, target string) error { + brokerURL, err := a.brokerIDToURL(ctx, source) + if err != nil { + return err + } + cl, err := NewHostClient(fs, p, brokerURL) + if err != nil { + return err + } + path := fmt.Sprintf("/v1/partitions/%s/%s/%d/transfer_leadership?target=%s", ns, topic, partition, target) + return cl.sendOne(ctx, http.MethodPost, path, nil, nil, false) +} diff --git a/src/go/rpk/pkg/adminapi/api_transform.go b/src/go/rpk/pkg/adminapi/api_transform.go index d964680977ab1..cb69f6d271793 100644 --- a/src/go/rpk/pkg/adminapi/api_transform.go +++ b/src/go/rpk/pkg/adminapi/api_transform.go @@ -41,11 +41,11 @@ func (a *AdminAPI) DeleteWasmTransform(ctx context.Context, name string) error { // PartitionTransformStatus is the status of a single transform that is running on an input partition. type PartitionTransformStatus struct { - NodeID int `json:"node_id"` - Partition int `json:"partition"` + NodeID int `json:"node_id" yaml:"node_id"` + Partition int `json:"partition" yaml:"partition"` // Status is an enum of: ["running", "inactive", "errored", "unknown"]. - Status string `json:"status"` - Lag int `json:"lag"` + Status string `json:"status" yaml:"status"` + Lag int `json:"lag" yaml:"lag"` } // EnvironmentVariable is a configuration key/value that can be injected into to a data transform. diff --git a/src/go/rpk/pkg/cli/acl/create.go b/src/go/rpk/pkg/cli/acl/create.go index 7839b5f6ae788..f46ffad38c012 100644 --- a/src/go/rpk/pkg/cli/acl/create.go +++ b/src/go/rpk/pkg/cli/acl/create.go @@ -46,7 +46,7 @@ Allow write permissions to user buzz to transactional id "txn": `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/delete.go b/src/go/rpk/pkg/cli/acl/delete.go index df4e2aca7e961..e1f0a5481bce8 100644 --- a/src/go/rpk/pkg/cli/acl/delete.go +++ b/src/go/rpk/pkg/cli/acl/delete.go @@ -55,7 +55,7 @@ resource names: * "literal" returns exact name matches `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/list.go b/src/go/rpk/pkg/cli/acl/list.go index 53bbfc415ee4c..2d47410a2bfc1 100644 --- a/src/go/rpk/pkg/cli/acl/list.go +++ b/src/go/rpk/pkg/cli/acl/list.go @@ -49,7 +49,7 @@ resource names: * "literal" returns exact name matches `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/create.go b/src/go/rpk/pkg/cli/acl/user/create.go index 2ff034e3ed8bc..cfaa6a9185321 100644 --- a/src/go/rpk/pkg/cli/acl/user/create.go +++ b/src/go/rpk/pkg/cli/acl/user/create.go @@ -57,6 +57,7 @@ acl help text for more info. } p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitNotServerlessAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/delete.go b/src/go/rpk/pkg/cli/acl/user/delete.go index 163d5561d3718..86cfccc9a2e38 100644 --- a/src/go/rpk/pkg/cli/acl/user/delete.go +++ b/src/go/rpk/pkg/cli/acl/user/delete.go @@ -37,6 +37,7 @@ delete any ACLs that may exist for this user. } p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitNotServerlessAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/list.go b/src/go/rpk/pkg/cli/acl/user/list.go index 9c0469c50e5a4..3e10390f2ea8f 100644 --- a/src/go/rpk/pkg/cli/acl/user/list.go +++ b/src/go/rpk/pkg/cli/acl/user/list.go @@ -29,6 +29,7 @@ func newListUsersCommand(fs afero.Fs, p *config.Params) *cobra.Command { } p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitNotServerlessAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) diff --git a/src/go/rpk/pkg/cli/acl/user/update.go b/src/go/rpk/pkg/cli/acl/user/update.go index a2db9af26eeb4..1b641e2cde9f8 100644 --- a/src/go/rpk/pkg/cli/acl/user/update.go +++ b/src/go/rpk/pkg/cli/acl/user/update.go @@ -29,6 +29,7 @@ func newUpdateCommand(fs afero.Fs, p *config.Params) *cobra.Command { f := p.Formatter p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitNotServerlessAdmin(p) cl, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin client: %v", err) @@ -45,8 +46,9 @@ func newUpdateCommand(fs afero.Fs, p *config.Params) *cobra.Command { } cmd.Flags().StringVar(&newPass, "new-password", "", "New user's password.") - cmd.Flags().StringVar(&mechanism, "mechanism", adminapi.ScramSha256, "SASL mechanism to use for the user you are creating (scram-sha-256, scram-sha-512, case insensitive)") + cmd.Flags().StringVar(&mechanism, "mechanism", adminapi.ScramSha256, "SASL mechanism to use for the user you are updating (scram-sha-256, scram-sha-512, case insensitive)") cmd.MarkFlagRequired("new-password") + cmd.MarkFlagRequired("mechanism") return cmd } diff --git a/src/go/rpk/pkg/cli/cloud/auth/auth.go b/src/go/rpk/pkg/cli/cloud/auth/auth.go index 7158db10b237c..44c6d5f64d58e 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/auth.go +++ b/src/go/rpk/pkg/cli/cloud/auth/auth.go @@ -45,7 +45,7 @@ only use a single SSO based login. } func validAuths(fs afero.Fs, p *config.Params) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { - return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { cfg, err := p.Load(fs) if err != nil { return nil, cobra.ShellCompDirectiveError diff --git a/src/go/rpk/pkg/cli/cloud/auth/create.go b/src/go/rpk/pkg/cli/cloud/auth/create.go index 71cb6aeab958a..6631e1ab30921 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/create.go +++ b/src/go/rpk/pkg/cli/cloud/auth/create.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/cobra" ) -func newCreateCommand(fs afero.Fs, p *config.Params) *cobra.Command { +func newCreateCommand(_ afero.Fs, _ *config.Params) *cobra.Command { cmd := &cobra.Command{ Use: "create [NAME]", Short: "Create an rpk cloud auth", diff --git a/src/go/rpk/pkg/cli/cloud/auth/rename.go b/src/go/rpk/pkg/cli/cloud/auth/rename.go index cecc486dd250f..7436eb23f4441 100644 --- a/src/go/rpk/pkg/cli/cloud/auth/rename.go +++ b/src/go/rpk/pkg/cli/cloud/auth/rename.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/cobra" ) -func newRenameToCommand(fs afero.Fs, p *config.Params) *cobra.Command { +func newRenameToCommand(_ afero.Fs, _ *config.Params) *cobra.Command { return &cobra.Command{ Use: "rename-to [NAME]", Short: "Rename the current rpk auth", diff --git a/src/go/rpk/pkg/cli/cloud/byoc/uninstall.go b/src/go/rpk/pkg/cli/cloud/byoc/uninstall.go index 9a79848540d16..5bdca16dc7b62 100644 --- a/src/go/rpk/pkg/cli/cloud/byoc/uninstall.go +++ b/src/go/rpk/pkg/cli/cloud/byoc/uninstall.go @@ -31,7 +31,7 @@ then you never need the plugin again. You can uninstall to save a small bit of disk space. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { pluginDir, err := plugin.DefaultBinPath() out.MaybeDie(err, "unable to determine managed plugin path: %w", err) byoc, pluginExists := plugin.ListPlugins(fs, []string{pluginDir}).Find("byoc") diff --git a/src/go/rpk/pkg/cli/cloud/login.go b/src/go/rpk/pkg/cli/cloud/login.go index 0fb02ef63e076..bd30f27238b7c 100644 --- a/src/go/rpk/pkg/cli/cloud/login.go +++ b/src/go/rpk/pkg/cli/cloud/login.go @@ -128,16 +128,6 @@ rpk will talk to a localhost:9092 cluster until you swap to a different profile. return } - // The current profile was auth'd to the current organization. - // We tell the status of what org the user is talking to. - if p.FromCloud { - fmt.Printf("You are talking to a cloud cluster %q (rpk profile name: %q)\n", p.CloudCluster.FullName(), p.Name) - fmt.Println("Select a different cluster to talk to (or ctrl+c to keep the current cluster)?") - err = profile.CreateFlow(cmd.Context(), fs, cfg, yAct, authVir, "", "", "prompt", false, nil, "", "") - profile.MaybeDieExistingName(err) - return - } - // Below here, the current profile is pointed to a // local container cluster or a self hosted cluster. // We want to create or swap to the cloud profile, @@ -155,8 +145,18 @@ rpk will talk to a localhost:9092 cluster until you swap to a different profile. return } + // The current profile was auth'd to the current organization. + // We tell the status of what org the user is talking to. + if p.FromCloud { + fmt.Printf("You are talking to a cloud cluster %q (rpk profile name: %q)\n", p.CloudCluster.FullName(), p.Name) + fmt.Println("Select a different cluster to talk to (or ctrl+c to keep the current cluster)?") + err = profile.CreateFlow(cmd.Context(), fs, cfg, yAct, authVir, "", "", "prompt", false, nil, "", "") + profile.MaybeDieExistingName(err) + return + } + if p.Name == common.ContainerProfileName { - fmt.Printf("You are talking to a localhost 'rpk container' cluster (rpk profile name: %q)", p.Name) + fmt.Printf("You are talking to a localhost 'rpk container' cluster (rpk profile name: %q)\n", p.Name) } else { fmt.Printf("You are talking to a self hosted cluster (rpk profile name: %q)\n", p.Name) } diff --git a/src/go/rpk/pkg/cli/cloud/logout.go b/src/go/rpk/pkg/cli/cloud/logout.go index 7c338ed580918..0ef786ed854a3 100644 --- a/src/go/rpk/pkg/cli/cloud/logout.go +++ b/src/go/rpk/pkg/cli/cloud/logout.go @@ -35,7 +35,7 @@ You can use the --all flag to log out of all organizations you may be logged into. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { if rpkos.IsRunningSudo() { out.Die("detected rpk is running with sudo; please execute this command without sudo to avoid saving the cloud configuration as a root owned file") } diff --git a/src/go/rpk/pkg/cli/cloud/namespace/create.go b/src/go/rpk/pkg/cli/cloud/namespace/create.go index 9737a21114d10..de98c4e29997e 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/create.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/create.go @@ -13,6 +13,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0" @@ -46,7 +47,7 @@ func createCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) authToken := authVir.AuthToken - cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) + cl, err := publicapi.NewControlPlaneClientSet(cfg.DevOverrides().PublicAPIURL, authToken) out.MaybeDie(err, "unable to create the public api client: %v", err) var ( @@ -54,16 +55,16 @@ func createCommand(fs afero.Fs, p *config.Params) *cobra.Command { exit1 bool ) for _, name := range args { - n, err := cl.Namespace.CreateNamespace(cmd.Context(), &controlplanev1beta1.CreateNamespaceRequest{ + n, err := cl.Namespace.CreateNamespace(cmd.Context(), connect.NewRequest(&controlplanev1beta1.CreateNamespaceRequest{ Namespace: &controlplanev1beta1.Namespace{ Name: name, }, - }) + })) if err != nil { res = append(res, createResponse{Name: name, Error: err.Error()}) exit1 = true } else { - res = append(res, createResponse{Name: n.Name, ID: n.Id}) + res = append(res, createResponse{Name: n.Msg.Name, ID: n.Msg.Id}) } } if exit1 { diff --git a/src/go/rpk/pkg/cli/cloud/namespace/delete.go b/src/go/rpk/pkg/cli/cloud/namespace/delete.go index c0fdec1670b8c..d81cfaeeac867 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/delete.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/delete.go @@ -1,8 +1,18 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + package namespace import ( "fmt" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0" @@ -34,25 +44,27 @@ func deleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { priorProfile := cfg.ActualProfile() _, authVir, clearedProfile, _, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) authToken := authVir.AuthToken - cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) + + cl, err := publicapi.NewControlPlaneClientSet(cfg.DevOverrides().PublicAPIURL, authToken) out.MaybeDie(err, "unable to create the public api client: %v", err) name := args[0] - listed, err := cl.Namespace.ListNamespaces(cmd.Context(), &controlplanev1beta1.ListNamespacesRequest{ + listed, err := cl.Namespace.ListNamespaces(cmd.Context(), connect.NewRequest(&controlplanev1beta1.ListNamespacesRequest{ Filter: &controlplanev1beta1.ListNamespacesRequest_Filter{Name: name}, - }) + })) out.MaybeDie(err, "unable to find namespace %q: %v", name, err) - if len(listed.Namespaces) == 0 { + if len(listed.Msg.Namespaces) == 0 { out.Die("unable to find namespace %q", name) } - if len(listed.Namespaces) > 1 { + if len(listed.Msg.Namespaces) > 1 { // This is currently not possible, the filter is an exact // filter. This is just being cautious. out.Die("multiple namespaces were found for %q, please provide an exact match", name) } - namespace := listed.Namespaces[0] + namespace := listed.Msg.Namespaces[0] if !noConfirm { confirmed, err := out.Confirm("Confirm deletion of namespace %q with ID %q?", namespace.Name, namespace.Id) out.MaybeDie(err, "unable to confirm deletion: %v", err) @@ -61,7 +73,7 @@ func deleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { } } - _, err = cl.Namespace.DeleteNamespace(cmd.Context(), &controlplanev1beta1.DeleteNamespaceRequest{Id: namespace.Id}) + _, err = cl.Namespace.DeleteNamespace(cmd.Context(), connect.NewRequest(&controlplanev1beta1.DeleteNamespaceRequest{Id: namespace.Id})) out.MaybeDie(err, "unable to delete namespace %q: %v", name, err) res := deleteResponse{namespace.Name, namespace.Id} if isText, _, s, err := f.Format(res); !isText { diff --git a/src/go/rpk/pkg/cli/cloud/namespace/list.go b/src/go/rpk/pkg/cli/cloud/namespace/list.go index 5ccd68f660fc5..061669908e1aa 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/list.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/list.go @@ -1,10 +1,21 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + package namespace import ( + "context" "fmt" "sort" "strings" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0" @@ -35,30 +46,53 @@ func listCommand(fs afero.Fs, p *config.Params) *cobra.Command { priorProfile := cfg.ActualProfile() _, authVir, clearedProfile, _, err := oauth.LoadFlow(cmd.Context(), fs, cfg, auth0.NewClient(cfg.DevOverrides()), false, false, cfg.DevOverrides().CloudAPIURL) out.MaybeDie(err, "unable to authenticate with Redpanda Cloud: %v", err) + oauth.MaybePrintSwapMessage(clearedProfile, priorProfile, authVir) authToken := authVir.AuthToken - cl, err := publicapi.NewClientSet(cmd.Context(), cfg.DevOverrides().PublicAPIURL, authToken) + cl, err := publicapi.NewControlPlaneClientSet(cfg.DevOverrides().PublicAPIURL, authToken) + out.MaybeDie(err, "unable to create the public api client: %v", err) - listed, err := cl.Namespace.ListNamespaces(cmd.Context(), &controlplanev1beta1.ListNamespacesRequest{}) + namespaces, err := listAllNamespaces(cmd.Context(), cl) out.MaybeDie(err, "unable to list namespaces: %v", err) - var res []listResponse - for _, n := range listed.Namespaces { - if n != nil { - res = append(res, listResponse{n.Name, n.Id}) - } - } - sort.Slice(res, func(i, j int) bool { return strings.ToLower(res[i].Name) < strings.ToLower(res[j].Name) }) - if isText, _, s, err := f.Format(res); !isText { + + if isText, _, s, err := f.Format(namespaces); !isText { out.MaybeDie(err, "unable to print in the required format %q: %v", f.Kind, err) fmt.Println(s) return } tw := out.NewTable("name", "id") defer tw.Flush() - for _, r := range res { - tw.PrintStructFields(r) + for _, n := range namespaces { + tw.PrintStructFields(n) } }, } } + +// listAllNamespaces uses the pagination feature to traverse all pages of the +// list request and return all namespaces. +func listAllNamespaces(ctx context.Context, cl *publicapi.ControlPlaneClientSet) ([]listResponse, error) { + var ( + pageToken string + listed []*controlplanev1beta1.Namespace + ) + for { + l, err := cl.Namespace.ListNamespaces(ctx, connect.NewRequest(&controlplanev1beta1.ListNamespacesRequest{PageToken: pageToken})) + if err != nil { + return nil, err + } + listed = append(listed, l.Msg.Namespaces...) + if pageToken = l.Msg.NextPageToken; pageToken == "" { + break + } + } + var res []listResponse + for _, n := range listed { + if n != nil { + res = append(res, listResponse{n.Name, n.Id}) + } + } + sort.Slice(res, func(i, j int) bool { return strings.ToLower(res[i].Name) < strings.ToLower(res[j].Name) }) + return res, nil +} diff --git a/src/go/rpk/pkg/cli/cloud/namespace/namespace.go b/src/go/rpk/pkg/cli/cloud/namespace/namespace.go index 2183c84ded66b..cb15115cb0cfd 100644 --- a/src/go/rpk/pkg/cli/cloud/namespace/namespace.go +++ b/src/go/rpk/pkg/cli/cloud/namespace/namespace.go @@ -21,6 +21,7 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command { Aliases: []string{"ns"}, SuggestFor: []string{"namespaces"}, Args: cobra.ExactArgs(0), + Hidden: true, Short: "Interact with Namespaces in Redpanda Cloud", } cmd.AddCommand( diff --git a/src/go/rpk/pkg/cli/cluster/config/get.go b/src/go/rpk/pkg/cli/cluster/config/get.go index fbfa03121a111..db9595cbed88f 100644 --- a/src/go/rpk/pkg/cli/cluster/config/get.go +++ b/src/go/rpk/pkg/cli/cluster/config/get.go @@ -11,6 +11,7 @@ package config import ( "fmt" + "math" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" @@ -48,10 +49,14 @@ output, use the 'edit' and 'export' commands respectively.`, } else { // currentConfig is the result of json.Unmarshal into a // map[string]interface{}. Due to json rules, all numbers - // are float64. We do not want to print floats, especially - // for large numbers. + // are float64. We do not want to print floats for large + // numbers. if f64, ok := val.(float64); ok { - val = int64(f64) + if math.Mod(f64, 1.0) == 0 { + val = int64(f64) + } else { + val = f64 + } } // Intentionally bare output, so that the output can be readily // consumed in a script. diff --git a/src/go/rpk/pkg/cli/cluster/config/import.go b/src/go/rpk/pkg/cli/cluster/config/import.go index 07ae94f2d94f2..8c9a1a5f51993 100644 --- a/src/go/rpk/pkg/cli/cluster/config/import.go +++ b/src/go/rpk/pkg/cli/cluster/config/import.go @@ -292,7 +292,8 @@ corresponding 'export' command. This downloads the current cluster configuration, calculates the difference with the YAML file, and updates any properties that were changed. If a property is removed from the YAML file, it is reset to its default value. `, - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) diff --git a/src/go/rpk/pkg/cli/cluster/config/lint.go b/src/go/rpk/pkg/cli/cluster/config/lint.go index b6f1233408583..3d7773a603f2e 100644 --- a/src/go/rpk/pkg/cli/cluster/config/lint.go +++ b/src/go/rpk/pkg/cli/cluster/config/lint.go @@ -51,7 +51,8 @@ Deprecated content includes properties which were set via redpanda.yaml in earlier versions of redpanda, but are now managed via Redpanda's central configuration store (and via 'rpk cluster config edit'). `, - Run: func(cmd *cobra.Command, propertyNames []string) { + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) p := cfg.VirtualProfile() diff --git a/src/go/rpk/pkg/cli/cluster/config/reset.go b/src/go/rpk/pkg/cli/cluster/config/reset.go index 1336c462d1ffe..3e456ea869b2d 100644 --- a/src/go/rpk/pkg/cli/cluster/config/reset.go +++ b/src/go/rpk/pkg/cli/cluster/config/reset.go @@ -41,7 +41,7 @@ the setting as if it was set to the default. WARNING: this should only be used when redpanda is not running. `, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, propertyNames []string) { + Run: func(_ *cobra.Command, propertyNames []string) { y, err := p.LoadVirtualRedpandaYaml(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/config/status.go b/src/go/rpk/pkg/cli/cluster/config/status.go index f541ae950a675..af3fca4483cba 100644 --- a/src/go/rpk/pkg/cli/cluster/config/status.go +++ b/src/go/rpk/pkg/cli/cluster/config/status.go @@ -31,7 +31,7 @@ Additionally show the version of cluster configuration that each node has applied: under normal circumstances these should all be equal, a lower number shows that a node is out of sync, perhaps because it is offline.`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) diff --git a/src/go/rpk/pkg/cli/cluster/health.go b/src/go/rpk/pkg/cli/cluster/health.go index 6dcb88cd1bb21..f3b43e535aeb6 100644 --- a/src/go/rpk/pkg/cli/cluster/health.go +++ b/src/go/rpk/pkg/cli/cluster/health.go @@ -68,7 +68,7 @@ following conditions are met: p.InstallSASLFlags(cmd) cmd.Flags().BoolVarP(&watch, "watch", "w", false, "Blocks and writes out all cluster health changes") - cmd.Flags().BoolVarP(&exit, "exit-when-healthy", "e", false, "Exits after cluster is back in healthy state") + cmd.Flags().BoolVarP(&exit, "exit-when-healthy", "e", false, "Exits when the cluster is back in a healthy state") return cmd } diff --git a/src/go/rpk/pkg/cli/cluster/license/info.go b/src/go/rpk/pkg/cli/cluster/license/info.go index 886d10be53eb4..0bed322ea994d 100644 --- a/src/go/rpk/pkg/cli/cluster/license/info.go +++ b/src/go/rpk/pkg/cli/cluster/license/info.go @@ -1,7 +1,6 @@ package license import ( - "encoding/json" "fmt" "os" "time" @@ -13,8 +12,16 @@ import ( "github.com/spf13/cobra" ) +type infoResponse struct { + Organization string `json:"organization" yaml:"organization"` + Type string `json:"type" yaml:"type"` + Expires string `json:"expires" yaml:"expires"` + ExpiresUnix int64 `json:"expires_unix" yaml:"expires_unix"` + Checksum string `json:"checksum_sha256,omitempty" yaml:"checksum_sha256,omitempty"` + Expired bool `json:"license_expired" yaml:"license_expired"` +} + func newInfoCommand(fs afero.Fs, p *config.Params) *cobra.Command { - var format string cmd := &cobra.Command{ Use: "info", Args: cobra.ExactArgs(0), @@ -24,9 +31,12 @@ func newInfoCommand(fs afero.Fs, p *config.Params) *cobra.Command { Organization: Organization the license was generated for. Type: Type of license: free, enterprise, etc. Expires: Expiration date of the license - Version: License schema version. `, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { + f := p.Formatter + if h, ok := f.Help(infoResponse{}); ok { + out.Exit(h) + } p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) @@ -36,56 +46,51 @@ func newInfoCommand(fs afero.Fs, p *config.Params) *cobra.Command { info, err := cl.GetLicenseInfo(cmd.Context()) out.MaybeDie(err, "unable to retrieve license info: %v", err) - if !info.Loaded { - if format == "json" { - out.Die("{}") - } else { - out.Die("this cluster is missing a license") - } - } - - if info.Properties != (adminapi.LicenseProperties{}) { - expired := info.Properties.Expires < 0 - if format == "json" { - tm := time.Unix(info.Properties.Expires, 0).Format("Jan 2 2006") - props, err := json.MarshalIndent(struct { - Organization string `json:"organization"` - Type string `json:"type"` - Expires string `json:"expires"` - Checksum string `json:"checksum_sha256,omitempty"` - Expired bool `json:"license_expired,omitempty"` - }{info.Properties.Organization, info.Properties.Type, tm, info.Properties.Checksum, expired}, "", " ") - out.MaybeDie(err, "unable to print license information as json: %v", err) - fmt.Printf("%s\n", props) - } else { - printLicenseInfo(info.Properties, expired) - } - } else { - out.Die("no license loaded") + out.Die("this cluster is missing a license") } + err = printLicenseInfo(f, info.Properties) + out.MaybeDieErr(err) }, } - - cmd.Flags().StringVar(&format, "format", "text", "Output format (text, json)") + p.InstallFormatFlag(cmd) return cmd } -func printLicenseInfo(p adminapi.LicenseProperties, expired bool) { +func printLicenseInfo(f config.OutFormatter, props adminapi.LicenseProperties) error { + ut := time.Unix(props.Expires, 0) + isExpired := ut.Before(time.Now()) + resp := infoResponse{ + Organization: props.Organization, + Type: props.Type, + Expires: ut.Format("Jan 2 2006"), + ExpiresUnix: props.Expires, + Checksum: props.Checksum, + Expired: isExpired, + } + if isText, _, formatted, err := f.Format(resp); !isText { + if err != nil { + return fmt.Errorf("unable to print license info in the required format %q: %v", f.Kind, err) + } + fmt.Println(formatted) + return nil + } + out.Section("LICENSE INFORMATION") licenseFormat := `Organization: %v Type: %v Expires: %v ` - if expired { - licenseFormat += `License Expired: true -` + if isExpired { + licenseFormat += "License Expired: true\n" } - tm := time.Unix(p.Expires, 0) - fmt.Printf(licenseFormat, p.Organization, p.Type, tm.Format("Jan 2 2006")) - diff := time.Until(tm) + fmt.Printf(licenseFormat, resp.Organization, resp.Type, resp.Expires) + + // Warn the user if the License is about to expire (<30 days left). + diff := time.Until(ut) daysLeft := int(diff.Hours() / 24) if daysLeft < 30 && daysLeft >= 0 { fmt.Fprintln(os.Stderr, "warning: your license will expire soon") } + return nil } diff --git a/src/go/rpk/pkg/cli/cluster/logdirs.go b/src/go/rpk/pkg/cli/cluster/logdirs.go index a09a7b915a266..6c8e0273f6b0f 100644 --- a/src/go/rpk/pkg/cli/cluster/logdirs.go +++ b/src/go/rpk/pkg/cli/cluster/logdirs.go @@ -69,7 +69,7 @@ where revision is a Redpanda internal concept. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) @@ -213,7 +213,7 @@ where revision is a Redpanda internal concept. cmd.Flags().StringVar(&aggregateInto, "aggregate-into", "", "If non-empty, what column to aggregate into starting from the partition column (broker, dir, topic)") cmd.Flags().BoolVarP(&human, "human-readable", "H", false, "Print the logdirs size in a human-readable form") - cmd.RegisterFlagCompletionFunc("aggregate-into", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cmd.RegisterFlagCompletionFunc("aggregate-into", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { opts := []string{"broker", "dir", "topic"} return opts, cobra.ShellCompDirectiveDefault }) diff --git a/src/go/rpk/pkg/cli/cluster/maintenance/status.go b/src/go/rpk/pkg/cli/cluster/maintenance/status.go index fa69cd6273180..21046705f1c20 100644 --- a/src/go/rpk/pkg/cli/cluster/maintenance/status.go +++ b/src/go/rpk/pkg/cli/cluster/maintenance/status.go @@ -80,7 +80,7 @@ Notes: transfer. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) diff --git a/src/go/rpk/pkg/cli/cluster/metadata.go b/src/go/rpk/pkg/cli/cluster/metadata.go index 7d024946f2b53..11ab5793fd121 100644 --- a/src/go/rpk/pkg/cli/cluster/metadata.go +++ b/src/go/rpk/pkg/cli/cluster/metadata.go @@ -53,7 +53,7 @@ flag. In the broker section, the controller node is suffixed with *. `, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/cluster/partitions/list.go b/src/go/rpk/pkg/cli/cluster/partitions/list.go index 4e0661d19129b..c018678658ef0 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/list.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/list.go @@ -33,6 +33,7 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { all bool disabledOnly bool partitions []int + nodeIDs []int logFallbackOnce sync.Once ) cmd := &cobra.Command{ @@ -72,6 +73,9 @@ List all partitions in the cluster. List all partitions in the cluster, filtering for topic foo and bar. rpk cluster partitions list foo bar +List partitions which replicas are assigned to brokers 1 and 2. + rpk cluster partitions list foo --node-ids 1,2 + List only the disabled partitions. rpk cluster partitions list -a --disabled-only @@ -150,12 +154,16 @@ List all in json format. if partitions != nil { clusterPartitions = filterPartition(clusterPartitions, partitions) } + if len(nodeIDs) > 0 { + clusterPartitions = filterBroker(clusterPartitions, nodeIDs) + } printClusterPartitions(f, clusterPartitions) }, } cmd.Flags().BoolVarP(&all, "all", "a", false, "If true, list all partitions in the cluster") cmd.Flags().BoolVar(&disabledOnly, "disabled-only", false, "If true, list disabled partitions only") cmd.Flags().IntSliceVarP(&partitions, "partition", "p", nil, "List of comma-separated partitions IDs that you wish to filter the results with") + cmd.Flags().IntSliceVarP(&nodeIDs, "node-ids", "n", nil, "List of comma-separated broker IDs that you wish to filter the results with") p.InstallFormatFlag(cmd) return cmd @@ -248,3 +256,29 @@ func filterPartition(cPartitions []adminapi.ClusterPartition, partitions []int) } return } + +// filterBroker filters cPartition and returns a slice with only the +// clusterPartitions with the same brokers present in the Replicas slice. +func filterBroker(cPartitions []adminapi.ClusterPartition, nodeIDs []int) (ret []adminapi.ClusterPartition) { + for _, p := range cPartitions { + rob := replicaOnBroker(p.Replicas, nodeIDs) + if rob { + ret = append(ret, p) + } + } + return +} + +// replicaOnBroker returns true when all nodes in the brokers slice +// exist in the Replicas slice. Otherwise, this function returns false. +func replicaOnBroker(replicas adminapi.Replicas, nodeIDs []int) bool { + foundCount := 0 + for _, r := range replicas { + for _, b := range nodeIDs { + if r.NodeID == b { + foundCount++ + } + } + } + return foundCount == len(nodeIDs) +} diff --git a/src/go/rpk/pkg/cli/cluster/partitions/partitions.go b/src/go/rpk/pkg/cli/cluster/partitions/partitions.go index b824e75ae0cc7..1d4333ff6a6b3 100644 --- a/src/go/rpk/pkg/cli/cluster/partitions/partitions.go +++ b/src/go/rpk/pkg/cli/cluster/partitions/partitions.go @@ -32,6 +32,7 @@ func NewPartitionsCommand(fs afero.Fs, p *config.Params) *cobra.Command { newPartitionDisableCommand(fs, p), newPartitionEnableCommand(fs, p), newPartitionMovementsStatusCommand(fs, p), + newTransferLeaderCommand(fs, p), newUnsafeRecoveryCommand(fs, p), ) return cmd diff --git a/src/go/rpk/pkg/cli/cluster/partitions/transfer_leadership.go b/src/go/rpk/pkg/cli/cluster/partitions/transfer_leadership.go new file mode 100644 index 0000000000000..2a868f39b69c0 --- /dev/null +++ b/src/go/rpk/pkg/cli/cluster/partitions/transfer_leadership.go @@ -0,0 +1,154 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package partitions + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +func newTransferLeaderCommand(fs afero.Fs, p *config.Params) *cobra.Command { + var partitionArg string + + cmd := &cobra.Command{ + Use: "transfer-leadership", + Short: "Transfer partition leadership between brokers", + Long: `Transfer partition leadership between brokers. + +This command allows you to transfer partition leadership. +You can transfer only one partition leader at a time. + +To transfer partition leadership, use the following syntax: + rpk cluster partitions transfer-leadership foo --partition 0:2 + +Here, the command transfers leadership for the partition "kafka/foo/0" +to broker 2. By default, it assumes the "kafka" namespace, but you can specify +an internal namespace using the "{namespace}/" prefix. + +Here is an equivalent command using different syntax: + rpk cluster partitions transfer-leadership --partition foo/0:2 + +Warning: Redpanda tries to balance leadership distribution across brokers by default. +If the distribution of leaders becomes uneven as a result of transferring leadership +across brokers, the cluster may move leadership back to the original +brokers automatically. +`, + + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, topicArg []string) { + f := p.Formatter + if h, ok := f.Help([]string{}); ok { + out.Exit(h) + } + + p, err := p.LoadVirtualProfile(fs) + out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitCloudAdmin(p) + + cl, err := adminapi.NewClient(fs, p) + out.MaybeDie(err, "unable to initialize admin client: %v", err) + + if len(topicArg) > 0 { // foo -p 0:1 + _, _, partition, target, err := extractNTPTarget(topicArg[0], partitionArg) + out.MaybeDie(err, "failed to extract topic/partition: %s\n", err) + + ns, topic := formatNT(topicArg[0]) + + partDetails, err := cl.GetPartition(cmd.Context(), ns, topic, partition) + out.MaybeDie(err, "failed to get partition details: %s\n", err) + + source := partDetails.LeaderID + + err = cl.TransferLeadership(cmd.Context(), fs, p, source, ns, topic, partition, target) + if err != nil { + fmt.Printf("failed to transfer the partition leadership: %v\n", err) + } else { + fmt.Println("Successfully began the partition leadership transfer(s).\n\nCheck the new leader assignment with 'rpk topic describe -p TOPIC'.") + } + } else { // -p foo/0:1 + ns, topic, partition, target, err := extractNTPTarget("", partitionArg) + out.MaybeDie(err, "failed to extract topic/partition: %s\n", err) + + partDetails, err := cl.GetPartition(cmd.Context(), ns, topic, partition) + out.MaybeDie(err, "failed to get partition details: %s\n", err) + + source := partDetails.LeaderID + + err = cl.TransferLeadership(cmd.Context(), fs, p, source, ns, topic, partition, target) + if err != nil { + fmt.Printf("failed to transfer the partition leader: %v\n", err) + } else { + fmt.Println("Successfully began the partition leadership transfer(s).\n\nCheck the new leader assignment with 'rpk topic describe -p TOPIC'.") + } + } + }, + } + cmd.Flags().StringVarP(&partitionArg, "partition", "p", "", "Topic-partition to transfer leadership and new leader location") + p.InstallFormatFlag(cmd) + return cmd +} + +// extractNTPTarget parses the partition flag with format; foo/0:1 or 0:1 +// and returns namespace', 'topic', 'partition', and 'target node' separately. +func extractNTPTarget(topic string, ntp string) (ns string, t string, p int, target string, err error) { + ntpReOnce.Do(func() { + ntpRe = regexp.MustCompile(`^((?:[^:]+/)?\d+):(\d+)$`) + }) + m := ntpRe.FindStringSubmatch(ntp) + if len(m) == 0 { + return "", "", -1, "", fmt.Errorf("invalid format for %s", ntp) + } + beforeColon := m[1] + target = m[2] + if topic != "" { + p, err = strconv.Atoi(beforeColon) + if err != nil { + return "", "", -1, "", fmt.Errorf("%s", err) + } + } else if n := strings.Split(beforeColon, "/"); len(n) == 3 { + ns = n[0] + t = n[1] + p, err = strconv.Atoi(n[2]) + if err != nil { + return "", "", -1, "", fmt.Errorf("%s", err) + } + } else if len(n) == 2 { + ns = "kafka" + t = n[0] + p, err = strconv.Atoi(n[1]) + if err != nil { + return "", "", -1, "", fmt.Errorf("%s", err) + } + } else { + return "", "", -1, "", fmt.Errorf("invalid format for %s", ntp) + } + return ns, t, p, target, nil +} + +// formatNT parse a given '(namespace)/topic' string +// and return 'namespace' and 'topic' separately. +func formatNT(t string) (ns string, topic string) { + if nt := strings.Split(t, "/"); len(nt) == 1 { + ns = "kafka" + topic = nt[0] + } else { + ns = nt[0] + topic = nt[1] + } + return +} diff --git a/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go b/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go index c8e191dcada84..7b15e5e0fc62a 100644 --- a/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go +++ b/src/go/rpk/pkg/cli/cluster/storage/recovery/start.go @@ -35,7 +35,7 @@ func newStartCommand(fs afero.Fs, p *config.Params) *cobra.Command { This command starts the process of restoring topics from the archival bucket. If the wait flag (--wait/-w) is set, the command will poll the status of the recovery process until it's finished.`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) diff --git a/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go b/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go index 7e34601aed782..e77a8ca2c9606 100644 --- a/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go +++ b/src/go/rpk/pkg/cli/cluster/storage/recovery/status.go @@ -27,7 +27,7 @@ func newStatusCommand(fs afero.Fs, p *config.Params) *cobra.Command { This command fetches the status of the process of restoring topics from the archival bucket.`, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) config.CheckExitCloudAdmin(p) diff --git a/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go b/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go index bab001de628a8..bf594e1183658 100644 --- a/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go +++ b/src/go/rpk/pkg/cli/cluster/txn/describe_producers.go @@ -65,7 +65,7 @@ To filter for specific topics, use --topics. You can additionally filter by partitions with --partitions. `, - Run: func(cmd *cobra.Command, txnIDs []string) { + Run: func(cmd *cobra.Command, _ []string) { f := p.Formatter if h, ok := f.Help([]describeProducersResponse{}); ok { out.Exit(h) diff --git a/src/go/rpk/pkg/cli/container/common/client.go b/src/go/rpk/pkg/cli/container/common/client.go index 91f564edb5d5b..ce6ebc61d9082 100644 --- a/src/go/rpk/pkg/cli/container/common/client.go +++ b/src/go/rpk/pkg/cli/container/common/client.go @@ -14,6 +14,8 @@ import ( "io" "os" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" @@ -21,21 +23,21 @@ import ( specs "github.com/opencontainers/image-spec/specs-go/v1" ) -// Defines an interface with the functions from Docker's *client.Client that are -// used, to make it possible to test the code that uses it. +// Client defines an interface with the functions from Docker's *client.Client +// that are used, to make it possible to test the code that uses it. type Client interface { Close() error ImagePull( ctx context.Context, ref string, - options types.ImagePullOptions, + options image.PullOptions, ) (io.ReadCloser, error) ImageList( ctx context.Context, - options types.ImageListOptions, - ) ([]types.ImageSummary, error) + options image.ListOptions, + ) ([]image.Summary, error) ContainerCreate( ctx context.Context, config *container.Config, @@ -48,7 +50,7 @@ type Client interface { ContainerStart( ctx context.Context, containerID string, - options types.ContainerStartOptions, + options container.StartOptions, ) error ContainerStop( @@ -59,7 +61,7 @@ type Client interface { ContainerList( ctx context.Context, - options types.ContainerListOptions, + options container.ListOptions, ) ([]types.Container, error) ContainerInspect( @@ -70,27 +72,27 @@ type Client interface { ContainerRemove( ctx context.Context, containerID string, - options types.ContainerRemoveOptions, + options container.RemoveOptions, ) error NetworkCreate( ctx context.Context, name string, - options types.NetworkCreate, - ) (types.NetworkCreateResponse, error) + options network.CreateOptions, + ) (network.CreateResponse, error) NetworkRemove(ctx context.Context, networkID string) error NetworkList( ctx context.Context, - options types.NetworkListOptions, - ) ([]types.NetworkResource, error) + options network.ListOptions, + ) ([]network.Inspect, error) NetworkInspect( ctx context.Context, networkID string, - options types.NetworkInspectOptions, - ) (types.NetworkResource, error) + options network.InspectOptions, + ) (network.Inspect, error) IsErrNotFound(err error) bool diff --git a/src/go/rpk/pkg/cli/container/common/common.go b/src/go/rpk/pkg/cli/container/common/common.go index bdfdaa5367061..0c6bed9948448 100644 --- a/src/go/rpk/pkg/cli/container/common/common.go +++ b/src/go/rpk/pkg/cli/container/common/common.go @@ -19,6 +19,8 @@ import ( "strings" "time" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -92,7 +94,7 @@ func GetExistingNodes(c Client) ([]*NodeState, error) { ctx, _ := DefaultCtx() containers, err := c.ContainerList( ctx, - types.ContainerListOptions{ + container.ListOptions{ All: true, Filters: filters, }, @@ -199,7 +201,7 @@ func CreateNetwork(c Client) (string, error) { args.Add("name", redpandaNetwork) networks, err := c.NetworkList( ctx, - types.NetworkListOptions{Filters: args}, + network.ListOptions{Filters: args}, ) if err != nil { return "", err @@ -213,7 +215,7 @@ func CreateNetwork(c Client) (string, error) { fmt.Printf("Creating network %s\n", redpandaNetwork) resp, err := c.NetworkCreate( - ctx, redpandaNetwork, types.NetworkCreate{ + ctx, redpandaNetwork, network.CreateOptions{ Driver: "bridge", IPAM: &network.IPAM{ Driver: "default", @@ -377,11 +379,11 @@ func CreateNode( }, nil } -func PullImage(c Client, image string) error { - fmt.Printf("Pulling image: %s\n", image) +func PullImage(c Client, img string) error { + fmt.Printf("Pulling image: %s\n", img) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - res, err := c.ImagePull(ctx, image, types.ImagePullOptions{}) + res, err := c.ImagePull(ctx, img, image.PullOptions{}) if res != nil { defer res.Close() buf := bytes.Buffer{} @@ -394,12 +396,12 @@ func PullImage(c Client, image string) error { return ctx.Err() } -func CheckIfImgPresent(c Client, image string) (bool, error) { +func CheckIfImgPresent(c Client, img string) (bool, error) { ctx, _ := DefaultCtx() filters := filters.NewArgs( - filters.Arg("reference", image), + filters.Arg("reference", img), ) - imgs, err := c.ImageList(ctx, types.ImageListOptions{ + imgs, err := c.ImageList(ctx, image.ListOptions{ Filters: filters, }) if err != nil { @@ -430,7 +432,7 @@ func getHostPort( func nodeIP(c Client, netID string, id uint) (string, error) { ctx, _ := DefaultCtx() - networkResource, err := c.NetworkInspect(ctx, netID, types.NetworkInspectOptions{}) + networkResource, err := c.NetworkInspect(ctx, netID, network.InspectOptions{}) if err != nil { return "", err } diff --git a/src/go/rpk/pkg/cli/container/common/test.go b/src/go/rpk/pkg/cli/container/common/test.go index 38cc1992ff8f4..cc1878bc1aacd 100644 --- a/src/go/rpk/pkg/cli/container/common/test.go +++ b/src/go/rpk/pkg/cli/container/common/test.go @@ -13,6 +13,8 @@ import ( "context" "io" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" @@ -26,13 +28,13 @@ type MockClient struct { MockImagePull func( ctx context.Context, ref string, - options types.ImagePullOptions, + options image.PullOptions, ) (io.ReadCloser, error) MockImageList func( ctx context.Context, - options types.ImageListOptions, - ) ([]types.ImageSummary, error) + options image.ListOptions, + ) ([]image.Summary, error) MockContainerCreate func( ctx context.Context, @@ -46,7 +48,7 @@ type MockClient struct { MockContainerStart func( ctx context.Context, containerID string, - options types.ContainerStartOptions, + options container.StartOptions, ) error MockContainerStop func( @@ -57,7 +59,7 @@ type MockClient struct { MockContainerList func( ctx context.Context, - options types.ContainerListOptions, + options container.ListOptions, ) ([]types.Container, error) MockContainerInspect func( @@ -68,14 +70,14 @@ type MockClient struct { MockContainerRemove func( ctx context.Context, containerID string, - options types.ContainerRemoveOptions, + options container.RemoveOptions, ) error MockNetworkCreate func( ctx context.Context, name string, - options types.NetworkCreate, - ) (types.NetworkCreateResponse, error) + options network.CreateOptions, + ) (network.CreateResponse, error) MockNetworkRemove func( ctx context.Context, @@ -84,14 +86,14 @@ type MockClient struct { MockNetworkList func( ctx context.Context, - options types.NetworkListOptions, - ) ([]types.NetworkResource, error) + options network.ListOptions, + ) ([]network.Inspect, error) MockNetworkInspect func( ctx context.Context, networkID string, - options types.NetworkInspectOptions, - ) (types.NetworkResource, error) + options network.InspectOptions, + ) (network.Inspect, error) MockIsErrNotFound func(err error) bool @@ -127,7 +129,7 @@ func (c *MockClient) ContainerCreate( } func (c *MockClient) ImagePull( - ctx context.Context, ref string, options types.ImagePullOptions, + ctx context.Context, ref string, options image.PullOptions, ) (io.ReadCloser, error) { if c.MockImagePull != nil { return c.MockImagePull(ctx, ref, options) @@ -136,16 +138,16 @@ func (c *MockClient) ImagePull( } func (c *MockClient) ImageList( - ctx context.Context, options types.ImageListOptions, -) ([]types.ImageSummary, error) { + ctx context.Context, options image.ListOptions, +) ([]image.Summary, error) { if c.MockImageList != nil { return c.MockImageList(ctx, options) } - return []types.ImageSummary{}, nil + return []image.Summary{}, nil } func (c *MockClient) ContainerStart( - ctx context.Context, containerID string, options types.ContainerStartOptions, + ctx context.Context, containerID string, options container.StartOptions, ) error { if c.MockContainerStart != nil { return c.MockContainerStart( @@ -165,7 +167,7 @@ func (c *MockClient) ContainerStop( } func (c *MockClient) ContainerList( - ctx context.Context, options types.ContainerListOptions, + ctx context.Context, options container.ListOptions, ) ([]types.Container, error) { if c.MockContainerList != nil { return c.MockContainerList(ctx, options) @@ -183,7 +185,7 @@ func (c *MockClient) ContainerInspect( } func (c *MockClient) ContainerRemove( - ctx context.Context, containerID string, options types.ContainerRemoveOptions, + ctx context.Context, containerID string, options container.RemoveOptions, ) error { if c.MockContainerRemove != nil { return c.MockContainerRemove(ctx, containerID, options) @@ -192,12 +194,12 @@ func (c *MockClient) ContainerRemove( } func (c *MockClient) NetworkCreate( - ctx context.Context, name string, options types.NetworkCreate, -) (types.NetworkCreateResponse, error) { + ctx context.Context, name string, options network.CreateOptions, +) (network.CreateResponse, error) { if c.MockNetworkCreate != nil { return c.MockNetworkCreate(ctx, name, options) } - return types.NetworkCreateResponse{}, nil + return network.CreateResponse{}, nil } func (c *MockClient) NetworkRemove(ctx context.Context, name string) error { @@ -208,21 +210,21 @@ func (c *MockClient) NetworkRemove(ctx context.Context, name string) error { } func (c *MockClient) NetworkList( - ctx context.Context, options types.NetworkListOptions, -) ([]types.NetworkResource, error) { + ctx context.Context, options network.ListOptions, +) ([]network.Inspect, error) { if c.MockNetworkList != nil { return c.MockNetworkList(ctx, options) } - return []types.NetworkResource{}, nil + return []network.Inspect{}, nil } func (c *MockClient) NetworkInspect( - ctx context.Context, networkID string, options types.NetworkInspectOptions, -) (types.NetworkResource, error) { + ctx context.Context, networkID string, options network.InspectOptions, +) (network.Inspect, error) { if c.MockNetworkInspect != nil { return c.MockNetworkInspect(ctx, networkID, options) } - return types.NetworkResource{}, nil + return network.Inspect{}, nil } func (c *MockClient) IsErrNotFound(err error) bool { diff --git a/src/go/rpk/pkg/cli/container/purge.go b/src/go/rpk/pkg/cli/container/purge.go index 06738e043039f..be69d67fe2448 100644 --- a/src/go/rpk/pkg/cli/container/purge.go +++ b/src/go/rpk/pkg/cli/container/purge.go @@ -15,7 +15,8 @@ import ( "os" "sync" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/container/common" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/profile" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" @@ -91,7 +92,7 @@ func purgeCluster(c common.Client) (purged bool, rerr error) { err := c.ContainerRemove( ctx, name, - types.ContainerRemoveOptions{ + container.RemoveOptions{ RemoveVolumes: true, Force: true, }, diff --git a/src/go/rpk/pkg/cli/container/start.go b/src/go/rpk/pkg/cli/container/start.go index 7e289a3180772..c22b4b1cb0c2d 100644 --- a/src/go/rpk/pkg/cli/container/start.go +++ b/src/go/rpk/pkg/cli/container/start.go @@ -21,8 +21,9 @@ import ( "sync" "time" + "github.com/docker/docker/api/types/container" + "github.com/avast/retry-go" - "github.com/docker/docker/api/types" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/container/common" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" vnet "github.com/redpanda-data/redpanda/src/go/rpk/pkg/net" @@ -311,7 +312,7 @@ func restartCluster( err = c.ContainerStart( ctx, state.ContainerID, - types.ContainerStartOptions{}, + container.StartOptions{}, ) if err != nil { return err @@ -343,7 +344,7 @@ func restartCluster( func startNode(c common.Client, containerID string) error { ctx, _ := common.DefaultCtx() - err := c.ContainerStart(ctx, containerID, types.ContainerStartOptions{}) + err := c.ContainerStart(ctx, containerID, container.StartOptions{}) return err } diff --git a/src/go/rpk/pkg/cli/debug/bundle/bundle.go b/src/go/rpk/pkg/cli/debug/bundle/bundle.go index e3b1292970429..9829a54bac90c 100644 --- a/src/go/rpk/pkg/cli/debug/bundle/bundle.go +++ b/src/go/rpk/pkg/cli/debug/bundle/bundle.go @@ -74,7 +74,7 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command { Use: "bundle", Short: "Collect environment data and create a bundle file for the Redpanda Data support team to inspect", Long: bundleHelpText, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { path, err := determineFilepath(fs, outFile, cmd.Flags().Changed(outputFlag)) out.MaybeDie(err, "unable to determine filepath %q: %v", outFile, err) diff --git a/src/go/rpk/pkg/cli/debug/bundle/bundle_k8s_linux.go b/src/go/rpk/pkg/cli/debug/bundle/bundle_k8s_linux.go index 97b27d2bf4bd9..cfd03ef97e5a7 100644 --- a/src/go/rpk/pkg/cli/debug/bundle/bundle_k8s_linux.go +++ b/src/go/rpk/pkg/cli/debug/bundle/bundle_k8s_linux.go @@ -79,6 +79,7 @@ func executeK8SBundle(ctx context.Context, bp bundleParams) error { saveNTPDrift(ps), saveResourceUsageData(ps, bp.y), saveSlabInfo(ps), + saveUname(ctx, ps), } adminAddresses, err := adminAddressesFromK8S(ctx, bp.namespace) diff --git a/src/go/rpk/pkg/cli/debug/bundle/bundle_linux.go b/src/go/rpk/pkg/cli/debug/bundle/bundle_linux.go index 3696f5cc9c0b8..814aeca0ffc8c 100644 --- a/src/go/rpk/pkg/cli/debug/bundle/bundle_linux.go +++ b/src/go/rpk/pkg/cli/debug/bundle/bundle_linux.go @@ -151,6 +151,7 @@ func executeBundle(ctx context.Context, bp bundleParams) error { saveSysctl(ctx, ps), saveSyslog(ps), saveTopOutput(ctx, ps), + saveUname(ctx, ps), saveVmstat(ctx, ps), } @@ -646,7 +647,6 @@ func saveNTPDrift(ps *stepParams) step { zap.L().Sugar().Debugf("Retrying (%d retries left)", retries-n) }), ) - if err != nil { return fmt.Errorf("error querying '%s': %w", host, err) } @@ -697,6 +697,13 @@ func saveDNSData(ctx context.Context, ps *stepParams) step { } } +// Saves the output of `uname -a`. +func saveUname(ctx context.Context, ps *stepParams) step { + return func() error { + return writeCommandOutputToZip(ctx, ps, filepath.Join(linuxUtilsRoot, "uname.txt"), "uname", "-a") + } +} + // Saves the disk usage total within redpanda's data directory. func saveDiskUsage(ctx context.Context, ps *stepParams, y *config.RedpandaYaml) step { return func() error { diff --git a/src/go/rpk/pkg/cli/generate/app.go b/src/go/rpk/pkg/cli/generate/app.go index a28dec067480a..241f12ff619bb 100644 --- a/src/go/rpk/pkg/cli/generate/app.go +++ b/src/go/rpk/pkg/cli/generate/app.go @@ -86,7 +86,7 @@ func newAppCmd(fs afero.Fs, p *config.Params) *cobra.Command { Use: "app", Short: "Generate a sample application to connect with Redpanda", Long: appHelpText, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/generate/generate.go b/src/go/rpk/pkg/cli/generate/generate.go index 2e121e037b834..a6012e1060d65 100644 --- a/src/go/rpk/pkg/cli/generate/generate.go +++ b/src/go/rpk/pkg/cli/generate/generate.go @@ -39,7 +39,7 @@ type fileSpec struct { } func validFiles(fileMap map[string]*fileSpec) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { - return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { opts := make([]string, 0, len(fileMap)) for k, v := range fileMap { // Cobra provides support for completion descriptions: diff --git a/src/go/rpk/pkg/cli/generate/grafana.go b/src/go/rpk/pkg/cli/generate/grafana.go index 64c0421efeb63..890dcf16ce42e 100644 --- a/src/go/rpk/pkg/cli/generate/grafana.go +++ b/src/go/rpk/pkg/cli/generate/grafana.go @@ -125,7 +125,7 @@ metrics endpoint used. To see a list of all available dashboards, use the '--dashboard help' flag. `, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { switch { case dashboard == "legacy": if datasource == "" { diff --git a/src/go/rpk/pkg/cli/generate/prometheus.go b/src/go/rpk/pkg/cli/generate/prometheus.go index c9a3ca4b9a364..b5a86e826c838 100644 --- a/src/go/rpk/pkg/cli/generate/prometheus.go +++ b/src/go/rpk/pkg/cli/generate/prometheus.go @@ -73,7 +73,8 @@ func newPrometheusConfigCmd(fs afero.Fs, p *config.Params) *cobra.Command { Use: "prometheus-config", Short: "Generate the Prometheus configuration to scrape Redpanda nodes", Long: prometheusHelpText, - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { y, err := p.LoadVirtualRedpandaYaml(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/group/describe.go b/src/go/rpk/pkg/cli/group/describe.go index cd534fc515f56..efe6a0cb2d5be 100644 --- a/src/go/rpk/pkg/cli/group/describe.go +++ b/src/go/rpk/pkg/cli/group/describe.go @@ -17,13 +17,14 @@ import ( "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/kafka" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/utils" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/twmb/franz-go/pkg/kadm" ) func NewDescribeCommand(fs afero.Fs, p *config.Params) *cobra.Command { - var summary, commitsOnly, lagPerTopic bool + var summary, commitsOnly, lagPerTopic, re bool cmd := &cobra.Command{ Use: "describe [GROUPS...]", Short: "Describe group offset status & lag", @@ -31,6 +32,22 @@ func NewDescribeCommand(fs afero.Fs, p *config.Params) *cobra.Command { This command describes group members, calculates their lag, and prints detailed information about the members. + +The --regex flag (-r) parses arguments as regular expressions +and describes groups that match any of the expressions. +`, + Example: ` +Describe groups foo and bar: + rpk group describe foo bar + +Describe any group starting with f and ending in r: + rpk group describe -r '^f.*' '.*r$' + +Describe all groups: + rpk group describe -r '*' + +Describe any one-character group: + rpk group describe -r . `, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, groups []string) { @@ -41,6 +58,14 @@ information about the members. out.MaybeDie(err, "unable to initialize kafka client: %v", err) defer adm.Close() + if re { + groups, err = regexGroups(adm, groups) + out.MaybeDie(err, "unable to filter groups by regex: %v", err) + } + if len(groups) == 0 { + out.Exit("did not match any groups, exiting.") + } + ctx, cancel := context.WithTimeout(cmd.Context(), p.Defaults().GetCommandTimeout()) defer cancel() @@ -62,6 +87,7 @@ information about the members. cmd.Flags().BoolVarP(&lagPerTopic, "print-lag-per-topic", "t", false, "Print the aggregated lag per topic") cmd.Flags().BoolVarP(&summary, "print-summary", "s", false, "Print only the group summary section") cmd.Flags().BoolVarP(&commitsOnly, "print-commits", "c", false, "Print only the group commits section") + cmd.Flags().BoolVarP(&re, "regex", "r", false, "Parse arguments as regex; describe any group that matches any input group expression") cmd.MarkFlagsMutuallyExclusive("print-summary", "print-commits") cmd.MarkFlagsMutuallyExclusive("print-lag-per-topic", "print-commits") return cmd @@ -234,3 +260,13 @@ func printLagPerTopic(groups kadm.DescribedGroupLags) { } } } + +func regexGroups(adm *kadm.Client, expressions []string) ([]string, error) { + // Now we list all groups to match against our expressions. + groups, err := adm.ListGroups(context.Background()) + if err != nil { + return nil, fmt.Errorf("unable to list groups: %w", err) + } + + return utils.RegexListedItems(groups.Groups(), expressions) +} diff --git a/src/go/rpk/pkg/cli/group/group.go b/src/go/rpk/pkg/cli/group/group.go index 1d04f88a59956..60be6bf170870 100644 --- a/src/go/rpk/pkg/cli/group/group.go +++ b/src/go/rpk/pkg/cli/group/group.go @@ -93,7 +93,7 @@ coordinator for the group. This command can be used to track down unknown groups, or to list groups that need to be cleaned up. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, _ []string) { + Run: func(_ *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) @@ -140,7 +140,7 @@ automatically are cleaned up, such as when you create temporary groups for quick investigation or testing. This command helps you do that. `, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/group/offset_delete.go b/src/go/rpk/pkg/cli/group/offset_delete.go index 8348487a8dc9e..3e2a20f6b5db9 100644 --- a/src/go/rpk/pkg/cli/group/offset_delete.go +++ b/src/go/rpk/pkg/cli/group/offset_delete.go @@ -51,7 +51,7 @@ topic_a 1 topic_b 0 `, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/group/seek.go b/src/go/rpk/pkg/cli/group/seek.go index 71873d64b2bf8..276b1a85db273 100644 --- a/src/go/rpk/pkg/cli/group/seek.go +++ b/src/go/rpk/pkg/cli/group/seek.go @@ -85,7 +85,7 @@ Seek group G to the beginning of a topic it was not previously consuming: rpk group seek G --to start --topics foo --allow-new-topics `, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/iotune/iotune.go b/src/go/rpk/pkg/cli/iotune/iotune.go index 5534566ab4a5b..6acf8a96b7398 100644 --- a/src/go/rpk/pkg/cli/iotune/iotune.go +++ b/src/go/rpk/pkg/cli/iotune/iotune.go @@ -34,7 +34,8 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command { cmd := &cobra.Command{ Use: "iotune", Short: "Measure filesystem performance and create IO configuration file", - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { timeout += duration y, err := p.LoadVirtualRedpandaYaml(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/profile/clear.go b/src/go/rpk/pkg/cli/profile/clear.go index 0fb376600bdc4..61c77ccba515c 100644 --- a/src/go/rpk/pkg/cli/profile/clear.go +++ b/src/go/rpk/pkg/cli/profile/clear.go @@ -26,7 +26,7 @@ This small command clears the current profile, which can be useful to unset an prod cluster profile. `, Args: cobra.ExactArgs(0), - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) y, ok := cfg.ActualRpkYaml() diff --git a/src/go/rpk/pkg/cli/profile/create.go b/src/go/rpk/pkg/cli/profile/create.go index 4f94a3503d211..c786a2b43d79c 100644 --- a/src/go/rpk/pkg/cli/profile/create.go +++ b/src/go/rpk/pkg/cli/profile/create.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi" container "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cli/container/common" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/cloudapi" @@ -26,6 +27,8 @@ import ( "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth/providers/auth0" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi" + controlplanev1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" "github.com/rs/xid" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -195,7 +198,7 @@ func CreateFlow( var err error o, err = createCloudProfile(ctx, yAuthVir, cfg, fromCloud) if err != nil { - if err == ErrNoCloudClusters { + if errors.Is(err, ErrNoCloudClusters) { fmt.Println("Your cloud account has no clusters available to select, avoiding creating a cloud profile.") return nil } @@ -324,7 +327,7 @@ func CreateFlow( func createCloudProfile(ctx context.Context, yAuthVir *config.RpkCloudAuth, cfg *config.Config, clusterIDOrName string) (CloudClusterOutputs, error) { if yAuthVir == nil { - return CloudClusterOutputs{}, errors.New("missing current coud auth, please login with 'rpk cloud login'") + return CloudClusterOutputs{}, errors.New("missing current cloud auth, please login with 'rpk cloud login'") } overrides := cfg.DevOverrides() @@ -338,8 +341,12 @@ func createCloudProfile(ctx context.Context, yAuthVir *config.RpkCloudAuth, cfg } cl := cloudapi.NewClient(overrides.CloudAPIURL, yAuthVir.AuthToken, httpapi.ReqTimeout(10*time.Second)) + cpCl, err := publicapi.NewControlPlaneClientSet(cfg.DevOverrides().PublicAPIURL, yAuthVir.AuthToken) + if err != nil { + return CloudClusterOutputs{}, fmt.Errorf("unable to create public API client: %v", err) + } if clusterIDOrName == "prompt" { - return PromptCloudClusterProfile(ctx, yAuthVir, cl) + return PromptCloudClusterProfile(ctx, yAuthVir, cl, cpCl) } var ( @@ -372,7 +379,9 @@ nameLookup: vc, err := cl.VirtualCluster(ctx, clusterID) if err != nil { // if we fail for a vcluster, we try again for a normal cluster - c, err := cl.Cluster(ctx, clusterID) + c, err := cpCl.Cluster.GetCluster(ctx, connect.NewRequest(&controlplanev1beta1.GetClusterRequest{ + Id: clusterID, + })) if err != nil { // If the input cluster looks like an xid, we try // parsing it as a cluster ID. If the xid lookup fails, @@ -385,11 +394,14 @@ nameLookup: } return CloudClusterOutputs{}, fmt.Errorf("unable to request details for cluster %q: %w", clusterID, err) } - ns, err := cl.NamespaceForID(ctx, c.NamespaceUUID) + ns, err := cl.NamespaceForID(ctx, c.Msg.NamespaceId) if err != nil { return CloudClusterOutputs{}, err } - return fromCloudCluster(yAuthVir, ns, c), nil + if c.Msg.State != controlplanev1beta1.Cluster_STATE_READY { + return CloudClusterOutputs{}, fmt.Errorf("selected cluster %q is not ready for profile creation yet; you may run this command again once the cluster is running", clusterID) + } + return fromCloudCluster(yAuthVir, ns, c.Msg), nil } ns, err := cl.NamespaceForID(ctx, vc.NamespaceUUID) if err != nil { @@ -463,34 +475,37 @@ func findNamedCluster(name string, nss []cloudapi.Namespace, vcs []cloudapi.Virt // fromCloudCluster returns an rpk profile from a cloud cluster, as well // as if the cluster requires mtls or sasl. -func fromCloudCluster(yAuth *config.RpkCloudAuth, ns cloudapi.Namespace, c cloudapi.Cluster) CloudClusterOutputs { +func fromCloudCluster(yAuth *config.RpkCloudAuth, ns cloudapi.Namespace, c *controlplanev1beta1.Cluster) CloudClusterOutputs { p := config.RpkProfile{ Name: c.Name, FromCloud: true, CloudCluster: config.RpkCloudCluster{ Namespace: ns.Name, - ClusterID: c.ID, + ClusterID: c.Id, ClusterName: c.Name, AuthOrgID: yAuth.OrgID, AuthKind: yAuth.Kind, + ClusterType: c.Type.String(), }, } - p.KafkaAPI.Brokers = c.Status.Listeners.Kafka.Default.URLs - var isMTLS, isSASL bool - if l := c.Spec.KafkaListeners.Listeners; len(l) > 0 { - if l[0].TLS != nil { + if c.DataplaneApi != nil { + p.CloudCluster.ClusterURL = c.DataplaneApi.Url + } + var isMTLS bool + if c.KafkaApi != nil { + p.KafkaAPI.Brokers = c.KafkaApi.SeedBrokers + if mtls := c.KafkaApi.Mtls; mtls != nil { p.KafkaAPI.TLS = new(config.TLS) - isMTLS = l[0].TLS.RequireClientAuth + isMTLS = mtls.Enabled } - isSASL = l[0].SASL != nil } return CloudClusterOutputs{ Profile: p, NamespaceName: ns.Name, ClusterName: c.Name, - ClusterID: c.ID, + ClusterID: c.Id, MessageMTLS: isMTLS, - MessageSASL: isSASL, + MessageSASL: true, } } @@ -515,6 +530,7 @@ func fromVirtualCluster(yAuth *config.RpkCloudAuth, ns cloudapi.Namespace, vc cl ClusterName: vc.Name, AuthOrgID: yAuth.OrgID, AuthKind: yAuth.Kind, + ClusterType: publicapi.ServerlessClusterType, // Virtual clusters do not include a type in the response yet. }, } @@ -592,7 +608,7 @@ func (o CloudClusterOutputs) FullName() string { // user. If their cloud account has only one cluster, a profile is created for // it automatically. This returns ErrNoCloudClusters if the user has no cloud // clusters. -func PromptCloudClusterProfile(ctx context.Context, yAuth *config.RpkCloudAuth, cl *cloudapi.Client) (CloudClusterOutputs, error) { +func PromptCloudClusterProfile(ctx context.Context, yAuth *config.RpkCloudAuth, cl *cloudapi.Client, cpCl *publicapi.ControlPlaneClientSet) (CloudClusterOutputs, error) { org, nss, vcs, cs, err := cl.OrgNamespacesClusters(ctx) if err != nil { return CloudClusterOutputs{}, err @@ -618,15 +634,17 @@ func PromptCloudClusterProfile(ctx context.Context, yAuth *config.RpkCloudAuth, // all information we need. We need to now directly request this // cluster's information. if selected.c != nil { - c, err := cl.Cluster(ctx, selected.c.ID) + c, err := cpCl.Cluster.GetCluster(ctx, connect.NewRequest(&controlplanev1beta1.GetClusterRequest{ + Id: selected.c.ID, + })) if err != nil { - return CloudClusterOutputs{}, fmt.Errorf("unable to get cluster %q information: %w", c.ID, err) + return CloudClusterOutputs{}, fmt.Errorf("unable to get cluster %q information: %w", selected.c.ID, err) } - ns, err := cl.NamespaceForID(ctx, c.NamespaceUUID) + ns, err := cl.NamespaceForID(ctx, c.Msg.NamespaceId) if err != nil { return CloudClusterOutputs{}, err } - o = fromCloudCluster(yAuth, ns, c) + o = fromCloudCluster(yAuth, ns, c.Msg) } else { c, err := cl.VirtualCluster(ctx, selected.vc.ID) if err != nil { @@ -696,6 +714,9 @@ func combineClusterNames(nss cloudapi.Namespaces, vcs []cloudapi.VirtualCluster, var nameAndCs []nameAndCluster for _, c := range cs { c := c + if strings.ToLower(c.State) != cloudapi.ClusterStateReady { + continue + } nameAndCs = append(nameAndCs, nameAndCluster{ name: fmt.Sprintf("%s/%s", nsIDToName[c.NamespaceUUID], c.Name), c: &c, diff --git a/src/go/rpk/pkg/cli/profile/current.go b/src/go/rpk/pkg/cli/profile/current.go index 4602f8bdc8f55..acea5f055c2e1 100644 --- a/src/go/rpk/pkg/cli/profile/current.go +++ b/src/go/rpk/pkg/cli/profile/current.go @@ -29,7 +29,7 @@ This is a tiny command that simply prints the current profile name, which may be useful in scripts, or a PS1, or to confirm what you have selected. `, Args: cobra.ExactArgs(0), - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/profile/print_globals.go b/src/go/rpk/pkg/cli/profile/print_globals.go index 20ccaabc32b82..f4343e9463087 100644 --- a/src/go/rpk/pkg/cli/profile/print_globals.go +++ b/src/go/rpk/pkg/cli/profile/print_globals.go @@ -24,7 +24,7 @@ func newPrintGlobalsCommand(fs afero.Fs, p *config.Params) *cobra.Command { Use: "print-globals", Short: "Print rpk global configuration", Args: cobra.ExactArgs(0), - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/profile/profile.go b/src/go/rpk/pkg/cli/profile/profile.go index fdad4ea34d2cb..300d039442c54 100644 --- a/src/go/rpk/pkg/cli/profile/profile.go +++ b/src/go/rpk/pkg/cli/profile/profile.go @@ -58,7 +58,7 @@ your configuration in one place. // ValidProfiles is a cobra.ValidArgsFunction that returns the names of // existing profiles. func ValidProfiles(fs afero.Fs, p *config.Params) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { - return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { cfg, err := p.Load(fs) if err != nil { return nil, cobra.ShellCompDirectiveError @@ -87,7 +87,7 @@ func ValidProfiles(fs afero.Fs, p *config.Params) func(*cobra.Command, []string, var ErrNoCloudClusters = errors.New("no cloud clusters available") // RpkCloudProfileName is the default profile name used when a user creates a -// cloud cluster profile with no name, or +// cloud cluster profile with no name. const RpkCloudProfileName = "rpk-cloud" // ProfileExistsError is returned from CreateFlow if trying to create a profile diff --git a/src/go/rpk/pkg/cli/profile/prompt.go b/src/go/rpk/pkg/cli/profile/prompt.go index 87a46f6069318..22d764c38fcd6 100644 --- a/src/go/rpk/pkg/cli/profile/prompt.go +++ b/src/go/rpk/pkg/cli/profile/prompt.go @@ -69,7 +69,7 @@ MODIFIERS Four modifiers are supported, "bold", "faint", "underline", and "invert". `, Args: cobra.ExactArgs(0), - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go index 0d62d849e3dc0..9af02a2fd21b5 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/brokers/decommission-status.go @@ -24,7 +24,7 @@ func newDecommissionBrokerStatus(fs afero.Fs, p *config.Params) *cobra.Command { cmd := &cobra.Command{ Use: "decommission-status [BROKER ID]", Short: "Show the progress of a node decommissioning", - Long: `Show the progrss of a node decommissioning. + Long: `Show the progress of a node decommissioning. When a node is being decommissioned, this command reports the decommissioning progress as follows, where PARTITION-SIZE is in bytes. Using -H, it prints the diff --git a/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go b/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go index 30964f64a72cb..d50a18692a796 100644 --- a/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go +++ b/src/go/rpk/pkg/cli/redpanda/admin/partitions/partitions.go @@ -43,7 +43,7 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { Aliases: []string{"ls"}, Short: "List the partitions in a broker in the cluster", Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { brokerID, err := strconv.Atoi(args[0]) out.MaybeDie(err, "invalid broker %s: %v", args[0], err) if brokerID < 0 { diff --git a/src/go/rpk/pkg/cli/redpanda/check.go b/src/go/rpk/pkg/cli/redpanda/check.go index 28a190871d1b1..452762e97bad3 100644 --- a/src/go/rpk/pkg/cli/redpanda/check.go +++ b/src/go/rpk/pkg/cli/redpanda/check.go @@ -29,7 +29,7 @@ func NewCheckCommand(fs afero.Fs, p *config.Params) *cobra.Command { cmd := &cobra.Command{ Use: "check", Short: "Check if system meets redpanda requirements", - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { y, err := p.LoadVirtualRedpandaYaml(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) err = executeCheck(fs, y, timeout) diff --git a/src/go/rpk/pkg/cli/redpanda/config.go b/src/go/rpk/pkg/cli/redpanda/config.go index 4c4e3dca91045..b3cba1b283aa1 100644 --- a/src/go/rpk/pkg/cli/redpanda/config.go +++ b/src/go/rpk/pkg/cli/redpanda/config.go @@ -74,7 +74,7 @@ You may also use = notation for setting configuration properties: rpk redpanda config set redpanda.kafka_api[0].port=9092 `, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) @@ -131,7 +131,7 @@ redpanda will listen on it. If your machine has multiple private IP addresses, you must use the --self flag to specify which ip redpanda should listen on. `, Args: cobra.ExactArgs(0), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { cfg, err := p.Load(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) y := cfg.ActualRedpandaYamlOrDefaults() // we modify fields in the raw file without writing env / flag overrides diff --git a/src/go/rpk/pkg/cli/redpanda/start.go b/src/go/rpk/pkg/cli/redpanda/start.go index d20a1fffb204a..48ee0a92a5a55 100644 --- a/src/go/rpk/pkg/cli/redpanda/start.go +++ b/src/go/rpk/pkg/cli/redpanda/start.go @@ -17,7 +17,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" "strings" "time" @@ -546,26 +545,6 @@ func flagsFromConf( return flagsMap } -func mergeFlags( - current map[string]interface{}, overrides []string, -) map[string]interface{} { - overridesMap := map[string]string{} - for _, o := range overrides { - pattern := regexp.MustCompile(`[\s=]+`) - parts := pattern.Split(o, 2) - flagName := strings.ReplaceAll(parts[0], "--", "") - if len(parts) == 2 { - overridesMap[flagName] = parts[1] - } else { - overridesMap[flagName] = "" - } - } - for k, v := range overridesMap { - current[k] = v - } - return current -} - func setConfig(y *config.RedpandaYaml, configKvs []string) error { for _, rawKv := range configKvs { parts := strings.SplitN(rawKv, "=", 2) diff --git a/src/go/rpk/pkg/cli/redpanda/start_test.go b/src/go/rpk/pkg/cli/redpanda/start_test.go index beab49e4712c7..c9f23b4a12ada 100644 --- a/src/go/rpk/pkg/cli/redpanda/start_test.go +++ b/src/go/rpk/pkg/cli/redpanda/start_test.go @@ -39,63 +39,6 @@ func (l *noopLauncher) Start(_ string, rpArgs *redpanda.RedpandaArgs) error { return nil } -func TestMergeFlags(t *testing.T) { - tests := []struct { - name string - current map[string]interface{} - overrides []string - expected map[string]string - }{ - { - name: "it should override the existent values", - current: map[string]interface{}{"a": "true", "b": "2", "c": "127.0.0.1"}, - overrides: []string{"--a false", "b 42"}, - expected: map[string]string{"a": "false", "b": "42", "c": "127.0.0.1"}, - }, { - name: "it should override the existent values (2)", - current: map[string]interface{}{"lock-memory": "true", "cpumask": "0-1", "logger-log-level": "'exception=debug'"}, - overrides: []string{ - "--overprovisioned", "--unsafe-bypass-fsync 1", - "--default-log-level=trace", "--logger-log-level='exception=debug'", - "--fail-on-abandoned-failed-futures", - }, - expected: map[string]string{ - "lock-memory": "true", - "cpumask": "0-1", - "logger-log-level": "'exception=debug'", - "overprovisioned": "", - "unsafe-bypass-fsync": "1", - "default-log-level": "trace", - "--fail-on-abandoned-failed-futures": "", - }, - }, { - name: "it should create values not present in the current flags", - current: map[string]interface{}{}, - overrides: []string{"b 42", "c 127.0.0.1"}, - expected: map[string]string{"b": "42", "c": "127.0.0.1"}, - }, { - name: "it shouldn't change the current flags if no overrides are given", - current: map[string]interface{}{"b": "42", "c": "127.0.0.1"}, - overrides: []string{}, - expected: map[string]string{"b": "42", "c": "127.0.0.1"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - flags := mergeFlags(tt.current, tt.overrides) - require.Equal(t, len(flags), len(tt.expected)) - if len(flags) != len(tt.expected) { - t.Fatal("the flags dicts differ in size") - } - - for k, v := range flags { - require.Equal(t, tt.expected[k], v) - } - }) - } -} - func TestParseNamedAuthNAddress(t *testing.T) { authNSasl := "sasl" tests := []struct { diff --git a/src/go/rpk/pkg/cli/redpanda/stop.go b/src/go/rpk/pkg/cli/redpanda/stop.go index efdd428ef2358..e61eef81aa3c3 100644 --- a/src/go/rpk/pkg/cli/redpanda/stop.go +++ b/src/go/rpk/pkg/cli/redpanda/stop.go @@ -36,7 +36,7 @@ func NewStopCommand(fs afero.Fs, p *config.Params) *cobra.Command { first sends SIGINT, and waits for the specified timeout. Then, if redpanda hasn't stopped, it sends SIGTERM. Lastly, it sends SIGKILL if it's still running.`, - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { y, err := p.LoadVirtualRedpandaYaml(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/redpanda/tune/tune.go b/src/go/rpk/pkg/cli/redpanda/tune/tune.go index bc9ad7c095d62..cd0c2ef98e595 100644 --- a/src/go/rpk/pkg/cli/redpanda/tune/tune.go +++ b/src/go/rpk/pkg/cli/redpanda/tune/tune.go @@ -58,7 +58,7 @@ Available tuners: To learn more about a tuner, run 'rpk redpanda tune help '. `, strings.Join(factory.AvailableTuners(), "\n - ")), - Args: func(cmd *cobra.Command, args []string) error { + Args: func(_ *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires the list of elements to tune") } @@ -75,7 +75,7 @@ To learn more about a tuner, run 'rpk redpanda tune help '. } return nil }, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { if !tunerParamsEmpty(&tunerParams) && p.ConfigFlag != "" { out.Die("use either tuner params or redpanda config file") } diff --git a/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go b/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go index e7d51d55ffe72..d4bff9d2cc33e 100644 --- a/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go +++ b/src/go/rpk/pkg/cli/registry/schema/check_compatibility.go @@ -23,7 +23,8 @@ import ( ) type compatCheckResponse struct { - Compatible bool `json:"compatible" yaml:"compatible"` + Compatible bool `json:"compatible" yaml:"compatible"` + Messages []string `json:"messages,omitempty" yaml:"messages,omitempty"` } func newCheckCompatibilityCommand(fs afero.Fs, p *config.Params) *cobra.Command { @@ -66,9 +67,10 @@ func newCheckCompatibilityCommand(fs afero.Fs, p *config.Params) *cobra.Command Type: t, References: references, } - compatible, err := cl.CheckCompatibility(cmd.Context(), subject, version, schema) + ctx := sr.WithParams(cmd.Context(), sr.Verbose) + compatible, err := cl.CheckCompatibility(ctx, subject, version, schema) out.MaybeDie(err, "unable to check compatibility: %v", err) - if isText, _, s, err := f.Format(compatCheckResponse{compatible.Is}); !isText { + if isText, _, s, err := f.Format(compatCheckResponse{compatible.Is, compatible.Messages}); !isText { out.MaybeDie(err, "unable to print in the required format %q: %v", f.Kind, err) out.Exit(s) } @@ -76,6 +78,8 @@ func newCheckCompatibilityCommand(fs afero.Fs, p *config.Params) *cobra.Command fmt.Println("Schema is compatible.") } else { fmt.Println("Schema is not compatible.") + messages := strings.Join(compatible.Messages, "\n") + fmt.Println(messages) } }, } diff --git a/src/go/rpk/pkg/cli/registry/schema/get.go b/src/go/rpk/pkg/cli/registry/schema/get.go index a2ee53d4b2a50..ef6b943ca29b7 100644 --- a/src/go/rpk/pkg/cli/registry/schema/get.go +++ b/src/go/rpk/pkg/cli/registry/schema/get.go @@ -137,7 +137,7 @@ To print the schema, use the '--print-schema' flag. cmd.Flags().StringVar(&schemaFile, "schema", "", "Schema file to check existence of, must be .avro or .proto; subject required") cmd.Flags().StringVar(&schemaType, "type", "", fmt.Sprintf("Schema type of the file used to lookup (%v); overrides schema file extension", strings.Join(supportedTypes, ","))) cmd.Flags().BoolVar(&deleted, "deleted", false, "If true, also return deleted schemas") - cmd.Flags().BoolVar(&printSchema, "print-schema", false, "If true, print the schema JSON") + cmd.Flags().BoolVar(&printSchema, "print-schema", false, "Prints the schema in JSON format") cmd.RegisterFlagCompletionFunc("type", validTypes()) return cmd diff --git a/src/go/rpk/pkg/cli/registry/schema/schema.go b/src/go/rpk/pkg/cli/registry/schema/schema.go index 711e380a19fdd..cbcaf2a38822f 100644 --- a/src/go/rpk/pkg/cli/registry/schema/schema.go +++ b/src/go/rpk/pkg/cli/registry/schema/schema.go @@ -106,7 +106,7 @@ func resolveSchemaType(typeFlag, schemaFile string) (t sr.SchemaType, err error) } func validTypes() func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { - return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return supportedTypes, cobra.ShellCompDirectiveDefault } } diff --git a/src/go/rpk/pkg/cli/topic/add_partitions.go b/src/go/rpk/pkg/cli/topic/add_partitions.go index e6bede57089fd..3039b7ee5d907 100644 --- a/src/go/rpk/pkg/cli/topic/add_partitions.go +++ b/src/go/rpk/pkg/cli/topic/add_partitions.go @@ -32,7 +32,7 @@ func newAddPartitionsCommand(fs afero.Fs, p *config.Params) *cobra.Command { Short: "Add partitions to existing topics", Args: cobra.MinimumNArgs(1), Long: `Add partitions to existing topics.`, - Run: func(cmd *cobra.Command, topics []string) { + Run: func(_ *cobra.Command, topics []string) { if !force { for _, t := range topics { if t == "__consumer_offsets" || t == "_schemas" || t == "__transaction_state" || t == "coprocessor_internal_topic" { diff --git a/src/go/rpk/pkg/cli/topic/config.go b/src/go/rpk/pkg/cli/topic/config.go index e174559485a44..81d6ee5e44c63 100644 --- a/src/go/rpk/pkg/cli/topic/config.go +++ b/src/go/rpk/pkg/cli/topic/config.go @@ -51,7 +51,7 @@ The --dry option will validate whether the requested configuration change is valid, but does not apply it. `, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, topics []string) { + Run: func(_ *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/consume.go b/src/go/rpk/pkg/cli/topic/consume.go index a5fd56e25f3c9..4c1eac621bd63 100644 --- a/src/go/rpk/pkg/cli/topic/consume.go +++ b/src/go/rpk/pkg/cli/topic/consume.go @@ -89,13 +89,15 @@ func newConsumeCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.MaybeDie(err, "unable to initialize admin kafka client: %v", err) // We fail if the topic does not exist. - listed, err := adm.ListTopics(cmd.Context(), topics...) - out.MaybeDie(err, "unable to check topic existence: %v", err) - listed.EachError(func(d kadm.TopicDetail) { - if errors.Is(d.Err, kerr.UnknownTopicOrPartition) { - out.Die("unable to consume topic %q: %v", d.Topic, d.Err.Error()) - } - }) + if !c.regex { + listed, err := adm.ListTopics(cmd.Context(), topics...) + out.MaybeDie(err, "unable to check topic existence: %v", err) + listed.EachError(func(d kadm.TopicDetail) { + if errors.Is(d.Err, kerr.UnknownTopicOrPartition) { + out.Die("unable to consume topic %q: %v", d.Topic, d.Err.Error()) + } + }) + } err = c.parseOffset(offset, topics, adm) out.MaybeDie(err, "invalid --offset %q: %v", offset, err) diff --git a/src/go/rpk/pkg/cli/topic/create.go b/src/go/rpk/pkg/cli/topic/create.go index eb36fe28ea22d..5e2ca5b410809 100644 --- a/src/go/rpk/pkg/cli/topic/create.go +++ b/src/go/rpk/pkg/cli/topic/create.go @@ -50,7 +50,7 @@ will create two topics, foo and bar, each with 20 partitions, 3 replicas, and the cleanup.policy=compact config option set. `, - Run: func(cmd *cobra.Command, topics []string) { + Run: func(_ *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/delete.go b/src/go/rpk/pkg/cli/topic/delete.go index 1d8406b22c236..028df58e3291c 100644 --- a/src/go/rpk/pkg/cli/topic/delete.go +++ b/src/go/rpk/pkg/cli/topic/delete.go @@ -51,7 +51,7 @@ For example, `, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, topics []string) { + Run: func(_ *cobra.Command, topics []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) diff --git a/src/go/rpk/pkg/cli/topic/describe.go b/src/go/rpk/pkg/cli/topic/describe.go index 06703e7fd0517..060ab0f6d854b 100644 --- a/src/go/rpk/pkg/cli/topic/describe.go +++ b/src/go/rpk/pkg/cli/topic/describe.go @@ -12,6 +12,7 @@ package topic import ( "context" "errors" + "fmt" "sort" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" @@ -31,21 +32,33 @@ func newDescribeCommand(fs afero.Fs, p *config.Params) *cobra.Command { summary bool configs bool partitions bool + re bool stable bool ) cmd := &cobra.Command{ - Use: "describe [TOPIC]", + Use: "describe [TOPICS]", Aliases: []string{"info"}, - Short: "Describe a topic", - Long: `Describe a topic. + Short: "Describe topics", + Long: `Describe topics. -This command prints detailed information about a topic. There are three -potential sections: a summary of the topic, the topic configs, and a detailed +This command prints detailed information about topics. The output contains +up to three sections: a summary of the topic, the topic configs, and a detailed partitions section. By default, the summary and configs sections are printed. + +The --regex flag (-r) parses arguments as regular expressions +and describes topics that match any of the expressions. + +For example, + + describe foo bar # describe topics foo and bar + describe -r '^f.*' '.*r$' # describe any topic starting with f and any topics ending in r + describe -r '*' # describe all topics + describe -r . # describe any one-character topics + `, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, topicArg []string) { + Args: cobra.MinimumNArgs(1), + Run: func(_ *cobra.Command, topicArg []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) @@ -53,31 +66,38 @@ partitions section. By default, the summary and configs sections are printed. out.MaybeDie(err, "unable to initialize kafka client: %v", err) defer cl.Close() - topic := topicArg[0] + adm, err := kafka.NewAdmin(fs, p) + out.MaybeDie(err, "unable to initialize kafka client: %v", err) + defer adm.Close() + + if re { + topicArg, err = regexTopics(adm, topicArg) + out.MaybeDie(err, "unable to filter topics by regex: %v", err) + } // By default, if neither are specified, we opt in to // the config section only. if !summary && !configs && !partitions { summary, configs = true, true } - if all { + + // We show all sections if: + // - "print-all" is used or + // - more than one topic are specified or matched. + if all || len(topicArg) > 1 { summary, configs, partitions = true, true, true + } else if len(topicArg) == 0 { + out.Exit("did not match any topics, exiting.") } - var t kmsg.MetadataResponseTopic - { - req := kmsg.NewPtrMetadataRequest() + req := kmsg.NewPtrMetadataRequest() + for _, topic := range topicArg { reqTopic := kmsg.NewMetadataRequestTopic() reqTopic.Topic = kmsg.StringPtr(topic) req.Topics = append(req.Topics, reqTopic) - - resp, err := req.RequestWith(context.Background(), cl) - out.MaybeDie(err, "unable to request topic metadata: %v", err) - if len(resp.Topics) != 1 { - out.Die("metadata response returned %d topics when we asked for 1", len(resp.Topics)) - } - t = resp.Topics[0] } + resp, err := req.RequestWith(context.Background(), cl) + out.MaybeDie(err, "unable to request topic metadata: %v", err) const ( secSummary = "summary" @@ -85,75 +105,82 @@ partitions section. By default, the summary and configs sections are printed. secPart = "partitions" ) - sections := out.NewMaybeHeaderSections( - out.ConditionalSectionHeaders(map[string]bool{ - secSummary: summary, - secConfigs: configs, - secPart: partitions, - })..., - ) - - sections.Add(secSummary, func() { - tw := out.NewTabWriter() - defer tw.Flush() - tw.PrintColumn("NAME", *t.Topic) - if t.IsInternal { - tw.PrintColumn("INTERNAL", t.IsInternal) - } - tw.PrintColumn("PARTITIONS", len(t.Partitions)) - if len(t.Partitions) > 0 { - p0 := &t.Partitions[0] - tw.PrintColumn("REPLICAS", len(p0.Replicas)) - } - if err := kerr.ErrorForCode(t.ErrorCode); err != nil { - tw.PrintColumn("ERROR", err) - } - }) - - sections.Add(secConfigs, func() { - req := kmsg.NewPtrDescribeConfigsRequest() - reqResource := kmsg.NewDescribeConfigsRequestResource() - reqResource.ResourceType = kmsg.ConfigResourceTypeTopic - reqResource.ResourceName = topic - req.Resources = append(req.Resources, reqResource) - - resp, err := req.RequestWith(context.Background(), cl) - out.MaybeDie(err, "unable to request configs: %v", err) - if len(resp.Resources) != 1 { - out.Die("config response returned %d resources when we asked for 1", len(resp.Resources)) - } - err = kerr.ErrorForCode(resp.Resources[0].ErrorCode) - out.MaybeDie(err, "config response contained error: %v", err) - - tw := out.NewTable("KEY", "VALUE", "SOURCE") - defer tw.Flush() - types.Sort(resp) - for _, config := range resp.Resources[0].Configs { - var val string - if config.IsSensitive { - val = "(sensitive)" - } else if config.Value != nil { - val = *config.Value + for i, topic := range resp.Topics { + sections := out.NewMaybeHeaderSections( + out.ConditionalSectionHeaders(map[string]bool{ + secSummary: summary, + secConfigs: configs, + secPart: partitions, + })..., + ) + + sections.Add(secSummary, func() { + tw := out.NewTabWriter() + defer tw.Flush() + tw.PrintColumn("NAME", *topic.Topic) + if topic.IsInternal { + tw.PrintColumn("INTERNAL", topic.IsInternal) } - tw.Print(config.Name, val, config.Source) - } - }) - - sections.Add(secPart, func() { - offsets := listStartEndOffsets(cl, topic, len(t.Partitions), stable) - - tw := out.NewTable(describePartitionsHeaders( - t.Partitions, - offsets, - )...) - defer tw.Flush() - for _, row := range describePartitionsRows( - t.Partitions, - offsets, - ) { - tw.Print(row...) + tw.PrintColumn("PARTITIONS", len(topic.Partitions)) + if len(topic.Partitions) > 0 { + p0 := &topic.Partitions[0] + tw.PrintColumn("REPLICAS", len(p0.Replicas)) + } + if err := kerr.ErrorForCode(topic.ErrorCode); err != nil { + tw.PrintColumn("ERROR", err) + } + }) + + sections.Add(secConfigs, func() { + req := kmsg.NewPtrDescribeConfigsRequest() + reqResource := kmsg.NewDescribeConfigsRequestResource() + reqResource.ResourceType = kmsg.ConfigResourceTypeTopic + reqResource.ResourceName = *topic.Topic + req.Resources = append(req.Resources, reqResource) + + resp, err := req.RequestWith(context.Background(), cl) + out.MaybeDie(err, "unable to request configs: %v", err) + if len(resp.Resources) != 1 { + out.Die("config response returned %d resources when we asked for 1", len(resp.Resources)) + } + err = kerr.ErrorForCode(resp.Resources[0].ErrorCode) + out.MaybeDie(err, "config response contained error: %v", err) + + tw := out.NewTable("KEY", "VALUE", "SOURCE") + defer tw.Flush() + types.Sort(resp) + for _, config := range resp.Resources[0].Configs { + var val string + if config.IsSensitive { + val = "(sensitive)" + } else if config.Value != nil { + val = *config.Value + } + tw.Print(config.Name, val, config.Source) + } + }) + + sections.Add(secPart, func() { + offsets := listStartEndOffsets(cl, *topic.Topic, len(topic.Partitions), stable) + + tw := out.NewTable(describePartitionsHeaders( + topic.Partitions, + offsets, + )...) + defer tw.Flush() + for _, row := range describePartitionsRows( + topic.Partitions, + offsets, + ) { + tw.Print(row...) + } + }) + + i++ + if i < len(resp.Topics) { + fmt.Println() } - }) + } }, } @@ -170,6 +197,7 @@ partitions section. By default, the summary and configs sections are printed. cmd.Flags().BoolVarP(&configs, "print-configs", "c", false, "Print the config section") cmd.Flags().BoolVarP(&partitions, "print-partitions", "p", false, "Print the detailed partitions section") cmd.Flags().BoolVarP(&all, "print-all", "a", false, "Print all sections") + cmd.Flags().BoolVarP(&re, "regex", "r", false, "Parse arguments as regex; describe any topic that matches any input topic expression") cmd.Flags().BoolVar(&stable, "stable", false, "Include the stable offsets column in the partitions section; only relevant if you produce to this topic transactionally") diff --git a/src/go/rpk/pkg/cli/topic/list.go b/src/go/rpk/pkg/cli/topic/list.go index 741ada4411ed4..fbd36488da5be 100644 --- a/src/go/rpk/pkg/cli/topic/list.go +++ b/src/go/rpk/pkg/cli/topic/list.go @@ -49,7 +49,7 @@ as such, specifying both -i and -r will exit with failure. Lastly, --detailed flag (-d) opts in to printing extra per-partition information. `, - Run: func(cmd *cobra.Command, topics []string) { + Run: func(_ *cobra.Command, topics []string) { // The purpose of the regex flag really is for users to // know what topics they will delete when using regex. // We forbid deleting internal topics (redpanda diff --git a/src/go/rpk/pkg/cli/topic/trim.go b/src/go/rpk/pkg/cli/topic/trim.go index 6aefb913b1efa..99a5889966c05 100644 --- a/src/go/rpk/pkg/cli/topic/trim.go +++ b/src/go/rpk/pkg/cli/topic/trim.go @@ -48,7 +48,7 @@ within the segment before the requested offset can no longer be read. The --offset/-o flag allows you to indicate which index you want to set the partition's low watermark (start offset) to. It can be a single integer value denoting the offset or a timestamp if you prefix the offset with an '@'. You may -select which partition you want to trim the offset from with the --partition/-p +select which partition you want to trim the offset from with the --partitions/-p flag. The --from-file option allows to trim the offsets specified in a text file with @@ -61,7 +61,7 @@ or the equivalent keyed JSON/YAML file. EXAMPLES Trim records in 'foo' topic to offset 120 in partition 1 - rpk topic trim-prefix foo --offset 120 --partition 1 + rpk topic trim-prefix foo --offset 120 --partitions 1 Trim records in all partitions of topic foo previous to an specific timestamp rpk topic trim-prefix foo -o "@1622505600" diff --git a/src/go/rpk/pkg/cli/topic/utils.go b/src/go/rpk/pkg/cli/topic/utils.go index 005dc3adab44a..3704931dd4e54 100644 --- a/src/go/rpk/pkg/cli/topic/utils.go +++ b/src/go/rpk/pkg/cli/topic/utils.go @@ -12,9 +12,9 @@ package topic import ( "context" "fmt" - "regexp" "strings" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/utils" "github.com/twmb/franz-go/pkg/kadm" ) @@ -57,39 +57,5 @@ func regexTopics(adm *kadm.Client, expressions []string) ([]string, error) { return nil, fmt.Errorf("unable to list topics: %w", err) } - return regexListedTopics(topics.Names(), expressions) -} - -func regexListedTopics(topics, expressions []string) ([]string, error) { - var compiled []*regexp.Regexp - for _, expression := range expressions { - if !strings.HasPrefix(expression, "^") { - expression = "^" + expression - } - if !strings.HasSuffix(expression, "$") { - expression += "$" - } - re, err := regexp.Compile(expression) - if err != nil { - return nil, fmt.Errorf("unable to compile regex %q: %w", expression, err) - } - compiled = append(compiled, re) - } - - var matched []string - for _, re := range compiled { - remaining := topics[:0] - for _, t := range topics { - if re.MatchString(t) { - matched = append(matched, t) - } else { - remaining = append(remaining, t) - } - } - topics = remaining - if len(topics) == 0 { - break - } - } - return matched, nil + return utils.RegexListedItems(topics.Names(), expressions) } diff --git a/src/go/rpk/pkg/cli/topic/utils_test.go b/src/go/rpk/pkg/cli/topic/utils_test.go index 2f2fa7e1f809b..ee22d5b21ecbe 100644 --- a/src/go/rpk/pkg/cli/topic/utils_test.go +++ b/src/go/rpk/pkg/cli/topic/utils_test.go @@ -81,53 +81,3 @@ func TestParseKVs(t *testing.T) { }) } } - -func TestRegexListedTopics(t *testing.T) { - for _, test := range []struct { - topics []string - exprs []string - exp []string - expErr bool - }{ - {}, // no topics, no expressions: no error - - { // topic, no expressions: no change - topics: []string{"foo", "bar"}, - }, - - { - topics: []string{"foo", "bar", "biz", "baz", "buzz"}, - exprs: []string{".a.", "^f.."}, - exp: []string{"bar", "baz", "foo"}, - }, - - { // dot matches nothing by default, because we anchor with ^ and $ - topics: []string{"foo", "bar", "biz", "baz", "buzz"}, - exprs: []string{"."}, - }, - - { // .* matches everything - topics: []string{"foo", "bar", "biz", "baz", "buzz"}, - exprs: []string{".*"}, - exp: []string{"foo", "bar", "biz", "baz", "buzz"}, - }, - - { - exprs: []string{"as[df"}, - expErr: true, - }, - - // - } { - got, err := regexListedTopics(test.topics, test.exprs) - - gotErr := err != nil - if gotErr != test.expErr { - t.Errorf("got err? %v, exp? %v", gotErr, test.expErr) - } - if test.expErr { - return - } - require.Equal(t, test.exp, got, "got topics != expected") - } -} diff --git a/src/go/rpk/pkg/cli/transform/delete.go b/src/go/rpk/pkg/cli/transform/delete.go index 211215416c1ab..e7e7c5d8d154b 100644 --- a/src/go/rpk/pkg/cli/transform/delete.go +++ b/src/go/rpk/pkg/cli/transform/delete.go @@ -12,9 +12,12 @@ package transform import ( "fmt" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi" + dataplanev1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -28,6 +31,7 @@ func newDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitServerlessAdmin(p) api, err := adminapi.NewClient(fs, p) out.MaybeDie(err, "unable to initialize admin api client: %v", err) @@ -40,9 +44,23 @@ func newDeleteCommand(fs afero.Fs, p *config.Params) *cobra.Command { out.Exit("Deletion canceled.") } } + if p.FromCloud && !p.CloudCluster.IsServerless() { + url, err := p.CloudCluster.CheckClusterURL() + out.MaybeDie(err, "unable to get cluster information: %v", err) - err = api.DeleteWasmTransform(cmd.Context(), functionName) - out.MaybeDie(err, "unable to delete transform %q: %v", functionName, err) + cl, err := publicapi.NewDataPlaneClientSet(url, p.CurrentAuth().AuthToken) + out.MaybeDie(err, "unable to initialize cloud client: %v", err) + + _, err = cl.Transform.DeleteTransform( + cmd.Context(), + connect.NewRequest(&dataplanev1alpha1.DeleteTransformRequest{ + Name: functionName, + })) + out.MaybeDie(err, "unable to delete transform %q: %v", functionName, err) + } else { + err = api.DeleteWasmTransform(cmd.Context(), functionName) + out.MaybeDie(err, "unable to delete transform %q: %v", functionName, err) + } fmt.Println("Delete successful!") }, } diff --git a/src/go/rpk/pkg/cli/transform/deploy.go b/src/go/rpk/pkg/cli/transform/deploy.go index 158b96bb13bdf..a2124b2434b24 100644 --- a/src/go/rpk/pkg/cli/transform/deploy.go +++ b/src/go/rpk/pkg/cli/transform/deploy.go @@ -26,6 +26,8 @@ import ( "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/httpapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi" + dataplanev1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -35,7 +37,7 @@ func newDeployCommand(fs afero.Fs, p *config.Params) *cobra.Command { var file string cmd := &cobra.Command{ - Use: "deploy [WASM]", + Use: "deploy", Short: "Deploy a transform", Long: `Deploy a transform. @@ -59,12 +61,10 @@ The --var flag can be repeated to specify multiple variables like so: rpk transform deploy --var FOO=BAR --var FIZZ=BUZZ `, Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) - - api, err := adminapi.NewClient(fs, p) - out.MaybeDie(err, "unable to initialize admin api client: %v", err) + config.CheckExitServerlessAdmin(p) cfg := fc.ToProjectConfig() @@ -110,16 +110,33 @@ The --var flag can be repeated to specify multiple variables like so: Status: nil, Environment: mapToEnvVars(cfg.Env), } - err = api.DeployWasmTransform(cmd.Context(), t, wasm) - if he := (*adminapi.HTTPResponseError)(nil); errors.As(err, &he) { - if he.Response.StatusCode == 400 { - body, bodyErr := he.DecodeGenericErrorBody() - if bodyErr == nil { - out.Die("unable to deploy transform %s: %s", cfg.Name, body.Message) + if p.FromCloud && !p.CloudCluster.IsServerless() { + url, err := p.CloudCluster.CheckClusterURL() + out.MaybeDie(err, "unable to get cluster information: %v", err) + + cl, err := publicapi.NewDataPlaneClientSet(url, p.CurrentAuth().AuthToken) + out.MaybeDie(err, "unable to initialize cloud client: %v", err) + + err = cl.Transform.DeployTransform(cmd.Context(), publicapi.DeployTransformRequest{ + Metadata: adminAPIToDataplaneMetadata(t), + WasmBinary: wasm, + }) + out.MaybeDie(err, "unable to deploy transform to Cloud Cluster: %v", err) + } else { + api, err := adminapi.NewClient(fs, p) + out.MaybeDie(err, "unable to initialize admin api client: %v", err) + + err = api.DeployWasmTransform(cmd.Context(), t, wasm) + if he := (*adminapi.HTTPResponseError)(nil); errors.As(err, &he) { + if he.Response.StatusCode == 400 { + body, bodyErr := he.DecodeGenericErrorBody() + if bodyErr == nil { + out.Die("unable to deploy transform %s: %s", cfg.Name, body.Message) + } } } + out.MaybeDie(err, "unable to deploy transform %s: %v", cfg.Name, err) } - out.MaybeDie(err, "unable to deploy transform %s: %v", cfg.Name, err) fmt.Printf("transform %q deployed.\n", cfg.Name) }, @@ -219,7 +236,7 @@ func mergeProjectConfigs(lhs project.Config, rhs project.Config) (out project.Co // isEmptyProjectConfig checks if a project config is completely empty. func isEmptyProjectConfig(cfg project.Config) bool { - return cfg.Name == "" && cfg.InputTopic == "" && cfg.OutputTopic == "" && (cfg.Env == nil || len(cfg.Env) == 0) + return cfg.Name == "" && cfg.InputTopic == "" && cfg.OutputTopic == "" && len(cfg.Env) == 0 } // validateProjectConfig validates the merged command line and file configurations. @@ -279,3 +296,19 @@ func mapToEnvVars(env map[string]string) (vars []adminapi.EnvironmentVariable) { } return } + +func adminAPIToDataplaneMetadata(m adminapi.TransformMetadata) *dataplanev1alpha1.DeployTransformRequest { + var envs []*dataplanev1alpha1.TransformMetadata_EnvironmentVariable + for _, e := range m.Environment { + envs = append(envs, &dataplanev1alpha1.TransformMetadata_EnvironmentVariable{ + Key: e.Key, + Value: e.Value, + }) + } + return &dataplanev1alpha1.DeployTransformRequest{ + Name: m.Name, + InputTopicName: m.InputTopic, + OutputTopicNames: m.OutputTopics, + EnvironmentVariables: envs, + } +} diff --git a/src/go/rpk/pkg/cli/transform/init.go b/src/go/rpk/pkg/cli/transform/init.go index 0fb0df79070a5..075119670f63d 100644 --- a/src/go/rpk/pkg/cli/transform/init.go +++ b/src/go/rpk/pkg/cli/transform/init.go @@ -135,6 +135,7 @@ This initializes a transform project in the foobar directory. } cmd.Flags().VarP(&lang, "language", "l", "The language used to develop the transform") cmd.Flags().Var(&install, "install-deps", "If dependencies should be installed for the project") + cmd.Flags().Lookup("install-deps").NoOptDefVal = "true" cmd.Flags().StringVar(&name, "name", "", "The name of the transform") return cmd } diff --git a/src/go/rpk/pkg/cli/transform/list.go b/src/go/rpk/pkg/cli/transform/list.go index e4fc5e85fc76b..9fcd5d31cae92 100644 --- a/src/go/rpk/pkg/cli/transform/list.go +++ b/src/go/rpk/pkg/cli/transform/list.go @@ -17,13 +17,34 @@ import ( "os" "strings" + "connectrpc.com/connect" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/config" "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi" + dataplanev1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" "github.com/spf13/afero" "github.com/spf13/cobra" ) +type ( + detailedTransformMetadata struct { + Name string `json:"name" yaml:"name"` + InputTopic string `json:"input_topic" yaml:"input_topic"` + OutputTopics []string `json:"output_topics" yaml:"output_topics"` + Environment map[string]string `json:"environment" yaml:"environment"` + Status []adminapi.PartitionTransformStatus `json:"status" yaml:"status"` + } + summarizedTransformMetadata struct { + Name string `json:"name" yaml:"name"` + InputTopic string `json:"input_topic" yaml:"input_topic"` + OutputTopics []string `json:"output_topics" yaml:"output_topics"` + Environment map[string]string `json:"environment" yaml:"environment"` + Running string `json:"running" yaml:"running"` + Lag int `json:"lag" yaml:"lag"` + } +) + func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command { var detailed bool cmd := &cobra.Command{ @@ -42,20 +63,40 @@ The --detailed flag (-d) opts in to printing extra per-processor information. `, Aliases: []string{"ls"}, Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { f := p.Formatter - if h, ok := f.Help([]string{}); ok { - out.Exit(h) + if detailed { + if h, ok := f.Help([]detailedTransformMetadata{}); ok { + out.Exit(h) + } + } else { + if h, ok := f.Help([]summarizedTransformMetadata{}); ok { + out.Exit(h) + } } p, err := p.LoadVirtualProfile(fs) out.MaybeDie(err, "rpk unable to load config: %v", err) + config.CheckExitServerlessAdmin(p) + + var l []adminapi.TransformMetadata + if p.FromCloud && !p.CloudCluster.IsServerless() { + url, err := p.CloudCluster.CheckClusterURL() + out.MaybeDie(err, "unable to get cluster information: %v", err) - api, err := adminapi.NewClient(fs, p) - out.MaybeDie(err, "unable to initialize admin api client: %v", err) + cl, err := publicapi.NewDataPlaneClientSet(url, p.CurrentAuth().AuthToken) + out.MaybeDie(err, "unable to initialize cloud client: %v", err) - l, err := api.ListWasmTransforms(cmd.Context()) - out.MaybeDie(err, "unable to list transforms: %v", err) + res, err := cl.Transform.ListTransforms(cmd.Context(), connect.NewRequest(&dataplanev1alpha1.ListTransformsRequest{})) + out.MaybeDie(err, "unable to list transforms from Cloud: %v", err) + l = dataplaneToAdminTransformMetadata(res.Msg.Transforms) + } else { + api, err := adminapi.NewClient(fs, p) + out.MaybeDie(err, "unable to initialize admin api client: %v", err) + + l, err = api.ListWasmTransforms(cmd.Context()) + out.MaybeDie(err, "unable to list transforms: %v", err) + } if detailed { d := detailView(l) @@ -71,24 +112,6 @@ The --detailed flag (-d) opts in to printing extra per-processor information. return cmd } -type ( - detailedTransformMetadata struct { - Name string `json:"name"` - InputTopic string `json:"input_topic"` - OutputTopics []string `json:"output_topics"` - Environment map[string]string `json:"environment"` - Status []adminapi.PartitionTransformStatus `json:"status"` - } - summarizedTransformMetadata struct { - Name string `json:"name"` - InputTopic string `json:"input_topic"` - OutputTopics []string `json:"output_topics"` - Environment map[string]string `json:"environment"` - Running string `json:"running"` - Lag int `json:"lag"` - } -) - func makeEnvMap(env []adminapi.EnvironmentVariable) map[string]string { out := make(map[string]string) for _, entry := range env { @@ -104,7 +127,7 @@ func summarizedView(metadata []adminapi.TransformMetadata) (resp []summarizedTra running := 0 lag := 0 for _, v := range meta.Status { - if v.Status == "running" { + if strings.ToLower(v.Status) == "running" { running++ } lag += v.Lag @@ -168,3 +191,41 @@ func printDetailed(f config.OutFormatter, d []detailedTransformMetadata, w io.Wr } } } + +func dataplaneToAdminTransformMetadata(transforms []*dataplanev1alpha1.TransformMetadata) []adminapi.TransformMetadata { + var transformMetadata []adminapi.TransformMetadata + for _, t := range transforms { + var ( + status []adminapi.PartitionTransformStatus + envs []adminapi.EnvironmentVariable + ) + if t != nil { + for _, s := range t.Statuses { + if s != nil { + status = append(status, adminapi.PartitionTransformStatus{ + NodeID: int(s.BrokerId), + Partition: int(s.PartitionId), + Status: strings.TrimPrefix(s.Status.String(), "PARTITION_STATUS_"), + Lag: int(s.Lag), + }) + } + } + for _, e := range t.EnvironmentVariables { + if e != nil { + envs = append(envs, adminapi.EnvironmentVariable{ + Key: e.Key, + Value: e.Value, + }) + } + } + transformMetadata = append(transformMetadata, adminapi.TransformMetadata{ + Name: t.Name, + InputTopic: t.InputTopicName, + OutputTopics: t.OutputTopicNames, + Status: status, + Environment: envs, + }) + } + } + return transformMetadata +} diff --git a/src/go/rpk/pkg/cli/transform/logs.go b/src/go/rpk/pkg/cli/transform/logs.go index fa52277b2a660..d0aff3de7ff4f 100644 --- a/src/go/rpk/pkg/cli/transform/logs.go +++ b/src/go/rpk/pkg/cli/transform/logs.go @@ -56,7 +56,7 @@ func newLogsCommand(fs afero.Fs, p *config.Params) *cobra.Command { Data transform's STDOUT and STDERR are captured during runtime and written to an internally managed topic _redpanda.transform_logs. -This command ouputs logs for a single transform over a period of time and +This command outputs logs for a single transform over a period of time and printing them to STDOUT. The logs can be printed in various formats. By default, only logs that have been emitted are displayed. @@ -67,7 +67,7 @@ FILTERING The --head and --tail flags are mutually exclusive and limit the number of log entries from the beginning or end of the range, respectively. -The --since and --until flags definate a time range. Use one of both flags to +The --since and --until flags define a time range. Use one of both flags to limit the log output to a desired period of time. Both flags accept values in the following formats: @@ -95,7 +95,7 @@ For example, The following command reads logs between noon and 1pm on March 12th: - rpk transform logs my-tranform --since=2024-03-12T12:00:00Z --until=2024-03-12T13:00:00Z + rpk transform logs my-transform --since=2024-03-12T12:00:00Z --until=2024-03-12T13:00:00Z FORMATTING @@ -127,6 +127,9 @@ the Open Telemetry LogRecord protocol buffer.`, topics, err := admin.ListTopics(cmd.Context(), "_redpanda.transform_logs") out.MaybeDie(err, "unable to get logs topic: %v", err) + if len(topics.TopicsList()) == 0 { + out.Die("unable to find logs topic - is Redpanda on the right version with Data Transforms enabled?") + } topic := topics.TopicsList()[0] partition := computeLogPartition(transformName, topic) @@ -170,7 +173,7 @@ the Open Telemetry LogRecord protocol buffer.`, } } zap.L().Sugar().Debugf("reading logs topic with bounds [%v, %v] on partition %d", startOffset, maxOffset, partition) - if startOffset > maxOffset { + if startOffset >= maxOffset { return } @@ -332,7 +335,7 @@ func (c *clientLogSource) PollFetches(ctx context.Context) (RecordIter, error) { if fetches.IsClientClosed() { return nil, kgo.ErrClientClosed } - fetches.EachError(func(t string, p int32, err error) { + fetches.EachError(func(_ string, _ int32, err error) { fmt.Fprintf(os.Stderr, "ERR: %v\n", err) }) return fetches.RecordIter(), nil diff --git a/src/go/rpk/pkg/cloud/gcp/gcp.go b/src/go/rpk/pkg/cloud/gcp/gcp.go index 794f67bd861e3..5e26a21fc8f65 100644 --- a/src/go/rpk/pkg/cloud/gcp/gcp.go +++ b/src/go/rpk/pkg/cloud/gcp/gcp.go @@ -10,6 +10,7 @@ package gcp import ( + "context" "errors" "net/http" "path/filepath" @@ -41,7 +42,7 @@ func (*GcpVendor) Init() (vendor.InitializedVendor, error) { } func (v *InitializedGcpVendor) VMType() (string, error) { - t, err := v.client.Get("instance/machine-type") + t, err := v.client.GetWithContext(context.Background(), "instance/machine-type") if err != nil { return "", err } @@ -60,7 +61,7 @@ func available(client *metadata.Client, timeout time.Duration) bool { result := make(chan bool) go func(c *metadata.Client, res chan<- bool) { - _, err := c.ProjectID() + _, err := c.ProjectIDWithContext(context.Background()) res <- err == nil }(client, result) diff --git a/src/go/rpk/pkg/cobraext/cobraext.go b/src/go/rpk/pkg/cobraext/cobraext.go index 74fefe3d73c5b..81ef0d02d512f 100644 --- a/src/go/rpk/pkg/cobraext/cobraext.go +++ b/src/go/rpk/pkg/cobraext/cobraext.go @@ -31,7 +31,7 @@ func DeprecatedCmd(name string, args int) *cobra.Command { Use: name, Short: "This command has been deprecated.", Args: cobra.ExactArgs(args), - Run: func(cmd *cobra.Command, args []string) {}, + Run: func(_ *cobra.Command, _ []string) {}, } } diff --git a/src/go/rpk/pkg/config/config.go b/src/go/rpk/pkg/config/config.go index ad3865a821fba..e3621f0502637 100644 --- a/src/go/rpk/pkg/config/config.go +++ b/src/go/rpk/pkg/config/config.go @@ -87,6 +87,22 @@ func CheckExitCloudAdmin(p *RpkProfile) { } } +// CheckExitServerlessAdmin exits if the profile has FromCloud=true and the +// cluster is a Serverless cluster. +func CheckExitServerlessAdmin(p *RpkProfile) { + if p.FromCloud && p.CloudCluster.IsServerless() { + out.Die("This admin API based command is not supported on Redpanda Cloud serverless clusters.") + } +} + +// CheckExitNotServerlessAdmin exits if the profile has FromCloud=true and the +// cluster is NOT a Serverless cluster. +func CheckExitNotServerlessAdmin(p *RpkProfile) { + if p.FromCloud && !p.CloudCluster.IsServerless() { + out.Die("This admin API based command is not supported on Redpanda Cloud clusters.") + } +} + // VirtualRedpandaYaml returns a redpanda.yaml, starting with defaults, // then decoding a potential file, then applying env vars and then flags. func (c *Config) VirtualRedpandaYaml() *RedpandaYaml { diff --git a/src/go/rpk/pkg/config/format.go b/src/go/rpk/pkg/config/format.go index bdccbe94c5319..8f82f558aab0d 100644 --- a/src/go/rpk/pkg/config/format.go +++ b/src/go/rpk/pkg/config/format.go @@ -84,13 +84,17 @@ func formatType(t any, includeTypeName bool) (string, error) { return nil case reflect.Pointer: - fmt.Fprintf(sb, "*") + fmt.Fprint(sb, "*") return walk(typ.Elem()) case reflect.Slice, reflect.Array: - fmt.Fprintf(sb, "[]") + fmt.Fprint(sb, "[]") return walk(typ.Elem()) + case reflect.Map: + fmt.Fprintf(sb, "map[%v]%v", typ.Key(), typ.Elem()) + return nil + case reflect.Struct: // rest of this function } @@ -99,12 +103,12 @@ func formatType(t any, includeTypeName bool) (string, error) { fmt.Fprintf(sb, "%s", typ.Name()) } - fmt.Fprintf(sb, "{\n") - defer fmt.Fprintf(sb, spaces()+"}") + fmt.Fprint(sb, "{\n") + defer fmt.Fprint(sb, spaces()+"}") _, seen := types[typ] if seen { - fmt.Fprintf(sb, spaces()+"CYCLE") + fmt.Fprint(sb, spaces()+"CYCLE") return nil // a cycle is fine, all types known in this cycle so far are valid } types[typ] = struct{}{} diff --git a/src/go/rpk/pkg/config/params.go b/src/go/rpk/pkg/config/params.go index ee289b05ed654..2c187a8ede8a2 100644 --- a/src/go/rpk/pkg/config/params.go +++ b/src/go/rpk/pkg/config/params.go @@ -85,7 +85,7 @@ const ( xkindGlobal // configuration for rpk.yaml globals ) -const currentRpkYAMLVersion = 3 +const currentRpkYAMLVersion = 4 type xflag struct { path string @@ -149,7 +149,7 @@ var xflags = map[string]xflag{ "kafka_api.tls.enabled", "true", xkindProfile, - func(v string, y *RpkYaml) error { + func(_ string, y *RpkYaml) error { p := y.Profile(y.CurrentProfile) mkKafkaTLS(&p.KafkaAPI) return nil @@ -244,7 +244,7 @@ var xflags = map[string]xflag{ "admin_api.tls.enabled", "false", xkindProfile, - func(v string, y *RpkYaml) error { + func(_ string, y *RpkYaml) error { p := y.Profile(y.CurrentProfile) mkAdminTLS(&p.AdminAPI) return nil @@ -307,7 +307,7 @@ var xflags = map[string]xflag{ "schema_registry.tls.enabled", "false", xkindProfile, - func(v string, y *RpkYaml) error { + func(_ string, y *RpkYaml) error { p := y.Profile(y.CurrentProfile) mkSchemaRegistryTLS(&p.SR) return nil @@ -834,7 +834,7 @@ func (p *Params) InstallFormatFlag(cmd *cobra.Command) { pf := cmd.PersistentFlags() pf.StringVar(&p.Formatter.Kind, "format", "text", fmt.Sprintf("Output format (%v)", strings.Join((&OutFormatter{}).SupportedFormats(), ","))) - cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cmd.RegisterFlagCompletionFunc("format", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return (&OutFormatter{}).SupportedFormats(), cobra.ShellCompDirectiveNoSpace }) } diff --git a/src/go/rpk/pkg/config/params_test.go b/src/go/rpk/pkg/config/params_test.go index f37ab994418f5..4985aaa49d6f1 100644 --- a/src/go/rpk/pkg/config/params_test.go +++ b/src/go/rpk/pkg/config/params_test.go @@ -723,7 +723,7 @@ rpk: pandaproxy: {} schema_registry: {} `, - expVirtualRpk: `version: 3 + expVirtualRpk: `version: 4 globals: prompt: "" no_default_cluster: false @@ -815,7 +815,7 @@ rpk: tune_disk_write_cache: true tune_disk_irq: true `, - expVirtualRpk: `version: 3 + expVirtualRpk: `version: 4 globals: prompt: "" no_default_cluster: false @@ -857,7 +857,7 @@ cloud_auth: // * admin api is defaulted, using kafka broker ip { name: "rpk.yaml exists", - rpkYaml: `version: 3 + rpkYaml: `version: 4 globals: prompt: "" no_default_cluster: false @@ -914,7 +914,7 @@ rpk: pandaproxy: {} schema_registry: {} `, - expVirtualRpk: `version: 3 + expVirtualRpk: `version: 4 globals: prompt: "" no_default_cluster: false @@ -975,7 +975,7 @@ rpk: tune_disk_write_cache: true tune_disk_irq: true `, - rpkYaml: `version: 3 + rpkYaml: `version: 4 globals: prompt: "" no_default_cluster: false @@ -1025,7 +1025,7 @@ rpk: tune_disk_irq: true `, - expVirtualRpk: `version: 3 + expVirtualRpk: `version: 4 globals: prompt: "" no_default_cluster: false diff --git a/src/go/rpk/pkg/config/rpk_yaml.go b/src/go/rpk/pkg/config/rpk_yaml.go index 9bc403981cd78..3f161dccb830e 100644 --- a/src/go/rpk/pkg/config/rpk_yaml.go +++ b/src/go/rpk/pkg/config/rpk_yaml.go @@ -17,6 +17,7 @@ import ( "reflect" "time" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi" "github.com/spf13/afero" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -38,7 +39,7 @@ func defaultVirtualRpkYaml() (RpkYaml, error) { path, _ := DefaultRpkYamlPath() // if err is non-nil, we fail in Write y := RpkYaml{ fileLocation: path, - Version: 3, + Version: 4, Profiles: []RpkProfile{DefaultRpkProfile()}, CloudAuths: []RpkCloudAuth{DefaultRpkCloudAuth()}, } @@ -69,7 +70,7 @@ func DefaultRpkCloudAuth() RpkCloudAuth { func emptyVirtualRpkYaml() RpkYaml { return RpkYaml{ - Version: 3, + Version: 4, } } @@ -153,6 +154,8 @@ type ( ClusterName string `json:"cluster_name" yaml:"cluster_name"` AuthOrgID string `json:"auth_org_id" yaml:"auth_org_id"` AuthKind string `json:"auth_kind" yaml:"auth_kind"` + ClusterType string `json:"cluster_type" yaml:"cluster_type"` + ClusterURL string `json:"cluster_url,omitempty" yaml:"cluster_url,omitempty"` } // RpkCloudAuth is unique by name and org ID. We support multiple auths @@ -424,6 +427,23 @@ func (c *RpkCloudCluster) HasAuth(a RpkCloudAuth) bool { return c.AuthOrgID == a.OrgID && c.AuthKind == a.Kind } +func (c *RpkCloudCluster) IsServerless() bool { + return c != nil && c.ClusterType == publicapi.ServerlessClusterType +} + +func (c *RpkCloudCluster) CheckClusterURL() (string, error) { + if c == nil { + return "", fmt.Errorf("cluster information not present in current profile; please delete and re-create the current profile with 'rpk profile create --from-cloud'") + } + if c.ClusterURL == "" { + if c.ClusterID != "" { + return "", fmt.Errorf("cluster URL not present in profile; please delete and re-create the current profile with 'rpk profile create --from-cloud=%v'", c.ClusterID) + } + return "", errors.New("cluster URL not present in profile; please delete and re-create the current profile with 'rpk profile create --from-cloud' and selecting this cluster") + } + return c.ClusterURL, nil +} + //////////////// // MISC TYPES // //////////////// diff --git a/src/go/rpk/pkg/config/rpk_yaml_test.go b/src/go/rpk/pkg/config/rpk_yaml_test.go index 74059f5009232..d974fa26662c0 100644 --- a/src/go/rpk/pkg/config/rpk_yaml_test.go +++ b/src/go/rpk/pkg/config/rpk_yaml_test.go @@ -25,11 +25,11 @@ func TestRpkYamlVersion(t *testing.T) { shastr := hex.EncodeToString(sha[:]) const ( - v3sha = "cc2faa9790df9307d1fbf3e532128152d473e31a951152d2e2514a915269f880" // 24-04-14 + v4sha = "d40eea0724c6f7c876e5551c9b0a90d71d409c0426efbf6c06f3c25fef4b262e" // 24-04-15 ) - if shastr != v3sha { - t.Errorf("rpk.yaml type shape has changed (got sha %s != exp %s, if fields were reordered, update the valid v3 sha, otherwise bump the rpk.yaml version number", shastr, v3sha) + if shastr != v4sha { + t.Errorf("rpk.yaml type shape has changed (got sha %s != exp %s, if fields were reordered, update the valid v3 sha, otherwise bump the rpk.yaml version number", shastr, v4sha) t.Errorf("current shape:\n%s\n", s) } } diff --git a/src/go/rpk/pkg/oauth/load.go b/src/go/rpk/pkg/oauth/load.go index 7b9a163125928..631283e46baff 100644 --- a/src/go/rpk/pkg/oauth/load.go +++ b/src/go/rpk/pkg/oauth/load.go @@ -82,7 +82,7 @@ func LoadFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, cl Client, n if inTests { zap.L().Sugar().Debug("returning fake organization because cloudAPIURL is empty") - return cloudapi.Organization{cloudapi.NameID{ + return cloudapi.Organization{NameID: cloudapi.NameID{ ID: "no-url-org-id", Name: "no-url-org", }}, nil @@ -280,7 +280,6 @@ func LoadFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, cl Client, n check(yAct, &authAct) check(yVir, &authVir) } - // We always write the auth name / org / orgID and update the // current auth. This is a no-op if check just above did stuff. @@ -303,7 +302,6 @@ func LoadFlow(ctx context.Context, fs afero.Fs, cfg *config.Config, cl Client, n yVir.CurrentCloudAuthKind = authVir.Kind yAct.CurrentCloudAuthKind = authVir.Kind // we use the virtual kind here -- clientID is updated below - } // We avoid copying the client secret, but we do keep the client ID. diff --git a/src/go/rpk/pkg/oauth/load_test.go b/src/go/rpk/pkg/oauth/load_test.go index dacf2df128ce2..2a5e6f6e768c2 100644 --- a/src/go/rpk/pkg/oauth/load_test.go +++ b/src/go/rpk/pkg/oauth/load_test.go @@ -117,7 +117,7 @@ func TestLoadFlow(t *testing.T) { hasClientID = true } - expFile := fmt.Sprintf(`version: 3 + expFile := fmt.Sprintf(`version: 4 globals: prompt: "" no_default_cluster: false diff --git a/src/go/rpk/pkg/publicapi/controlplane.go b/src/go/rpk/pkg/publicapi/controlplane.go new file mode 100644 index 0000000000000..411aad646df5d --- /dev/null +++ b/src/go/rpk/pkg/publicapi/controlplane.go @@ -0,0 +1,46 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package publicapi + +import ( + "net/http" + "time" + + "connectrpc.com/connect" + "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect" +) + +// ControlPlaneClientSet holds the respective service clients to interact with +// the control plane endpoints of the Public API. +type ControlPlaneClientSet struct { + Namespace controlplanev1beta1connect.NamespaceServiceClient + Cluster controlplanev1beta1connect.ClusterServiceClient +} + +// NewControlPlaneClientSet creates a Public API client set with the service +// clients of each resource available to interact with this package. +func NewControlPlaneClientSet(host, authToken string, opts ...connect.ClientOption) (*ControlPlaneClientSet, error) { + if host == "" { + host = ControlPlaneProdURL + } + opts = append([]connect.ClientOption{ + connect.WithInterceptors( + newAuthInterceptor(authToken), // Add the Bearer token. + newLoggerInterceptor(), // Add logs to every request. + ), + }, opts...) + + httpCl := &http.Client{Timeout: 15 * time.Second} + + return &ControlPlaneClientSet{ + Namespace: controlplanev1beta1connect.NewNamespaceServiceClient(httpCl, host, opts...), + Cluster: controlplanev1beta1connect.NewClusterServiceClient(httpCl, host, opts...), + }, nil +} diff --git a/src/go/rpk/pkg/publicapi/dataplane.go b/src/go/rpk/pkg/publicapi/dataplane.go new file mode 100644 index 0000000000000..d445ed0ca52a3 --- /dev/null +++ b/src/go/rpk/pkg/publicapi/dataplane.go @@ -0,0 +1,41 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package publicapi + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" +) + +// DataPlaneClientSet holds the respective service clients to interact with +// the data plane endpoints of the Public API. +type DataPlaneClientSet struct { + Transform transformServiceClient +} + +// NewDataPlaneClientSet creates a Public API client set with the service +// clients of each resource available to interact with this package. +func NewDataPlaneClientSet(host, authToken string, opts ...connect.ClientOption) (*DataPlaneClientSet, error) { + if host == "" { + return nil, fmt.Errorf("dataplane host is empty") + } + opts = append([]connect.ClientOption{ + connect.WithInterceptors( + newAuthInterceptor(authToken), // Add the Bearer token. + newLoggerInterceptor(), // Add logs to every request. + ), + }, opts...) + + return &DataPlaneClientSet{ + Transform: newTransformServiceClient(http.DefaultClient, host, authToken, opts...), + }, nil +} diff --git a/src/go/rpk/pkg/publicapi/publicapi.go b/src/go/rpk/pkg/publicapi/publicapi.go index 8736edcfb33b3..537d057bdb18f 100644 --- a/src/go/rpk/pkg/publicapi/publicapi.go +++ b/src/go/rpk/pkg/publicapi/publicapi.go @@ -11,60 +11,32 @@ package publicapi import ( "context" - "crypto/tls" "fmt" - controlplanev1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/metadata" + "connectrpc.com/connect" + "go.uber.org/zap" ) -// ProdURL is the host of the Cloud Redpanda API. -const ProdURL = "api.redpanda.com:443" - -// ClientSet holds the respective service clients to interact with the Public -// API. -type ClientSet struct { - Namespace controlplanev1beta1.NamespaceServiceClient -} - -// NewClientSet creates a Public API client set with the service clients of -// each resource available to interact with this package. -func NewClientSet(ctx context.Context, host, authToken string, opts ...grpc.DialOption) (*ClientSet, error) { - if host == "" { - host = ProdURL - } - opts = append([]grpc.DialOption{ - // Intercept to add the Bearer token. - grpc.WithUnaryInterceptor( - func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - return invoker(metadata.AppendToOutgoingContext(ctx, "authorization", fmt.Sprintf("Bearer %s", authToken)), method, req, reply, cc, opts...) - }, - ), - // Provide TLS config. - grpc.WithTransportCredentials( - credentials.NewTLS( - &tls.Config{ - MinVersion: tls.VersionTLS12, - }, - ), - ), - }, opts...) +// ControlPlaneProdURL is the host of the Cloud Redpanda API. +const ( + ControlPlaneProdURL = "https://api.redpanda.com" + ServerlessClusterType = "TYPE_SERVERLESS" +) - conn, err := clientConnection(ctx, host, opts...) - if err != nil { - return nil, fmt.Errorf("unable to create a connection with %q", host) +func newAuthInterceptor(token string) connect.UnaryInterceptorFunc { + return func(next connect.UnaryFunc) connect.UnaryFunc { + return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { + req.Header().Set("Authorization", fmt.Sprintf("Bearer %s", token)) + return next(ctx, req) + } } - return &ClientSet{ - Namespace: controlplanev1beta1.NewNamespaceServiceClient(conn), - }, nil } -func clientConnection(ctx context.Context, url string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { - return grpc.DialContext( - ctx, - url, - opts..., - ) +func newLoggerInterceptor() connect.UnaryInterceptorFunc { + return func(next connect.UnaryFunc) connect.UnaryFunc { + return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { + zap.L().Debug("Sending request", zap.String("host", req.Peer().Addr), zap.String("procedure", req.Spec().Procedure)) + return next(ctx, req) + } + } } diff --git a/src/go/rpk/pkg/publicapi/transform.go b/src/go/rpk/pkg/publicapi/transform.go new file mode 100644 index 0000000000000..ae8877e94fd65 --- /dev/null +++ b/src/go/rpk/pkg/publicapi/transform.go @@ -0,0 +1,116 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package publicapi + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + + "connectrpc.com/connect" + "github.com/redpanda-data/redpanda/src/go/rpk/pkg/httpapi" + commonv1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/common/v1alpha1" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect" +) + +const transformPath = "/v1alpha1/transforms" + +// transformServiceClient is an extension of the +// dataplanev1alpha1connect.TransformServiceClient to support the +// DeployTransform request. +type transformServiceClient struct { + tCl dataplanev1alpha1connect.TransformServiceClient + httpCl *httpapi.Client +} + +type DeployTransformRequest struct { + Metadata *v1alpha1.DeployTransformRequest + WasmBinary io.Reader +} + +func newTransformServiceClient(httpClient *http.Client, host, authToken string, opts ...connect.ClientOption) transformServiceClient { + httpOpts := []httpapi.Opt{ + httpapi.Host(host), + httpapi.BearerAuth(authToken), + httpapi.HTTPClient(httpClient), + httpapi.Err4xx(func(code int) error { + return &ConnectError{StatusCode: code} + }), + } + return transformServiceClient{ + tCl: dataplanev1alpha1connect.NewTransformServiceClient(httpClient, host, opts...), + httpCl: httpapi.NewClient(httpOpts...), + } +} + +func (tsc *transformServiceClient) ListTransforms(ctx context.Context, r *connect.Request[v1alpha1.ListTransformsRequest]) (*connect.Response[v1alpha1.ListTransformsResponse], error) { + return tsc.tCl.ListTransforms(ctx, r) +} + +func (tsc *transformServiceClient) GetTransform(ctx context.Context, r *connect.Request[v1alpha1.GetTransformRequest]) (*connect.Response[v1alpha1.GetTransformResponse], error) { + return tsc.tCl.GetTransform(ctx, r) +} + +func (tsc *transformServiceClient) DeleteTransform(ctx context.Context, r *connect.Request[v1alpha1.DeleteTransformRequest]) (*connect.Response[v1alpha1.DeleteTransformResponse], error) { + return tsc.tCl.DeleteTransform(ctx, r) +} + +func (tsc *transformServiceClient) DeployTransform(ctx context.Context, r DeployTransformRequest) error { + body := &bytes.Buffer{} + // The body is a multipart/form data with 2 fields: metadata, and + // wasm_binary. + writer := multipart.NewWriter(body) + + // Write metadata to the form. + metadataPart, err := writer.CreateFormField("metadata") + if err != nil { + return fmt.Errorf("unable to create 'metadata' form field: %v", err) + } + if err := json.NewEncoder(metadataPart).Encode(r.Metadata); err != nil { + return fmt.Errorf("unable to encode 'metadata': %v", err) + } + + // Write binary bytes to the form. + wasmPart, err := writer.CreateFormFile("wasm_binary", r.Metadata.Name+".wasm") + if err != nil { + return fmt.Errorf("unable to create form file: %v", err) + } + _, err = io.Copy(wasmPart, r.WasmBinary) + if err != nil { + return fmt.Errorf("unable to copy wasm binary: %v", err) + } + + err = writer.Close() + if err != nil { + return fmt.Errorf("unable to close form writer: %v", err) + } + + tsc.httpCl = tsc.httpCl.With(httpapi.Headers("Content-Type", writer.FormDataContentType())) + err = tsc.httpCl.Put(ctx, transformPath, nil, body.Bytes(), nil) + if err != nil { + return fmt.Errorf("unable to request transform deployment: %v", err) + } + return nil +} + +// ConnectError is the error returned by the data plane API. +type ConnectError struct { + commonv1alpha1.ErrorStatus + StatusCode int +} + +func (e *ConnectError) Error() string { + return fmt.Sprintf("unexpected status code %d: %v", e.StatusCode, e.Message) +} diff --git a/src/go/rpk/pkg/schemaregistry/client.go b/src/go/rpk/pkg/schemaregistry/client.go index 0d7e35dfbeabf..99de2e0f85c67 100644 --- a/src/go/rpk/pkg/schemaregistry/client.go +++ b/src/go/rpk/pkg/schemaregistry/client.go @@ -39,7 +39,7 @@ func NewClient(fs afero.Fs, p *config.RpkProfile) (*sr.Client, error) { } } - opts := []sr.Opt{ + opts := []sr.ClientOpt{ sr.URLs(urls...), sr.UserAgent("rpk"), } diff --git a/src/go/rpk/pkg/serde/proto.go b/src/go/rpk/pkg/serde/proto.go index d19e94628b57a..3604b9b20a6a3 100644 --- a/src/go/rpk/pkg/serde/proto.go +++ b/src/go/rpk/pkg/serde/proto.go @@ -66,7 +66,7 @@ func newProtoEncoder(compiledFiles linker.Files, protoFQN string, schemaID int) }, nil } -// newProtoDecoder will generate a deserializar function that decodes a record +// newProtoDecoder will generate a deserializer function that decodes a record // with the given schema. The generated function expects the record to contain // the index of the message (schema ID should already be stripped away) in the // first bytes of the record. diff --git a/src/go/rpk/pkg/tuners/gcp_disk_write_cache_tuner.go b/src/go/rpk/pkg/tuners/gcp_disk_write_cache_tuner.go index 01739c7f7cb50..882c8a4693844 100644 --- a/src/go/rpk/pkg/tuners/gcp_disk_write_cache_tuner.go +++ b/src/go/rpk/pkg/tuners/gcp_disk_write_cache_tuner.go @@ -73,9 +73,7 @@ func tuneWriteCache( if err != nil { return NewTuneError(err) } - err = executor.Execute( - commands.NewWriteFileCmd(fs, featureFile, disk.CachePolicyWriteThrough)) - + err = executor.Execute(commands.NewWriteFileCmd(fs, featureFile, disk.CachePolicyWriteThrough)) if err != nil { return NewTuneError(err) } diff --git a/src/go/rpk/pkg/tuners/irq/cpu_masks.go b/src/go/rpk/pkg/tuners/irq/cpu_masks.go index 6a2229e802e0a..d4c09ed2f459c 100644 --- a/src/go/rpk/pkg/tuners/irq/cpu_masks.go +++ b/src/go/rpk/pkg/tuners/irq/cpu_masks.go @@ -34,7 +34,7 @@ type CPUMasks interface { GetNumberOfCores(mask string) (uint, error) GetNumberOfPUs(mask string) (uint, error) GetAllCpusMask() (string, error) - GetLogicalCoreIdsFromPhysCore(core uint) ([]uint, error) + GetLogicalCoreIDsFromPhysCore(core uint) ([]uint, error) IsSupported() bool } @@ -212,7 +212,7 @@ func (masks *cpuMasks) GetNumberOfPUs(mask string) (uint, error) { return masks.hwloc.GetNumberOfPUs(mask) } -func (masks *cpuMasks) GetLogicalCoreIdsFromPhysCore( +func (masks *cpuMasks) GetLogicalCoreIDsFromPhysCore( core uint, ) ([]uint, error) { return masks.hwloc.GetPhysIntersection("PU", fmt.Sprintf("core:%d", core)) diff --git a/src/go/rpk/pkg/utils/regex_filter.go b/src/go/rpk/pkg/utils/regex_filter.go new file mode 100644 index 0000000000000..457e96bddedc6 --- /dev/null +++ b/src/go/rpk/pkg/utils/regex_filter.go @@ -0,0 +1,51 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package utils + +import ( + "fmt" + "regexp" + "strings" +) + +// RegexListedItems returns items that match the 'expressions' from the 'list'. +func RegexListedItems(list, expressions []string) ([]string, error) { + var compiled []*regexp.Regexp + for _, expression := range expressions { + if !strings.HasPrefix(expression, "^") { + expression = "^" + expression + } + if !strings.HasSuffix(expression, "$") { + expression += "$" + } + re, err := regexp.Compile(expression) + if err != nil { + return nil, fmt.Errorf("unable to compile regex %q: %w", expression, err) + } + compiled = append(compiled, re) + } + + var matched []string + for _, re := range compiled { + remaining := list[:0] + for _, item := range list { + if re.MatchString(item) { + matched = append(matched, item) + } else { + remaining = append(remaining, item) + } + } + list = remaining + if len(list) == 0 { + break + } + } + return matched, nil +} diff --git a/src/go/rpk/pkg/utils/regex_filter_test.go b/src/go/rpk/pkg/utils/regex_filter_test.go new file mode 100644 index 0000000000000..e053f4c4b2d76 --- /dev/null +++ b/src/go/rpk/pkg/utils/regex_filter_test.go @@ -0,0 +1,88 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRegexListedItems(t *testing.T) { + tests := []struct { + name string + list []string + exprs []string + want []string + wantErr bool + }{ + { + name: "no list, no expressions: no error", + }, + + { + name: "list, no expressions: no change", + list: []string{"foo", "bar"}, + }, + + { + name: "matching ^f", + list: []string{"foo", "bar", "fdsa"}, + exprs: []string{"^f.*"}, + want: []string{"foo", "fdsa"}, + }, + + { + name: "matching three", + list: []string{"foo", "bar", "biz", "baz", "buzz"}, + exprs: []string{".a.", "^f.."}, + want: []string{"bar", "baz", "foo"}, + }, + + { + name: "dot matches nothing by default, because we anchor with ^ and $", + list: []string{"foo", "bar", "biz", "baz", "buzz"}, + exprs: []string{"."}, + }, + + { + name: ".* matches everything", + list: []string{"foo", "bar", "biz", "baz", "buzz"}, + exprs: []string{".*"}, + want: []string{"foo", "bar", "biz", "baz", "buzz"}, + }, + + { + name: "* matches everything", + list: []string{"foo", "bar", "biz", "baz", "buzz"}, + exprs: []string{".*"}, + want: []string{"foo", "bar", "biz", "baz", "buzz"}, + }, + + { + name: "no list", + exprs: []string{"as[df"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := RegexListedItems(tt.list, tt.exprs) + gotErr := err != nil + if gotErr != tt.wantErr { + t.Errorf("got err? %v, exp? %v", gotErr, tt.wantErr) + } + if tt.wantErr { + return + } + require.Equal(t, tt.want, got, "got topics != expected") + }) + } +} diff --git a/src/go/rpk/pkg/utils/strings.go b/src/go/rpk/pkg/utils/strings.go deleted file mode 100644 index 58a4a51135bf1..0000000000000 --- a/src/go/rpk/pkg/utils/strings.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Redpanda Data, Inc. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.md -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0 - -package utils - -func StringInSlice(str string, ss []string) bool { - for _, s := range ss { - if str == s { - return true - } - } - return false -} diff --git a/src/go/rpk/pkg/utils/strings_test.go b/src/go/rpk/pkg/utils/strings_test.go deleted file mode 100644 index 910a126e951d6..0000000000000 --- a/src/go/rpk/pkg/utils/strings_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 Redpanda Data, Inc. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.md -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0 - -package utils_test - -import ( - "testing" - - "github.com/redpanda-data/redpanda/src/go/rpk/pkg/utils" - "github.com/stretchr/testify/require" -) - -func TestStringInSlice(t *testing.T) { - tests := []struct { - name string - str string - slice []string - expected bool - }{ - { - name: "it should return true if the slice contains the string", - str: "best", - slice: []string{"redpanda", "is", "the", "best", "there", "is"}, - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(st *testing.T) { - actual := utils.StringInSlice(tt.str, tt.slice) - require.Equal(st, tt.expected, actual) - }) - } -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/common/v1alpha1/common.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/common/v1alpha1/common.pb.go index 9338407e3cf89..b57e6de283a6c 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/common/v1alpha1/common.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/common/v1alpha1/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/common/v1alpha1/common.proto @@ -44,21 +44,24 @@ const ( Reason_REASON_TOO_MANY_REQUESTS Reason = 8 // The request timed out. Reason_REASON_TIMEOUT Reason = 9 + // The feature is not configured. + Reason_REASON_FEATURE_NOT_CONFIGURED Reason = 10 ) // Enum value maps for Reason. var ( Reason_name = map[int32]string{ - 0: "REASON_UNSPECIFIED", - 1: "REASON_RESOURCE_NOT_FOUND", - 2: "REASON_INVALID_INPUT", - 3: "REASON_NO_AUTHENTICATION_TOKEN", - 4: "REASON_AUTHENTICATION_TOKEN_EXPIRED", - 5: "REASON_AUTHENTICATION_TOKEN_INVALID", - 6: "REASON_PERMISSION_DENIED", - 7: "REASON_SERVER_ERROR", - 8: "REASON_TOO_MANY_REQUESTS", - 9: "REASON_TIMEOUT", + 0: "REASON_UNSPECIFIED", + 1: "REASON_RESOURCE_NOT_FOUND", + 2: "REASON_INVALID_INPUT", + 3: "REASON_NO_AUTHENTICATION_TOKEN", + 4: "REASON_AUTHENTICATION_TOKEN_EXPIRED", + 5: "REASON_AUTHENTICATION_TOKEN_INVALID", + 6: "REASON_PERMISSION_DENIED", + 7: "REASON_SERVER_ERROR", + 8: "REASON_TOO_MANY_REQUESTS", + 9: "REASON_TIMEOUT", + 10: "REASON_FEATURE_NOT_CONFIGURED", } Reason_value = map[string]int32{ "REASON_UNSPECIFIED": 0, @@ -71,6 +74,7 @@ var ( "REASON_SERVER_ERROR": 7, "REASON_TOO_MANY_REQUESTS": 8, "REASON_TIMEOUT": 9, + "REASON_FEATURE_NOT_CONFIGURED": 10, } ) @@ -199,7 +203,7 @@ var file_redpanda_api_common_v1alpha1_common_proto_rawDesc = []byte{ 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, - 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2a, 0xb8, 0x02, 0x0a, 0x06, 0x52, + 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2a, 0xdb, 0x02, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, @@ -219,26 +223,28 @@ var file_redpanda_api_common_v1alpha1_common_proto_rawDesc = []byte{ 0x52, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4d, 0x41, 0x4e, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x53, 0x10, 0x08, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, - 0x4f, 0x55, 0x54, 0x10, 0x09, 0x42, 0xa9, 0x02, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x65, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, - 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, - 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, - 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x1c, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1c, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x28, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, - 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, - 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4f, 0x55, 0x54, 0x10, 0x09, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, + 0x49, 0x47, 0x55, 0x52, 0x45, 0x44, 0x10, 0x0a, 0x42, 0xa9, 0x02, 0x0a, 0x20, 0x63, 0x6f, 0x6d, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0b, 0x43, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x65, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, + 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x1c, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1c, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x28, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, + 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster.pb.go index 985ccfaf4d5b2..318faeb6f8ed6 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/cluster.proto @@ -1040,6 +1040,8 @@ type Cluster_KafkaAPI struct { // Kafka API Seed Brokers (also known as Bootstrap servers). SeedBrokers []string `protobuf:"bytes,1,rep,name=seed_brokers,proto3" json:"seed_brokers,omitempty"` + // Kafka TLS configurations. + Mtls *MTLSSpec `protobuf:"bytes,2,opt,name=mtls,proto3" json:"mtls,omitempty"` } func (x *Cluster_KafkaAPI) Reset() { @@ -1081,6 +1083,13 @@ func (x *Cluster_KafkaAPI) GetSeedBrokers() []string { return nil } +func (x *Cluster_KafkaAPI) GetMtls() *MTLSSpec { + if x != nil { + return x.Mtls + } + return nil +} + type Cluster_HTTPProxy struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1523,6 +1532,8 @@ type Cluster_CustomerManagedResources_GCP struct { GkeServiceAccount *Cluster_CustomerManagedResources_GCP_ServiceAccount `protobuf:"bytes,6,opt,name=gke_service_account,proto3" json:"gke_service_account,omitempty"` // GCP storage bucket for tiered storage. TieredStorageBucket *CustomerManagedGoogleCloudStorageBucket `protobuf:"bytes,7,opt,name=tiered_storage_bucket,proto3" json:"tiered_storage_bucket,omitempty"` + // NAT subnet name if GCP Private Service Connect (a.k.a Private Link) is enabled. + PscNatSubnetName string `protobuf:"bytes,8,opt,name=psc_nat_subnet_name,json=pscNatSubnetName,proto3" json:"psc_nat_subnet_name,omitempty"` } func (x *Cluster_CustomerManagedResources_GCP) Reset() { @@ -1606,6 +1617,13 @@ func (x *Cluster_CustomerManagedResources_GCP) GetTieredStorageBucket() *Custome return nil } +func (x *Cluster_CustomerManagedResources_GCP) GetPscNatSubnetName() string { + if x != nil { + return x.PscNatSubnetName + } + return "" +} + // Subnet defines the GCP subnet where Redpanda cluster is deployed. // GCP API: https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks type Cluster_CustomerManagedResources_GCP_Subnet struct { @@ -1832,8 +1850,8 @@ type Cluster_PrivateLinkSpec_GCP struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ConsumerAcceptList []string `protobuf:"bytes,2,rep,name=consumer_accept_list,json=consumerAcceptList,proto3" json:"consumer_accept_list,omitempty"` - ConsumerRejectList []string `protobuf:"bytes,3,rep,name=consumer_reject_list,json=consumerRejectList,proto3" json:"consumer_reject_list,omitempty"` + // consumer_accept_list is the list of consumers that are allowed to establish a connection through PSC. + ConsumerAcceptList []*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer `protobuf:"bytes,4,rep,name=consumer_accept_list,json=consumerAcceptList,proto3" json:"consumer_accept_list,omitempty"` } func (x *Cluster_PrivateLinkSpec_GCP) Reset() { @@ -1868,20 +1886,13 @@ func (*Cluster_PrivateLinkSpec_GCP) Descriptor() ([]byte, []int) { return file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDescGZIP(), []int{4, 6, 1} } -func (x *Cluster_PrivateLinkSpec_GCP) GetConsumerAcceptList() []string { +func (x *Cluster_PrivateLinkSpec_GCP) GetConsumerAcceptList() []*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer { if x != nil { return x.ConsumerAcceptList } return nil } -func (x *Cluster_PrivateLinkSpec_GCP) GetConsumerRejectList() []string { - if x != nil { - return x.ConsumerRejectList - } - return nil -} - // PrivateLinkStatus defines the status of Redpanda Private Link Service. type Cluster_PrivateLinkSpec_PrivateLinkStatus struct { state protoimpl.MessageState @@ -1966,6 +1977,54 @@ func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_Aws) isCluster_PrivateLinkSpec_ func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_Gcp) isCluster_PrivateLinkSpec_PrivateLinkStatus_CloudProvider() { } +// PrivateServiceConnectConsumer contains the info for a PSC consumer. +type Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` +} + +func (x *Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) Reset() { + *x = Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) ProtoMessage() {} + +func (x *Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer.ProtoReflect.Descriptor instead. +func (*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) Descriptor() ([]byte, []int) { + return file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDescGZIP(), []int{4, 6, 1, 0} +} + +func (x *Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + type Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1995,7 +2054,7 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS struct { func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS) Reset() { *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[29] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2008,7 +2067,7 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS) String() string { func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS) ProtoMessage() {} func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[29] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2112,8 +2171,6 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP struct { CreatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // Timestamp when Redpanda GCP PSC was deleted. DeletedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` - // The list of VPC endpoints established with GCP PSC. - EndpointConnections []*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection `protobuf:"bytes,4,rep,name=endpoint_connections,json=endpointConnections,proto3" json:"endpoint_connections,omitempty"` // The port of Kafka API seed service. KafkaApiSeedPort int32 `protobuf:"varint,5,opt,name=kafka_api_seed_port,json=kafkaApiSeedPort,proto3" json:"kafka_api_seed_port,omitempty"` // The port of Schema Registry seed service. @@ -2124,12 +2181,14 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP struct { KafkaApiNodeBasePort int32 `protobuf:"varint,8,opt,name=kafka_api_node_base_port,json=kafkaApiNodeBasePort,proto3" json:"kafka_api_node_base_port,omitempty"` // The base port of Redpanda Proxy node service. The port for node i (0 .. node_count-1) is redpanda_proxy_node_base_port + i. RedpandaProxyNodeBasePort int32 `protobuf:"varint,9,opt,name=redpanda_proxy_node_base_port,json=redpandaProxyNodeBasePort,proto3" json:"redpanda_proxy_node_base_port,omitempty"` + // The list of VPC endpoints established with GCP PSC. + ConnectedEndpoints []*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint `protobuf:"bytes,10,rep,name=connected_endpoints,json=connectedEndpoints,proto3" json:"connected_endpoints,omitempty"` } func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) Reset() { *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[30] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2142,7 +2201,7 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) String() string { func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) ProtoMessage() {} func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[30] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2179,13 +2238,6 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) GetDeletedAt() *timestam return nil } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) GetEndpointConnections() []*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection { - if x != nil { - return x.EndpointConnections - } - return nil -} - func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) GetKafkaApiSeedPort() int32 { if x != nil { return x.KafkaApiSeedPort @@ -2221,6 +2273,13 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) GetRedpandaProxyNodeBase return 0 } +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP) GetConnectedEndpoints() []*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint { + if x != nil { + return x.ConnectedEndpoints + } + return nil +} + type Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2243,7 +2302,7 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection struct func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection) Reset() { *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[31] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2256,7 +2315,7 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection) St func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection) ProtoMessage() {} func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[31] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2334,7 +2393,7 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntr func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry) Reset() { *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[32] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2347,7 +2406,7 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNS func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry) ProtoMessage() {} func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[32] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2377,7 +2436,8 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNS return "" } -type Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection struct { +// ConnectedEndpoint defines endpoint connection connected to Redpanda GCP PSC (Private Service Connect) service. +type Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -2390,27 +2450,27 @@ type Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection struct { // The endpoint of connection. // e.g. https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west2/forwardingRules/vpc-consumer-psc Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - // The state of the endpoint (ACCEPTED | REJECTED). - State string `protobuf:"bytes,4,opt,name=state,proto3" json:"state,omitempty"` + // status is the status of the endpoint (ACCEPTED | REJECTED). + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) Reset() { - *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection{} +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) Reset() { + *x = Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[33] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) String() string { +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) ProtoMessage() {} +func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) ProtoMessage() {} -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[33] +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2421,35 +2481,35 @@ func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) Proto return mi.MessageOf(x) } -// Deprecated: Use Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection.ProtoReflect.Descriptor instead. -func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) Descriptor() ([]byte, []int) { +// Deprecated: Use Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint.ProtoReflect.Descriptor instead. +func (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) Descriptor() ([]byte, []int) { return file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDescGZIP(), []int{4, 6, 2, 1, 0} } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) GetConnectionId() string { +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) GetConnectionId() string { if x != nil { return x.ConnectionId } return "" } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) GetConsumerNetwork() string { +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) GetConsumerNetwork() string { if x != nil { return x.ConsumerNetwork } return "" } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) GetEndpoint() string { +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) GetEndpoint() string { if x != nil { return x.Endpoint } return "" } -func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection) GetState() string { +func (x *Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint) GetStatus() string { if x != nil { - return x.State + return x.Status } return "" } @@ -2463,12 +2523,13 @@ type ListClustersRequest_Filter struct { Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Region string `protobuf:"bytes,3,opt,name=region,proto3" json:"region,omitempty"` CloudProvider CloudProvider `protobuf:"varint,4,opt,name=cloud_provider,json=cloudProvider,proto3,enum=redpanda.api.controlplane.v1beta1.CloudProvider" json:"cloud_provider,omitempty"` + NetworkId string `protobuf:"bytes,5,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` } func (x *ListClustersRequest_Filter) Reset() { *x = ListClustersRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[34] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2481,7 +2542,7 @@ func (x *ListClustersRequest_Filter) String() string { func (*ListClustersRequest_Filter) ProtoMessage() {} func (x *ListClustersRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[34] + mi := &file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2525,6 +2586,13 @@ func (x *ListClustersRequest_Filter) GetCloudProvider() CloudProvider { return CloudProvider_CLOUD_PROVIDER_UNSPECIFIED } +func (x *ListClustersRequest_Filter) GetNetworkId() string { + if x != nil { + return x.NetworkId + } + return "" +} + var File_redpanda_api_controlplane_v1beta1_cluster_proto protoreflect.FileDescriptor var file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDesc = []byte{ @@ -2596,7 +2664,7 @@ var file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDesc = []byte{ 0x6e, 0x4d, 0x49, 0x49, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x5c, 0x6e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x52, 0x13, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x6d, 0x22, 0xea, 0x57, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x6d, 0x22, 0xf8, 0x58, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x20, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0xba, 0x48, 0x17, 0xd0, 0x01, 0x01, 0x72, 0x12, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x76, 0x30, 0x2d, 0x39, 0x5d, @@ -2680,914 +2748,926 @@ var file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDesc = []byte{ 0x29, 0x2e, 0x4a, 0x18, 0x22, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x22, 0xe0, 0x41, 0x05, 0xba, 0x48, 0x08, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, 0x01, 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x70, 0x0a, 0x0a, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x50, 0x92, 0x41, 0x36, 0x32, 0x1c, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x4a, 0x16, 0x22, 0x63, 0x6a, 0x63, 0x75, 0x71, 0x37, 0x39, 0x63, 0x34, 0x76, 0x73, - 0x39, 0x34, 0x66, 0x63, 0x75, 0x66, 0x63, 0x32, 0x67, 0x22, 0xba, 0x48, 0x14, 0x72, 0x12, 0x32, - 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x76, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x32, 0x30, 0x7d, 0x98, 0x01, - 0x14, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x12, 0xa9, 0x01, - 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x42, 0x4f, 0x92, 0x41, 0x4c, 0x2a, 0x07, 0x64, - 0x65, 0x66, 0x66, 0x66, 0x66, 0x66, 0x32, 0x2b, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x4a, 0x14, 0x22, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, - 0x49, 0x44, 0x45, 0x52, 0x5f, 0x47, 0x43, 0x50, 0x22, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0xaf, 0x03, 0x0a, 0x06, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, 0x96, 0x03, 0x92, 0x41, 0x8f, - 0x03, 0x32, 0xfd, 0x02, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x3c, 0x62, 0x72, 0x3e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x3a, 0x3c, 0x62, 0x72, 0x3e, 0x47, 0x43, 0x50, 0x3a, 0x3c, 0x62, 0x72, - 0x3e, 0x2d, 0x20, 0x61, 0x73, 0x69, 0x61, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x31, 0x3c, 0x62, - 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x73, 0x69, 0x61, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, - 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, - 0x69, 0x61, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, - 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x31, 0x3c, - 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2d, 0x77, 0x65, 0x73, 0x74, - 0x32, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x61, 0x6d, 0x65, 0x72, - 0x69, 0x63, 0x61, 0x2d, 0x6e, 0x6f, 0x72, 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, - 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x31, 0x3c, - 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, - 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x41, 0x57, 0x53, 0x3a, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, - 0x66, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, - 0x70, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, - 0x3e, 0x2d, 0x20, 0x61, 0x70, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x2d, - 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x63, 0x61, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, - 0x6c, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x2d, 0x63, 0x65, 0x6e, 0x74, - 0x72, 0x61, 0x6c, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x2d, 0x77, 0x65, - 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, - 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, 0x74, - 0x2d, 0x32, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x2d, - 0x32, 0x4a, 0x0d, 0x22, 0x75, 0x73, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x31, 0x22, - 0xe0, 0x41, 0x05, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x7a, - 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, - 0x73, 0x12, 0x59, 0x0a, 0x09, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x2e, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x50, 0x49, 0x42, 0x06, 0xe0, 0x41, 0x05, 0xe0, 0x41, - 0x03, 0x52, 0x09, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x12, 0x5c, 0x0a, 0x0a, - 0x68, 0x74, 0x74, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, - 0x50, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x06, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0x52, 0x0a, - 0x68, 0x74, 0x74, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x66, 0x0a, 0x10, 0x72, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x12, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, - 0x52, 0x10, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, - 0x6c, 0x65, 0x12, 0x63, 0x0a, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, - 0x74, 0x68, 0x65, 0x75, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x65, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x0a, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x66, 0x92, 0x41, 0x4c, 0x32, 0x32, 0x49, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x62, 0x65, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x4a, 0x16, 0x22, 0x63, 0x6a, 0x63, 0x75, + 0x71, 0x37, 0x39, 0x63, 0x34, 0x76, 0x73, 0x39, 0x34, 0x66, 0x63, 0x75, 0x66, 0x63, 0x32, 0x67, + 0x22, 0xba, 0x48, 0x14, 0x72, 0x12, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x76, 0x30, 0x2d, 0x39, + 0x5d, 0x7b, 0x32, 0x30, 0x7d, 0x98, 0x01, 0x14, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x5f, 0x69, 0x64, 0x12, 0xa9, 0x01, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x42, + 0x4f, 0x92, 0x41, 0x4c, 0x2a, 0x07, 0x64, 0x65, 0x66, 0x66, 0x66, 0x66, 0x66, 0x32, 0x2b, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x20, 0x77, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, + 0x69, 0x73, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x4a, 0x14, 0x22, 0x43, 0x4c, 0x4f, + 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x47, 0x43, 0x50, 0x22, + 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x12, 0xaf, 0x03, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x96, 0x03, 0x92, 0x41, 0x8f, 0x03, 0x32, 0xfd, 0x02, 0x52, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x3c, 0x62, 0x72, 0x3e, 0x41, 0x76, 0x61, 0x69, 0x6c, + 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x3a, 0x3c, 0x62, 0x72, 0x3e, + 0x47, 0x43, 0x50, 0x3a, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x73, 0x69, 0x61, 0x2d, 0x73, + 0x6f, 0x75, 0x74, 0x68, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x73, 0x69, 0x61, 0x2d, + 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, + 0x61, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, + 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2d, 0x77, 0x65, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x65, 0x75, 0x72, 0x6f, + 0x70, 0x65, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x32, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x6e, 0x6f, + 0x72, 0x74, 0x68, 0x61, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2d, 0x6e, 0x6f, 0x72, 0x74, 0x68, + 0x65, 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x63, 0x65, + 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, 0x73, 0x2d, 0x65, + 0x61, 0x73, 0x74, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x41, 0x57, 0x53, 0x3a, + 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x66, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x2d, 0x31, + 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x70, 0x2d, 0x73, 0x6f, 0x75, 0x74, 0x68, 0x65, 0x61, + 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x61, 0x70, 0x2d, 0x73, 0x6f, 0x75, + 0x74, 0x68, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x63, 0x61, + 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, + 0x65, 0x75, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, + 0x2d, 0x20, 0x65, 0x75, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, + 0x20, 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x31, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, + 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x32, 0x3c, 0x62, 0x72, 0x3e, 0x2d, 0x20, 0x75, + 0x73, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x2d, 0x32, 0x4a, 0x0d, 0x22, 0x75, 0x73, 0x2d, 0x63, 0x65, + 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x31, 0x22, 0xe0, 0x41, 0x05, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x09, 0x6b, 0x61, 0x66, 0x6b, + 0x61, 0x5f, 0x61, 0x70, 0x69, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, - 0x75, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x12, 0x83, - 0x01, 0x0a, 0x1a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x50, 0x49, + 0x52, 0x09, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x12, 0x5c, 0x0a, 0x0a, 0x68, + 0x74, 0x74, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, 0x50, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x06, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0x52, 0x0a, 0x68, + 0x74, 0x74, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x66, 0x0a, 0x10, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x1a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x12, 0x5d, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, - 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x65, 0x64, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x52, + 0x10, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x12, 0x63, 0x0a, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, - 0x69, 0x6e, 0x6b, 0x12, 0x5d, 0x0a, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x5f, 0x61, 0x70, 0x69, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x55, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, + 0x68, 0x65, 0x75, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x41, 0x50, 0x49, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x61, - 0x70, 0x69, 0x12, 0x62, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, - 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x42, 0x08, 0xba, 0x48, 0x05, 0x9a, 0x01, 0x02, 0x10, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x41, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, + 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x12, 0x83, 0x01, + 0x0a, 0x1a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x15, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x1a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, + 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x12, 0x5d, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6c, + 0x69, 0x6e, 0x6b, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, + 0x6b, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, + 0x6e, 0x6b, 0x12, 0x5d, 0x0a, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, + 0x61, 0x70, 0x69, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x41, + 0x50, 0x49, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x61, 0x70, + 0x69, 0x12, 0x62, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, + 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x9a, 0x01, 0x02, 0x10, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x54, 0x61, 0x67, 0x73, 0x1a, 0xaf, 0x01, 0x0a, 0x08, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x50, 0x49, 0x12, 0x2a, 0x0a, 0x0c, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x06, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, - 0x52, 0x0c, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x73, 0x3a, 0x38, - 0x92, 0x41, 0x35, 0x0a, 0x33, 0x32, 0x2f, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, - 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, - 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x27, 0x73, 0x20, 0x4b, 0x61, 0x66, 0x6b, - 0x61, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x40, 0x01, 0x1a, 0x8d, 0x02, 0x0a, 0x09, 0x48, 0x54, 0x54, - 0x50, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4d, 0x54, 0x4c, 0x53, 0x53, 0x70, 0x65, - 0x63, 0x52, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x7b, 0x92, 0x41, 0x72, 0x32, 0x1c, 0x55, 0x52, 0x4c, 0x20, - 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x27, 0x73, 0x20, 0x48, 0x54, - 0x54, 0x50, 0x20, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4a, 0x52, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, 0x61, - 0x30, 0x30, 0x30, 0x30, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, 0x31, 0x63, 0x34, - 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, 0x6d, 0x63, 0x2e, - 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x3a, 0x39, 0x30, 0x39, 0x32, 0x22, 0xe0, 0x41, 0x05, 0xe0, - 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x2f, 0x92, 0x41, 0x2c, 0x0a, 0x2a, 0x32, 0x28, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x54, - 0x54, 0x50, 0x20, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x1a, 0xe0, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x12, 0x8a, 0x01, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x78, 0x92, 0x41, 0x6f, 0x32, - 0x1b, 0x55, 0x52, 0x4c, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x41, 0x50, 0x49, 0x40, 0x01, 0x4a, 0x4e, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, - 0x2d, 0x61, 0x61, 0x30, 0x30, 0x30, 0x30, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, - 0x31, 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, - 0x6d, 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x22, 0xe0, 0x41, - 0x05, 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x40, 0x92, 0x41, 0x3d, 0x0a, 0x3b, - 0x32, 0x37, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, - 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x40, 0x01, 0x1a, 0xaa, 0x01, 0x0a, 0x0e, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x3f, - 0x0a, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, + 0x52, 0x0c, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x3f, + 0x0a, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4d, 0x54, 0x4c, 0x53, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x12, - 0x18, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xe0, 0x41, - 0x05, 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x3d, 0x92, 0x41, 0x3a, 0x0a, 0x38, - 0x32, 0x36, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x20, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x1a, 0xf9, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, - 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x12, 0xa6, 0x01, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x93, 0x01, 0x92, 0x41, 0x89, 0x01, 0x32, 0x15, 0x55, 0x52, - 0x4c, 0x20, 0x6f, 0x66, 0x20, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x20, - 0x41, 0x50, 0x49, 0x40, 0x01, 0x4a, 0x6e, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2d, 0x61, 0x61, 0x30, 0x30, 0x30, 0x30, 0x6c, 0x30, - 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, 0x31, 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, - 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, 0x6d, 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, - 0x74, 0x68, 0x65, 0x75, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x22, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, - 0x3a, 0x42, 0x92, 0x41, 0x3f, 0x0a, 0x3d, 0x32, 0x39, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, - 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, - 0x73, 0x20, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x40, 0x01, 0x1a, 0x92, 0x12, 0x0a, 0x18, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, - 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x12, 0x5b, 0x0a, 0x03, 0x67, 0x63, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x47, + 0x2e, 0x4d, 0x54, 0x4c, 0x53, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x3a, + 0x36, 0x92, 0x41, 0x33, 0x0a, 0x31, 0x32, 0x2f, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, + 0x74, 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x27, 0x73, 0x20, 0x4b, 0x61, 0x66, + 0x6b, 0x61, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x1a, 0x8d, 0x02, 0x0a, 0x09, 0x48, 0x54, 0x54, 0x50, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x3f, 0x0a, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4d, 0x54, 0x4c, 0x53, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x7b, 0x92, 0x41, 0x72, 0x32, 0x1c, 0x55, 0x52, 0x4c, 0x20, 0x6f, + 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x27, 0x73, 0x20, 0x48, 0x54, 0x54, + 0x50, 0x20, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4a, 0x52, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x2f, 0x2f, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, 0x61, 0x30, + 0x30, 0x30, 0x30, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, 0x31, 0x63, 0x34, 0x76, + 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, 0x6d, 0x63, 0x2e, 0x70, + 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x3a, 0x39, 0x30, 0x39, 0x32, 0x22, 0xe0, 0x41, 0x05, 0xe0, 0x41, + 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x2f, 0x92, 0x41, 0x2c, 0x0a, 0x2a, 0x32, 0x28, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x54, 0x54, + 0x50, 0x20, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x1a, 0xe0, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x78, 0x92, 0x41, 0x6f, 0x32, 0x1b, + 0x55, 0x52, 0x4c, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, + 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x41, 0x50, 0x49, 0x40, 0x01, 0x4a, 0x4e, 0x22, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2d, + 0x61, 0x61, 0x30, 0x30, 0x30, 0x30, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, 0x31, + 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, 0x6d, + 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x22, 0xe0, 0x41, 0x05, + 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x40, 0x92, 0x41, 0x3d, 0x0a, 0x3b, 0x32, + 0x37, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x40, 0x01, 0x1a, 0xaa, 0x01, 0x0a, 0x0e, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, + 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x4d, 0x54, 0x4c, 0x53, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x12, 0x18, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xe0, 0x41, 0x05, + 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x3d, 0x92, 0x41, 0x3a, 0x0a, 0x38, 0x32, + 0x36, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x20, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x1a, 0xf9, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x6d, + 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x12, 0xa6, 0x01, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x93, 0x01, 0x92, 0x41, 0x89, 0x01, 0x32, 0x15, 0x55, 0x52, 0x4c, + 0x20, 0x6f, 0x66, 0x20, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x20, 0x41, + 0x50, 0x49, 0x40, 0x01, 0x4a, 0x6e, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2d, 0x61, 0x61, 0x30, 0x30, 0x30, 0x30, 0x6c, 0x30, 0x2e, + 0x63, 0x6a, 0x62, 0x36, 0x39, 0x68, 0x31, 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, + 0x38, 0x39, 0x73, 0x30, 0x2e, 0x66, 0x6d, 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, + 0x68, 0x65, 0x75, 0x73, 0x2f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x22, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, + 0x42, 0x92, 0x41, 0x3f, 0x0a, 0x3d, 0x32, 0x39, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, + 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, + 0x20, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x40, 0x01, 0x1a, 0xc1, 0x12, 0x0a, 0x18, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x12, 0x5b, 0x0a, 0x03, 0x67, 0x63, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x47, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, 0x52, 0x03, 0x67, 0x63, 0x70, 0x1a, 0xae, 0x11, + 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, 0x6e, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x73, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0x94, 0x01, 0x0a, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, + 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x98, 0x01, 0x0a, + 0x17, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, 0x52, 0x03, 0x67, 0x63, 0x70, 0x1a, 0xff, - 0x10, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, 0x6e, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, - 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, - 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0x94, 0x01, 0x0a, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, - 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x98, 0x01, - 0x0a, 0x17, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, - 0x17, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x9c, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x17, + 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x9c, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, - 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x19, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xaa, 0x01, 0x0a, 0x20, 0x72, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, - 0x01, 0x01, 0x52, 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x90, 0x01, 0x0a, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, - 0x01, 0x01, 0x52, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x88, 0x01, 0x0a, 0x15, 0x74, 0x69, 0x65, 0x72, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0xaa, 0x01, 0x0a, 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, + 0x01, 0x52, 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x90, 0x01, 0x0a, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x56, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, + 0x01, 0x52, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x88, 0x01, 0x0a, 0x15, 0x74, 0x69, 0x65, 0x72, 0x65, + 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x75, 0x63, 0x6b, + 0x65, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x15, 0x74, 0x69, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, - 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x15, 0x74, 0x69, 0x65, - 0x72, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x1a, 0xc5, 0x05, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0x3f, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xe0, 0x41, 0x05, - 0xba, 0x48, 0x25, 0xc8, 0x01, 0x01, 0x72, 0x20, 0x18, 0x3e, 0x32, 0x1c, 0x5e, 0x5b, 0x61, 0x2d, - 0x7a, 0x5d, 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, - 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0xa7, - 0x01, 0x0a, 0x19, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, - 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x61, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x75, 0x62, 0x6e, - 0x65, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x49, 0x50, 0x76, 0x34, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x19, 0x73, - 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, - 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x64, 0x73, 0x12, 0xaf, 0x01, 0x0a, 0x1d, 0x73, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, - 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x70, 0x73, 0x63, 0x5f, 0x6e, 0x61, 0x74, 0x5f, 0x73, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x70, 0x73, 0x63, 0x4e, 0x61, 0x74, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x1a, 0xc5, 0x05, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0x3f, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xe0, 0x41, 0x05, 0xba, 0x48, + 0x25, 0xc8, 0x01, 0x01, 0x72, 0x20, 0x18, 0x3e, 0x32, 0x1c, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x5d, + 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, + 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0xa7, 0x01, 0x0a, + 0x19, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x61, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x49, 0x50, 0x76, 0x34, 0x52, 0x61, - 0x6e, 0x67, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x1d, 0x73, 0x65, 0x63, + 0x6e, 0x67, 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x19, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, - 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x6b, 0x38, + 0x65, 0x5f, 0x70, 0x6f, 0x64, 0x73, 0x12, 0xaf, 0x01, 0x0a, 0x1d, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x61, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2e, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x49, 0x50, 0x76, 0x34, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x1d, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x6b, 0x38, 0x73, 0x5f, + 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x05, 0x52, 0x15, 0x6b, 0x38, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x05, 0x52, 0x15, - 0x6b, 0x38, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, - 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x55, 0x0a, 0x12, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, - 0x72, 0x79, 0x49, 0x50, 0x76, 0x34, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xe0, 0x41, 0x05, 0xba, 0x48, - 0x25, 0xc8, 0x01, 0x01, 0x72, 0x20, 0x18, 0x3e, 0x32, 0x1c, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x5d, - 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, - 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x8b, 0x01, 0x92, - 0x41, 0x87, 0x01, 0x0a, 0x84, 0x01, 0x2a, 0x06, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x32, 0x1f, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x69, 0x6e, 0x67, 0x20, 0x47, 0x43, 0x50, 0x20, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2e, 0xd2, - 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x19, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, - 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x6f, - 0x64, 0x73, 0xd2, 0x01, 0x1d, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, - 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0xd2, 0x01, 0x15, 0x6b, 0x38, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, - 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x86, 0x01, 0x0a, 0x0e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, - 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, - 0x05, 0xba, 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, - 0x69, 0x6c, 0x3a, 0x4f, 0x92, 0x41, 0x4c, 0x0a, 0x4a, 0x2a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x2f, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x47, 0x43, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0xd2, 0x01, 0x05, 0x65, 0x6d, - 0x61, 0x69, 0x6c, 0x3a, 0x9a, 0x02, 0x92, 0x41, 0x96, 0x02, 0x0a, 0x93, 0x02, 0x2a, 0x1d, 0x47, - 0x43, 0x50, 0x20, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x64, 0x20, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x65, 0x73, 0x32, 0x4a, 0x47, 0x43, - 0x50, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, - 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, - 0x6c, 0x6f, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, - 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0xd2, 0x01, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, - 0x74, 0xd2, 0x01, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x17, 0x63, 0x6f, 0x6e, 0x73, - 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, - 0x01, 0x20, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0xd2, 0x01, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x15, 0x74, 0x69, 0x65, 0x72, 0x65, - 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, - 0x42, 0x17, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x1a, 0x8e, 0x20, 0x0a, 0x0f, 0x50, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x03, 0x61, 0x77, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x6e, 0x67, 0x65, 0x1a, 0x55, 0x0a, 0x12, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x49, 0x50, 0x76, 0x34, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xe0, 0x41, 0x05, 0xba, 0x48, 0x25, 0xc8, + 0x01, 0x01, 0x72, 0x20, 0x18, 0x3e, 0x32, 0x1c, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x5d, 0x28, 0x5b, + 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, + 0x5d, 0x29, 0x3f, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x8b, 0x01, 0x92, 0x41, 0x87, + 0x01, 0x0a, 0x84, 0x01, 0x2a, 0x06, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x32, 0x1f, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x69, 0x6e, + 0x67, 0x20, 0x47, 0x43, 0x50, 0x20, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2e, 0xd2, 0x01, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x19, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x6f, 0x64, 0x73, + 0xd2, 0x01, 0x1d, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x76, + 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0xd2, 0x01, 0x15, 0x6b, 0x38, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x70, + 0x76, 0x34, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x86, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xe0, 0x41, 0x05, 0xba, + 0x48, 0x07, 0xc8, 0x01, 0x01, 0x72, 0x02, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x3a, 0x4f, 0x92, 0x41, 0x4c, 0x0a, 0x4a, 0x2a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x2f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x73, 0x20, 0x47, 0x43, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x20, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0xd2, 0x01, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x3a, 0x9a, 0x02, 0x92, 0x41, 0x96, 0x02, 0x0a, 0x93, 0x02, 0x2a, 0x1d, 0x47, 0x43, 0x50, + 0x20, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x64, 0x20, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x65, 0x73, 0x32, 0x4a, 0x47, 0x43, 0x50, 0x20, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, + 0x79, 0x69, 0x6e, 0x67, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0xd2, 0x01, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0xd2, + 0x01, 0x15, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x17, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0xd2, 0x01, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x20, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0xd2, 0x01, 0x13, 0x67, 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0xd2, 0x01, 0x15, 0x74, 0x69, 0x65, 0x72, 0x65, 0x64, 0x5f, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x17, + 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x1a, 0x9e, 0x20, 0x0a, 0x0f, 0x50, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x03, 0x61, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x41, + 0x57, 0x53, 0x48, 0x00, 0x52, 0x03, 0x61, 0x77, 0x73, 0x12, 0x52, 0x0a, 0x03, 0x67, 0x63, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, + 0x65, 0x63, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, 0x52, 0x03, 0x67, 0x63, 0x70, 0x12, 0x69, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x4c, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, 0x41, 0x03, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0xc2, 0x02, 0x0a, 0x03, 0x41, 0x57, 0x53, + 0x12, 0xba, 0x02, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x69, + 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x42, 0x8a, 0x02, + 0x92, 0x41, 0x86, 0x02, 0x32, 0x92, 0x01, 0x54, 0x68, 0x65, 0x20, 0x41, 0x52, 0x4e, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x20, 0x54, 0x6f, 0x20, + 0x67, 0x72, 0x61, 0x6e, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, + 0x61, 0x6c, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x69, 0x73, 0x6b, 0x20, 0x28, 0x2a, 0x29, 0x2e, 0x4a, 0x6f, 0x22, 0x61, 0x72, 0x6e, 0x3a, + 0x61, 0x77, 0x73, 0x3a, 0x69, 0x61, 0x6d, 0x3a, 0x3a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x2d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x2d, + 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x3a, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x6e, 0x3a, 0x61, 0x77, 0x73, + 0x3a, 0x69, 0x61, 0x6d, 0x3a, 0x3a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x2d, 0x68, 0x79, 0x70, + 0x68, 0x65, 0x6e, 0x73, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x64, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x1a, 0xa3, 0x03, + 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, 0x80, 0x02, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x5c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, - 0x2e, 0x41, 0x57, 0x53, 0x48, 0x00, 0x52, 0x03, 0x61, 0x77, 0x73, 0x12, 0x52, 0x0a, 0x03, 0x67, - 0x63, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, - 0x53, 0x70, 0x65, 0x63, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, 0x52, 0x03, 0x67, 0x63, 0x70, 0x12, - 0x69, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x4c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x03, 0xe0, - 0x41, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0xc2, 0x02, 0x0a, 0x03, 0x41, - 0x57, 0x53, 0x12, 0xba, 0x02, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x70, - 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x42, - 0x8a, 0x02, 0x92, 0x41, 0x86, 0x02, 0x32, 0x92, 0x01, 0x54, 0x68, 0x65, 0x20, 0x41, 0x52, 0x4e, - 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, - 0x6c, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x61, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, - 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x20, 0x54, - 0x6f, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, - 0x69, 0x70, 0x61, 0x6c, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x73, - 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x20, 0x28, 0x2a, 0x29, 0x2e, 0x4a, 0x6f, 0x22, 0x61, 0x72, - 0x6e, 0x3a, 0x61, 0x77, 0x73, 0x3a, 0x69, 0x61, 0x6d, 0x3a, 0x3a, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x2d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x2d, 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x3a, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x75, - 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x6e, 0x3a, 0x61, - 0x77, 0x73, 0x3a, 0x69, 0x61, 0x6d, 0x3a, 0x3a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2d, - 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x2d, 0x68, - 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x52, 0x11, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x1a, - 0x9d, 0x03, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, 0xc4, 0x01, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6d, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x91, 0x01, 0x92, 0x41, 0x8d, 0x01, 0x32, 0x7a, 0x54, - 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x20, 0x61, 0x72, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x20, 0x47, 0x43, 0x50, 0x20, 0x50, 0x53, 0x43, 0x20, 0x28, 0x50, 0x72, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x29, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x61, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x0f, 0x22, 0x67, 0x63, 0x70, 0x5f, - 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x31, 0x22, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6d, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0xc8, - 0x01, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x42, 0x95, 0x01, - 0x92, 0x41, 0x91, 0x01, 0x32, 0x7e, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, - 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, + 0x2e, 0x47, 0x43, 0x50, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x72, 0x42, 0x70, 0x92, 0x41, 0x6d, 0x32, 0x6b, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x47, 0x43, 0x50, 0x20, 0x50, 0x53, 0x43, 0x20, 0x28, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x29, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x0f, 0x22, 0x67, 0x63, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x31, 0x22, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x52, - 0x65, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, - 0xd6, 0x17, 0x0a, 0x11, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x64, 0x0a, 0x03, 0x61, 0x77, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x2e, 0x41, 0x57, 0x53, 0x48, 0x00, 0x52, 0x03, 0x61, 0x77, 0x73, 0x12, 0x64, 0x0a, 0x03, 0x67, - 0x63, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, - 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, 0x52, 0x03, 0x67, 0x63, - 0x70, 0x1a, 0x9f, 0x0f, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x12, 0x77, 0x0a, 0x0a, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x58, 0x92, - 0x41, 0x55, 0x32, 0x35, 0x54, 0x68, 0x65, 0x20, 0x49, 0x44, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x1c, 0x22, 0x76, 0x70, 0x63, 0x65, - 0x2d, 0x73, 0x76, 0x63, 0x2d, 0x30, 0x35, 0x66, 0x66, 0x66, 0x32, 0x31, 0x31, 0x37, 0x64, 0x36, - 0x34, 0x38, 0x64, 0x61, 0x33, 0x35, 0x22, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x9a, 0x01, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x77, 0x92, 0x41, 0x74, 0x32, 0x37, - 0x54, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x39, 0x22, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6d, - 0x61, 0x7a, 0x6f, 0x6e, 0x61, 0x77, 0x73, 0x2e, 0x76, 0x70, 0x63, 0x65, 0x2e, 0x75, 0x73, 0x2d, - 0x77, 0x65, 0x73, 0x74, 0x2d, 0x32, 0x2e, 0x76, 0x70, 0x63, 0x65, 0x2d, 0x73, 0x76, 0x63, 0x2d, - 0x30, 0x35, 0x66, 0x66, 0x66, 0x32, 0x31, 0x31, 0x37, 0x64, 0x36, 0x34, 0x38, 0x64, 0x61, 0x33, - 0x35, 0x22, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x97, 0x01, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x72, 0x92, 0x41, 0x6f, 0x32, 0x38, 0x54, 0x68, - 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, - 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x33, 0x22, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x20, 0x7c, 0x20, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7c, 0x20, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x20, 0x7c, 0x20, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x52, 0x0c, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x92, 0x01, 0x0a, 0x1d, 0x50, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x12, 0x71, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x59, 0x92, 0x41, 0x56, 0x32, + 0x43, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x69, 0x74, 0x68, + 0x65, 0x72, 0x20, 0x62, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x43, 0x50, 0x20, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x72, 0x20, + 0x69, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x20, 0x49, 0x44, 0x2e, 0x4a, 0x0f, 0x22, 0x67, 0x63, 0x70, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x2d, 0x31, 0x22, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x04, 0x1a, 0xe0, 0x17, 0x0a, 0x11, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, + 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x64, 0x0a, 0x03, 0x61, 0x77, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, + 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x48, 0x00, 0x52, 0x03, 0x61, 0x77, 0x73, 0x12, + 0x64, 0x0a, 0x03, 0x67, 0x63, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x50, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x43, 0x50, 0x48, 0x00, + 0x52, 0x03, 0x67, 0x63, 0x70, 0x1a, 0x9f, 0x0f, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x12, 0x77, 0x0a, + 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x58, 0x92, 0x41, 0x55, 0x32, 0x35, 0x54, 0x68, 0x65, 0x20, 0x49, 0x44, 0x20, 0x6f, + 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x1c, 0x22, + 0x76, 0x70, 0x63, 0x65, 0x2d, 0x73, 0x76, 0x63, 0x2d, 0x30, 0x35, 0x66, 0x66, 0x66, 0x32, 0x31, + 0x31, 0x37, 0x64, 0x36, 0x34, 0x38, 0x64, 0x61, 0x33, 0x35, 0x22, 0x52, 0x09, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x9a, 0x01, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x77, 0x92, + 0x41, 0x74, 0x32, 0x37, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x39, 0x22, 0x63, 0x6f, + 0x6d, 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x61, 0x77, 0x73, 0x2e, 0x76, 0x70, 0x63, 0x65, + 0x2e, 0x75, 0x73, 0x2d, 0x77, 0x65, 0x73, 0x74, 0x2d, 0x32, 0x2e, 0x76, 0x70, 0x63, 0x65, 0x2d, + 0x73, 0x76, 0x63, 0x2d, 0x30, 0x35, 0x66, 0x66, 0x66, 0x32, 0x31, 0x31, 0x37, 0x64, 0x36, 0x34, + 0x38, 0x64, 0x61, 0x33, 0x35, 0x22, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x97, 0x01, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x72, 0x92, 0x41, 0x6f, + 0x32, 0x38, 0x54, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x33, 0x22, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x20, 0x7c, 0x20, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x7c, 0x20, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x52, + 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0xa0, 0x01, 0x0a, 0x18, 0x76, 0x70, 0x63, 0x5f, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x66, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, + 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x2e, 0x56, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x16, + 0x76, 0x70, 0x63, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, + 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x10, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, 0x69, 0x53, 0x65, 0x65, + 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, + 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, + 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x15, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, 0x72, 0x6f, 0x78, + 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x18, 0x6b, 0x61, 0x66, + 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6b, 0x61, 0x66, + 0x6b, 0x61, 0x41, 0x70, 0x69, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x72, + 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, + 0x6f, 0x78, 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, + 0x72, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, + 0x6f, 0x72, 0x74, 0x1a, 0xb1, 0x07, 0x0a, 0x15, 0x56, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x12, 0xc3, 0x01, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x42, 0xac, 0x01, 0x92, 0x41, 0xa8, 0x01, 0x32, 0x52, 0x54, 0x68, 0x65, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, + 0x74, 0x6f, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, + 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x52, + 0x22, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x20, 0x7c, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7c, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x7c, 0x20, + 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7c, 0x20, 0x66, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x22, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, - 0xa0, 0x01, 0x0a, 0x18, 0x76, 0x70, 0x63, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x66, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x2e, 0x41, 0x57, 0x53, 0x2e, 0x56, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x16, 0x76, 0x70, 0x63, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x5f, - 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x10, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, 0x69, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, - 0x74, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x18, - 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, - 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, - 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x65, - 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x18, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, - 0x70, 0x69, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x72, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, - 0x69, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, - 0x1d, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, - 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, 0x72, - 0x6f, 0x78, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x1a, - 0xb1, 0x07, 0x0a, 0x15, 0x56, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, - 0xc3, 0x01, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, - 0xac, 0x01, 0x92, 0x41, 0xa8, 0x01, 0x32, 0x52, 0x54, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x52, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x52, 0x22, 0x70, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x7c, - 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x7c, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7c, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x20, - 0x7c, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x7c, 0x20, 0x72, 0x65, 0x6a, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x20, 0x7c, 0x20, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0xa2, 0x01, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x7d, 0x92, 0x41, 0x7a, 0x32, 0x5a, 0x54, - 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x44, - 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x1c, 0x22, 0x76, 0x70, 0x63, 0x65, - 0x2d, 0x63, 0x6f, 0x6e, 0x2d, 0x30, 0x30, 0x62, 0x39, 0x63, 0x62, 0x61, 0x33, 0x33, 0x36, 0x30, - 0x66, 0x65, 0x34, 0x61, 0x65, 0x63, 0x22, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x5f, 0x61, 0x72, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x10, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x41, - 0x72, 0x6e, 0x73, 0x12, 0x90, 0x01, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, - 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x6f, 0x2e, 0x72, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, - 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, - 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x2e, 0x56, 0x50, 0x43, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x44, 0x4e, 0x53, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x64, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x8a, 0x02, 0x0a, 0x08, 0x44, 0x4e, 0x53, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0xd7, 0x01, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xbb, 0x01, 0x92, 0x41, 0xb7, 0x01, 0x32, 0x5a, 0x54, - 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x44, - 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x59, 0x22, 0x76, 0x70, 0x63, 0x65, - 0x2d, 0x30, 0x37, 0x35, 0x31, 0x62, 0x37, 0x61, 0x64, 0x38, 0x61, 0x35, 0x31, 0x37, 0x37, 0x37, - 0x66, 0x32, 0x2d, 0x31, 0x68, 0x70, 0x69, 0x65, 0x76, 0x66, 0x35, 0x2e, 0x76, 0x70, 0x63, 0x65, - 0x2d, 0x73, 0x76, 0x63, 0x2d, 0x30, 0x64, 0x34, 0x38, 0x39, 0x66, 0x61, 0x38, 0x39, 0x66, 0x32, - 0x34, 0x65, 0x33, 0x38, 0x30, 0x32, 0x2e, 0x75, 0x73, 0x2d, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x32, - 0x2e, 0x76, 0x70, 0x63, 0x65, 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x61, 0x77, 0x73, 0x2e, - 0x63, 0x6f, 0x6d, 0x22, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, - 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x5a, 0x6f, 0x6e, - 0x65, 0x49, 0x64, 0x1a, 0xf9, 0x05, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, 0x2d, 0x0a, 0x12, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x96, 0x01, 0x0a, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x63, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x65, 0x64, 0x41, 0x74, 0x12, 0xa2, 0x01, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x7d, 0x92, 0x41, + 0x7a, 0x32, 0x5a, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x49, 0x44, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x1c, 0x22, + 0x76, 0x70, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x2d, 0x30, 0x30, 0x62, 0x39, 0x63, 0x62, 0x61, + 0x33, 0x33, 0x36, 0x30, 0x66, 0x65, 0x34, 0x61, 0x65, 0x63, 0x22, 0x52, 0x0c, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x61, + 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x5f, 0x61, 0x72, 0x6e, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x72, 0x41, 0x72, 0x6e, 0x73, 0x12, 0x90, 0x01, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, + 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x6f, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x2e, + 0x56, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x4e, 0x53, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, + 0x64, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x8a, 0x02, 0x0a, 0x08, 0x44, + 0x4e, 0x53, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0xd7, 0x01, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xbb, 0x01, 0x92, 0x41, 0xb7, + 0x01, 0x32, 0x5a, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x49, 0x44, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x50, 0x43, 0x20, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x41, 0x57, 0x53, 0x20, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x59, 0x22, + 0x76, 0x70, 0x63, 0x65, 0x2d, 0x30, 0x37, 0x35, 0x31, 0x62, 0x37, 0x61, 0x64, 0x38, 0x61, 0x35, + 0x31, 0x37, 0x37, 0x37, 0x66, 0x32, 0x2d, 0x31, 0x68, 0x70, 0x69, 0x65, 0x76, 0x66, 0x35, 0x2e, + 0x76, 0x70, 0x63, 0x65, 0x2d, 0x73, 0x76, 0x63, 0x2d, 0x30, 0x64, 0x34, 0x38, 0x39, 0x66, 0x61, + 0x38, 0x39, 0x66, 0x32, 0x34, 0x65, 0x33, 0x38, 0x30, 0x32, 0x2e, 0x75, 0x73, 0x2d, 0x65, 0x61, + 0x73, 0x74, 0x2d, 0x32, 0x2e, 0x76, 0x70, 0x63, 0x65, 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, + 0x61, 0x77, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x22, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x65, + 0x64, 0x5a, 0x6f, 0x6e, 0x65, 0x49, 0x64, 0x1a, 0x83, 0x06, 0x0a, 0x03, 0x47, 0x43, 0x50, 0x12, + 0x2d, 0x0a, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x39, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, + 0x69, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x10, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, 0x69, 0x53, 0x65, 0x65, 0x64, 0x50, + 0x6f, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x37, + 0x0a, 0x18, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x15, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, + 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x18, 0x6b, 0x61, 0x66, 0x6b, 0x61, + 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6b, 0x61, 0x66, 0x6b, 0x61, + 0x41, 0x70, 0x69, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, + 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, 0x50, 0x6f, 0x72, + 0x74, 0x12, 0x93, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x62, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x43, - 0x50, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6b, 0x61, 0x66, - 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, 0x69, - 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, - 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, - 0x6f, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, - 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x18, - 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, - 0x61, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, - 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x41, 0x70, 0x69, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, 0x73, 0x65, - 0x50, 0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x72, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x42, 0x61, - 0x73, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x1a, 0x96, 0x01, 0x0a, 0x12, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, + 0x50, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0x9d, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x3a, - 0x3e, 0x92, 0x41, 0x3b, 0x0a, 0x39, 0x32, 0x35, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x6f, 0x66, 0x20, - 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x40, 0x01, 0x42, - 0x17, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x1a, 0xca, 0x01, 0x0a, 0x0c, 0x44, - 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x41, 0x50, 0x49, 0x12, 0x7b, 0x0a, 0x03, 0x75, - 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x60, 0x32, 0x14, 0x55, - 0x52, 0x4c, 0x20, 0x6f, 0x66, 0x20, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x20, - 0x41, 0x50, 0x49, 0x40, 0x01, 0x4a, 0x46, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x61, 0x70, 0x69, 0x2d, 0x61, 0x62, 0x31, 0x32, 0x33, 0x34, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, - 0x36, 0x39, 0x68, 0x31, 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, - 0x30, 0x2e, 0x66, 0x6d, 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x22, 0xe0, 0x41, 0x05, - 0xe0, 0x41, 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x3d, 0x92, 0x41, 0x3a, 0x0a, 0x38, 0x32, - 0x34, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, - 0x66, 0x69, 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, - 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x20, 0x41, 0x50, 0x49, 0x20, 0x65, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x40, 0x01, 0x1a, 0x3c, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x75, 0x64, - 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, - 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, - 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, - 0x41, 0x44, 0x59, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, - 0x54, 0x10, 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x50, 0x47, - 0x52, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, - 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x07, 0x22, 0x3f, 0x0a, 0x04, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x44, 0x45, 0x44, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x59, 0x4f, 0x43, 0x10, 0x02, 0x22, 0x6a, 0x0a, 0x0e, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, - 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, - 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x52, - 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x02, 0x3a, 0xa0, 0x01, 0x92, 0x41, 0x9c, 0x01, 0x0a, 0x99, - 0x01, 0x2a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x32, 0x1e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x69, 0x6e, 0x67, 0x20, - 0x61, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0xd2, 0x01, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0xd2, 0x01, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0xd2, 0x01, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0xd2, 0x01, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, - 0xd2, 0x01, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0xd2, 0x01, 0x0f, 0x74, 0x68, 0x72, 0x6f, - 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0xd2, 0x01, 0x04, 0x74, 0x79, - 0x70, 0x65, 0xd2, 0x01, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x14, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0xad, 0x01, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x42, 0x67, 0xba, 0x48, 0x64, 0xba, 0x01, 0x61, 0x0a, 0x2e, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x3d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x69, 0x64, 0x20, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x69, 0x64, 0x5f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x20, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x69, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, - 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x1a, 0x0d, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x69, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x22, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xa4, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, + 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x3a, 0x3e, 0x92, + 0x41, 0x3b, 0x0a, 0x39, 0x32, 0x35, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4c, + 0x69, 0x6e, 0x6b, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x40, 0x01, 0x42, 0x17, 0x0a, + 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, + 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x1a, 0xca, 0x01, 0x0a, 0x0c, 0x44, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x41, 0x50, 0x49, 0x12, 0x7b, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x60, 0x32, 0x14, 0x55, 0x52, 0x4c, + 0x20, 0x6f, 0x66, 0x20, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x20, 0x41, 0x50, + 0x49, 0x40, 0x01, 0x4a, 0x46, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x70, + 0x69, 0x2d, 0x61, 0x62, 0x31, 0x32, 0x33, 0x34, 0x6c, 0x30, 0x2e, 0x63, 0x6a, 0x62, 0x36, 0x39, + 0x68, 0x31, 0x63, 0x34, 0x76, 0x73, 0x34, 0x32, 0x70, 0x63, 0x61, 0x38, 0x39, 0x73, 0x30, 0x2e, + 0x66, 0x6d, 0x63, 0x2e, 0x70, 0x72, 0x64, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x22, 0xe0, 0x41, 0x05, 0xe0, 0x41, + 0x03, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x3d, 0x92, 0x41, 0x3a, 0x0a, 0x38, 0x32, 0x34, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x20, 0x41, 0x50, 0x49, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x40, 0x01, 0x1a, 0x3c, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x54, 0x61, + 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, + 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, + 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x12, + 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, + 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x44, + 0x59, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x4c, + 0x45, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, + 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x50, 0x47, 0x52, 0x41, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x07, 0x22, 0x3f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, + 0x45, 0x44, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x42, 0x59, 0x4f, 0x43, 0x10, 0x02, 0x22, 0x6a, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x56, + 0x41, 0x54, 0x45, 0x10, 0x02, 0x3a, 0xa0, 0x01, 0x92, 0x41, 0x9c, 0x01, 0x0a, 0x99, 0x01, 0x2a, + 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x32, 0x1e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xd2, + 0x01, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0xd2, 0x01, + 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0xd2, + 0x01, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0xd2, 0x01, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0xd2, 0x01, + 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0xd2, 0x01, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, + 0x68, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x69, 0x65, 0x72, 0xd2, 0x01, 0x04, 0x74, 0x79, 0x70, 0x65, + 0xd2, 0x01, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x4f, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, 0x09, - 0xba, 0x48, 0x06, 0xc0, 0x01, 0x01, 0xc8, 0x01, 0x01, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, - 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, - 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, - 0x5d, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0xbf, - 0x03, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x67, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x42, 0x49, 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, - 0x6f, 0x66, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x59, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0xc7, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x12, 0x2e, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd0, 0x01, 0x01, 0x72, - 0x03, 0xb0, 0x01, 0x01, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x61, 0x0a, - 0x0e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, - 0x01, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x22, 0xa3, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x08, 0x63, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, 0x25, 0x92, 0x41, 0x22, 0x32, 0x1d, 0x43, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0xa0, 0x01, 0x64, 0x52, 0x08, - 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x9b, 0x01, 0x0a, 0x0f, 0x6e, 0x65, 0x78, - 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x73, 0x92, 0x41, 0x70, 0x32, 0x6e, 0x50, 0x61, 0x67, 0x65, 0x20, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x6e, 0x65, 0x78, 0x74, 0x20, 0x70, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x64, 0x20, 0x61, 0x73, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x78, 0x74, - 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x17, - 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x83, 0x17, 0x0a, 0x0e, 0x43, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xfc, 0x03, 0x0a, 0x0d, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x37, 0x2e, 0x72, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x74, 0x12, 0xad, 0x01, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, + 0x67, 0xba, 0x48, 0x64, 0xba, 0x01, 0x61, 0x0a, 0x2e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x3d, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x69, 0x64, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x69, + 0x64, 0x5f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x20, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x69, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x1a, 0x0d, 0x74, 0x68, 0x69, 0x73, 0x2e, + 0x69, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x22, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xa4, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x4f, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, 0x09, 0xba, 0x48, + 0x06, 0xc0, 0x01, 0x01, 0xc8, 0x01, 0x01, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, + 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x5d, 0x0a, + 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0xfa, 0x03, 0x0a, + 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x67, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x49, + 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, + 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x82, 0x02, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, + 0x2e, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xd0, 0x01, 0x01, 0x72, 0x03, 0xb0, + 0x01, 0x01, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x61, 0x0a, 0x0e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, + 0x0d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x39, + 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1a, 0xba, 0x48, 0x17, 0xd0, 0x01, 0x01, 0x72, 0x12, 0x32, 0x0d, 0x5e, 0x5b, + 0x61, 0x2d, 0x76, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x32, 0x30, 0x7d, 0x98, 0x01, 0x14, 0x52, 0x09, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x22, 0xa3, 0x02, 0x0a, 0x14, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x42, 0x25, 0x92, 0x41, 0x22, 0x32, 0x1d, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x20, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0xa0, 0x01, 0x64, 0x52, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x12, 0x9b, 0x01, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x73, 0x92, 0x41, 0x70, + 0x32, 0x6e, 0x50, 0x61, 0x67, 0x65, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, + 0x66, 0x65, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x20, 0x70, + 0x61, 0x67, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x69, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x83, 0x17, 0x0a, 0x0e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0xfc, 0x03, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x83, 0x03, 0x92, 0x41, 0xc2, 0x02, 0x12, 0x0e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x3c, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x20, 0x61, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, - 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x2d, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, - 0x3c, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x2e, 0x1a, - 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x83, 0x03, 0x92, + 0x41, 0xc2, 0x02, 0x12, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x1a, 0x3c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x52, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, + 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x2d, 0x72, + 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, 0x3c, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x2c, 0x0a, 0x03, 0x34, 0x30, 0x30, 0x12, 0x25, 0x0a, + 0x0b, 0x42, 0x61, 0x64, 0x20, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x14, + 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x4a, 0x29, 0x0a, 0x03, 0x34, 0x30, 0x39, 0x12, 0x22, 0x0a, 0x08, 0x43, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, + 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, + 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, + 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x17, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, + 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x12, 0xe2, 0x03, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x12, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x22, 0xf1, 0x02, 0x92, 0x41, 0xb5, 0x02, 0x12, 0x0b, 0x47, 0x65, 0x74, 0x20, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x47, 0x65, 0x74, 0x20, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4a, 0x3b, + 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x34, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x2e, 0x0a, 0x2c, 0x1a, + 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x2c, 0x0a, - 0x03, 0x34, 0x30, 0x30, 0x12, 0x25, 0x0a, 0x0b, 0x42, 0x61, 0x64, 0x20, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x29, 0x0a, 0x03, 0x34, - 0x30, 0x39, 0x12, 0x22, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x16, - 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, + 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4a, 0x7c, 0x0a, 0x03, 0x34, + 0x30, 0x34, 0x12, 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0x3c, 0x7b, 0x22, 0x63, + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x3a, 0x22, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, + 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, + 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, + 0xbf, 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, + 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x8c, 0x04, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x93, 0x03, 0x92, 0x41, 0xc5, 0x02, 0x12, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, + 0x61, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, 0x3c, 0x0a, 0x08, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x7c, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, + 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, + 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0x3c, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, + 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, + 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x17, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x07, 0x63, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0xe2, 0x03, 0x0a, 0x0a, 0x47, 0x65, - 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, - 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0xf1, 0x02, 0x92, 0x41, 0xb5, - 0x02, 0x12, 0x0b, 0x47, 0x65, 0x74, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x15, - 0x47, 0x65, 0x74, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4a, 0x3b, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x34, 0x0a, 0x02, - 0x4f, 0x4b, 0x12, 0x2e, 0x0a, 0x2c, 0x1a, 0x2a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x4a, 0x7c, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, - 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, - 0x6f, 0x6e, 0x12, 0x3c, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, - 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, - 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, - 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, - 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x6f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x8c, - 0x04, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x93, 0x03, 0x92, 0x41, 0xc5, 0x02, 0x12, 0x0e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x1a, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, - 0x32, 0x12, 0x3c, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, - 0x2e, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, - 0x7c, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, - 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x10, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, - 0x3c, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, - 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, 0x4a, 0x54, 0x0a, - 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, - 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, - 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x17, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x6f, 0x72, 0x67, - 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x32, 0x1e, 0x2f, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, - 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x81, 0x03, - 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x36, - 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0xff, 0x01, 0x92, 0x41, 0xc8, 0x01, 0x12, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x52, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x4a, 0x48, - 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x41, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x3b, 0x0a, 0x39, 0x1a, + 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x07, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x32, 0x1e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x81, 0x03, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x36, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, - 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, - 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, - 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, - 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x73, 0x12, 0x9b, 0x04, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xff, 0x01, 0x92, 0x41, 0xc8, 0x01, 0x12, + 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x17, + 0x4c, 0x69, 0x73, 0x74, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x4a, 0x48, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x41, + 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x3b, 0x0a, 0x39, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, + 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, + 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, + 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, + 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x9b, 0x04, 0x0a, 0x0d, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x03, 0x92, 0x41, 0xe5, - 0x02, 0x12, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x1a, 0x3a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x52, 0x65, 0x74, 0x75, - 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x2d, 0x72, 0x75, 0x6e, 0x6e, 0x69, - 0x6e, 0x67, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x43, 0x0a, - 0x03, 0x32, 0x30, 0x32, 0x12, 0x3c, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x4a, 0x7c, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, - 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, - 0x6f, 0x6e, 0x12, 0x3c, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, - 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, - 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, - 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, - 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x17, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, - 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, - 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, - 0x88, 0x01, 0x0a, 0x13, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, - 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x03, 0x92, 0x41, 0xe5, 0x02, 0x12, 0x0e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x3a, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x2e, 0x20, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6c, + 0x6f, 0x6e, 0x67, 0x2d, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, 0x3c, 0x0a, + 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x7c, 0x0a, 0x03, 0x34, + 0x30, 0x34, 0x12, 0x75, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0x3c, 0x7b, 0x22, 0x63, + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x3a, 0x22, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, + 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, + 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, + 0xbf, 0x07, 0x17, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, + 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x88, 0x01, 0x0a, 0x13, 0x44, 0x75, 0x6d, + 0x6d, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x88, 0x01, 0x0a, 0x13, 0x44, - 0x75, 0x6d, 0x6d, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x88, 0x01, 0x0a, 0x13, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x88, 0x01, 0x0a, 0x13, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x37, 0x2e, - 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x88, + 0x01, 0x0a, 0x13, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x1a, 0x3b, 0x92, 0x41, 0x38, 0x12, 0x36, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x42, 0xcd, 0x02, - 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0c, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, - 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, - 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, - 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, - 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x92, 0x41, 0x38, 0x12, 0x36, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x42, 0xcd, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x42, 0x0c, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x6f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, + 0x02, 0x2d, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x24, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, + 0x3a, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3603,7 +3683,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDescGZIP() []byte { } var file_redpanda_api_controlplane_v1beta1_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 35) +var file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 36) var file_redpanda_api_controlplane_v1beta1_cluster_proto_goTypes = []interface{}{ (Cluster_State)(0), // 0: redpanda.api.controlplane.v1beta1.Cluster.State (Cluster_Type)(0), // 1: redpanda.api.controlplane.v1beta1.Cluster.Type @@ -3637,27 +3717,28 @@ var file_redpanda_api_controlplane_v1beta1_cluster_proto_goTypes = []interface{} (*Cluster_PrivateLinkSpec_AWS)(nil), // 29: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.AWS (*Cluster_PrivateLinkSpec_GCP)(nil), // 30: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP (*Cluster_PrivateLinkSpec_PrivateLinkStatus)(nil), // 31: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus - (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS)(nil), // 32: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS - (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP)(nil), // 33: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP - (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection)(nil), // 34: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection - (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry)(nil), // 35: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.DNSEntry - (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection)(nil), // 36: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.EndpointConnection - (*ListClustersRequest_Filter)(nil), // 37: redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter - (*timestamppb.Timestamp)(nil), // 38: google.protobuf.Timestamp - (*status.Status)(nil), // 39: google.rpc.Status - (CloudProvider)(0), // 40: redpanda.api.controlplane.v1beta1.CloudProvider - (*fieldmaskpb.FieldMask)(nil), // 41: google.protobuf.FieldMask - (*CustomerManagedGoogleCloudStorageBucket)(nil), // 42: redpanda.api.controlplane.v1beta1.CustomerManagedGoogleCloudStorageBucket - (*Operation)(nil), // 43: redpanda.api.controlplane.v1beta1.Operation + (*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer)(nil), // 32: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP.PrivateServiceConnectConsumer + (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS)(nil), // 33: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS + (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP)(nil), // 34: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP + (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection)(nil), // 35: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection + (*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry)(nil), // 36: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.DNSEntry + (*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint)(nil), // 37: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.ConnectedEndpoint + (*ListClustersRequest_Filter)(nil), // 38: redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter + (*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp + (*status.Status)(nil), // 40: google.rpc.Status + (CloudProvider)(0), // 41: redpanda.api.controlplane.v1beta1.CloudProvider + (*fieldmaskpb.FieldMask)(nil), // 42: google.protobuf.FieldMask + (*CustomerManagedGoogleCloudStorageBucket)(nil), // 43: redpanda.api.controlplane.v1beta1.CustomerManagedGoogleCloudStorageBucket + (*Operation)(nil), // 44: redpanda.api.controlplane.v1beta1.Operation } var file_redpanda_api_controlplane_v1beta1_cluster_proto_depIdxs = []int32{ - 38, // 0: redpanda.api.controlplane.v1beta1.Cluster.created_at:type_name -> google.protobuf.Timestamp - 38, // 1: redpanda.api.controlplane.v1beta1.Cluster.updated_at:type_name -> google.protobuf.Timestamp + 39, // 0: redpanda.api.controlplane.v1beta1.Cluster.created_at:type_name -> google.protobuf.Timestamp + 39, // 1: redpanda.api.controlplane.v1beta1.Cluster.updated_at:type_name -> google.protobuf.Timestamp 0, // 2: redpanda.api.controlplane.v1beta1.Cluster.state:type_name -> redpanda.api.controlplane.v1beta1.Cluster.State - 39, // 3: redpanda.api.controlplane.v1beta1.Cluster.state_description:type_name -> google.rpc.Status + 40, // 3: redpanda.api.controlplane.v1beta1.Cluster.state_description:type_name -> google.rpc.Status 1, // 4: redpanda.api.controlplane.v1beta1.Cluster.type:type_name -> redpanda.api.controlplane.v1beta1.Cluster.Type 2, // 5: redpanda.api.controlplane.v1beta1.Cluster.connection_type:type_name -> redpanda.api.controlplane.v1beta1.Cluster.ConnectionType - 40, // 6: redpanda.api.controlplane.v1beta1.Cluster.cloud_provider:type_name -> redpanda.api.controlplane.v1beta1.CloudProvider + 41, // 6: redpanda.api.controlplane.v1beta1.Cluster.cloud_provider:type_name -> redpanda.api.controlplane.v1beta1.CloudProvider 16, // 7: redpanda.api.controlplane.v1beta1.Cluster.kafka_api:type_name -> redpanda.api.controlplane.v1beta1.Cluster.KafkaAPI 17, // 8: redpanda.api.controlplane.v1beta1.Cluster.http_proxy:type_name -> redpanda.api.controlplane.v1beta1.Cluster.HTTPProxy 18, // 9: redpanda.api.controlplane.v1beta1.Cluster.redpanda_console:type_name -> redpanda.api.controlplane.v1beta1.Cluster.RedpandaConsole @@ -3669,57 +3750,59 @@ var file_redpanda_api_controlplane_v1beta1_cluster_proto_depIdxs = []int32{ 24, // 15: redpanda.api.controlplane.v1beta1.Cluster.cloud_tags:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CloudTagsEntry 7, // 16: redpanda.api.controlplane.v1beta1.CreateClusterRequest.cluster:type_name -> redpanda.api.controlplane.v1beta1.Cluster 7, // 17: redpanda.api.controlplane.v1beta1.UpdateClusterRequest.cluster:type_name -> redpanda.api.controlplane.v1beta1.Cluster - 41, // 18: redpanda.api.controlplane.v1beta1.UpdateClusterRequest.update_mask:type_name -> google.protobuf.FieldMask + 42, // 18: redpanda.api.controlplane.v1beta1.UpdateClusterRequest.update_mask:type_name -> google.protobuf.FieldMask 7, // 19: redpanda.api.controlplane.v1beta1.UpdateClusterResponse.cluster:type_name -> redpanda.api.controlplane.v1beta1.Cluster - 37, // 20: redpanda.api.controlplane.v1beta1.ListClustersRequest.filter:type_name -> redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter + 38, // 20: redpanda.api.controlplane.v1beta1.ListClustersRequest.filter:type_name -> redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter 7, // 21: redpanda.api.controlplane.v1beta1.ListClustersResponse.clusters:type_name -> redpanda.api.controlplane.v1beta1.Cluster - 6, // 22: redpanda.api.controlplane.v1beta1.Cluster.HTTPProxy.mtls:type_name -> redpanda.api.controlplane.v1beta1.MTLSSpec - 6, // 23: redpanda.api.controlplane.v1beta1.Cluster.SchemaRegistry.mtls:type_name -> redpanda.api.controlplane.v1beta1.MTLSSpec - 25, // 24: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP - 29, // 25: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.aws:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.AWS - 30, // 26: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP - 31, // 27: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.status:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus - 26, // 28: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.subnet:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet - 27, // 29: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.agent_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount - 27, // 30: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.console_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount - 27, // 31: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.connector_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount - 27, // 32: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.redpanda_cluster_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount - 27, // 33: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.gke_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount - 42, // 34: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.tiered_storage_bucket:type_name -> redpanda.api.controlplane.v1beta1.CustomerManagedGoogleCloudStorageBucket - 28, // 35: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.secondary_ipv4_range_pods:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.SecondaryIPv4Range - 28, // 36: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.secondary_ipv4_range_services:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.SecondaryIPv4Range - 32, // 37: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.aws:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS - 33, // 38: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP - 38, // 39: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.created_at:type_name -> google.protobuf.Timestamp - 38, // 40: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.deleted_at:type_name -> google.protobuf.Timestamp - 34, // 41: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.vpc_endpoint_connections:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection - 38, // 42: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.created_at:type_name -> google.protobuf.Timestamp - 38, // 43: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.deleted_at:type_name -> google.protobuf.Timestamp - 36, // 44: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.endpoint_connections:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.EndpointConnection - 38, // 45: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.created_at:type_name -> google.protobuf.Timestamp - 35, // 46: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.dns_entries:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.DNSEntry - 40, // 47: redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter.cloud_provider:type_name -> redpanda.api.controlplane.v1beta1.CloudProvider - 8, // 48: redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster:input_type -> redpanda.api.controlplane.v1beta1.CreateClusterRequest - 9, // 49: redpanda.api.controlplane.v1beta1.ClusterService.GetCluster:input_type -> redpanda.api.controlplane.v1beta1.GetClusterRequest - 10, // 50: redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster:input_type -> redpanda.api.controlplane.v1beta1.UpdateClusterRequest - 12, // 51: redpanda.api.controlplane.v1beta1.ClusterService.ListClusters:input_type -> redpanda.api.controlplane.v1beta1.ListClustersRequest - 14, // 52: redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster:input_type -> redpanda.api.controlplane.v1beta1.DeleteClusterRequest - 8, // 53: redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata:input_type -> redpanda.api.controlplane.v1beta1.CreateClusterRequest - 10, // 54: redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata:input_type -> redpanda.api.controlplane.v1beta1.UpdateClusterRequest - 14, // 55: redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata:input_type -> redpanda.api.controlplane.v1beta1.DeleteClusterRequest - 43, // 56: redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation - 7, // 57: redpanda.api.controlplane.v1beta1.ClusterService.GetCluster:output_type -> redpanda.api.controlplane.v1beta1.Cluster - 43, // 58: redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation - 13, // 59: redpanda.api.controlplane.v1beta1.ClusterService.ListClusters:output_type -> redpanda.api.controlplane.v1beta1.ListClustersResponse - 43, // 60: redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation - 3, // 61: redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata:output_type -> redpanda.api.controlplane.v1beta1.CreateClusterMetadata - 5, // 62: redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata:output_type -> redpanda.api.controlplane.v1beta1.UpdateClusterMetadata - 4, // 63: redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata:output_type -> redpanda.api.controlplane.v1beta1.DeleteClusterMetadata - 56, // [56:64] is the sub-list for method output_type - 48, // [48:56] is the sub-list for method input_type - 48, // [48:48] is the sub-list for extension type_name - 48, // [48:48] is the sub-list for extension extendee - 0, // [0:48] is the sub-list for field type_name + 6, // 22: redpanda.api.controlplane.v1beta1.Cluster.KafkaAPI.mtls:type_name -> redpanda.api.controlplane.v1beta1.MTLSSpec + 6, // 23: redpanda.api.controlplane.v1beta1.Cluster.HTTPProxy.mtls:type_name -> redpanda.api.controlplane.v1beta1.MTLSSpec + 6, // 24: redpanda.api.controlplane.v1beta1.Cluster.SchemaRegistry.mtls:type_name -> redpanda.api.controlplane.v1beta1.MTLSSpec + 25, // 25: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP + 29, // 26: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.aws:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.AWS + 30, // 27: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP + 31, // 28: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.status:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus + 26, // 29: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.subnet:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet + 27, // 30: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.agent_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount + 27, // 31: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.console_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount + 27, // 32: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.connector_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount + 27, // 33: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.redpanda_cluster_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount + 27, // 34: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.gke_service_account:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.ServiceAccount + 43, // 35: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.tiered_storage_bucket:type_name -> redpanda.api.controlplane.v1beta1.CustomerManagedGoogleCloudStorageBucket + 28, // 36: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.secondary_ipv4_range_pods:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.SecondaryIPv4Range + 28, // 37: redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.secondary_ipv4_range_services:type_name -> redpanda.api.controlplane.v1beta1.Cluster.CustomerManagedResources.GCP.Subnet.SecondaryIPv4Range + 32, // 38: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP.consumer_accept_list:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.GCP.PrivateServiceConnectConsumer + 33, // 39: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.aws:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS + 34, // 40: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.gcp:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP + 39, // 41: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.created_at:type_name -> google.protobuf.Timestamp + 39, // 42: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.deleted_at:type_name -> google.protobuf.Timestamp + 35, // 43: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.vpc_endpoint_connections:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection + 39, // 44: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.created_at:type_name -> google.protobuf.Timestamp + 39, // 45: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.deleted_at:type_name -> google.protobuf.Timestamp + 37, // 46: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.connected_endpoints:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.GCP.ConnectedEndpoint + 39, // 47: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.created_at:type_name -> google.protobuf.Timestamp + 36, // 48: redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.dns_entries:type_name -> redpanda.api.controlplane.v1beta1.Cluster.PrivateLinkSpec.PrivateLinkStatus.AWS.VPCEndpointConnection.DNSEntry + 41, // 49: redpanda.api.controlplane.v1beta1.ListClustersRequest.Filter.cloud_provider:type_name -> redpanda.api.controlplane.v1beta1.CloudProvider + 8, // 50: redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster:input_type -> redpanda.api.controlplane.v1beta1.CreateClusterRequest + 9, // 51: redpanda.api.controlplane.v1beta1.ClusterService.GetCluster:input_type -> redpanda.api.controlplane.v1beta1.GetClusterRequest + 10, // 52: redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster:input_type -> redpanda.api.controlplane.v1beta1.UpdateClusterRequest + 12, // 53: redpanda.api.controlplane.v1beta1.ClusterService.ListClusters:input_type -> redpanda.api.controlplane.v1beta1.ListClustersRequest + 14, // 54: redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster:input_type -> redpanda.api.controlplane.v1beta1.DeleteClusterRequest + 8, // 55: redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata:input_type -> redpanda.api.controlplane.v1beta1.CreateClusterRequest + 10, // 56: redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata:input_type -> redpanda.api.controlplane.v1beta1.UpdateClusterRequest + 14, // 57: redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata:input_type -> redpanda.api.controlplane.v1beta1.DeleteClusterRequest + 44, // 58: redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation + 7, // 59: redpanda.api.controlplane.v1beta1.ClusterService.GetCluster:output_type -> redpanda.api.controlplane.v1beta1.Cluster + 44, // 60: redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation + 13, // 61: redpanda.api.controlplane.v1beta1.ClusterService.ListClusters:output_type -> redpanda.api.controlplane.v1beta1.ListClustersResponse + 44, // 62: redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster:output_type -> redpanda.api.controlplane.v1beta1.Operation + 3, // 63: redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata:output_type -> redpanda.api.controlplane.v1beta1.CreateClusterMetadata + 5, // 64: redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata:output_type -> redpanda.api.controlplane.v1beta1.UpdateClusterMetadata + 4, // 65: redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata:output_type -> redpanda.api.controlplane.v1beta1.DeleteClusterMetadata + 58, // [58:66] is the sub-list for method output_type + 50, // [50:58] is the sub-list for method input_type + 50, // [50:50] is the sub-list for extension type_name + 50, // [50:50] is the sub-list for extension extendee + 0, // [0:50] is the sub-list for field type_name } func init() { file_redpanda_api_controlplane_v1beta1_cluster_proto_init() } @@ -4067,7 +4150,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS); i { + switch v := v.(*Cluster_PrivateLinkSpec_GCP_PrivateServiceConnectConsumer); i { case 0: return &v.state case 1: @@ -4079,7 +4162,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP); i { + switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS); i { case 0: return &v.state case 1: @@ -4091,7 +4174,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection); i { + switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP); i { case 0: return &v.state case 1: @@ -4103,7 +4186,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry); i { + switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection); i { case 0: return &v.state case 1: @@ -4115,7 +4198,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_EndpointConnection); i { + switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_AWS_VPCEndpointConnection_DNSEntry); i { case 0: return &v.state case 1: @@ -4127,6 +4210,18 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { } } file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Cluster_PrivateLinkSpec_PrivateLinkStatus_GCP_ConnectedEndpoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_controlplane_v1beta1_cluster_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListClustersRequest_Filter); i { case 0: return &v.state @@ -4156,7 +4251,7 @@ func file_redpanda_api_controlplane_v1beta1_cluster_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_redpanda_api_controlplane_v1beta1_cluster_proto_rawDesc, NumEnums: 3, - NumMessages: 35, + NumMessages: 36, NumExtensions: 0, NumServices: 1, }, diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster_grpc.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster_grpc.pb.go deleted file mode 100644 index 3987a142af0a9..0000000000000 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/cluster_grpc.pb.go +++ /dev/null @@ -1,424 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: redpanda/api/controlplane/v1beta1/cluster.proto - -package controlplanev1beta1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - ClusterService_CreateCluster_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/CreateCluster" - ClusterService_GetCluster_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/GetCluster" - ClusterService_UpdateCluster_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/UpdateCluster" - ClusterService_ListClusters_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/ListClusters" - ClusterService_DeleteCluster_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/DeleteCluster" - ClusterService_DummyCreateMetadata_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyCreateMetadata" - ClusterService_DummyUpdateMetadata_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyUpdateMetadata" - ClusterService_DummyDeleteMetadata_FullMethodName = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyDeleteMetadata" -) - -// ClusterServiceClient is the client API for ClusterService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ClusterServiceClient interface { - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateCluster create a Redpanda cluster. The input contains the spec, that describes the cluster. - // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a cluster has finished. - CreateCluster(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*Operation, error) - // GetCluster retrieves the cluster's information - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - GetCluster(ctx context.Context, in *GetClusterRequest, opts ...grpc.CallOption) (*Cluster, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // UpdateCluster updates the cluster. It returns a Operation that can be used to wait for the Update to be applied. - UpdateCluster(ctx context.Context, in *UpdateClusterRequest, opts ...grpc.CallOption) (*Operation, error) - // ListClusters lists clusters. - ListClusters(ctx context.Context, in *ListClustersRequest, opts ...grpc.CallOption) (*ListClustersResponse, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteCluster deletes a cluster. It returns a Operation, that can be used to wait for the deletion to be finished. - DeleteCluster(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*Operation, error) - // Force openapi generator to generate the CreateClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyCreateMetadata(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*CreateClusterMetadata, error) - // Force openapi generator to generate the UpdateClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyUpdateMetadata(ctx context.Context, in *UpdateClusterRequest, opts ...grpc.CallOption) (*UpdateClusterMetadata, error) - // Force openapi generator to generate the DeleteClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyDeleteMetadata(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*DeleteClusterMetadata, error) -} - -type clusterServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewClusterServiceClient(cc grpc.ClientConnInterface) ClusterServiceClient { - return &clusterServiceClient{cc} -} - -func (c *clusterServiceClient) CreateCluster(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, ClusterService_CreateCluster_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) GetCluster(ctx context.Context, in *GetClusterRequest, opts ...grpc.CallOption) (*Cluster, error) { - out := new(Cluster) - err := c.cc.Invoke(ctx, ClusterService_GetCluster_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) UpdateCluster(ctx context.Context, in *UpdateClusterRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, ClusterService_UpdateCluster_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) ListClusters(ctx context.Context, in *ListClustersRequest, opts ...grpc.CallOption) (*ListClustersResponse, error) { - out := new(ListClustersResponse) - err := c.cc.Invoke(ctx, ClusterService_ListClusters_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) DeleteCluster(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, ClusterService_DeleteCluster_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) DummyCreateMetadata(ctx context.Context, in *CreateClusterRequest, opts ...grpc.CallOption) (*CreateClusterMetadata, error) { - out := new(CreateClusterMetadata) - err := c.cc.Invoke(ctx, ClusterService_DummyCreateMetadata_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) DummyUpdateMetadata(ctx context.Context, in *UpdateClusterRequest, opts ...grpc.CallOption) (*UpdateClusterMetadata, error) { - out := new(UpdateClusterMetadata) - err := c.cc.Invoke(ctx, ClusterService_DummyUpdateMetadata_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clusterServiceClient) DummyDeleteMetadata(ctx context.Context, in *DeleteClusterRequest, opts ...grpc.CallOption) (*DeleteClusterMetadata, error) { - out := new(DeleteClusterMetadata) - err := c.cc.Invoke(ctx, ClusterService_DummyDeleteMetadata_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ClusterServiceServer is the server API for ClusterService service. -// All implementations should embed UnimplementedClusterServiceServer -// for forward compatibility -type ClusterServiceServer interface { - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateCluster create a Redpanda cluster. The input contains the spec, that describes the cluster. - // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a cluster has finished. - CreateCluster(context.Context, *CreateClusterRequest) (*Operation, error) - // GetCluster retrieves the cluster's information - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - GetCluster(context.Context, *GetClusterRequest) (*Cluster, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // UpdateCluster updates the cluster. It returns a Operation that can be used to wait for the Update to be applied. - UpdateCluster(context.Context, *UpdateClusterRequest) (*Operation, error) - // ListClusters lists clusters. - ListClusters(context.Context, *ListClustersRequest) (*ListClustersResponse, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteCluster deletes a cluster. It returns a Operation, that can be used to wait for the deletion to be finished. - DeleteCluster(context.Context, *DeleteClusterRequest) (*Operation, error) - // Force openapi generator to generate the CreateClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyCreateMetadata(context.Context, *CreateClusterRequest) (*CreateClusterMetadata, error) - // Force openapi generator to generate the UpdateClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyUpdateMetadata(context.Context, *UpdateClusterRequest) (*UpdateClusterMetadata, error) - // Force openapi generator to generate the DeleteClusterMetadata, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyDeleteMetadata(context.Context, *DeleteClusterRequest) (*DeleteClusterMetadata, error) -} - -// UnimplementedClusterServiceServer should be embedded to have forward compatible implementations. -type UnimplementedClusterServiceServer struct { -} - -func (UnimplementedClusterServiceServer) CreateCluster(context.Context, *CreateClusterRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateCluster not implemented") -} -func (UnimplementedClusterServiceServer) GetCluster(context.Context, *GetClusterRequest) (*Cluster, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetCluster not implemented") -} -func (UnimplementedClusterServiceServer) UpdateCluster(context.Context, *UpdateClusterRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateCluster not implemented") -} -func (UnimplementedClusterServiceServer) ListClusters(context.Context, *ListClustersRequest) (*ListClustersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListClusters not implemented") -} -func (UnimplementedClusterServiceServer) DeleteCluster(context.Context, *DeleteClusterRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteCluster not implemented") -} -func (UnimplementedClusterServiceServer) DummyCreateMetadata(context.Context, *CreateClusterRequest) (*CreateClusterMetadata, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyCreateMetadata not implemented") -} -func (UnimplementedClusterServiceServer) DummyUpdateMetadata(context.Context, *UpdateClusterRequest) (*UpdateClusterMetadata, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyUpdateMetadata not implemented") -} -func (UnimplementedClusterServiceServer) DummyDeleteMetadata(context.Context, *DeleteClusterRequest) (*DeleteClusterMetadata, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyDeleteMetadata not implemented") -} - -// UnsafeClusterServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ClusterServiceServer will -// result in compilation errors. -type UnsafeClusterServiceServer interface { - mustEmbedUnimplementedClusterServiceServer() -} - -func RegisterClusterServiceServer(s grpc.ServiceRegistrar, srv ClusterServiceServer) { - s.RegisterService(&ClusterService_ServiceDesc, srv) -} - -func _ClusterService_CreateCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).CreateCluster(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_CreateCluster_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).CreateCluster(ctx, req.(*CreateClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_GetCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).GetCluster(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_GetCluster_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).GetCluster(ctx, req.(*GetClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_UpdateCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).UpdateCluster(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_UpdateCluster_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).UpdateCluster(ctx, req.(*UpdateClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_ListClusters_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListClustersRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).ListClusters(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_ListClusters_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).ListClusters(ctx, req.(*ListClustersRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_DeleteCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).DeleteCluster(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_DeleteCluster_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).DeleteCluster(ctx, req.(*DeleteClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_DummyCreateMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).DummyCreateMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_DummyCreateMetadata_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).DummyCreateMetadata(ctx, req.(*CreateClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_DummyUpdateMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).DummyUpdateMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_DummyUpdateMetadata_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).DummyUpdateMetadata(ctx, req.(*UpdateClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClusterService_DummyDeleteMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteClusterRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClusterServiceServer).DummyDeleteMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ClusterService_DummyDeleteMetadata_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClusterServiceServer).DummyDeleteMetadata(ctx, req.(*DeleteClusterRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// ClusterService_ServiceDesc is the grpc.ServiceDesc for ClusterService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ClusterService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "redpanda.api.controlplane.v1beta1.ClusterService", - HandlerType: (*ClusterServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CreateCluster", - Handler: _ClusterService_CreateCluster_Handler, - }, - { - MethodName: "GetCluster", - Handler: _ClusterService_GetCluster_Handler, - }, - { - MethodName: "UpdateCluster", - Handler: _ClusterService_UpdateCluster_Handler, - }, - { - MethodName: "ListClusters", - Handler: _ClusterService_ListClusters_Handler, - }, - { - MethodName: "DeleteCluster", - Handler: _ClusterService_DeleteCluster_Handler, - }, - { - MethodName: "DummyCreateMetadata", - Handler: _ClusterService_DummyCreateMetadata_Handler, - }, - { - MethodName: "DummyUpdateMetadata", - Handler: _ClusterService_DummyUpdateMetadata_Handler, - }, - { - MethodName: "DummyDeleteMetadata", - Handler: _ClusterService_DummyDeleteMetadata_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "redpanda/api/controlplane/v1beta1/cluster.proto", -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/common.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/common.pb.go index c7c36e8291434..289ce728189b0 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/common.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/common.proto @@ -29,6 +29,7 @@ const ( CloudProvider_CLOUD_PROVIDER_UNSPECIFIED CloudProvider = 0 CloudProvider_CLOUD_PROVIDER_AWS CloudProvider = 1 CloudProvider_CLOUD_PROVIDER_GCP CloudProvider = 2 + CloudProvider_CLOUD_PROVIDER_AZURE CloudProvider = 3 ) // Enum value maps for CloudProvider. @@ -37,11 +38,13 @@ var ( 0: "CLOUD_PROVIDER_UNSPECIFIED", 1: "CLOUD_PROVIDER_AWS", 2: "CLOUD_PROVIDER_GCP", + 3: "CLOUD_PROVIDER_AZURE", } CloudProvider_value = map[string]int32{ "CLOUD_PROVIDER_UNSPECIFIED": 0, "CLOUD_PROVIDER_AWS": 1, "CLOUD_PROVIDER_GCP": 2, + "CLOUD_PROVIDER_AZURE": 3, } ) @@ -146,34 +149,36 @@ var file_redpanda_api_controlplane_v1beta1_common_proto_rawDesc = []byte{ 0x65, 0x20, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x32, 0x20, 0x47, 0x43, 0x50, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x20, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x2a, 0x5f, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x2a, 0x79, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x41, 0x57, 0x53, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x47, 0x43, 0x50, - 0x10, 0x02, 0x42, 0xcc, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0b, 0x43, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6f, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, - 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, - 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, - 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x52, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, - 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x52, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, + 0x49, 0x44, 0x45, 0x52, 0x5f, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x10, 0x03, 0x42, 0xcc, 0x02, 0x0a, + 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, + 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, + 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, + 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, + 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/cluster.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/cluster.connect.go new file mode 100644 index 0000000000000..d92988bf2b0fb --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/cluster.connect.go @@ -0,0 +1,384 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/controlplane/v1beta1/cluster.proto + +package controlplanev1beta1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // ClusterServiceName is the fully-qualified name of the ClusterService service. + ClusterServiceName = "redpanda.api.controlplane.v1beta1.ClusterService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // ClusterServiceCreateClusterProcedure is the fully-qualified name of the ClusterService's + // CreateCluster RPC. + ClusterServiceCreateClusterProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/CreateCluster" + // ClusterServiceGetClusterProcedure is the fully-qualified name of the ClusterService's GetCluster + // RPC. + ClusterServiceGetClusterProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/GetCluster" + // ClusterServiceUpdateClusterProcedure is the fully-qualified name of the ClusterService's + // UpdateCluster RPC. + ClusterServiceUpdateClusterProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/UpdateCluster" + // ClusterServiceListClustersProcedure is the fully-qualified name of the ClusterService's + // ListClusters RPC. + ClusterServiceListClustersProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/ListClusters" + // ClusterServiceDeleteClusterProcedure is the fully-qualified name of the ClusterService's + // DeleteCluster RPC. + ClusterServiceDeleteClusterProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/DeleteCluster" + // ClusterServiceDummyCreateMetadataProcedure is the fully-qualified name of the ClusterService's + // DummyCreateMetadata RPC. + ClusterServiceDummyCreateMetadataProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyCreateMetadata" + // ClusterServiceDummyUpdateMetadataProcedure is the fully-qualified name of the ClusterService's + // DummyUpdateMetadata RPC. + ClusterServiceDummyUpdateMetadataProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyUpdateMetadata" + // ClusterServiceDummyDeleteMetadataProcedure is the fully-qualified name of the ClusterService's + // DummyDeleteMetadata RPC. + ClusterServiceDummyDeleteMetadataProcedure = "/redpanda.api.controlplane.v1beta1.ClusterService/DummyDeleteMetadata" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + clusterServiceServiceDescriptor = v1beta1.File_redpanda_api_controlplane_v1beta1_cluster_proto.Services().ByName("ClusterService") + clusterServiceCreateClusterMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("CreateCluster") + clusterServiceGetClusterMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("GetCluster") + clusterServiceUpdateClusterMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("UpdateCluster") + clusterServiceListClustersMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("ListClusters") + clusterServiceDeleteClusterMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("DeleteCluster") + clusterServiceDummyCreateMetadataMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("DummyCreateMetadata") + clusterServiceDummyUpdateMetadataMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("DummyUpdateMetadata") + clusterServiceDummyDeleteMetadataMethodDescriptor = clusterServiceServiceDescriptor.Methods().ByName("DummyDeleteMetadata") +) + +// ClusterServiceClient is a client for the redpanda.api.controlplane.v1beta1.ClusterService +// service. +type ClusterServiceClient interface { + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateCluster create a Redpanda cluster. The input contains the spec, that describes the cluster. + // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a cluster has finished. + CreateCluster(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // GetCluster retrieves the cluster's information + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + GetCluster(context.Context, *connect.Request[v1beta1.GetClusterRequest]) (*connect.Response[v1beta1.Cluster], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // UpdateCluster updates the cluster. It returns a Operation that can be used to wait for the Update to be applied. + UpdateCluster(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // ListClusters lists clusters. + ListClusters(context.Context, *connect.Request[v1beta1.ListClustersRequest]) (*connect.Response[v1beta1.ListClustersResponse], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteCluster deletes a cluster. It returns a Operation, that can be used to wait for the deletion to be finished. + DeleteCluster(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // Force openapi generator to generate the CreateClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.CreateClusterMetadata], error) + // Force openapi generator to generate the UpdateClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyUpdateMetadata(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.UpdateClusterMetadata], error) + // Force openapi generator to generate the DeleteClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.DeleteClusterMetadata], error) +} + +// NewClusterServiceClient constructs a client for the +// redpanda.api.controlplane.v1beta1.ClusterService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewClusterServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ClusterServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &clusterServiceClient{ + createCluster: connect.NewClient[v1beta1.CreateClusterRequest, v1beta1.Operation]( + httpClient, + baseURL+ClusterServiceCreateClusterProcedure, + connect.WithSchema(clusterServiceCreateClusterMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getCluster: connect.NewClient[v1beta1.GetClusterRequest, v1beta1.Cluster]( + httpClient, + baseURL+ClusterServiceGetClusterProcedure, + connect.WithSchema(clusterServiceGetClusterMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateCluster: connect.NewClient[v1beta1.UpdateClusterRequest, v1beta1.Operation]( + httpClient, + baseURL+ClusterServiceUpdateClusterProcedure, + connect.WithSchema(clusterServiceUpdateClusterMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listClusters: connect.NewClient[v1beta1.ListClustersRequest, v1beta1.ListClustersResponse]( + httpClient, + baseURL+ClusterServiceListClustersProcedure, + connect.WithSchema(clusterServiceListClustersMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteCluster: connect.NewClient[v1beta1.DeleteClusterRequest, v1beta1.Operation]( + httpClient, + baseURL+ClusterServiceDeleteClusterProcedure, + connect.WithSchema(clusterServiceDeleteClusterMethodDescriptor), + connect.WithClientOptions(opts...), + ), + dummyCreateMetadata: connect.NewClient[v1beta1.CreateClusterRequest, v1beta1.CreateClusterMetadata]( + httpClient, + baseURL+ClusterServiceDummyCreateMetadataProcedure, + connect.WithSchema(clusterServiceDummyCreateMetadataMethodDescriptor), + connect.WithClientOptions(opts...), + ), + dummyUpdateMetadata: connect.NewClient[v1beta1.UpdateClusterRequest, v1beta1.UpdateClusterMetadata]( + httpClient, + baseURL+ClusterServiceDummyUpdateMetadataProcedure, + connect.WithSchema(clusterServiceDummyUpdateMetadataMethodDescriptor), + connect.WithClientOptions(opts...), + ), + dummyDeleteMetadata: connect.NewClient[v1beta1.DeleteClusterRequest, v1beta1.DeleteClusterMetadata]( + httpClient, + baseURL+ClusterServiceDummyDeleteMetadataProcedure, + connect.WithSchema(clusterServiceDummyDeleteMetadataMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// clusterServiceClient implements ClusterServiceClient. +type clusterServiceClient struct { + createCluster *connect.Client[v1beta1.CreateClusterRequest, v1beta1.Operation] + getCluster *connect.Client[v1beta1.GetClusterRequest, v1beta1.Cluster] + updateCluster *connect.Client[v1beta1.UpdateClusterRequest, v1beta1.Operation] + listClusters *connect.Client[v1beta1.ListClustersRequest, v1beta1.ListClustersResponse] + deleteCluster *connect.Client[v1beta1.DeleteClusterRequest, v1beta1.Operation] + dummyCreateMetadata *connect.Client[v1beta1.CreateClusterRequest, v1beta1.CreateClusterMetadata] + dummyUpdateMetadata *connect.Client[v1beta1.UpdateClusterRequest, v1beta1.UpdateClusterMetadata] + dummyDeleteMetadata *connect.Client[v1beta1.DeleteClusterRequest, v1beta1.DeleteClusterMetadata] +} + +// CreateCluster calls redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster. +func (c *clusterServiceClient) CreateCluster(ctx context.Context, req *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.createCluster.CallUnary(ctx, req) +} + +// GetCluster calls redpanda.api.controlplane.v1beta1.ClusterService.GetCluster. +func (c *clusterServiceClient) GetCluster(ctx context.Context, req *connect.Request[v1beta1.GetClusterRequest]) (*connect.Response[v1beta1.Cluster], error) { + return c.getCluster.CallUnary(ctx, req) +} + +// UpdateCluster calls redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster. +func (c *clusterServiceClient) UpdateCluster(ctx context.Context, req *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.updateCluster.CallUnary(ctx, req) +} + +// ListClusters calls redpanda.api.controlplane.v1beta1.ClusterService.ListClusters. +func (c *clusterServiceClient) ListClusters(ctx context.Context, req *connect.Request[v1beta1.ListClustersRequest]) (*connect.Response[v1beta1.ListClustersResponse], error) { + return c.listClusters.CallUnary(ctx, req) +} + +// DeleteCluster calls redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster. +func (c *clusterServiceClient) DeleteCluster(ctx context.Context, req *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.deleteCluster.CallUnary(ctx, req) +} + +// DummyCreateMetadata calls redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata. +func (c *clusterServiceClient) DummyCreateMetadata(ctx context.Context, req *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.CreateClusterMetadata], error) { + return c.dummyCreateMetadata.CallUnary(ctx, req) +} + +// DummyUpdateMetadata calls redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata. +func (c *clusterServiceClient) DummyUpdateMetadata(ctx context.Context, req *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.UpdateClusterMetadata], error) { + return c.dummyUpdateMetadata.CallUnary(ctx, req) +} + +// DummyDeleteMetadata calls redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata. +func (c *clusterServiceClient) DummyDeleteMetadata(ctx context.Context, req *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.DeleteClusterMetadata], error) { + return c.dummyDeleteMetadata.CallUnary(ctx, req) +} + +// ClusterServiceHandler is an implementation of the +// redpanda.api.controlplane.v1beta1.ClusterService service. +type ClusterServiceHandler interface { + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateCluster create a Redpanda cluster. The input contains the spec, that describes the cluster. + // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a cluster has finished. + CreateCluster(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // GetCluster retrieves the cluster's information + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + GetCluster(context.Context, *connect.Request[v1beta1.GetClusterRequest]) (*connect.Response[v1beta1.Cluster], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // UpdateCluster updates the cluster. It returns a Operation that can be used to wait for the Update to be applied. + UpdateCluster(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // ListClusters lists clusters. + ListClusters(context.Context, *connect.Request[v1beta1.ListClustersRequest]) (*connect.Response[v1beta1.ListClustersResponse], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteCluster deletes a cluster. It returns a Operation, that can be used to wait for the deletion to be finished. + DeleteCluster(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.Operation], error) + // Force openapi generator to generate the CreateClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.CreateClusterMetadata], error) + // Force openapi generator to generate the UpdateClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyUpdateMetadata(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.UpdateClusterMetadata], error) + // Force openapi generator to generate the DeleteClusterMetadata, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.DeleteClusterMetadata], error) +} + +// NewClusterServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewClusterServiceHandler(svc ClusterServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + clusterServiceCreateClusterHandler := connect.NewUnaryHandler( + ClusterServiceCreateClusterProcedure, + svc.CreateCluster, + connect.WithSchema(clusterServiceCreateClusterMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceGetClusterHandler := connect.NewUnaryHandler( + ClusterServiceGetClusterProcedure, + svc.GetCluster, + connect.WithSchema(clusterServiceGetClusterMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceUpdateClusterHandler := connect.NewUnaryHandler( + ClusterServiceUpdateClusterProcedure, + svc.UpdateCluster, + connect.WithSchema(clusterServiceUpdateClusterMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceListClustersHandler := connect.NewUnaryHandler( + ClusterServiceListClustersProcedure, + svc.ListClusters, + connect.WithSchema(clusterServiceListClustersMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceDeleteClusterHandler := connect.NewUnaryHandler( + ClusterServiceDeleteClusterProcedure, + svc.DeleteCluster, + connect.WithSchema(clusterServiceDeleteClusterMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceDummyCreateMetadataHandler := connect.NewUnaryHandler( + ClusterServiceDummyCreateMetadataProcedure, + svc.DummyCreateMetadata, + connect.WithSchema(clusterServiceDummyCreateMetadataMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceDummyUpdateMetadataHandler := connect.NewUnaryHandler( + ClusterServiceDummyUpdateMetadataProcedure, + svc.DummyUpdateMetadata, + connect.WithSchema(clusterServiceDummyUpdateMetadataMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + clusterServiceDummyDeleteMetadataHandler := connect.NewUnaryHandler( + ClusterServiceDummyDeleteMetadataProcedure, + svc.DummyDeleteMetadata, + connect.WithSchema(clusterServiceDummyDeleteMetadataMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.controlplane.v1beta1.ClusterService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case ClusterServiceCreateClusterProcedure: + clusterServiceCreateClusterHandler.ServeHTTP(w, r) + case ClusterServiceGetClusterProcedure: + clusterServiceGetClusterHandler.ServeHTTP(w, r) + case ClusterServiceUpdateClusterProcedure: + clusterServiceUpdateClusterHandler.ServeHTTP(w, r) + case ClusterServiceListClustersProcedure: + clusterServiceListClustersHandler.ServeHTTP(w, r) + case ClusterServiceDeleteClusterProcedure: + clusterServiceDeleteClusterHandler.ServeHTTP(w, r) + case ClusterServiceDummyCreateMetadataProcedure: + clusterServiceDummyCreateMetadataHandler.ServeHTTP(w, r) + case ClusterServiceDummyUpdateMetadataProcedure: + clusterServiceDummyUpdateMetadataHandler.ServeHTTP(w, r) + case ClusterServiceDummyDeleteMetadataProcedure: + clusterServiceDummyDeleteMetadataHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedClusterServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedClusterServiceHandler struct{} + +func (UnimplementedClusterServiceHandler) CreateCluster(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.CreateCluster is not implemented")) +} + +func (UnimplementedClusterServiceHandler) GetCluster(context.Context, *connect.Request[v1beta1.GetClusterRequest]) (*connect.Response[v1beta1.Cluster], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.GetCluster is not implemented")) +} + +func (UnimplementedClusterServiceHandler) UpdateCluster(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.UpdateCluster is not implemented")) +} + +func (UnimplementedClusterServiceHandler) ListClusters(context.Context, *connect.Request[v1beta1.ListClustersRequest]) (*connect.Response[v1beta1.ListClustersResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.ListClusters is not implemented")) +} + +func (UnimplementedClusterServiceHandler) DeleteCluster(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.DeleteCluster is not implemented")) +} + +func (UnimplementedClusterServiceHandler) DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateClusterRequest]) (*connect.Response[v1beta1.CreateClusterMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.DummyCreateMetadata is not implemented")) +} + +func (UnimplementedClusterServiceHandler) DummyUpdateMetadata(context.Context, *connect.Request[v1beta1.UpdateClusterRequest]) (*connect.Response[v1beta1.UpdateClusterMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.DummyUpdateMetadata is not implemented")) +} + +func (UnimplementedClusterServiceHandler) DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteClusterRequest]) (*connect.Response[v1beta1.DeleteClusterMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.ClusterService.DummyDeleteMetadata is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/dummy.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/dummy.connect.go new file mode 100644 index 0000000000000..019875319980d --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/dummy.connect.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/controlplane/v1beta1/dummy.proto + +package controlplanev1beta1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" + emptypb "google.golang.org/protobuf/types/known/emptypb" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // DummyServiceName is the fully-qualified name of the DummyService service. + DummyServiceName = "redpanda.api.controlplane.v1beta1.DummyService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // DummyServiceDummyMethodProcedure is the fully-qualified name of the DummyService's DummyMethod + // RPC. + DummyServiceDummyMethodProcedure = "/redpanda.api.controlplane.v1beta1.DummyService/DummyMethod" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + dummyServiceServiceDescriptor = v1beta1.File_redpanda_api_controlplane_v1beta1_dummy_proto.Services().ByName("DummyService") + dummyServiceDummyMethodMethodDescriptor = dummyServiceServiceDescriptor.Methods().ByName("DummyMethod") +) + +// DummyServiceClient is a client for the redpanda.api.controlplane.v1beta1.DummyService service. +type DummyServiceClient interface { + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1beta1.DummyMethodResponse], error) +} + +// NewDummyServiceClient constructs a client for the redpanda.api.controlplane.v1beta1.DummyService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewDummyServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) DummyServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &dummyServiceClient{ + dummyMethod: connect.NewClient[emptypb.Empty, v1beta1.DummyMethodResponse]( + httpClient, + baseURL+DummyServiceDummyMethodProcedure, + connect.WithSchema(dummyServiceDummyMethodMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// dummyServiceClient implements DummyServiceClient. +type dummyServiceClient struct { + dummyMethod *connect.Client[emptypb.Empty, v1beta1.DummyMethodResponse] +} + +// DummyMethod calls redpanda.api.controlplane.v1beta1.DummyService.DummyMethod. +func (c *dummyServiceClient) DummyMethod(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1beta1.DummyMethodResponse], error) { + return c.dummyMethod.CallUnary(ctx, req) +} + +// DummyServiceHandler is an implementation of the redpanda.api.controlplane.v1beta1.DummyService +// service. +type DummyServiceHandler interface { + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1beta1.DummyMethodResponse], error) +} + +// NewDummyServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewDummyServiceHandler(svc DummyServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + dummyServiceDummyMethodHandler := connect.NewUnaryHandler( + DummyServiceDummyMethodProcedure, + svc.DummyMethod, + connect.WithSchema(dummyServiceDummyMethodMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.controlplane.v1beta1.DummyService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case DummyServiceDummyMethodProcedure: + dummyServiceDummyMethodHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedDummyServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedDummyServiceHandler struct{} + +func (UnimplementedDummyServiceHandler) DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1beta1.DummyMethodResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.DummyService.DummyMethod is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/namespace.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/namespace.connect.go new file mode 100644 index 0000000000000..96186be50f5ed --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/namespace.connect.go @@ -0,0 +1,262 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/controlplane/v1beta1/namespace.proto + +package controlplanev1beta1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // NamespaceServiceName is the fully-qualified name of the NamespaceService service. + NamespaceServiceName = "redpanda.api.controlplane.v1beta1.NamespaceService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // NamespaceServiceCreateNamespaceProcedure is the fully-qualified name of the NamespaceService's + // CreateNamespace RPC. + NamespaceServiceCreateNamespaceProcedure = "/redpanda.api.controlplane.v1beta1.NamespaceService/CreateNamespace" + // NamespaceServiceUpdateNamespaceProcedure is the fully-qualified name of the NamespaceService's + // UpdateNamespace RPC. + NamespaceServiceUpdateNamespaceProcedure = "/redpanda.api.controlplane.v1beta1.NamespaceService/UpdateNamespace" + // NamespaceServiceGetNamespaceProcedure is the fully-qualified name of the NamespaceService's + // GetNamespace RPC. + NamespaceServiceGetNamespaceProcedure = "/redpanda.api.controlplane.v1beta1.NamespaceService/GetNamespace" + // NamespaceServiceListNamespacesProcedure is the fully-qualified name of the NamespaceService's + // ListNamespaces RPC. + NamespaceServiceListNamespacesProcedure = "/redpanda.api.controlplane.v1beta1.NamespaceService/ListNamespaces" + // NamespaceServiceDeleteNamespaceProcedure is the fully-qualified name of the NamespaceService's + // DeleteNamespace RPC. + NamespaceServiceDeleteNamespaceProcedure = "/redpanda.api.controlplane.v1beta1.NamespaceService/DeleteNamespace" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + namespaceServiceServiceDescriptor = v1beta1.File_redpanda_api_controlplane_v1beta1_namespace_proto.Services().ByName("NamespaceService") + namespaceServiceCreateNamespaceMethodDescriptor = namespaceServiceServiceDescriptor.Methods().ByName("CreateNamespace") + namespaceServiceUpdateNamespaceMethodDescriptor = namespaceServiceServiceDescriptor.Methods().ByName("UpdateNamespace") + namespaceServiceGetNamespaceMethodDescriptor = namespaceServiceServiceDescriptor.Methods().ByName("GetNamespace") + namespaceServiceListNamespacesMethodDescriptor = namespaceServiceServiceDescriptor.Methods().ByName("ListNamespaces") + namespaceServiceDeleteNamespaceMethodDescriptor = namespaceServiceServiceDescriptor.Methods().ByName("DeleteNamespace") +) + +// NamespaceServiceClient is a client for the redpanda.api.controlplane.v1beta1.NamespaceService +// service. +type NamespaceServiceClient interface { + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateNamespace creates the namespace + CreateNamespace(context.Context, *connect.Request[v1beta1.CreateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // UpdateNamespace updates the namespace. It returns a Operation that can be used to wait for the Update to be applied. + UpdateNamespace(context.Context, *connect.Request[v1beta1.UpdateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // GetNamespace retrieves the namespace's information + GetNamespace(context.Context, *connect.Request[v1beta1.GetNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // ListNamespaces list namespaces. + ListNamespaces(context.Context, *connect.Request[v1beta1.ListNamespacesRequest]) (*connect.Response[v1beta1.ListNamespacesResponse], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteNamespace deletes a namespace. + DeleteNamespace(context.Context, *connect.Request[v1beta1.DeleteNamespaceRequest]) (*connect.Response[v1beta1.DeleteNamespaceResponse], error) +} + +// NewNamespaceServiceClient constructs a client for the +// redpanda.api.controlplane.v1beta1.NamespaceService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewNamespaceServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) NamespaceServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &namespaceServiceClient{ + createNamespace: connect.NewClient[v1beta1.CreateNamespaceRequest, v1beta1.Namespace]( + httpClient, + baseURL+NamespaceServiceCreateNamespaceProcedure, + connect.WithSchema(namespaceServiceCreateNamespaceMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateNamespace: connect.NewClient[v1beta1.UpdateNamespaceRequest, v1beta1.Namespace]( + httpClient, + baseURL+NamespaceServiceUpdateNamespaceProcedure, + connect.WithSchema(namespaceServiceUpdateNamespaceMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getNamespace: connect.NewClient[v1beta1.GetNamespaceRequest, v1beta1.Namespace]( + httpClient, + baseURL+NamespaceServiceGetNamespaceProcedure, + connect.WithSchema(namespaceServiceGetNamespaceMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listNamespaces: connect.NewClient[v1beta1.ListNamespacesRequest, v1beta1.ListNamespacesResponse]( + httpClient, + baseURL+NamespaceServiceListNamespacesProcedure, + connect.WithSchema(namespaceServiceListNamespacesMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteNamespace: connect.NewClient[v1beta1.DeleteNamespaceRequest, v1beta1.DeleteNamespaceResponse]( + httpClient, + baseURL+NamespaceServiceDeleteNamespaceProcedure, + connect.WithSchema(namespaceServiceDeleteNamespaceMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// namespaceServiceClient implements NamespaceServiceClient. +type namespaceServiceClient struct { + createNamespace *connect.Client[v1beta1.CreateNamespaceRequest, v1beta1.Namespace] + updateNamespace *connect.Client[v1beta1.UpdateNamespaceRequest, v1beta1.Namespace] + getNamespace *connect.Client[v1beta1.GetNamespaceRequest, v1beta1.Namespace] + listNamespaces *connect.Client[v1beta1.ListNamespacesRequest, v1beta1.ListNamespacesResponse] + deleteNamespace *connect.Client[v1beta1.DeleteNamespaceRequest, v1beta1.DeleteNamespaceResponse] +} + +// CreateNamespace calls redpanda.api.controlplane.v1beta1.NamespaceService.CreateNamespace. +func (c *namespaceServiceClient) CreateNamespace(ctx context.Context, req *connect.Request[v1beta1.CreateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return c.createNamespace.CallUnary(ctx, req) +} + +// UpdateNamespace calls redpanda.api.controlplane.v1beta1.NamespaceService.UpdateNamespace. +func (c *namespaceServiceClient) UpdateNamespace(ctx context.Context, req *connect.Request[v1beta1.UpdateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return c.updateNamespace.CallUnary(ctx, req) +} + +// GetNamespace calls redpanda.api.controlplane.v1beta1.NamespaceService.GetNamespace. +func (c *namespaceServiceClient) GetNamespace(ctx context.Context, req *connect.Request[v1beta1.GetNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return c.getNamespace.CallUnary(ctx, req) +} + +// ListNamespaces calls redpanda.api.controlplane.v1beta1.NamespaceService.ListNamespaces. +func (c *namespaceServiceClient) ListNamespaces(ctx context.Context, req *connect.Request[v1beta1.ListNamespacesRequest]) (*connect.Response[v1beta1.ListNamespacesResponse], error) { + return c.listNamespaces.CallUnary(ctx, req) +} + +// DeleteNamespace calls redpanda.api.controlplane.v1beta1.NamespaceService.DeleteNamespace. +func (c *namespaceServiceClient) DeleteNamespace(ctx context.Context, req *connect.Request[v1beta1.DeleteNamespaceRequest]) (*connect.Response[v1beta1.DeleteNamespaceResponse], error) { + return c.deleteNamespace.CallUnary(ctx, req) +} + +// NamespaceServiceHandler is an implementation of the +// redpanda.api.controlplane.v1beta1.NamespaceService service. +type NamespaceServiceHandler interface { + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateNamespace creates the namespace + CreateNamespace(context.Context, *connect.Request[v1beta1.CreateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // UpdateNamespace updates the namespace. It returns a Operation that can be used to wait for the Update to be applied. + UpdateNamespace(context.Context, *connect.Request[v1beta1.UpdateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // GetNamespace retrieves the namespace's information + GetNamespace(context.Context, *connect.Request[v1beta1.GetNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) + // ListNamespaces list namespaces. + ListNamespaces(context.Context, *connect.Request[v1beta1.ListNamespacesRequest]) (*connect.Response[v1beta1.ListNamespacesResponse], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteNamespace deletes a namespace. + DeleteNamespace(context.Context, *connect.Request[v1beta1.DeleteNamespaceRequest]) (*connect.Response[v1beta1.DeleteNamespaceResponse], error) +} + +// NewNamespaceServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewNamespaceServiceHandler(svc NamespaceServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + namespaceServiceCreateNamespaceHandler := connect.NewUnaryHandler( + NamespaceServiceCreateNamespaceProcedure, + svc.CreateNamespace, + connect.WithSchema(namespaceServiceCreateNamespaceMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + namespaceServiceUpdateNamespaceHandler := connect.NewUnaryHandler( + NamespaceServiceUpdateNamespaceProcedure, + svc.UpdateNamespace, + connect.WithSchema(namespaceServiceUpdateNamespaceMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + namespaceServiceGetNamespaceHandler := connect.NewUnaryHandler( + NamespaceServiceGetNamespaceProcedure, + svc.GetNamespace, + connect.WithSchema(namespaceServiceGetNamespaceMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + namespaceServiceListNamespacesHandler := connect.NewUnaryHandler( + NamespaceServiceListNamespacesProcedure, + svc.ListNamespaces, + connect.WithSchema(namespaceServiceListNamespacesMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + namespaceServiceDeleteNamespaceHandler := connect.NewUnaryHandler( + NamespaceServiceDeleteNamespaceProcedure, + svc.DeleteNamespace, + connect.WithSchema(namespaceServiceDeleteNamespaceMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.controlplane.v1beta1.NamespaceService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case NamespaceServiceCreateNamespaceProcedure: + namespaceServiceCreateNamespaceHandler.ServeHTTP(w, r) + case NamespaceServiceUpdateNamespaceProcedure: + namespaceServiceUpdateNamespaceHandler.ServeHTTP(w, r) + case NamespaceServiceGetNamespaceProcedure: + namespaceServiceGetNamespaceHandler.ServeHTTP(w, r) + case NamespaceServiceListNamespacesProcedure: + namespaceServiceListNamespacesHandler.ServeHTTP(w, r) + case NamespaceServiceDeleteNamespaceProcedure: + namespaceServiceDeleteNamespaceHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedNamespaceServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedNamespaceServiceHandler struct{} + +func (UnimplementedNamespaceServiceHandler) CreateNamespace(context.Context, *connect.Request[v1beta1.CreateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NamespaceService.CreateNamespace is not implemented")) +} + +func (UnimplementedNamespaceServiceHandler) UpdateNamespace(context.Context, *connect.Request[v1beta1.UpdateNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NamespaceService.UpdateNamespace is not implemented")) +} + +func (UnimplementedNamespaceServiceHandler) GetNamespace(context.Context, *connect.Request[v1beta1.GetNamespaceRequest]) (*connect.Response[v1beta1.Namespace], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NamespaceService.GetNamespace is not implemented")) +} + +func (UnimplementedNamespaceServiceHandler) ListNamespaces(context.Context, *connect.Request[v1beta1.ListNamespacesRequest]) (*connect.Response[v1beta1.ListNamespacesResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NamespaceService.ListNamespaces is not implemented")) +} + +func (UnimplementedNamespaceServiceHandler) DeleteNamespace(context.Context, *connect.Request[v1beta1.DeleteNamespaceRequest]) (*connect.Response[v1beta1.DeleteNamespaceResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NamespaceService.DeleteNamespace is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/network.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/network.connect.go new file mode 100644 index 0000000000000..1478faa42cef6 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/network.connect.go @@ -0,0 +1,308 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/controlplane/v1beta1/network.proto + +package controlplanev1beta1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // NetworkServiceName is the fully-qualified name of the NetworkService service. + NetworkServiceName = "redpanda.api.controlplane.v1beta1.NetworkService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // NetworkServiceCreateNetworkProcedure is the fully-qualified name of the NetworkService's + // CreateNetwork RPC. + NetworkServiceCreateNetworkProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/CreateNetwork" + // NetworkServiceGetNetworkProcedure is the fully-qualified name of the NetworkService's GetNetwork + // RPC. + NetworkServiceGetNetworkProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/GetNetwork" + // NetworkServiceListNetworksProcedure is the fully-qualified name of the NetworkService's + // ListNetworks RPC. + NetworkServiceListNetworksProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/ListNetworks" + // NetworkServiceDeleteNetworkProcedure is the fully-qualified name of the NetworkService's + // DeleteNetwork RPC. + NetworkServiceDeleteNetworkProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/DeleteNetwork" + // NetworkServiceDummyCreateMetadataProcedure is the fully-qualified name of the NetworkService's + // DummyCreateMetadata RPC. + NetworkServiceDummyCreateMetadataProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/DummyCreateMetadata" + // NetworkServiceDummyDeleteMetadataProcedure is the fully-qualified name of the NetworkService's + // DummyDeleteMetadata RPC. + NetworkServiceDummyDeleteMetadataProcedure = "/redpanda.api.controlplane.v1beta1.NetworkService/DummyDeleteMetadata" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + networkServiceServiceDescriptor = v1beta1.File_redpanda_api_controlplane_v1beta1_network_proto.Services().ByName("NetworkService") + networkServiceCreateNetworkMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("CreateNetwork") + networkServiceGetNetworkMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("GetNetwork") + networkServiceListNetworksMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("ListNetworks") + networkServiceDeleteNetworkMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("DeleteNetwork") + networkServiceDummyCreateMetadataMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("DummyCreateMetadata") + networkServiceDummyDeleteMetadataMethodDescriptor = networkServiceServiceDescriptor.Methods().ByName("DummyDeleteMetadata") +) + +// NetworkServiceClient is a client for the redpanda.api.controlplane.v1beta1.NetworkService +// service. +type NetworkServiceClient interface { + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateNetwork create a Redpanda managed network. The input contains the spec, that describes the network. + // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a network has finished. + CreateNetwork(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.Operation], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // GetNetwork retrieves the network's information + GetNetwork(context.Context, *connect.Request[v1beta1.GetNetworkRequest]) (*connect.Response[v1beta1.Network], error) + // ListNetworks list networks. + ListNetworks(context.Context, *connect.Request[v1beta1.ListNetworksRequest]) (*connect.Response[v1beta1.ListNetworksResponse], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteNetwork deletes a network. It returns a Operation, that can be used to wait for the deletion to be finished. + DeleteNetwork(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.Operation], error) + // Force openapi generator to generate the CreateClusterResponse, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.CreateNetworkMetadata], error) + // Force openapi generator to generate the DeleteClusterResponse, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.DeleteNetworkMetadata], error) +} + +// NewNetworkServiceClient constructs a client for the +// redpanda.api.controlplane.v1beta1.NetworkService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewNetworkServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) NetworkServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &networkServiceClient{ + createNetwork: connect.NewClient[v1beta1.CreateNetworkRequest, v1beta1.Operation]( + httpClient, + baseURL+NetworkServiceCreateNetworkProcedure, + connect.WithSchema(networkServiceCreateNetworkMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getNetwork: connect.NewClient[v1beta1.GetNetworkRequest, v1beta1.Network]( + httpClient, + baseURL+NetworkServiceGetNetworkProcedure, + connect.WithSchema(networkServiceGetNetworkMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listNetworks: connect.NewClient[v1beta1.ListNetworksRequest, v1beta1.ListNetworksResponse]( + httpClient, + baseURL+NetworkServiceListNetworksProcedure, + connect.WithSchema(networkServiceListNetworksMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteNetwork: connect.NewClient[v1beta1.DeleteNetworkRequest, v1beta1.Operation]( + httpClient, + baseURL+NetworkServiceDeleteNetworkProcedure, + connect.WithSchema(networkServiceDeleteNetworkMethodDescriptor), + connect.WithClientOptions(opts...), + ), + dummyCreateMetadata: connect.NewClient[v1beta1.CreateNetworkRequest, v1beta1.CreateNetworkMetadata]( + httpClient, + baseURL+NetworkServiceDummyCreateMetadataProcedure, + connect.WithSchema(networkServiceDummyCreateMetadataMethodDescriptor), + connect.WithClientOptions(opts...), + ), + dummyDeleteMetadata: connect.NewClient[v1beta1.DeleteNetworkRequest, v1beta1.DeleteNetworkMetadata]( + httpClient, + baseURL+NetworkServiceDummyDeleteMetadataProcedure, + connect.WithSchema(networkServiceDummyDeleteMetadataMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// networkServiceClient implements NetworkServiceClient. +type networkServiceClient struct { + createNetwork *connect.Client[v1beta1.CreateNetworkRequest, v1beta1.Operation] + getNetwork *connect.Client[v1beta1.GetNetworkRequest, v1beta1.Network] + listNetworks *connect.Client[v1beta1.ListNetworksRequest, v1beta1.ListNetworksResponse] + deleteNetwork *connect.Client[v1beta1.DeleteNetworkRequest, v1beta1.Operation] + dummyCreateMetadata *connect.Client[v1beta1.CreateNetworkRequest, v1beta1.CreateNetworkMetadata] + dummyDeleteMetadata *connect.Client[v1beta1.DeleteNetworkRequest, v1beta1.DeleteNetworkMetadata] +} + +// CreateNetwork calls redpanda.api.controlplane.v1beta1.NetworkService.CreateNetwork. +func (c *networkServiceClient) CreateNetwork(ctx context.Context, req *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.createNetwork.CallUnary(ctx, req) +} + +// GetNetwork calls redpanda.api.controlplane.v1beta1.NetworkService.GetNetwork. +func (c *networkServiceClient) GetNetwork(ctx context.Context, req *connect.Request[v1beta1.GetNetworkRequest]) (*connect.Response[v1beta1.Network], error) { + return c.getNetwork.CallUnary(ctx, req) +} + +// ListNetworks calls redpanda.api.controlplane.v1beta1.NetworkService.ListNetworks. +func (c *networkServiceClient) ListNetworks(ctx context.Context, req *connect.Request[v1beta1.ListNetworksRequest]) (*connect.Response[v1beta1.ListNetworksResponse], error) { + return c.listNetworks.CallUnary(ctx, req) +} + +// DeleteNetwork calls redpanda.api.controlplane.v1beta1.NetworkService.DeleteNetwork. +func (c *networkServiceClient) DeleteNetwork(ctx context.Context, req *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.deleteNetwork.CallUnary(ctx, req) +} + +// DummyCreateMetadata calls redpanda.api.controlplane.v1beta1.NetworkService.DummyCreateMetadata. +func (c *networkServiceClient) DummyCreateMetadata(ctx context.Context, req *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.CreateNetworkMetadata], error) { + return c.dummyCreateMetadata.CallUnary(ctx, req) +} + +// DummyDeleteMetadata calls redpanda.api.controlplane.v1beta1.NetworkService.DummyDeleteMetadata. +func (c *networkServiceClient) DummyDeleteMetadata(ctx context.Context, req *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.DeleteNetworkMetadata], error) { + return c.dummyDeleteMetadata.CallUnary(ctx, req) +} + +// NetworkServiceHandler is an implementation of the +// redpanda.api.controlplane.v1beta1.NetworkService service. +type NetworkServiceHandler interface { + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // CreateNetwork create a Redpanda managed network. The input contains the spec, that describes the network. + // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a network has finished. + CreateNetwork(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.Operation], error) + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // GetNetwork retrieves the network's information + GetNetwork(context.Context, *connect.Request[v1beta1.GetNetworkRequest]) (*connect.Response[v1beta1.Network], error) + // ListNetworks list networks. + ListNetworks(context.Context, *connect.Request[v1beta1.ListNetworksRequest]) (*connect.Response[v1beta1.ListNetworksResponse], error) + // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + // DeleteNetwork deletes a network. It returns a Operation, that can be used to wait for the deletion to be finished. + DeleteNetwork(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.Operation], error) + // Force openapi generator to generate the CreateClusterResponse, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.CreateNetworkMetadata], error) + // Force openapi generator to generate the DeleteClusterResponse, so we can use it in OpenAPI schema. + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.DeleteNetworkMetadata], error) +} + +// NewNetworkServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewNetworkServiceHandler(svc NetworkServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + networkServiceCreateNetworkHandler := connect.NewUnaryHandler( + NetworkServiceCreateNetworkProcedure, + svc.CreateNetwork, + connect.WithSchema(networkServiceCreateNetworkMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + networkServiceGetNetworkHandler := connect.NewUnaryHandler( + NetworkServiceGetNetworkProcedure, + svc.GetNetwork, + connect.WithSchema(networkServiceGetNetworkMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + networkServiceListNetworksHandler := connect.NewUnaryHandler( + NetworkServiceListNetworksProcedure, + svc.ListNetworks, + connect.WithSchema(networkServiceListNetworksMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + networkServiceDeleteNetworkHandler := connect.NewUnaryHandler( + NetworkServiceDeleteNetworkProcedure, + svc.DeleteNetwork, + connect.WithSchema(networkServiceDeleteNetworkMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + networkServiceDummyCreateMetadataHandler := connect.NewUnaryHandler( + NetworkServiceDummyCreateMetadataProcedure, + svc.DummyCreateMetadata, + connect.WithSchema(networkServiceDummyCreateMetadataMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + networkServiceDummyDeleteMetadataHandler := connect.NewUnaryHandler( + NetworkServiceDummyDeleteMetadataProcedure, + svc.DummyDeleteMetadata, + connect.WithSchema(networkServiceDummyDeleteMetadataMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.controlplane.v1beta1.NetworkService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case NetworkServiceCreateNetworkProcedure: + networkServiceCreateNetworkHandler.ServeHTTP(w, r) + case NetworkServiceGetNetworkProcedure: + networkServiceGetNetworkHandler.ServeHTTP(w, r) + case NetworkServiceListNetworksProcedure: + networkServiceListNetworksHandler.ServeHTTP(w, r) + case NetworkServiceDeleteNetworkProcedure: + networkServiceDeleteNetworkHandler.ServeHTTP(w, r) + case NetworkServiceDummyCreateMetadataProcedure: + networkServiceDummyCreateMetadataHandler.ServeHTTP(w, r) + case NetworkServiceDummyDeleteMetadataProcedure: + networkServiceDummyDeleteMetadataHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedNetworkServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedNetworkServiceHandler struct{} + +func (UnimplementedNetworkServiceHandler) CreateNetwork(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.CreateNetwork is not implemented")) +} + +func (UnimplementedNetworkServiceHandler) GetNetwork(context.Context, *connect.Request[v1beta1.GetNetworkRequest]) (*connect.Response[v1beta1.Network], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.GetNetwork is not implemented")) +} + +func (UnimplementedNetworkServiceHandler) ListNetworks(context.Context, *connect.Request[v1beta1.ListNetworksRequest]) (*connect.Response[v1beta1.ListNetworksResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.ListNetworks is not implemented")) +} + +func (UnimplementedNetworkServiceHandler) DeleteNetwork(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.DeleteNetwork is not implemented")) +} + +func (UnimplementedNetworkServiceHandler) DummyCreateMetadata(context.Context, *connect.Request[v1beta1.CreateNetworkRequest]) (*connect.Response[v1beta1.CreateNetworkMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.DummyCreateMetadata is not implemented")) +} + +func (UnimplementedNetworkServiceHandler) DummyDeleteMetadata(context.Context, *connect.Request[v1beta1.DeleteNetworkRequest]) (*connect.Response[v1beta1.DeleteNetworkMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.NetworkService.DummyDeleteMetadata is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/operation.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/operation.connect.go new file mode 100644 index 0000000000000..7d021f656fdf7 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/controlplanev1beta1connect/operation.connect.go @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/controlplane/v1beta1/operation.proto + +package controlplanev1beta1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1beta1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // OperationServiceName is the fully-qualified name of the OperationService service. + OperationServiceName = "redpanda.api.controlplane.v1beta1.OperationService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // OperationServiceGetOperationProcedure is the fully-qualified name of the OperationService's + // GetOperation RPC. + OperationServiceGetOperationProcedure = "/redpanda.api.controlplane.v1beta1.OperationService/GetOperation" + // OperationServiceListOperationsProcedure is the fully-qualified name of the OperationService's + // ListOperations RPC. + OperationServiceListOperationsProcedure = "/redpanda.api.controlplane.v1beta1.OperationService/ListOperations" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + operationServiceServiceDescriptor = v1beta1.File_redpanda_api_controlplane_v1beta1_operation_proto.Services().ByName("OperationService") + operationServiceGetOperationMethodDescriptor = operationServiceServiceDescriptor.Methods().ByName("GetOperation") + operationServiceListOperationsMethodDescriptor = operationServiceServiceDescriptor.Methods().ByName("ListOperations") +) + +// OperationServiceClient is a client for the redpanda.api.controlplane.v1beta1.OperationService +// service. +type OperationServiceClient interface { + GetOperation(context.Context, *connect.Request[v1beta1.GetOperationRequest]) (*connect.Response[v1beta1.Operation], error) + ListOperations(context.Context, *connect.Request[v1beta1.ListOperationsRequest]) (*connect.Response[v1beta1.ListOperationsResponse], error) +} + +// NewOperationServiceClient constructs a client for the +// redpanda.api.controlplane.v1beta1.OperationService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewOperationServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) OperationServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &operationServiceClient{ + getOperation: connect.NewClient[v1beta1.GetOperationRequest, v1beta1.Operation]( + httpClient, + baseURL+OperationServiceGetOperationProcedure, + connect.WithSchema(operationServiceGetOperationMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listOperations: connect.NewClient[v1beta1.ListOperationsRequest, v1beta1.ListOperationsResponse]( + httpClient, + baseURL+OperationServiceListOperationsProcedure, + connect.WithSchema(operationServiceListOperationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// operationServiceClient implements OperationServiceClient. +type operationServiceClient struct { + getOperation *connect.Client[v1beta1.GetOperationRequest, v1beta1.Operation] + listOperations *connect.Client[v1beta1.ListOperationsRequest, v1beta1.ListOperationsResponse] +} + +// GetOperation calls redpanda.api.controlplane.v1beta1.OperationService.GetOperation. +func (c *operationServiceClient) GetOperation(ctx context.Context, req *connect.Request[v1beta1.GetOperationRequest]) (*connect.Response[v1beta1.Operation], error) { + return c.getOperation.CallUnary(ctx, req) +} + +// ListOperations calls redpanda.api.controlplane.v1beta1.OperationService.ListOperations. +func (c *operationServiceClient) ListOperations(ctx context.Context, req *connect.Request[v1beta1.ListOperationsRequest]) (*connect.Response[v1beta1.ListOperationsResponse], error) { + return c.listOperations.CallUnary(ctx, req) +} + +// OperationServiceHandler is an implementation of the +// redpanda.api.controlplane.v1beta1.OperationService service. +type OperationServiceHandler interface { + GetOperation(context.Context, *connect.Request[v1beta1.GetOperationRequest]) (*connect.Response[v1beta1.Operation], error) + ListOperations(context.Context, *connect.Request[v1beta1.ListOperationsRequest]) (*connect.Response[v1beta1.ListOperationsResponse], error) +} + +// NewOperationServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewOperationServiceHandler(svc OperationServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + operationServiceGetOperationHandler := connect.NewUnaryHandler( + OperationServiceGetOperationProcedure, + svc.GetOperation, + connect.WithSchema(operationServiceGetOperationMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + operationServiceListOperationsHandler := connect.NewUnaryHandler( + OperationServiceListOperationsProcedure, + svc.ListOperations, + connect.WithSchema(operationServiceListOperationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.controlplane.v1beta1.OperationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case OperationServiceGetOperationProcedure: + operationServiceGetOperationHandler.ServeHTTP(w, r) + case OperationServiceListOperationsProcedure: + operationServiceListOperationsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedOperationServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedOperationServiceHandler struct{} + +func (UnimplementedOperationServiceHandler) GetOperation(context.Context, *connect.Request[v1beta1.GetOperationRequest]) (*connect.Response[v1beta1.Operation], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.OperationService.GetOperation is not implemented")) +} + +func (UnimplementedOperationServiceHandler) ListOperations(context.Context, *connect.Request[v1beta1.ListOperationsRequest]) (*connect.Response[v1beta1.ListOperationsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.controlplane.v1beta1.OperationService.ListOperations is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy.pb.go index 6095ac7dbb846..7f545626a7ac7 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/dummy.proto diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy_grpc.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy_grpc.pb.go deleted file mode 100644 index a62e5269e1ea6..0000000000000 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/dummy_grpc.pb.go +++ /dev/null @@ -1,114 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: redpanda/api/controlplane/v1beta1/dummy.proto - -package controlplanev1beta1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - DummyService_DummyMethod_FullMethodName = "/redpanda.api.controlplane.v1beta1.DummyService/DummyMethod" -) - -// DummyServiceClient is the client API for DummyService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type DummyServiceClient interface { - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyMethod(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DummyMethodResponse, error) -} - -type dummyServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewDummyServiceClient(cc grpc.ClientConnInterface) DummyServiceClient { - return &dummyServiceClient{cc} -} - -func (c *dummyServiceClient) DummyMethod(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DummyMethodResponse, error) { - out := new(DummyMethodResponse) - err := c.cc.Invoke(ctx, DummyService_DummyMethod_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// DummyServiceServer is the server API for DummyService service. -// All implementations should embed UnimplementedDummyServiceServer -// for forward compatibility -type DummyServiceServer interface { - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyMethod(context.Context, *emptypb.Empty) (*DummyMethodResponse, error) -} - -// UnimplementedDummyServiceServer should be embedded to have forward compatible implementations. -type UnimplementedDummyServiceServer struct { -} - -func (UnimplementedDummyServiceServer) DummyMethod(context.Context, *emptypb.Empty) (*DummyMethodResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyMethod not implemented") -} - -// UnsafeDummyServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to DummyServiceServer will -// result in compilation errors. -type UnsafeDummyServiceServer interface { - mustEmbedUnimplementedDummyServiceServer() -} - -func RegisterDummyServiceServer(s grpc.ServiceRegistrar, srv DummyServiceServer) { - s.RegisterService(&DummyService_ServiceDesc, srv) -} - -func _DummyService_DummyMethod_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(DummyServiceServer).DummyMethod(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: DummyService_DummyMethod_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DummyServiceServer).DummyMethod(ctx, req.(*emptypb.Empty)) - } - return interceptor(ctx, in, info, handler) -} - -// DummyService_ServiceDesc is the grpc.ServiceDesc for DummyService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var DummyService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "redpanda.api.controlplane.v1beta1.DummyService", - HandlerType: (*DummyServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "DummyMethod", - Handler: _DummyService_DummyMethod_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "redpanda/api/controlplane/v1beta1/dummy.proto", -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/error.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/error.pb.go index e92777a885089..f412e334bfe2d 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/error.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/error.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/error.proto @@ -69,6 +69,16 @@ const ( Reason_REASON_CLOUD_PROVIDER_QUOTA_EXCEEDED Reason = 21 // Network still contains clusters. Only empty networks can be deleted. Reason_REASON_NETWORK_CONTAINS_CLUSTERS Reason = 22 + // Rate Limit exceeded. Request rate must be reduced. + Reason_REASON_RATE_LIMIT_EXCEEDED Reason = 23 + // Provided page token is invalid. + Reason_REASON_INVALID_PAGE_TOKEN Reason = 24 + // Page token filter does not match. + Reason_REASON_PAGE_TOKEN_FILTER_MISMATCH Reason = 25 + // Current subscription does not support the requested product. + Reason_REASON_PRODUCT_NOT_SUPPORTED_BY_SUBSCRIPTION Reason = 26 + // Not available capacity for the requested cloud provider + Reason_REASON_NOT_AVAILABLE_CAPACITY_FOR_CLOUD_PROVIDER Reason = 27 ) // Enum value maps for Reason. @@ -97,6 +107,11 @@ var ( 20: "REASON_CLOUD_PROVIDER_MACHINE_TYPE_UNSUPPORTED_IN_ZONE", 21: "REASON_CLOUD_PROVIDER_QUOTA_EXCEEDED", 22: "REASON_NETWORK_CONTAINS_CLUSTERS", + 23: "REASON_RATE_LIMIT_EXCEEDED", + 24: "REASON_INVALID_PAGE_TOKEN", + 25: "REASON_PAGE_TOKEN_FILTER_MISMATCH", + 26: "REASON_PRODUCT_NOT_SUPPORTED_BY_SUBSCRIPTION", + 27: "REASON_NOT_AVAILABLE_CAPACITY_FOR_CLOUD_PROVIDER", } Reason_value = map[string]int32{ "REASON_UNSPECIFIED": 0, @@ -122,6 +137,11 @@ var ( "REASON_CLOUD_PROVIDER_MACHINE_TYPE_UNSUPPORTED_IN_ZONE": 20, "REASON_CLOUD_PROVIDER_QUOTA_EXCEEDED": 21, "REASON_NETWORK_CONTAINS_CLUSTERS": 22, + "REASON_RATE_LIMIT_EXCEEDED": 23, + "REASON_INVALID_PAGE_TOKEN": 24, + "REASON_PAGE_TOKEN_FILTER_MISMATCH": 25, + "REASON_PRODUCT_NOT_SUPPORTED_BY_SUBSCRIPTION": 26, + "REASON_NOT_AVAILABLE_CAPACITY_FOR_CLOUD_PROVIDER": 27, } ) @@ -160,7 +180,7 @@ var file_redpanda_api_controlplane_v1beta1_error_proto_rawDesc = []byte{ 0x74, 0x61, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2a, 0xd0, 0x06, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x61, 0x31, 0x2a, 0x9e, 0x08, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, @@ -213,28 +233,41 @@ var file_redpanda_api_controlplane_v1beta1_error_proto_rawDesc = []byte{ 0x55, 0x4f, 0x54, 0x41, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x15, 0x12, 0x24, 0x0a, 0x20, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x41, 0x49, 0x4e, 0x53, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, - 0x45, 0x52, 0x53, 0x10, 0x16, 0x42, 0xcb, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, - 0x0a, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6f, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, - 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, - 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, - 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x52, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x52, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x45, 0x52, 0x53, 0x10, 0x16, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x52, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, + 0x44, 0x45, 0x44, 0x10, 0x17, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x4f, 0x4b, + 0x45, 0x4e, 0x10, 0x18, 0x12, 0x25, 0x0a, 0x21, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, + 0x41, 0x47, 0x45, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, + 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x19, 0x12, 0x30, 0x0a, 0x2c, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x42, 0x59, 0x5f, 0x53, + 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x1a, 0x12, 0x34, 0x0a, + 0x30, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x41, 0x56, 0x41, 0x49, + 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x43, 0x41, 0x50, 0x41, 0x43, 0x49, 0x54, 0x59, 0x5f, 0x46, + 0x4f, 0x52, 0x5f, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, + 0x52, 0x10, 0x1b, 0x42, 0xcb, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0a, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, + 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, + 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, + 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace.pb.go index 55ad65be0a6ce..b9f019667a630 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/namespace.proto diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace_grpc.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace_grpc.pb.go deleted file mode 100644 index 7cd04ef231eb1..0000000000000 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/namespace_grpc.pb.go +++ /dev/null @@ -1,281 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: redpanda/api/controlplane/v1beta1/namespace.proto - -package controlplanev1beta1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - NamespaceService_CreateNamespace_FullMethodName = "/redpanda.api.controlplane.v1beta1.NamespaceService/CreateNamespace" - NamespaceService_UpdateNamespace_FullMethodName = "/redpanda.api.controlplane.v1beta1.NamespaceService/UpdateNamespace" - NamespaceService_GetNamespace_FullMethodName = "/redpanda.api.controlplane.v1beta1.NamespaceService/GetNamespace" - NamespaceService_ListNamespaces_FullMethodName = "/redpanda.api.controlplane.v1beta1.NamespaceService/ListNamespaces" - NamespaceService_DeleteNamespace_FullMethodName = "/redpanda.api.controlplane.v1beta1.NamespaceService/DeleteNamespace" -) - -// NamespaceServiceClient is the client API for NamespaceService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type NamespaceServiceClient interface { - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateNamespace creates the namespace - CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // UpdateNamespace updates the namespace. It returns a Operation that can be used to wait for the Update to be applied. - UpdateNamespace(ctx context.Context, in *UpdateNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // GetNamespace retrieves the namespace's information - GetNamespace(ctx context.Context, in *GetNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) - // ListNamespaces list namespaces. - ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteNamespace deletes a namespace. - DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error) -} - -type namespaceServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewNamespaceServiceClient(cc grpc.ClientConnInterface) NamespaceServiceClient { - return &namespaceServiceClient{cc} -} - -func (c *namespaceServiceClient) CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) { - out := new(Namespace) - err := c.cc.Invoke(ctx, NamespaceService_CreateNamespace_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *namespaceServiceClient) UpdateNamespace(ctx context.Context, in *UpdateNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) { - out := new(Namespace) - err := c.cc.Invoke(ctx, NamespaceService_UpdateNamespace_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *namespaceServiceClient) GetNamespace(ctx context.Context, in *GetNamespaceRequest, opts ...grpc.CallOption) (*Namespace, error) { - out := new(Namespace) - err := c.cc.Invoke(ctx, NamespaceService_GetNamespace_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *namespaceServiceClient) ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) { - out := new(ListNamespacesResponse) - err := c.cc.Invoke(ctx, NamespaceService_ListNamespaces_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *namespaceServiceClient) DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error) { - out := new(DeleteNamespaceResponse) - err := c.cc.Invoke(ctx, NamespaceService_DeleteNamespace_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// NamespaceServiceServer is the server API for NamespaceService service. -// All implementations should embed UnimplementedNamespaceServiceServer -// for forward compatibility -type NamespaceServiceServer interface { - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateNamespace creates the namespace - CreateNamespace(context.Context, *CreateNamespaceRequest) (*Namespace, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // UpdateNamespace updates the namespace. It returns a Operation that can be used to wait for the Update to be applied. - UpdateNamespace(context.Context, *UpdateNamespaceRequest) (*Namespace, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // GetNamespace retrieves the namespace's information - GetNamespace(context.Context, *GetNamespaceRequest) (*Namespace, error) - // ListNamespaces list namespaces. - ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteNamespace deletes a namespace. - DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error) -} - -// UnimplementedNamespaceServiceServer should be embedded to have forward compatible implementations. -type UnimplementedNamespaceServiceServer struct { -} - -func (UnimplementedNamespaceServiceServer) CreateNamespace(context.Context, *CreateNamespaceRequest) (*Namespace, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateNamespace not implemented") -} -func (UnimplementedNamespaceServiceServer) UpdateNamespace(context.Context, *UpdateNamespaceRequest) (*Namespace, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateNamespace not implemented") -} -func (UnimplementedNamespaceServiceServer) GetNamespace(context.Context, *GetNamespaceRequest) (*Namespace, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetNamespace not implemented") -} -func (UnimplementedNamespaceServiceServer) ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListNamespaces not implemented") -} -func (UnimplementedNamespaceServiceServer) DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteNamespace not implemented") -} - -// UnsafeNamespaceServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to NamespaceServiceServer will -// result in compilation errors. -type UnsafeNamespaceServiceServer interface { - mustEmbedUnimplementedNamespaceServiceServer() -} - -func RegisterNamespaceServiceServer(s grpc.ServiceRegistrar, srv NamespaceServiceServer) { - s.RegisterService(&NamespaceService_ServiceDesc, srv) -} - -func _NamespaceService_CreateNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateNamespaceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NamespaceServiceServer).CreateNamespace(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NamespaceService_CreateNamespace_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NamespaceServiceServer).CreateNamespace(ctx, req.(*CreateNamespaceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NamespaceService_UpdateNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateNamespaceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NamespaceServiceServer).UpdateNamespace(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NamespaceService_UpdateNamespace_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NamespaceServiceServer).UpdateNamespace(ctx, req.(*UpdateNamespaceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NamespaceService_GetNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetNamespaceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NamespaceServiceServer).GetNamespace(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NamespaceService_GetNamespace_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NamespaceServiceServer).GetNamespace(ctx, req.(*GetNamespaceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NamespaceService_ListNamespaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListNamespacesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NamespaceServiceServer).ListNamespaces(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NamespaceService_ListNamespaces_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NamespaceServiceServer).ListNamespaces(ctx, req.(*ListNamespacesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NamespaceService_DeleteNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteNamespaceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NamespaceServiceServer).DeleteNamespace(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NamespaceService_DeleteNamespace_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NamespaceServiceServer).DeleteNamespace(ctx, req.(*DeleteNamespaceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// NamespaceService_ServiceDesc is the grpc.ServiceDesc for NamespaceService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var NamespaceService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "redpanda.api.controlplane.v1beta1.NamespaceService", - HandlerType: (*NamespaceServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CreateNamespace", - Handler: _NamespaceService_CreateNamespace_Handler, - }, - { - MethodName: "UpdateNamespace", - Handler: _NamespaceService_UpdateNamespace_Handler, - }, - { - MethodName: "GetNamespace", - Handler: _NamespaceService_GetNamespace_Handler, - }, - { - MethodName: "ListNamespaces", - Handler: _NamespaceService_ListNamespaces_Handler, - }, - { - MethodName: "DeleteNamespace", - Handler: _NamespaceService_DeleteNamespace_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "redpanda/api/controlplane/v1beta1/namespace.proto", -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network.pb.go index ca72488f88cc9..d347d466e8ad9 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/network.proto diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network_grpc.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network_grpc.pb.go deleted file mode 100644 index 563a038809673..0000000000000 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/network_grpc.pb.go +++ /dev/null @@ -1,334 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: redpanda/api/controlplane/v1beta1/network.proto - -package controlplanev1beta1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - NetworkService_CreateNetwork_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/CreateNetwork" - NetworkService_GetNetwork_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/GetNetwork" - NetworkService_ListNetworks_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/ListNetworks" - NetworkService_DeleteNetwork_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/DeleteNetwork" - NetworkService_DummyCreateMetadata_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/DummyCreateMetadata" - NetworkService_DummyDeleteMetadata_FullMethodName = "/redpanda.api.controlplane.v1beta1.NetworkService/DummyDeleteMetadata" -) - -// NetworkServiceClient is the client API for NetworkService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type NetworkServiceClient interface { - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateNetwork create a Redpanda managed network. The input contains the spec, that describes the network. - // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a network has finished. - CreateNetwork(ctx context.Context, in *CreateNetworkRequest, opts ...grpc.CallOption) (*Operation, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // GetNetwork retrieves the network's information - GetNetwork(ctx context.Context, in *GetNetworkRequest, opts ...grpc.CallOption) (*Network, error) - // ListNetworks list networks. - ListNetworks(ctx context.Context, in *ListNetworksRequest, opts ...grpc.CallOption) (*ListNetworksResponse, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteNetwork deletes a network. It returns a Operation, that can be used to wait for the deletion to be finished. - DeleteNetwork(ctx context.Context, in *DeleteNetworkRequest, opts ...grpc.CallOption) (*Operation, error) - // Force openapi generator to generate the CreateClusterResponse, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyCreateMetadata(ctx context.Context, in *CreateNetworkRequest, opts ...grpc.CallOption) (*CreateNetworkMetadata, error) - // Force openapi generator to generate the DeleteClusterResponse, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyDeleteMetadata(ctx context.Context, in *DeleteNetworkRequest, opts ...grpc.CallOption) (*DeleteNetworkMetadata, error) -} - -type networkServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewNetworkServiceClient(cc grpc.ClientConnInterface) NetworkServiceClient { - return &networkServiceClient{cc} -} - -func (c *networkServiceClient) CreateNetwork(ctx context.Context, in *CreateNetworkRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, NetworkService_CreateNetwork_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *networkServiceClient) GetNetwork(ctx context.Context, in *GetNetworkRequest, opts ...grpc.CallOption) (*Network, error) { - out := new(Network) - err := c.cc.Invoke(ctx, NetworkService_GetNetwork_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *networkServiceClient) ListNetworks(ctx context.Context, in *ListNetworksRequest, opts ...grpc.CallOption) (*ListNetworksResponse, error) { - out := new(ListNetworksResponse) - err := c.cc.Invoke(ctx, NetworkService_ListNetworks_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *networkServiceClient) DeleteNetwork(ctx context.Context, in *DeleteNetworkRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, NetworkService_DeleteNetwork_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *networkServiceClient) DummyCreateMetadata(ctx context.Context, in *CreateNetworkRequest, opts ...grpc.CallOption) (*CreateNetworkMetadata, error) { - out := new(CreateNetworkMetadata) - err := c.cc.Invoke(ctx, NetworkService_DummyCreateMetadata_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *networkServiceClient) DummyDeleteMetadata(ctx context.Context, in *DeleteNetworkRequest, opts ...grpc.CallOption) (*DeleteNetworkMetadata, error) { - out := new(DeleteNetworkMetadata) - err := c.cc.Invoke(ctx, NetworkService_DummyDeleteMetadata_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// NetworkServiceServer is the server API for NetworkService service. -// All implementations should embed UnimplementedNetworkServiceServer -// for forward compatibility -type NetworkServiceServer interface { - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // CreateNetwork create a Redpanda managed network. The input contains the spec, that describes the network. - // A Operation is returned. This task allows the caller to find out when the long-running operation of creating a network has finished. - CreateNetwork(context.Context, *CreateNetworkRequest) (*Operation, error) - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // GetNetwork retrieves the network's information - GetNetwork(context.Context, *GetNetworkRequest) (*Network, error) - // ListNetworks list networks. - ListNetworks(context.Context, *ListNetworksRequest) (*ListNetworksResponse, error) - // Ignore these linter rules, because we intentionally return a generic Operation message for all long-running operations. - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - // DeleteNetwork deletes a network. It returns a Operation, that can be used to wait for the deletion to be finished. - DeleteNetwork(context.Context, *DeleteNetworkRequest) (*Operation, error) - // Force openapi generator to generate the CreateClusterResponse, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyCreateMetadata(context.Context, *CreateNetworkRequest) (*CreateNetworkMetadata, error) - // Force openapi generator to generate the DeleteClusterResponse, so we can use it in OpenAPI schema. - // buf:lint:ignore RPC_REQUEST_STANDARD_NAME - // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME - // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE - DummyDeleteMetadata(context.Context, *DeleteNetworkRequest) (*DeleteNetworkMetadata, error) -} - -// UnimplementedNetworkServiceServer should be embedded to have forward compatible implementations. -type UnimplementedNetworkServiceServer struct { -} - -func (UnimplementedNetworkServiceServer) CreateNetwork(context.Context, *CreateNetworkRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateNetwork not implemented") -} -func (UnimplementedNetworkServiceServer) GetNetwork(context.Context, *GetNetworkRequest) (*Network, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetNetwork not implemented") -} -func (UnimplementedNetworkServiceServer) ListNetworks(context.Context, *ListNetworksRequest) (*ListNetworksResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListNetworks not implemented") -} -func (UnimplementedNetworkServiceServer) DeleteNetwork(context.Context, *DeleteNetworkRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteNetwork not implemented") -} -func (UnimplementedNetworkServiceServer) DummyCreateMetadata(context.Context, *CreateNetworkRequest) (*CreateNetworkMetadata, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyCreateMetadata not implemented") -} -func (UnimplementedNetworkServiceServer) DummyDeleteMetadata(context.Context, *DeleteNetworkRequest) (*DeleteNetworkMetadata, error) { - return nil, status.Errorf(codes.Unimplemented, "method DummyDeleteMetadata not implemented") -} - -// UnsafeNetworkServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to NetworkServiceServer will -// result in compilation errors. -type UnsafeNetworkServiceServer interface { - mustEmbedUnimplementedNetworkServiceServer() -} - -func RegisterNetworkServiceServer(s grpc.ServiceRegistrar, srv NetworkServiceServer) { - s.RegisterService(&NetworkService_ServiceDesc, srv) -} - -func _NetworkService_CreateNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateNetworkRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).CreateNetwork(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_CreateNetwork_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).CreateNetwork(ctx, req.(*CreateNetworkRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NetworkService_GetNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetNetworkRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).GetNetwork(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_GetNetwork_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).GetNetwork(ctx, req.(*GetNetworkRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NetworkService_ListNetworks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListNetworksRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).ListNetworks(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_ListNetworks_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).ListNetworks(ctx, req.(*ListNetworksRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NetworkService_DeleteNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteNetworkRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).DeleteNetwork(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_DeleteNetwork_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).DeleteNetwork(ctx, req.(*DeleteNetworkRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NetworkService_DummyCreateMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateNetworkRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).DummyCreateMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_DummyCreateMetadata_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).DummyCreateMetadata(ctx, req.(*CreateNetworkRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _NetworkService_DummyDeleteMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteNetworkRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServiceServer).DummyDeleteMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: NetworkService_DummyDeleteMetadata_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServiceServer).DummyDeleteMetadata(ctx, req.(*DeleteNetworkRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// NetworkService_ServiceDesc is the grpc.ServiceDesc for NetworkService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var NetworkService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "redpanda.api.controlplane.v1beta1.NetworkService", - HandlerType: (*NetworkServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CreateNetwork", - Handler: _NetworkService_CreateNetwork_Handler, - }, - { - MethodName: "GetNetwork", - Handler: _NetworkService_GetNetwork_Handler, - }, - { - MethodName: "ListNetworks", - Handler: _NetworkService_ListNetworks_Handler, - }, - { - MethodName: "DeleteNetwork", - Handler: _NetworkService_DeleteNetwork_Handler, - }, - { - MethodName: "DummyCreateMetadata", - Handler: _NetworkService_DummyCreateMetadata_Handler, - }, - { - MethodName: "DummyDeleteMetadata", - Handler: _NetworkService_DummyDeleteMetadata_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "redpanda/api/controlplane/v1beta1/network.proto", -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation.pb.go index 3d052dc5aa128..537c00108977c 100644 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation.pb.go +++ b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: redpanda/api/controlplane/v1beta1/operation.proto @@ -80,6 +80,64 @@ func (Operation_State) EnumDescriptor() ([]byte, []int) { return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP(), []int{0, 0} } +type Operation_Type int32 + +const ( + Operation_TYPE_UNSPECIFIED Operation_Type = 0 + Operation_TYPE_CREATE_CLUSTER Operation_Type = 1 + Operation_TYPE_UPDATE_CLUSTER Operation_Type = 2 + Operation_TYPE_DELETE_CLUSTER Operation_Type = 3 + Operation_TYPE_CREATE_NETWORK Operation_Type = 4 + Operation_TYPE_DELETE_NETWORK Operation_Type = 5 +) + +// Enum value maps for Operation_Type. +var ( + Operation_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "TYPE_CREATE_CLUSTER", + 2: "TYPE_UPDATE_CLUSTER", + 3: "TYPE_DELETE_CLUSTER", + 4: "TYPE_CREATE_NETWORK", + 5: "TYPE_DELETE_NETWORK", + } + Operation_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "TYPE_CREATE_CLUSTER": 1, + "TYPE_UPDATE_CLUSTER": 2, + "TYPE_DELETE_CLUSTER": 3, + "TYPE_CREATE_NETWORK": 4, + "TYPE_DELETE_NETWORK": 5, + } +) + +func (x Operation_Type) Enum() *Operation_Type { + p := new(Operation_Type) + *p = x + return p +} + +func (x Operation_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Operation_Type) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_controlplane_v1beta1_operation_proto_enumTypes[1].Descriptor() +} + +func (Operation_Type) Type() protoreflect.EnumType { + return &file_redpanda_api_controlplane_v1beta1_operation_proto_enumTypes[1] +} + +func (x Operation_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Operation_Type.Descriptor instead. +func (Operation_Type) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP(), []int{0, 1} +} + // Operation describes a long running operation type Operation struct { state protoimpl.MessageState @@ -102,6 +160,10 @@ type Operation struct { StartedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` // Timestamp when the operation has finished. FinishedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=finished_at,json=finishedAt,proto3" json:"finished_at,omitempty"` + // Operation type. + Type Operation_Type `protobuf:"varint,8,opt,name=type,proto3,enum=redpanda.api.controlplane.v1beta1.Operation_Type" json:"type,omitempty"` + // ID of the associated resource + ResourceId *string `protobuf:"bytes,9,opt,name=resource_id,json=resourceId,proto3,oneof" json:"resource_id,omitempty"` } func (x *Operation) Reset() { @@ -192,6 +254,20 @@ func (x *Operation) GetFinishedAt() *timestamppb.Timestamp { return nil } +func (x *Operation) GetType() Operation_Type { + if x != nil { + return x.Type + } + return Operation_TYPE_UNSPECIFIED +} + +func (x *Operation) GetResourceId() string { + if x != nil && x.ResourceId != nil { + return *x.ResourceId + } + return "" +} + type isOperation_Result interface { isOperation_Result() } @@ -256,6 +332,190 @@ func (x *GetOperationRequest) GetId() string { return "" } +// ListOperationsRequest is the request of ListOperations +type ListOperationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Filter the list of operations by type and state, if not set, operation in + // every state an operation of every type will be returned. + Filter *ListOperationsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + // Limit the number of operations returned per page, the page is capped to 100 operations. + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Value of the next_page_token field returned by the previous response. If + // not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListOperationsRequest) Reset() { + *x = ListOperationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOperationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOperationsRequest) ProtoMessage() {} + +func (x *ListOperationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOperationsRequest.ProtoReflect.Descriptor instead. +func (*ListOperationsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP(), []int{2} +} + +func (x *ListOperationsRequest) GetFilter() *ListOperationsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListOperationsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListOperationsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// ListOperationsResponse is the response of ListOperations +type ListOperationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Operations []*Operation `protobuf:"bytes,1,rep,name=operations,proto3" json:"operations,omitempty"` + // Page Token to fetch the next page. The value can be used as + // next_page_token in the next call to this endpoint. since the pagination is + // cursor based the last page will be encountered when the next page token is + // not set and the operations list is empty. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListOperationsResponse) Reset() { + *x = ListOperationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOperationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOperationsResponse) ProtoMessage() {} + +func (x *ListOperationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOperationsResponse.ProtoReflect.Descriptor instead. +func (*ListOperationsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP(), []int{3} +} + +func (x *ListOperationsResponse) GetOperations() []*Operation { + if x != nil { + return x.Operations + } + return nil +} + +func (x *ListOperationsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type ListOperationsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TypeIn []Operation_Type `protobuf:"varint,1,rep,packed,name=type_in,json=typeIn,proto3,enum=redpanda.api.controlplane.v1beta1.Operation_Type" json:"type_in,omitempty"` + State Operation_State `protobuf:"varint,2,opt,name=state,proto3,enum=redpanda.api.controlplane.v1beta1.Operation_State" json:"state,omitempty"` +} + +func (x *ListOperationsRequest_Filter) Reset() { + *x = ListOperationsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOperationsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOperationsRequest_Filter) ProtoMessage() {} + +func (x *ListOperationsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOperationsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListOperationsRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *ListOperationsRequest_Filter) GetTypeIn() []Operation_Type { + if x != nil { + return x.TypeIn + } + return nil +} + +func (x *ListOperationsRequest_Filter) GetState() Operation_State { + if x != nil { + return x.State + } + return Operation_STATE_UNSPECIFIED +} + var File_redpanda_api_controlplane_v1beta1_operation_proto protoreflect.FileDescriptor var file_redpanda_api_controlplane_v1beta1_operation_proto_rawDesc = []byte{ @@ -281,7 +541,7 @@ var file_redpanda_api_controlplane_v1beta1_operation_proto_rawDesc = []byte{ 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xe1, 0x04, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6f, 0x74, 0x6f, 0x22, 0xfa, 0x06, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0xba, 0x48, 0x14, 0x72, 0x12, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x76, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x32, 0x30, 0x7d, 0x98, 0x01, 0x14, 0x52, 0x02, 0x69, 0x64, @@ -308,80 +568,162 @@ var file_redpanda_api_controlplane_v1beta1_operation_proto_rawDesc = []byte{ 0x12, 0x3b, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x41, 0x74, 0x22, 0x5c, 0x0a, - 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, - 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, - 0x53, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, - 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x3a, 0x40, 0x92, 0x41, 0x3d, - 0x0a, 0x3b, 0x2a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x2c, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, - 0x67, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x40, 0x01, 0x42, 0x08, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0xe3, - 0x04, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0xce, 0x04, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, - 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xd7, 0x03, 0x92, 0x41, 0x99, - 0x03, 0x12, 0x0d, 0x47, 0x65, 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x5b, 0x47, 0x65, 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x69, 0x63, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, - 0x6c, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x20, 0x6f, - 0x66, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x3d, 0x0a, - 0x03, 0x32, 0x30, 0x30, 0x12, 0x36, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, - 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x95, 0x01, 0x0a, - 0x03, 0x34, 0x30, 0x34, 0x12, 0x8d, 0x01, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, - 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x68, 0x0a, 0x26, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, - 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, - 0x78, 0x69, 0x73, 0x74, 0x12, 0x3e, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, - 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, - 0x49, 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, - 0x74, 0x2e, 0x22, 0x7d, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, - 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x16, 0x72, 0x65, - 0x61, 0x64, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, - 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x62, - 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, - 0x7b, 0x69, 0x64, 0x7d, 0x42, 0xcf, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x41, 0x74, 0x12, 0x45, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0a, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x22, 0x5c, 0x0a, 0x05, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, + 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, + 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0x99, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, 0x01, + 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, + 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, + 0x45, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, + 0x52, 0x4b, 0x10, 0x05, 0x3a, 0x40, 0x92, 0x41, 0x3d, 0x0a, 0x3b, 0x2a, 0x09, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x2c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x6f, + 0x6e, 0x67, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x40, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xf6, 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x57, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x3f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xba, + 0x48, 0x06, 0x1a, 0x04, 0x18, 0x64, 0x28, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, + 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x1a, 0xbc, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x07, + 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x31, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, + 0x42, 0x12, 0xba, 0x48, 0x0f, 0x92, 0x01, 0x0c, 0x18, 0x01, 0x22, 0x08, 0x82, 0x01, 0x05, 0x10, + 0x01, 0x22, 0x01, 0x00, 0x52, 0x06, 0x74, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x12, 0x52, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, + 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x22, 0xb7, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x0a, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x27, 0x92, + 0x41, 0x24, 0x32, 0x1f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0xa0, 0x01, 0x64, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xb6, 0x08, 0x0a, 0x10, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0xce, 0x04, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x36, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xd7, 0x03, 0x92, 0x41, 0x99, 0x03, 0x12, 0x0d, 0x47, + 0x65, 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x5b, 0x47, 0x65, + 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x20, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x20, 0x6f, 0x66, + 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x3d, 0x0a, 0x03, 0x32, 0x30, 0x30, + 0x12, 0x36, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x30, 0x0a, 0x2e, 0x1a, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x6f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, - 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, - 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, - 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, - 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, - 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, - 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, - 0x02, 0x2d, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x24, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, - 0x3a, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x95, 0x01, 0x0a, 0x03, 0x34, 0x30, 0x34, + 0x12, 0x8d, 0x01, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, + 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x68, 0x0a, 0x26, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, + 0x44, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, + 0x12, 0x3e, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x36, 0x2c, 0x22, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x3a, 0x22, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x49, 0x44, 0x20, 0x64, + 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2e, 0x22, 0x7d, + 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, + 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, + 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x12, 0xd0, 0x03, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc8, 0x02, 0x92, 0x41, 0x8f, 0x02, 0x12, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x1a, 0x5a, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x69, 0x63, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x6e, + 0x67, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x4a, 0x0a, 0x03, + 0x32, 0x30, 0x30, 0x12, 0x43, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x3d, 0x0a, 0x3b, 0x1a, 0x39, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0x54, 0x0a, 0x03, 0x35, 0x30, 0x30, 0x12, + 0x4d, 0x0a, 0x33, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0xb2, 0xbf, + 0x07, 0x16, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, + 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x42, 0xcf, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x6f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0xa2, 0x02, 0x03, 0x52, 0x41, 0x43, 0xaa, 0x02, 0x21, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, + 0x2d, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x24, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -396,30 +738,41 @@ func file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescGZIP() []byte return file_redpanda_api_controlplane_v1beta1_operation_proto_rawDescData } -var file_redpanda_api_controlplane_v1beta1_operation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_redpanda_api_controlplane_v1beta1_operation_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_redpanda_api_controlplane_v1beta1_operation_proto_goTypes = []interface{}{ - (Operation_State)(0), // 0: redpanda.api.controlplane.v1beta1.Operation.State - (*Operation)(nil), // 1: redpanda.api.controlplane.v1beta1.Operation - (*GetOperationRequest)(nil), // 2: redpanda.api.controlplane.v1beta1.GetOperationRequest - (*anypb.Any)(nil), // 3: google.protobuf.Any - (*status.Status)(nil), // 4: google.rpc.Status - (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp + (Operation_State)(0), // 0: redpanda.api.controlplane.v1beta1.Operation.State + (Operation_Type)(0), // 1: redpanda.api.controlplane.v1beta1.Operation.Type + (*Operation)(nil), // 2: redpanda.api.controlplane.v1beta1.Operation + (*GetOperationRequest)(nil), // 3: redpanda.api.controlplane.v1beta1.GetOperationRequest + (*ListOperationsRequest)(nil), // 4: redpanda.api.controlplane.v1beta1.ListOperationsRequest + (*ListOperationsResponse)(nil), // 5: redpanda.api.controlplane.v1beta1.ListOperationsResponse + (*ListOperationsRequest_Filter)(nil), // 6: redpanda.api.controlplane.v1beta1.ListOperationsRequest.Filter + (*anypb.Any)(nil), // 7: google.protobuf.Any + (*status.Status)(nil), // 8: google.rpc.Status + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } var file_redpanda_api_controlplane_v1beta1_operation_proto_depIdxs = []int32{ - 3, // 0: redpanda.api.controlplane.v1beta1.Operation.metadata:type_name -> google.protobuf.Any - 0, // 1: redpanda.api.controlplane.v1beta1.Operation.state:type_name -> redpanda.api.controlplane.v1beta1.Operation.State - 4, // 2: redpanda.api.controlplane.v1beta1.Operation.error:type_name -> google.rpc.Status - 3, // 3: redpanda.api.controlplane.v1beta1.Operation.response:type_name -> google.protobuf.Any - 5, // 4: redpanda.api.controlplane.v1beta1.Operation.started_at:type_name -> google.protobuf.Timestamp - 5, // 5: redpanda.api.controlplane.v1beta1.Operation.finished_at:type_name -> google.protobuf.Timestamp - 2, // 6: redpanda.api.controlplane.v1beta1.OperationService.GetOperation:input_type -> redpanda.api.controlplane.v1beta1.GetOperationRequest - 1, // 7: redpanda.api.controlplane.v1beta1.OperationService.GetOperation:output_type -> redpanda.api.controlplane.v1beta1.Operation - 7, // [7:8] is the sub-list for method output_type - 6, // [6:7] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 7, // 0: redpanda.api.controlplane.v1beta1.Operation.metadata:type_name -> google.protobuf.Any + 0, // 1: redpanda.api.controlplane.v1beta1.Operation.state:type_name -> redpanda.api.controlplane.v1beta1.Operation.State + 8, // 2: redpanda.api.controlplane.v1beta1.Operation.error:type_name -> google.rpc.Status + 7, // 3: redpanda.api.controlplane.v1beta1.Operation.response:type_name -> google.protobuf.Any + 9, // 4: redpanda.api.controlplane.v1beta1.Operation.started_at:type_name -> google.protobuf.Timestamp + 9, // 5: redpanda.api.controlplane.v1beta1.Operation.finished_at:type_name -> google.protobuf.Timestamp + 1, // 6: redpanda.api.controlplane.v1beta1.Operation.type:type_name -> redpanda.api.controlplane.v1beta1.Operation.Type + 6, // 7: redpanda.api.controlplane.v1beta1.ListOperationsRequest.filter:type_name -> redpanda.api.controlplane.v1beta1.ListOperationsRequest.Filter + 2, // 8: redpanda.api.controlplane.v1beta1.ListOperationsResponse.operations:type_name -> redpanda.api.controlplane.v1beta1.Operation + 1, // 9: redpanda.api.controlplane.v1beta1.ListOperationsRequest.Filter.type_in:type_name -> redpanda.api.controlplane.v1beta1.Operation.Type + 0, // 10: redpanda.api.controlplane.v1beta1.ListOperationsRequest.Filter.state:type_name -> redpanda.api.controlplane.v1beta1.Operation.State + 3, // 11: redpanda.api.controlplane.v1beta1.OperationService.GetOperation:input_type -> redpanda.api.controlplane.v1beta1.GetOperationRequest + 4, // 12: redpanda.api.controlplane.v1beta1.OperationService.ListOperations:input_type -> redpanda.api.controlplane.v1beta1.ListOperationsRequest + 2, // 13: redpanda.api.controlplane.v1beta1.OperationService.GetOperation:output_type -> redpanda.api.controlplane.v1beta1.Operation + 5, // 14: redpanda.api.controlplane.v1beta1.OperationService.ListOperations:output_type -> redpanda.api.controlplane.v1beta1.ListOperationsResponse + 13, // [13:15] is the sub-list for method output_type + 11, // [11:13] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_redpanda_api_controlplane_v1beta1_operation_proto_init() } @@ -452,6 +805,42 @@ func file_redpanda_api_controlplane_v1beta1_operation_proto_init() { return nil } } + file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOperationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOperationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOperationsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_redpanda_api_controlplane_v1beta1_operation_proto_msgTypes[0].OneofWrappers = []interface{}{ (*Operation_Error)(nil), @@ -462,8 +851,8 @@ func file_redpanda_api_controlplane_v1beta1_operation_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_redpanda_api_controlplane_v1beta1_operation_proto_rawDesc, - NumEnums: 1, - NumMessages: 2, + NumEnums: 2, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation_grpc.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation_grpc.pb.go deleted file mode 100644 index 7001e2b8e7281..0000000000000 --- a/src/go/rpk/proto/gen/go/redpanda/api/controlplane/v1beta1/operation_grpc.pb.go +++ /dev/null @@ -1,107 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: redpanda/api/controlplane/v1beta1/operation.proto - -package controlplanev1beta1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - OperationService_GetOperation_FullMethodName = "/redpanda.api.controlplane.v1beta1.OperationService/GetOperation" -) - -// OperationServiceClient is the client API for OperationService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type OperationServiceClient interface { - GetOperation(ctx context.Context, in *GetOperationRequest, opts ...grpc.CallOption) (*Operation, error) -} - -type operationServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewOperationServiceClient(cc grpc.ClientConnInterface) OperationServiceClient { - return &operationServiceClient{cc} -} - -func (c *operationServiceClient) GetOperation(ctx context.Context, in *GetOperationRequest, opts ...grpc.CallOption) (*Operation, error) { - out := new(Operation) - err := c.cc.Invoke(ctx, OperationService_GetOperation_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// OperationServiceServer is the server API for OperationService service. -// All implementations should embed UnimplementedOperationServiceServer -// for forward compatibility -type OperationServiceServer interface { - GetOperation(context.Context, *GetOperationRequest) (*Operation, error) -} - -// UnimplementedOperationServiceServer should be embedded to have forward compatible implementations. -type UnimplementedOperationServiceServer struct { -} - -func (UnimplementedOperationServiceServer) GetOperation(context.Context, *GetOperationRequest) (*Operation, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOperation not implemented") -} - -// UnsafeOperationServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to OperationServiceServer will -// result in compilation errors. -type UnsafeOperationServiceServer interface { - mustEmbedUnimplementedOperationServiceServer() -} - -func RegisterOperationServiceServer(s grpc.ServiceRegistrar, srv OperationServiceServer) { - s.RegisterService(&OperationService_ServiceDesc, srv) -} - -func _OperationService_GetOperation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetOperationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(OperationServiceServer).GetOperation(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: OperationService_GetOperation_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(OperationServiceServer).GetOperation(ctx, req.(*GetOperationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// OperationService_ServiceDesc is the grpc.ServiceDesc for OperationService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var OperationService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "redpanda.api.controlplane.v1beta1.OperationService", - HandlerType: (*OperationServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetOperation", - Handler: _OperationService_GetOperation_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "redpanda/api/controlplane/v1beta1/operation.proto", -} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/acl.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/acl.pb.go new file mode 100644 index 0000000000000..a5a126736fb84 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/acl.pb.go @@ -0,0 +1,1750 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/acl.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + status "google.golang.org/genproto/googleapis/rpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ACL_ResourceType int32 + +const ( + ACL_RESOURCE_TYPE_UNSPECIFIED ACL_ResourceType = 0 + ACL_RESOURCE_TYPE_ANY ACL_ResourceType = 1 + ACL_RESOURCE_TYPE_TOPIC ACL_ResourceType = 2 + ACL_RESOURCE_TYPE_GROUP ACL_ResourceType = 3 + ACL_RESOURCE_TYPE_CLUSTER ACL_ResourceType = 4 + ACL_RESOURCE_TYPE_TRANSACTIONAL_ID ACL_ResourceType = 5 + ACL_RESOURCE_TYPE_DELEGATION_TOKEN ACL_ResourceType = 6 + ACL_RESOURCE_TYPE_USER ACL_ResourceType = 7 +) + +// Enum value maps for ACL_ResourceType. +var ( + ACL_ResourceType_name = map[int32]string{ + 0: "RESOURCE_TYPE_UNSPECIFIED", + 1: "RESOURCE_TYPE_ANY", + 2: "RESOURCE_TYPE_TOPIC", + 3: "RESOURCE_TYPE_GROUP", + 4: "RESOURCE_TYPE_CLUSTER", + 5: "RESOURCE_TYPE_TRANSACTIONAL_ID", + 6: "RESOURCE_TYPE_DELEGATION_TOKEN", + 7: "RESOURCE_TYPE_USER", + } + ACL_ResourceType_value = map[string]int32{ + "RESOURCE_TYPE_UNSPECIFIED": 0, + "RESOURCE_TYPE_ANY": 1, + "RESOURCE_TYPE_TOPIC": 2, + "RESOURCE_TYPE_GROUP": 3, + "RESOURCE_TYPE_CLUSTER": 4, + "RESOURCE_TYPE_TRANSACTIONAL_ID": 5, + "RESOURCE_TYPE_DELEGATION_TOKEN": 6, + "RESOURCE_TYPE_USER": 7, + } +) + +func (x ACL_ResourceType) Enum() *ACL_ResourceType { + p := new(ACL_ResourceType) + *p = x + return p +} + +func (x ACL_ResourceType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ACL_ResourceType) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[0].Descriptor() +} + +func (ACL_ResourceType) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[0] +} + +func (x ACL_ResourceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ACL_ResourceType.Descriptor instead. +func (ACL_ResourceType) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{0, 0} +} + +type ACL_ResourcePatternType int32 + +const ( + ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED ACL_ResourcePatternType = 0 + ACL_RESOURCE_PATTERN_TYPE_ANY ACL_ResourcePatternType = 1 + ACL_RESOURCE_PATTERN_TYPE_MATCH ACL_ResourcePatternType = 2 + ACL_RESOURCE_PATTERN_TYPE_LITERAL ACL_ResourcePatternType = 3 + ACL_RESOURCE_PATTERN_TYPE_PREFIXED ACL_ResourcePatternType = 4 +) + +// Enum value maps for ACL_ResourcePatternType. +var ( + ACL_ResourcePatternType_name = map[int32]string{ + 0: "RESOURCE_PATTERN_TYPE_UNSPECIFIED", + 1: "RESOURCE_PATTERN_TYPE_ANY", + 2: "RESOURCE_PATTERN_TYPE_MATCH", + 3: "RESOURCE_PATTERN_TYPE_LITERAL", + 4: "RESOURCE_PATTERN_TYPE_PREFIXED", + } + ACL_ResourcePatternType_value = map[string]int32{ + "RESOURCE_PATTERN_TYPE_UNSPECIFIED": 0, + "RESOURCE_PATTERN_TYPE_ANY": 1, + "RESOURCE_PATTERN_TYPE_MATCH": 2, + "RESOURCE_PATTERN_TYPE_LITERAL": 3, + "RESOURCE_PATTERN_TYPE_PREFIXED": 4, + } +) + +func (x ACL_ResourcePatternType) Enum() *ACL_ResourcePatternType { + p := new(ACL_ResourcePatternType) + *p = x + return p +} + +func (x ACL_ResourcePatternType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ACL_ResourcePatternType) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[1].Descriptor() +} + +func (ACL_ResourcePatternType) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[1] +} + +func (x ACL_ResourcePatternType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ACL_ResourcePatternType.Descriptor instead. +func (ACL_ResourcePatternType) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{0, 1} +} + +type ACL_Operation int32 + +const ( + ACL_OPERATION_UNSPECIFIED ACL_Operation = 0 + ACL_OPERATION_ANY ACL_Operation = 1 + ACL_OPERATION_ALL ACL_Operation = 2 + ACL_OPERATION_READ ACL_Operation = 3 + ACL_OPERATION_WRITE ACL_Operation = 4 + ACL_OPERATION_CREATE ACL_Operation = 5 + ACL_OPERATION_DELETE ACL_Operation = 6 + ACL_OPERATION_ALTER ACL_Operation = 7 + ACL_OPERATION_DESCRIBE ACL_Operation = 8 + ACL_OPERATION_CLUSTER_ACTION ACL_Operation = 9 + ACL_OPERATION_DESCRIBE_CONFIGS ACL_Operation = 10 + ACL_OPERATION_ALTER_CONFIGS ACL_Operation = 11 + ACL_OPERATION_IDEMPOTENT_WRITE ACL_Operation = 12 + ACL_OPERATION_CREATE_TOKENS ACL_Operation = 13 + ACL_OPERATION_DESCRIBE_TOKENS ACL_Operation = 14 +) + +// Enum value maps for ACL_Operation. +var ( + ACL_Operation_name = map[int32]string{ + 0: "OPERATION_UNSPECIFIED", + 1: "OPERATION_ANY", + 2: "OPERATION_ALL", + 3: "OPERATION_READ", + 4: "OPERATION_WRITE", + 5: "OPERATION_CREATE", + 6: "OPERATION_DELETE", + 7: "OPERATION_ALTER", + 8: "OPERATION_DESCRIBE", + 9: "OPERATION_CLUSTER_ACTION", + 10: "OPERATION_DESCRIBE_CONFIGS", + 11: "OPERATION_ALTER_CONFIGS", + 12: "OPERATION_IDEMPOTENT_WRITE", + 13: "OPERATION_CREATE_TOKENS", + 14: "OPERATION_DESCRIBE_TOKENS", + } + ACL_Operation_value = map[string]int32{ + "OPERATION_UNSPECIFIED": 0, + "OPERATION_ANY": 1, + "OPERATION_ALL": 2, + "OPERATION_READ": 3, + "OPERATION_WRITE": 4, + "OPERATION_CREATE": 5, + "OPERATION_DELETE": 6, + "OPERATION_ALTER": 7, + "OPERATION_DESCRIBE": 8, + "OPERATION_CLUSTER_ACTION": 9, + "OPERATION_DESCRIBE_CONFIGS": 10, + "OPERATION_ALTER_CONFIGS": 11, + "OPERATION_IDEMPOTENT_WRITE": 12, + "OPERATION_CREATE_TOKENS": 13, + "OPERATION_DESCRIBE_TOKENS": 14, + } +) + +func (x ACL_Operation) Enum() *ACL_Operation { + p := new(ACL_Operation) + *p = x + return p +} + +func (x ACL_Operation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ACL_Operation) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[2].Descriptor() +} + +func (ACL_Operation) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[2] +} + +func (x ACL_Operation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ACL_Operation.Descriptor instead. +func (ACL_Operation) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{0, 2} +} + +type ACL_PermissionType int32 + +const ( + ACL_PERMISSION_TYPE_UNSPECIFIED ACL_PermissionType = 0 + ACL_PERMISSION_TYPE_ANY ACL_PermissionType = 1 + ACL_PERMISSION_TYPE_DENY ACL_PermissionType = 2 + ACL_PERMISSION_TYPE_ALLOW ACL_PermissionType = 3 +) + +// Enum value maps for ACL_PermissionType. +var ( + ACL_PermissionType_name = map[int32]string{ + 0: "PERMISSION_TYPE_UNSPECIFIED", + 1: "PERMISSION_TYPE_ANY", + 2: "PERMISSION_TYPE_DENY", + 3: "PERMISSION_TYPE_ALLOW", + } + ACL_PermissionType_value = map[string]int32{ + "PERMISSION_TYPE_UNSPECIFIED": 0, + "PERMISSION_TYPE_ANY": 1, + "PERMISSION_TYPE_DENY": 2, + "PERMISSION_TYPE_ALLOW": 3, + } +) + +func (x ACL_PermissionType) Enum() *ACL_PermissionType { + p := new(ACL_PermissionType) + *p = x + return p +} + +func (x ACL_PermissionType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ACL_PermissionType) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[3].Descriptor() +} + +func (ACL_PermissionType) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes[3] +} + +func (x ACL_PermissionType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ACL_PermissionType.Descriptor instead. +func (ACL_PermissionType) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{0, 3} +} + +type ACL struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ACL) Reset() { + *x = ACL{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ACL) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ACL) ProtoMessage() {} + +func (x *ACL) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ACL.ProtoReflect.Descriptor instead. +func (*ACL) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{0} +} + +type ListACLsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListACLsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListACLsRequest) Reset() { + *x = ListACLsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListACLsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACLsRequest) ProtoMessage() {} + +func (x *ListACLsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACLsRequest.ProtoReflect.Descriptor instead. +func (*ListACLsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{1} +} + +func (x *ListACLsRequest) GetFilter() *ListACLsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListACLsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListACLsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type ListACLsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Resources []*ListACLsResponse_Resource `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"` +} + +func (x *ListACLsResponse) Reset() { + *x = ListACLsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListACLsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACLsResponse) ProtoMessage() {} + +func (x *ListACLsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACLsResponse.ProtoReflect.Descriptor instead. +func (*ListACLsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{2} +} + +func (x *ListACLsResponse) GetResources() []*ListACLsResponse_Resource { + if x != nil { + return x.Resources + } + return nil +} + +type CreateACLRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ResourceType determines the type of the resource (Topic, ConsumerGroup etc) this + // ACL shall target. + ResourceType ACL_ResourceType `protobuf:"varint,1,opt,name=resource_type,json=resourceType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourceType" json:"resource_type,omitempty"` + // ResourceName is the name of the resource this acl entry will be on. + // For requests with resource_type CLUSTER, this will default to the expected + // value "kafka-cluster". + ResourceName string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` + // ResourcePattern type determines the strategy how the provided resource_name + // is matched (exact match, prefixed, ...) against the actual resource names. + ResourcePatternType ACL_ResourcePatternType `protobuf:"varint,3,opt,name=resource_pattern_type,json=resourcePatternType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourcePatternType" json:"resource_pattern_type,omitempty"` + // Principal is the user to apply this acl for. With the Kafka simple + // authorizer, this must begin with "User:". + Principal string `protobuf:"bytes,4,opt,name=principal,proto3" json:"principal,omitempty"` + // Host is the host address to use for this acl. Each host to allow + // the principal access from must be specified as a new creation. + Host string `protobuf:"bytes,5,opt,name=host,proto3" json:"host,omitempty"` + // Operation is the operation that shall be allowed (e.g. READ). + Operation ACL_Operation `protobuf:"varint,6,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_Operation" json:"operation,omitempty"` + // PermissionType determines whether the operation should be allowed or denied. + PermissionType ACL_PermissionType `protobuf:"varint,7,opt,name=permission_type,json=permissionType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_PermissionType" json:"permission_type,omitempty"` +} + +func (x *CreateACLRequest) Reset() { + *x = CreateACLRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateACLRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateACLRequest) ProtoMessage() {} + +func (x *CreateACLRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateACLRequest.ProtoReflect.Descriptor instead. +func (*CreateACLRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateACLRequest) GetResourceType() ACL_ResourceType { + if x != nil { + return x.ResourceType + } + return ACL_RESOURCE_TYPE_UNSPECIFIED +} + +func (x *CreateACLRequest) GetResourceName() string { + if x != nil { + return x.ResourceName + } + return "" +} + +func (x *CreateACLRequest) GetResourcePatternType() ACL_ResourcePatternType { + if x != nil { + return x.ResourcePatternType + } + return ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED +} + +func (x *CreateACLRequest) GetPrincipal() string { + if x != nil { + return x.Principal + } + return "" +} + +func (x *CreateACLRequest) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *CreateACLRequest) GetOperation() ACL_Operation { + if x != nil { + return x.Operation + } + return ACL_OPERATION_UNSPECIFIED +} + +func (x *CreateACLRequest) GetPermissionType() ACL_PermissionType { + if x != nil { + return x.PermissionType + } + return ACL_PERMISSION_TYPE_UNSPECIFIED +} + +type CreateACLResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CreateACLResponse) Reset() { + *x = CreateACLResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateACLResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateACLResponse) ProtoMessage() {} + +func (x *CreateACLResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateACLResponse.ProtoReflect.Descriptor instead. +func (*CreateACLResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{4} +} + +type DeleteACLsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *DeleteACLsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *DeleteACLsRequest) Reset() { + *x = DeleteACLsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteACLsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteACLsRequest) ProtoMessage() {} + +func (x *DeleteACLsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteACLsRequest.ProtoReflect.Descriptor instead. +func (*DeleteACLsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteACLsRequest) GetFilter() *DeleteACLsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type DeleteACLsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MatchingAcls []*DeleteACLsResponse_MatchingACL `protobuf:"bytes,1,rep,name=matching_acls,json=matchingAcls,proto3" json:"matching_acls,omitempty"` +} + +func (x *DeleteACLsResponse) Reset() { + *x = DeleteACLsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteACLsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteACLsResponse) ProtoMessage() {} + +func (x *DeleteACLsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteACLsResponse.ProtoReflect.Descriptor instead. +func (*DeleteACLsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteACLsResponse) GetMatchingAcls() []*DeleteACLsResponse_MatchingACL { + if x != nil { + return x.MatchingAcls + } + return nil +} + +type ListACLsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceType ACL_ResourceType `protobuf:"varint,1,opt,name=resource_type,json=resourceType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourceType" json:"resource_type,omitempty"` + ResourceName *string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3,oneof" json:"resource_name,omitempty"` + ResourcePatternType ACL_ResourcePatternType `protobuf:"varint,3,opt,name=resource_pattern_type,json=resourcePatternType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourcePatternType" json:"resource_pattern_type,omitempty"` + Principal *string `protobuf:"bytes,4,opt,name=principal,proto3,oneof" json:"principal,omitempty"` + Host *string `protobuf:"bytes,5,opt,name=host,proto3,oneof" json:"host,omitempty"` + Operation ACL_Operation `protobuf:"varint,6,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_Operation" json:"operation,omitempty"` + PermissionType ACL_PermissionType `protobuf:"varint,7,opt,name=permission_type,json=permissionType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_PermissionType" json:"permission_type,omitempty"` +} + +func (x *ListACLsRequest_Filter) Reset() { + *x = ListACLsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListACLsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACLsRequest_Filter) ProtoMessage() {} + +func (x *ListACLsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACLsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListACLsRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *ListACLsRequest_Filter) GetResourceType() ACL_ResourceType { + if x != nil { + return x.ResourceType + } + return ACL_RESOURCE_TYPE_UNSPECIFIED +} + +func (x *ListACLsRequest_Filter) GetResourceName() string { + if x != nil && x.ResourceName != nil { + return *x.ResourceName + } + return "" +} + +func (x *ListACLsRequest_Filter) GetResourcePatternType() ACL_ResourcePatternType { + if x != nil { + return x.ResourcePatternType + } + return ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED +} + +func (x *ListACLsRequest_Filter) GetPrincipal() string { + if x != nil && x.Principal != nil { + return *x.Principal + } + return "" +} + +func (x *ListACLsRequest_Filter) GetHost() string { + if x != nil && x.Host != nil { + return *x.Host + } + return "" +} + +func (x *ListACLsRequest_Filter) GetOperation() ACL_Operation { + if x != nil { + return x.Operation + } + return ACL_OPERATION_UNSPECIFIED +} + +func (x *ListACLsRequest_Filter) GetPermissionType() ACL_PermissionType { + if x != nil { + return x.PermissionType + } + return ACL_PERMISSION_TYPE_UNSPECIFIED +} + +type ListACLsResponse_Policy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Principal string `protobuf:"bytes,1,opt,name=principal,proto3" json:"principal,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Operation ACL_Operation `protobuf:"varint,3,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_Operation" json:"operation,omitempty"` + PermissionType ACL_PermissionType `protobuf:"varint,4,opt,name=permission_type,json=permissionType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_PermissionType" json:"permission_type,omitempty"` +} + +func (x *ListACLsResponse_Policy) Reset() { + *x = ListACLsResponse_Policy{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListACLsResponse_Policy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACLsResponse_Policy) ProtoMessage() {} + +func (x *ListACLsResponse_Policy) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACLsResponse_Policy.ProtoReflect.Descriptor instead. +func (*ListACLsResponse_Policy) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *ListACLsResponse_Policy) GetPrincipal() string { + if x != nil { + return x.Principal + } + return "" +} + +func (x *ListACLsResponse_Policy) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *ListACLsResponse_Policy) GetOperation() ACL_Operation { + if x != nil { + return x.Operation + } + return ACL_OPERATION_UNSPECIFIED +} + +func (x *ListACLsResponse_Policy) GetPermissionType() ACL_PermissionType { + if x != nil { + return x.PermissionType + } + return ACL_PERMISSION_TYPE_UNSPECIFIED +} + +type ListACLsResponse_Resource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceType ACL_ResourceType `protobuf:"varint,1,opt,name=resource_type,json=resourceType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourceType" json:"resource_type,omitempty"` + ResourceName string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` + ResourcePatternType ACL_ResourcePatternType `protobuf:"varint,3,opt,name=resource_pattern_type,json=resourcePatternType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourcePatternType" json:"resource_pattern_type,omitempty"` + Acls []*ListACLsResponse_Policy `protobuf:"bytes,4,rep,name=acls,proto3" json:"acls,omitempty"` +} + +func (x *ListACLsResponse_Resource) Reset() { + *x = ListACLsResponse_Resource{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListACLsResponse_Resource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACLsResponse_Resource) ProtoMessage() {} + +func (x *ListACLsResponse_Resource) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACLsResponse_Resource.ProtoReflect.Descriptor instead. +func (*ListACLsResponse_Resource) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *ListACLsResponse_Resource) GetResourceType() ACL_ResourceType { + if x != nil { + return x.ResourceType + } + return ACL_RESOURCE_TYPE_UNSPECIFIED +} + +func (x *ListACLsResponse_Resource) GetResourceName() string { + if x != nil { + return x.ResourceName + } + return "" +} + +func (x *ListACLsResponse_Resource) GetResourcePatternType() ACL_ResourcePatternType { + if x != nil { + return x.ResourcePatternType + } + return ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED +} + +func (x *ListACLsResponse_Resource) GetAcls() []*ListACLsResponse_Policy { + if x != nil { + return x.Acls + } + return nil +} + +type DeleteACLsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceType ACL_ResourceType `protobuf:"varint,1,opt,name=resource_type,json=resourceType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourceType" json:"resource_type,omitempty"` + ResourceName *string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3,oneof" json:"resource_name,omitempty"` + ResourcePatternType ACL_ResourcePatternType `protobuf:"varint,3,opt,name=resource_pattern_type,json=resourcePatternType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourcePatternType" json:"resource_pattern_type,omitempty"` + Principal *string `protobuf:"bytes,4,opt,name=principal,proto3,oneof" json:"principal,omitempty"` + Host *string `protobuf:"bytes,5,opt,name=host,proto3,oneof" json:"host,omitempty"` + Operation ACL_Operation `protobuf:"varint,6,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_Operation" json:"operation,omitempty"` + PermissionType ACL_PermissionType `protobuf:"varint,7,opt,name=permission_type,json=permissionType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_PermissionType" json:"permission_type,omitempty"` +} + +func (x *DeleteACLsRequest_Filter) Reset() { + *x = DeleteACLsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteACLsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteACLsRequest_Filter) ProtoMessage() {} + +func (x *DeleteACLsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteACLsRequest_Filter.ProtoReflect.Descriptor instead. +func (*DeleteACLsRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *DeleteACLsRequest_Filter) GetResourceType() ACL_ResourceType { + if x != nil { + return x.ResourceType + } + return ACL_RESOURCE_TYPE_UNSPECIFIED +} + +func (x *DeleteACLsRequest_Filter) GetResourceName() string { + if x != nil && x.ResourceName != nil { + return *x.ResourceName + } + return "" +} + +func (x *DeleteACLsRequest_Filter) GetResourcePatternType() ACL_ResourcePatternType { + if x != nil { + return x.ResourcePatternType + } + return ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED +} + +func (x *DeleteACLsRequest_Filter) GetPrincipal() string { + if x != nil && x.Principal != nil { + return *x.Principal + } + return "" +} + +func (x *DeleteACLsRequest_Filter) GetHost() string { + if x != nil && x.Host != nil { + return *x.Host + } + return "" +} + +func (x *DeleteACLsRequest_Filter) GetOperation() ACL_Operation { + if x != nil { + return x.Operation + } + return ACL_OPERATION_UNSPECIFIED +} + +func (x *DeleteACLsRequest_Filter) GetPermissionType() ACL_PermissionType { + if x != nil { + return x.PermissionType + } + return ACL_PERMISSION_TYPE_UNSPECIFIED +} + +type DeleteACLsResponse_MatchingACL struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceType ACL_ResourceType `protobuf:"varint,1,opt,name=resource_type,json=resourceType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourceType" json:"resource_type,omitempty"` + ResourceName string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` + ResourcePatternType ACL_ResourcePatternType `protobuf:"varint,3,opt,name=resource_pattern_type,json=resourcePatternType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_ResourcePatternType" json:"resource_pattern_type,omitempty"` + Principal string `protobuf:"bytes,4,opt,name=principal,proto3" json:"principal,omitempty"` + Host string `protobuf:"bytes,5,opt,name=host,proto3" json:"host,omitempty"` + Operation ACL_Operation `protobuf:"varint,6,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_Operation" json:"operation,omitempty"` + PermissionType ACL_PermissionType `protobuf:"varint,7,opt,name=permission_type,json=permissionType,proto3,enum=redpanda.api.dataplane.v1alpha1.ACL_PermissionType" json:"permission_type,omitempty"` + Error *status.Status `protobuf:"bytes,8,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *DeleteACLsResponse_MatchingACL) Reset() { + *x = DeleteACLsResponse_MatchingACL{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteACLsResponse_MatchingACL) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteACLsResponse_MatchingACL) ProtoMessage() {} + +func (x *DeleteACLsResponse_MatchingACL) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteACLsResponse_MatchingACL.ProtoReflect.Descriptor instead. +func (*DeleteACLsResponse_MatchingACL) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *DeleteACLsResponse_MatchingACL) GetResourceType() ACL_ResourceType { + if x != nil { + return x.ResourceType + } + return ACL_RESOURCE_TYPE_UNSPECIFIED +} + +func (x *DeleteACLsResponse_MatchingACL) GetResourceName() string { + if x != nil { + return x.ResourceName + } + return "" +} + +func (x *DeleteACLsResponse_MatchingACL) GetResourcePatternType() ACL_ResourcePatternType { + if x != nil { + return x.ResourcePatternType + } + return ACL_RESOURCE_PATTERN_TYPE_UNSPECIFIED +} + +func (x *DeleteACLsResponse_MatchingACL) GetPrincipal() string { + if x != nil { + return x.Principal + } + return "" +} + +func (x *DeleteACLsResponse_MatchingACL) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DeleteACLsResponse_MatchingACL) GetOperation() ACL_Operation { + if x != nil { + return x.Operation + } + return ACL_OPERATION_UNSPECIFIED +} + +func (x *DeleteACLsResponse_MatchingACL) GetPermissionType() ACL_PermissionType { + if x != nil { + return x.PermissionType + } + return ACL_PERMISSION_TYPE_UNSPECIFIED +} + +func (x *DeleteACLsResponse_MatchingACL) GetError() *status.Status { + if x != nil { + return x.Error + } + return nil +} + +var File_redpanda_api_dataplane_v1alpha1_acl_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDesc = []byte{ + 0x0a, 0x29, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x61, 0x63, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, 0x62, 0x75, + 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, + 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xc8, 0x07, 0x0a, 0x03, 0x41, 0x43, 0x4c, 0x22, 0xf1, 0x01, 0x0a, 0x0c, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x01, + 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x54, 0x4f, 0x50, 0x49, 0x43, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, + 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, 0x04, 0x12, 0x22, 0x0a, + 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, + 0x52, 0x41, 0x4e, 0x53, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x44, 0x10, + 0x05, 0x12, 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x4f, + 0x4b, 0x45, 0x4e, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x07, 0x22, 0xc3, 0x01, + 0x0a, 0x13, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x21, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, + 0x45, 0x5f, 0x50, 0x41, 0x54, 0x54, 0x45, 0x52, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, + 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x41, 0x54, 0x54, 0x45, 0x52, 0x4e, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, + 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x41, 0x54, 0x54, 0x45, 0x52, 0x4e, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, + 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x41, 0x54, 0x54, 0x45, 0x52, 0x4e, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x03, 0x12, + 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x50, 0x41, 0x54, 0x54, + 0x45, 0x52, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x46, 0x49, 0x58, 0x45, + 0x44, 0x10, 0x04, 0x22, 0x85, 0x03, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, + 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x01, 0x12, + 0x11, 0x0a, 0x0d, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4c, 0x4c, + 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x52, 0x45, 0x41, 0x44, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x4f, + 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, + 0x05, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, + 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x50, 0x45, 0x52, 0x41, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x07, 0x12, 0x16, 0x0a, 0x12, + 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, + 0x42, 0x45, 0x10, 0x08, 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, + 0x10, 0x09, 0x12, 0x1e, 0x0a, 0x1a, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x53, + 0x10, 0x0a, 0x12, 0x1b, 0x0a, 0x17, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x41, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x53, 0x10, 0x0b, 0x12, + 0x1e, 0x0a, 0x1a, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x44, 0x45, + 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x0c, 0x12, + 0x1b, 0x0a, 0x17, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x52, 0x45, + 0x41, 0x54, 0x45, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x53, 0x10, 0x0d, 0x12, 0x1d, 0x0a, 0x19, + 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, + 0x42, 0x45, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x53, 0x10, 0x0e, 0x22, 0x7f, 0x0a, 0x0e, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x1b, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x52, 0x4d, 0x49, + 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4e, 0x59, 0x10, + 0x02, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x22, 0x9d, 0x06, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x4f, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x66, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x42, 0x49, 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0xb1, 0x04, 0x0a, 0x06, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x60, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, + 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x76, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, + 0x10, 0x01, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, + 0x69, 0x70, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, 0x70, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x68, 0x6f, + 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x56, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, + 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0f, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x08, 0xba, 0x48, 0x05, 0x82, 0x01, + 0x02, 0x10, 0x01, 0x52, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, + 0x70, 0x61, 0x6c, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x22, 0x9b, 0x05, 0x0a, + 0x10, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x58, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0xe6, 0x01, 0x0a, 0x06, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, + 0x70, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, + 0x69, 0x70, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, + 0x4c, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x1a, 0xc3, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x56, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x6c, + 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, + 0x72, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x41, 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4c, 0x0a, 0x04, + 0x61, 0x63, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x52, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x22, 0xd4, 0x08, 0x0a, 0x10, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x6a, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x12, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x0c, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x06, 0x10, 0x01, 0x22, 0x02, 0x00, 0x01, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x80, 0x01, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, + 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x12, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x0c, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x06, 0x10, 0x01, 0x1a, 0x02, 0x03, 0x04, 0x52, 0x13, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x30, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0c, 0xc8, 0x01, + 0x01, 0x72, 0x07, 0x3a, 0x05, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x52, 0x09, 0x70, 0x72, 0x69, 0x6e, + 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x9a, 0x01, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x85, 0x01, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x7f, 0xba, 0x01, 0x79, + 0x0a, 0x16, 0x77, 0x69, 0x6c, 0x64, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6f, 0x72, 0x5f, 0x69, 0x70, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3d, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x68, 0x6f, 0x73, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x69, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x77, 0x69, 0x6c, 0x64, 0x63, 0x61, 0x72, 0x64, 0x20, 0x28, 0x2a, 0x29, + 0x20, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x49, 0x50, 0x20, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x1a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3d, 0x3d, + 0x20, 0x27, 0x2a, 0x27, 0x20, 0x3f, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x3a, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x69, 0x73, 0x49, 0x70, 0x28, 0x29, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x68, 0x6f, + 0x73, 0x74, 0x12, 0x60, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x12, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0c, 0xc8, 0x01, 0x01, + 0x82, 0x01, 0x06, 0x10, 0x01, 0x22, 0x02, 0x00, 0x01, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x70, 0x0a, 0x0f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x41, 0x43, 0x4c, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x42, 0x12, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0c, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x06, + 0x10, 0x01, 0x1a, 0x02, 0x02, 0x03, 0x52, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x88, 0x03, 0xba, 0x48, 0x84, 0x03, 0x1a, 0x81, 0x03, + 0x0a, 0x3a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, + 0x6d, 0x75, 0x73, 0x74, 0x5f, 0x62, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, + 0x70, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xc2, 0x02, 0x74, + 0x68, 0x69, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x28, 0x74, + 0x68, 0x69, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x3f, 0x20, 0x27, 0x27, 0x3a, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x21, 0x3d, 0x20, 0x27, + 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x2d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x27, 0x20, 0x3f, + 0x20, 0x27, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x22, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x2d, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x77, 0x68, + 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x27, 0x3a, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x21, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x73, 0x69, 0x7a, 0x65, + 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x3f, 0x20, 0x27, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x27, 0x3a, 0x20, 0x27, + 0x27, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc9, 0x05, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5c, 0x0a, 0x06, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x09, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x03, 0xc8, + 0x01, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0xd5, 0x04, 0x0a, 0x06, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x69, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, + 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, + 0x11, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0b, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, + 0x01, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x7f, 0x0a, 0x15, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x42, 0x11, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0b, 0xc8, 0x01, 0x01, 0x82, 0x01, + 0x05, 0x10, 0x01, 0x22, 0x01, 0x00, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x70, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, + 0x52, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x17, + 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, + 0x68, 0x6f, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x5f, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, + 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x11, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x0b, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, 0x01, 0x00, 0x52, 0x09, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6f, 0x0a, 0x0f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x11, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0b, 0xc8, 0x01, + 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, 0x01, 0x00, 0x52, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, 0x6f, + 0x73, 0x74, 0x22, 0xfd, 0x04, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0d, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x63, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x3f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x41, 0x43, + 0x4c, 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x6c, 0x73, 0x1a, + 0x80, 0x04, 0x0a, 0x0b, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x41, 0x43, 0x4c, 0x12, + 0x56, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x6c, 0x0a, 0x15, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, + 0x4c, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, + 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0f, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x32, 0xf8, 0x05, 0x0a, 0x0a, 0x41, 0x43, 0x4c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0xe4, 0x01, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x12, 0x30, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x73, 0x92, 0x41, 0x5a, 0x12, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x41, + 0x43, 0x4c, 0x73, 0x1a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x41, 0x43, 0x4c, 0x73, 0x4a, 0x42, + 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3b, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x35, 0x0a, 0x33, 0x1a, + 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x61, 0x63, 0x6c, 0x73, 0x12, 0xed, 0x01, 0x0a, 0x09, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x12, 0x31, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, + 0x43, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x41, 0x43, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x79, 0x92, + 0x41, 0x5d, 0x12, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x41, 0x43, 0x4c, 0x1a, 0x0a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x41, 0x43, 0x4c, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, + 0x31, 0x12, 0x3c, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x36, 0x0a, 0x34, 0x1a, 0x32, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x61, 0x63, 0x6c, 0x73, 0x12, 0x92, 0x02, 0x0a, 0x0a, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x9a, 0x01, 0x92, 0x41, 0x80, 0x01, 0x12, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, + 0x41, 0x43, 0x4c, 0x73, 0x1a, 0x2b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x6c, + 0x20, 0x41, 0x43, 0x4c, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x4a, 0x44, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3d, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x37, + 0x0a, 0x35, 0x1a, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x43, 0x4c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x61, 0x63, 0x6c, 0x73, 0x42, 0xbb, 0x02, + 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x08, 0x41, 0x63, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, + 0x03, 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x41, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_redpanda_api_dataplane_v1alpha1_acl_proto_goTypes = []interface{}{ + (ACL_ResourceType)(0), // 0: redpanda.api.dataplane.v1alpha1.ACL.ResourceType + (ACL_ResourcePatternType)(0), // 1: redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + (ACL_Operation)(0), // 2: redpanda.api.dataplane.v1alpha1.ACL.Operation + (ACL_PermissionType)(0), // 3: redpanda.api.dataplane.v1alpha1.ACL.PermissionType + (*ACL)(nil), // 4: redpanda.api.dataplane.v1alpha1.ACL + (*ListACLsRequest)(nil), // 5: redpanda.api.dataplane.v1alpha1.ListACLsRequest + (*ListACLsResponse)(nil), // 6: redpanda.api.dataplane.v1alpha1.ListACLsResponse + (*CreateACLRequest)(nil), // 7: redpanda.api.dataplane.v1alpha1.CreateACLRequest + (*CreateACLResponse)(nil), // 8: redpanda.api.dataplane.v1alpha1.CreateACLResponse + (*DeleteACLsRequest)(nil), // 9: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest + (*DeleteACLsResponse)(nil), // 10: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse + (*ListACLsRequest_Filter)(nil), // 11: redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter + (*ListACLsResponse_Policy)(nil), // 12: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Policy + (*ListACLsResponse_Resource)(nil), // 13: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Resource + (*DeleteACLsRequest_Filter)(nil), // 14: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter + (*DeleteACLsResponse_MatchingACL)(nil), // 15: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL + (*status.Status)(nil), // 16: google.rpc.Status +} +var file_redpanda_api_dataplane_v1alpha1_acl_proto_depIdxs = []int32{ + 11, // 0: redpanda.api.dataplane.v1alpha1.ListACLsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter + 13, // 1: redpanda.api.dataplane.v1alpha1.ListACLsResponse.resources:type_name -> redpanda.api.dataplane.v1alpha1.ListACLsResponse.Resource + 0, // 2: redpanda.api.dataplane.v1alpha1.CreateACLRequest.resource_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourceType + 1, // 3: redpanda.api.dataplane.v1alpha1.CreateACLRequest.resource_pattern_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + 2, // 4: redpanda.api.dataplane.v1alpha1.CreateACLRequest.operation:type_name -> redpanda.api.dataplane.v1alpha1.ACL.Operation + 3, // 5: redpanda.api.dataplane.v1alpha1.CreateACLRequest.permission_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.PermissionType + 14, // 6: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter + 15, // 7: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.matching_acls:type_name -> redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL + 0, // 8: redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter.resource_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourceType + 1, // 9: redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter.resource_pattern_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + 2, // 10: redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter.operation:type_name -> redpanda.api.dataplane.v1alpha1.ACL.Operation + 3, // 11: redpanda.api.dataplane.v1alpha1.ListACLsRequest.Filter.permission_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.PermissionType + 2, // 12: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Policy.operation:type_name -> redpanda.api.dataplane.v1alpha1.ACL.Operation + 3, // 13: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Policy.permission_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.PermissionType + 0, // 14: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Resource.resource_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourceType + 1, // 15: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Resource.resource_pattern_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + 12, // 16: redpanda.api.dataplane.v1alpha1.ListACLsResponse.Resource.acls:type_name -> redpanda.api.dataplane.v1alpha1.ListACLsResponse.Policy + 0, // 17: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter.resource_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourceType + 1, // 18: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter.resource_pattern_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + 2, // 19: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter.operation:type_name -> redpanda.api.dataplane.v1alpha1.ACL.Operation + 3, // 20: redpanda.api.dataplane.v1alpha1.DeleteACLsRequest.Filter.permission_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.PermissionType + 0, // 21: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL.resource_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourceType + 1, // 22: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL.resource_pattern_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.ResourcePatternType + 2, // 23: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL.operation:type_name -> redpanda.api.dataplane.v1alpha1.ACL.Operation + 3, // 24: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL.permission_type:type_name -> redpanda.api.dataplane.v1alpha1.ACL.PermissionType + 16, // 25: redpanda.api.dataplane.v1alpha1.DeleteACLsResponse.MatchingACL.error:type_name -> google.rpc.Status + 5, // 26: redpanda.api.dataplane.v1alpha1.ACLService.ListACLs:input_type -> redpanda.api.dataplane.v1alpha1.ListACLsRequest + 7, // 27: redpanda.api.dataplane.v1alpha1.ACLService.CreateACL:input_type -> redpanda.api.dataplane.v1alpha1.CreateACLRequest + 9, // 28: redpanda.api.dataplane.v1alpha1.ACLService.DeleteACLs:input_type -> redpanda.api.dataplane.v1alpha1.DeleteACLsRequest + 6, // 29: redpanda.api.dataplane.v1alpha1.ACLService.ListACLs:output_type -> redpanda.api.dataplane.v1alpha1.ListACLsResponse + 8, // 30: redpanda.api.dataplane.v1alpha1.ACLService.CreateACL:output_type -> redpanda.api.dataplane.v1alpha1.CreateACLResponse + 10, // 31: redpanda.api.dataplane.v1alpha1.ACLService.DeleteACLs:output_type -> redpanda.api.dataplane.v1alpha1.DeleteACLsResponse + 29, // [29:32] is the sub-list for method output_type + 26, // [26:29] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_acl_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_acl_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_acl_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ACL); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListACLsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListACLsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateACLRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateACLResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteACLsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteACLsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListACLsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListACLsResponse_Policy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListACLsResponse_Resource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteACLsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteACLsResponse_MatchingACL); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[7].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes[10].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDesc, + NumEnums: 4, + NumMessages: 12, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_acl_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_acl_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_acl_proto_enumTypes, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_acl_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_acl_proto = out.File + file_redpanda_api_dataplane_v1alpha1_acl_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_acl_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_acl_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/common.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/common.pb.go new file mode 100644 index 0000000000000..67f641a465335 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/common.pb.go @@ -0,0 +1,427 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/common.proto + +package dataplanev1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ConfigSource int32 + +const ( + ConfigSource_CONFIG_SOURCE_UNSPECIFIED ConfigSource = 0 + ConfigSource_CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG ConfigSource = 1 + ConfigSource_CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG ConfigSource = 2 + ConfigSource_CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG ConfigSource = 3 + ConfigSource_CONFIG_SOURCE_STATIC_BROKER_CONFIG ConfigSource = 4 + ConfigSource_CONFIG_SOURCE_DEFAULT_CONFIG ConfigSource = 5 + ConfigSource_CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG ConfigSource = 6 +) + +// Enum value maps for ConfigSource. +var ( + ConfigSource_name = map[int32]string{ + 0: "CONFIG_SOURCE_UNSPECIFIED", + 1: "CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG", + 2: "CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG", + 3: "CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG", + 4: "CONFIG_SOURCE_STATIC_BROKER_CONFIG", + 5: "CONFIG_SOURCE_DEFAULT_CONFIG", + 6: "CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG", + } + ConfigSource_value = map[string]int32{ + "CONFIG_SOURCE_UNSPECIFIED": 0, + "CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG": 1, + "CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG": 2, + "CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG": 3, + "CONFIG_SOURCE_STATIC_BROKER_CONFIG": 4, + "CONFIG_SOURCE_DEFAULT_CONFIG": 5, + "CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG": 6, + } +) + +func (x ConfigSource) Enum() *ConfigSource { + p := new(ConfigSource) + *p = x + return p +} + +func (x ConfigSource) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConfigSource) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[0].Descriptor() +} + +func (ConfigSource) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[0] +} + +func (x ConfigSource) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConfigSource.Descriptor instead. +func (ConfigSource) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescGZIP(), []int{0} +} + +type ConfigType int32 + +const ( + ConfigType_CONFIG_TYPE_UNSPECIFIED ConfigType = 0 + ConfigType_CONFIG_TYPE_BOOLEAN ConfigType = 1 + ConfigType_CONFIG_TYPE_STRING ConfigType = 2 + ConfigType_CONFIG_TYPE_INT ConfigType = 3 + ConfigType_CONFIG_TYPE_SHORT ConfigType = 4 + ConfigType_CONFIG_TYPE_LONG ConfigType = 5 + ConfigType_CONFIG_TYPE_DOUBLE ConfigType = 6 + ConfigType_CONFIG_TYPE_LIST ConfigType = 7 + ConfigType_CONFIG_TYPE_CLASS ConfigType = 8 + ConfigType_CONFIG_TYPE_PASSWORD ConfigType = 9 +) + +// Enum value maps for ConfigType. +var ( + ConfigType_name = map[int32]string{ + 0: "CONFIG_TYPE_UNSPECIFIED", + 1: "CONFIG_TYPE_BOOLEAN", + 2: "CONFIG_TYPE_STRING", + 3: "CONFIG_TYPE_INT", + 4: "CONFIG_TYPE_SHORT", + 5: "CONFIG_TYPE_LONG", + 6: "CONFIG_TYPE_DOUBLE", + 7: "CONFIG_TYPE_LIST", + 8: "CONFIG_TYPE_CLASS", + 9: "CONFIG_TYPE_PASSWORD", + } + ConfigType_value = map[string]int32{ + "CONFIG_TYPE_UNSPECIFIED": 0, + "CONFIG_TYPE_BOOLEAN": 1, + "CONFIG_TYPE_STRING": 2, + "CONFIG_TYPE_INT": 3, + "CONFIG_TYPE_SHORT": 4, + "CONFIG_TYPE_LONG": 5, + "CONFIG_TYPE_DOUBLE": 6, + "CONFIG_TYPE_LIST": 7, + "CONFIG_TYPE_CLASS": 8, + "CONFIG_TYPE_PASSWORD": 9, + } +) + +func (x ConfigType) Enum() *ConfigType { + p := new(ConfigType) + *p = x + return p +} + +func (x ConfigType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConfigType) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[1].Descriptor() +} + +func (ConfigType) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[1] +} + +func (x ConfigType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConfigType.Descriptor instead. +func (ConfigType) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescGZIP(), []int{1} +} + +type ConfigAlterOperation int32 + +const ( + ConfigAlterOperation_CONFIG_ALTER_OPERATION_UNSPECIFIED ConfigAlterOperation = 0 + ConfigAlterOperation_CONFIG_ALTER_OPERATION_SET ConfigAlterOperation = 1 + ConfigAlterOperation_CONFIG_ALTER_OPERATION_DELETE ConfigAlterOperation = 2 + ConfigAlterOperation_CONFIG_ALTER_OPERATION_APPEND ConfigAlterOperation = 3 + ConfigAlterOperation_CONFIG_ALTER_OPERATION_SUBTRACT ConfigAlterOperation = 4 +) + +// Enum value maps for ConfigAlterOperation. +var ( + ConfigAlterOperation_name = map[int32]string{ + 0: "CONFIG_ALTER_OPERATION_UNSPECIFIED", + 1: "CONFIG_ALTER_OPERATION_SET", + 2: "CONFIG_ALTER_OPERATION_DELETE", + 3: "CONFIG_ALTER_OPERATION_APPEND", + 4: "CONFIG_ALTER_OPERATION_SUBTRACT", + } + ConfigAlterOperation_value = map[string]int32{ + "CONFIG_ALTER_OPERATION_UNSPECIFIED": 0, + "CONFIG_ALTER_OPERATION_SET": 1, + "CONFIG_ALTER_OPERATION_DELETE": 2, + "CONFIG_ALTER_OPERATION_APPEND": 3, + "CONFIG_ALTER_OPERATION_SUBTRACT": 4, + } +) + +func (x ConfigAlterOperation) Enum() *ConfigAlterOperation { + p := new(ConfigAlterOperation) + *p = x + return p +} + +func (x ConfigAlterOperation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConfigAlterOperation) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[2].Descriptor() +} + +func (ConfigAlterOperation) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes[2] +} + +func (x ConfigAlterOperation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConfigAlterOperation.Descriptor instead. +func (ConfigAlterOperation) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescGZIP(), []int{2} +} + +type ConfigSynonym struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value *string `protobuf:"bytes,2,opt,name=value,proto3,oneof" json:"value,omitempty"` + Source ConfigSource `protobuf:"varint,3,opt,name=source,proto3,enum=redpanda.api.dataplane.v1alpha1.ConfigSource" json:"source,omitempty"` +} + +func (x *ConfigSynonym) Reset() { + *x = ConfigSynonym{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigSynonym) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigSynonym) ProtoMessage() {} + +func (x *ConfigSynonym) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigSynonym.ProtoReflect.Descriptor instead. +func (*ConfigSynonym) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescGZIP(), []int{0} +} + +func (x *ConfigSynonym) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ConfigSynonym) GetValue() string { + if x != nil && x.Value != nil { + return *x.Value + } + return "" +} + +func (x *ConfigSynonym) GetSource() ConfigSource { + if x != nil { + return x.Source + } + return ConfigSource_CONFIG_SOURCE_UNSPECIFIED +} + +var File_redpanda_api_dataplane_v1alpha1_common_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_common_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x22, + 0x8f, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x79, 0x6e, 0x6f, 0x6e, 0x79, + 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x45, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2a, 0xa9, 0x02, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, 0x52, + 0x43, 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x5f, 0x54, 0x4f, 0x50, 0x49, 0x43, + 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x01, 0x12, 0x27, 0x0a, 0x23, 0x43, 0x4f, 0x4e, + 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, 0x4d, + 0x49, 0x43, 0x5f, 0x42, 0x52, 0x4f, 0x4b, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, + 0x10, 0x02, 0x12, 0x2f, 0x0a, 0x2b, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x5f, 0x44, 0x45, 0x46, 0x41, + 0x55, 0x4c, 0x54, 0x5f, 0x42, 0x52, 0x4f, 0x4b, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, + 0x47, 0x10, 0x03, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x42, 0x52, 0x4f, 0x4b, + 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x20, 0x0a, 0x1c, 0x43, + 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x45, 0x46, + 0x41, 0x55, 0x4c, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x05, 0x12, 0x2e, 0x0a, + 0x2a, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, + 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x5f, 0x42, 0x52, 0x4f, 0x4b, 0x45, 0x52, 0x5f, 0x4c, 0x4f, + 0x47, 0x47, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x06, 0x2a, 0xfb, 0x01, + 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, + 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, + 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, + 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x10, 0x03, 0x12, + 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, + 0x48, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, + 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x55, 0x42, + 0x4c, 0x45, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x10, + 0x08, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x09, 0x2a, 0xc9, 0x01, 0x0a, 0x14, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x41, + 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, + 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x41, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x4f, 0x50, 0x45, + 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, + 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x41, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x4f, 0x50, 0x45, + 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x12, + 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x41, 0x4c, 0x54, 0x45, 0x52, 0x5f, + 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, + 0x10, 0x03, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x41, 0x4c, 0x54, + 0x45, 0x52, 0x5f, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x55, 0x42, + 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, 0x04, 0x42, 0xbe, 0x02, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, + 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, + 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, + 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, + 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_common_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_common_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_redpanda_api_dataplane_v1alpha1_common_proto_goTypes = []interface{}{ + (ConfigSource)(0), // 0: redpanda.api.dataplane.v1alpha1.ConfigSource + (ConfigType)(0), // 1: redpanda.api.dataplane.v1alpha1.ConfigType + (ConfigAlterOperation)(0), // 2: redpanda.api.dataplane.v1alpha1.ConfigAlterOperation + (*ConfigSynonym)(nil), // 3: redpanda.api.dataplane.v1alpha1.ConfigSynonym +} +var file_redpanda_api_dataplane_v1alpha1_common_proto_depIdxs = []int32{ + 0, // 0: redpanda.api.dataplane.v1alpha1.ConfigSynonym.source:type_name -> redpanda.api.dataplane.v1alpha1.ConfigSource + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_common_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_common_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigSynonym); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes[0].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_common_proto_rawDesc, + NumEnums: 3, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_common_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_common_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_common_proto_enumTypes, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_common_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_common_proto = out.File + file_redpanda_api_dataplane_v1alpha1_common_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_common_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_common_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/acl.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/acl.connect.go new file mode 100644 index 0000000000000..1fc6a1af90a78 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/acl.connect.go @@ -0,0 +1,170 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/acl.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // ACLServiceName is the fully-qualified name of the ACLService service. + ACLServiceName = "redpanda.api.dataplane.v1alpha1.ACLService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // ACLServiceListACLsProcedure is the fully-qualified name of the ACLService's ListACLs RPC. + ACLServiceListACLsProcedure = "/redpanda.api.dataplane.v1alpha1.ACLService/ListACLs" + // ACLServiceCreateACLProcedure is the fully-qualified name of the ACLService's CreateACL RPC. + ACLServiceCreateACLProcedure = "/redpanda.api.dataplane.v1alpha1.ACLService/CreateACL" + // ACLServiceDeleteACLsProcedure is the fully-qualified name of the ACLService's DeleteACLs RPC. + ACLServiceDeleteACLsProcedure = "/redpanda.api.dataplane.v1alpha1.ACLService/DeleteACLs" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + aCLServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_acl_proto.Services().ByName("ACLService") + aCLServiceListACLsMethodDescriptor = aCLServiceServiceDescriptor.Methods().ByName("ListACLs") + aCLServiceCreateACLMethodDescriptor = aCLServiceServiceDescriptor.Methods().ByName("CreateACL") + aCLServiceDeleteACLsMethodDescriptor = aCLServiceServiceDescriptor.Methods().ByName("DeleteACLs") +) + +// ACLServiceClient is a client for the redpanda.api.dataplane.v1alpha1.ACLService service. +type ACLServiceClient interface { + ListACLs(context.Context, *connect.Request[v1alpha1.ListACLsRequest]) (*connect.Response[v1alpha1.ListACLsResponse], error) + CreateACL(context.Context, *connect.Request[v1alpha1.CreateACLRequest]) (*connect.Response[v1alpha1.CreateACLResponse], error) + DeleteACLs(context.Context, *connect.Request[v1alpha1.DeleteACLsRequest]) (*connect.Response[v1alpha1.DeleteACLsResponse], error) +} + +// NewACLServiceClient constructs a client for the redpanda.api.dataplane.v1alpha1.ACLService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewACLServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ACLServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &aCLServiceClient{ + listACLs: connect.NewClient[v1alpha1.ListACLsRequest, v1alpha1.ListACLsResponse]( + httpClient, + baseURL+ACLServiceListACLsProcedure, + connect.WithSchema(aCLServiceListACLsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + createACL: connect.NewClient[v1alpha1.CreateACLRequest, v1alpha1.CreateACLResponse]( + httpClient, + baseURL+ACLServiceCreateACLProcedure, + connect.WithSchema(aCLServiceCreateACLMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteACLs: connect.NewClient[v1alpha1.DeleteACLsRequest, v1alpha1.DeleteACLsResponse]( + httpClient, + baseURL+ACLServiceDeleteACLsProcedure, + connect.WithSchema(aCLServiceDeleteACLsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// aCLServiceClient implements ACLServiceClient. +type aCLServiceClient struct { + listACLs *connect.Client[v1alpha1.ListACLsRequest, v1alpha1.ListACLsResponse] + createACL *connect.Client[v1alpha1.CreateACLRequest, v1alpha1.CreateACLResponse] + deleteACLs *connect.Client[v1alpha1.DeleteACLsRequest, v1alpha1.DeleteACLsResponse] +} + +// ListACLs calls redpanda.api.dataplane.v1alpha1.ACLService.ListACLs. +func (c *aCLServiceClient) ListACLs(ctx context.Context, req *connect.Request[v1alpha1.ListACLsRequest]) (*connect.Response[v1alpha1.ListACLsResponse], error) { + return c.listACLs.CallUnary(ctx, req) +} + +// CreateACL calls redpanda.api.dataplane.v1alpha1.ACLService.CreateACL. +func (c *aCLServiceClient) CreateACL(ctx context.Context, req *connect.Request[v1alpha1.CreateACLRequest]) (*connect.Response[v1alpha1.CreateACLResponse], error) { + return c.createACL.CallUnary(ctx, req) +} + +// DeleteACLs calls redpanda.api.dataplane.v1alpha1.ACLService.DeleteACLs. +func (c *aCLServiceClient) DeleteACLs(ctx context.Context, req *connect.Request[v1alpha1.DeleteACLsRequest]) (*connect.Response[v1alpha1.DeleteACLsResponse], error) { + return c.deleteACLs.CallUnary(ctx, req) +} + +// ACLServiceHandler is an implementation of the redpanda.api.dataplane.v1alpha1.ACLService service. +type ACLServiceHandler interface { + ListACLs(context.Context, *connect.Request[v1alpha1.ListACLsRequest]) (*connect.Response[v1alpha1.ListACLsResponse], error) + CreateACL(context.Context, *connect.Request[v1alpha1.CreateACLRequest]) (*connect.Response[v1alpha1.CreateACLResponse], error) + DeleteACLs(context.Context, *connect.Request[v1alpha1.DeleteACLsRequest]) (*connect.Response[v1alpha1.DeleteACLsResponse], error) +} + +// NewACLServiceHandler builds an HTTP handler from the service implementation. It returns the path +// on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewACLServiceHandler(svc ACLServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + aCLServiceListACLsHandler := connect.NewUnaryHandler( + ACLServiceListACLsProcedure, + svc.ListACLs, + connect.WithSchema(aCLServiceListACLsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + aCLServiceCreateACLHandler := connect.NewUnaryHandler( + ACLServiceCreateACLProcedure, + svc.CreateACL, + connect.WithSchema(aCLServiceCreateACLMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + aCLServiceDeleteACLsHandler := connect.NewUnaryHandler( + ACLServiceDeleteACLsProcedure, + svc.DeleteACLs, + connect.WithSchema(aCLServiceDeleteACLsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.ACLService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case ACLServiceListACLsProcedure: + aCLServiceListACLsHandler.ServeHTTP(w, r) + case ACLServiceCreateACLProcedure: + aCLServiceCreateACLHandler.ServeHTTP(w, r) + case ACLServiceDeleteACLsProcedure: + aCLServiceDeleteACLsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedACLServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedACLServiceHandler struct{} + +func (UnimplementedACLServiceHandler) ListACLs(context.Context, *connect.Request[v1alpha1.ListACLsRequest]) (*connect.Response[v1alpha1.ListACLsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.ACLService.ListACLs is not implemented")) +} + +func (UnimplementedACLServiceHandler) CreateACL(context.Context, *connect.Request[v1alpha1.CreateACLRequest]) (*connect.Response[v1alpha1.CreateACLResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.ACLService.CreateACL is not implemented")) +} + +func (UnimplementedACLServiceHandler) DeleteACLs(context.Context, *connect.Request[v1alpha1.DeleteACLsRequest]) (*connect.Response[v1alpha1.DeleteACLsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.ACLService.DeleteACLs is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/dummy.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/dummy.connect.go new file mode 100644 index 0000000000000..1e6bc6db1e047 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/dummy.connect.go @@ -0,0 +1,123 @@ +// This file is a trick to force protoc-gen-openapiv2 into including the types used here into the openapi spec. They are not normally included, because they are not explicitly referenced in any proto (as protobuf ANY is used in errordetails). + +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/dummy.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + emptypb "google.golang.org/protobuf/types/known/emptypb" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // DummyServiceName is the fully-qualified name of the DummyService service. + DummyServiceName = "redpanda.api.dataplane.v1alpha1.DummyService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // DummyServiceDummyMethodProcedure is the fully-qualified name of the DummyService's DummyMethod + // RPC. + DummyServiceDummyMethodProcedure = "/redpanda.api.dataplane.v1alpha1.DummyService/DummyMethod" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + dummyServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_dummy_proto.Services().ByName("DummyService") + dummyServiceDummyMethodMethodDescriptor = dummyServiceServiceDescriptor.Methods().ByName("DummyMethod") +) + +// DummyServiceClient is a client for the redpanda.api.dataplane.v1alpha1.DummyService service. +type DummyServiceClient interface { + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1alpha1.DummyMethodResponse], error) +} + +// NewDummyServiceClient constructs a client for the redpanda.api.dataplane.v1alpha1.DummyService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewDummyServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) DummyServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &dummyServiceClient{ + dummyMethod: connect.NewClient[emptypb.Empty, v1alpha1.DummyMethodResponse]( + httpClient, + baseURL+DummyServiceDummyMethodProcedure, + connect.WithSchema(dummyServiceDummyMethodMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// dummyServiceClient implements DummyServiceClient. +type dummyServiceClient struct { + dummyMethod *connect.Client[emptypb.Empty, v1alpha1.DummyMethodResponse] +} + +// DummyMethod calls redpanda.api.dataplane.v1alpha1.DummyService.DummyMethod. +func (c *dummyServiceClient) DummyMethod(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1alpha1.DummyMethodResponse], error) { + return c.dummyMethod.CallUnary(ctx, req) +} + +// DummyServiceHandler is an implementation of the redpanda.api.dataplane.v1alpha1.DummyService +// service. +type DummyServiceHandler interface { + // buf:lint:ignore RPC_REQUEST_STANDARD_NAME + // buf:lint:ignore RPC_RESPONSE_STANDARD_NAME + // buf:lint:ignore RPC_REQUEST_RESPONSE_UNIQUE + DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1alpha1.DummyMethodResponse], error) +} + +// NewDummyServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewDummyServiceHandler(svc DummyServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + dummyServiceDummyMethodHandler := connect.NewUnaryHandler( + DummyServiceDummyMethodProcedure, + svc.DummyMethod, + connect.WithSchema(dummyServiceDummyMethodMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.DummyService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case DummyServiceDummyMethodProcedure: + dummyServiceDummyMethodHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedDummyServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedDummyServiceHandler struct{} + +func (UnimplementedDummyServiceHandler) DummyMethod(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1alpha1.DummyMethodResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.DummyService.DummyMethod is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/kafka_connect.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/kafka_connect.connect.go new file mode 100644 index 0000000000000..1d244b1b68f32 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/kafka_connect.connect.go @@ -0,0 +1,604 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/kafka_connect.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + emptypb "google.golang.org/protobuf/types/known/emptypb" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // KafkaConnectServiceName is the fully-qualified name of the KafkaConnectService service. + KafkaConnectServiceName = "redpanda.api.dataplane.v1alpha1.KafkaConnectService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // KafkaConnectServiceListConnectClustersProcedure is the fully-qualified name of the + // KafkaConnectService's ListConnectClusters RPC. + KafkaConnectServiceListConnectClustersProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/ListConnectClusters" + // KafkaConnectServiceGetConnectClusterProcedure is the fully-qualified name of the + // KafkaConnectService's GetConnectCluster RPC. + KafkaConnectServiceGetConnectClusterProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/GetConnectCluster" + // KafkaConnectServiceListConnectorsProcedure is the fully-qualified name of the + // KafkaConnectService's ListConnectors RPC. + KafkaConnectServiceListConnectorsProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/ListConnectors" + // KafkaConnectServiceCreateConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's CreateConnector RPC. + KafkaConnectServiceCreateConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/CreateConnector" + // KafkaConnectServiceRestartConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's RestartConnector RPC. + KafkaConnectServiceRestartConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/RestartConnector" + // KafkaConnectServiceGetConnectorProcedure is the fully-qualified name of the KafkaConnectService's + // GetConnector RPC. + KafkaConnectServiceGetConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/GetConnector" + // KafkaConnectServiceGetConnectorStatusProcedure is the fully-qualified name of the + // KafkaConnectService's GetConnectorStatus RPC. + KafkaConnectServiceGetConnectorStatusProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/GetConnectorStatus" + // KafkaConnectServicePauseConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's PauseConnector RPC. + KafkaConnectServicePauseConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/PauseConnector" + // KafkaConnectServiceResumeConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's ResumeConnector RPC. + KafkaConnectServiceResumeConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/ResumeConnector" + // KafkaConnectServiceStopConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's StopConnector RPC. + KafkaConnectServiceStopConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/StopConnector" + // KafkaConnectServiceDeleteConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's DeleteConnector RPC. + KafkaConnectServiceDeleteConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/DeleteConnector" + // KafkaConnectServiceUpsertConnectorProcedure is the fully-qualified name of the + // KafkaConnectService's UpsertConnector RPC. + KafkaConnectServiceUpsertConnectorProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/UpsertConnector" + // KafkaConnectServiceGetConnectorConfigProcedure is the fully-qualified name of the + // KafkaConnectService's GetConnectorConfig RPC. + KafkaConnectServiceGetConnectorConfigProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/GetConnectorConfig" + // KafkaConnectServiceListConnectorTopicsProcedure is the fully-qualified name of the + // KafkaConnectService's ListConnectorTopics RPC. + KafkaConnectServiceListConnectorTopicsProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/ListConnectorTopics" + // KafkaConnectServiceResetConnectorTopicsProcedure is the fully-qualified name of the + // KafkaConnectService's ResetConnectorTopics RPC. + KafkaConnectServiceResetConnectorTopicsProcedure = "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/ResetConnectorTopics" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + kafkaConnectServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_kafka_connect_proto.Services().ByName("KafkaConnectService") + kafkaConnectServiceListConnectClustersMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("ListConnectClusters") + kafkaConnectServiceGetConnectClusterMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("GetConnectCluster") + kafkaConnectServiceListConnectorsMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("ListConnectors") + kafkaConnectServiceCreateConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("CreateConnector") + kafkaConnectServiceRestartConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("RestartConnector") + kafkaConnectServiceGetConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("GetConnector") + kafkaConnectServiceGetConnectorStatusMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("GetConnectorStatus") + kafkaConnectServicePauseConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("PauseConnector") + kafkaConnectServiceResumeConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("ResumeConnector") + kafkaConnectServiceStopConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("StopConnector") + kafkaConnectServiceDeleteConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("DeleteConnector") + kafkaConnectServiceUpsertConnectorMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("UpsertConnector") + kafkaConnectServiceGetConnectorConfigMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("GetConnectorConfig") + kafkaConnectServiceListConnectorTopicsMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("ListConnectorTopics") + kafkaConnectServiceResetConnectorTopicsMethodDescriptor = kafkaConnectServiceServiceDescriptor.Methods().ByName("ResetConnectorTopics") +) + +// KafkaConnectServiceClient is a client for the redpanda.api.dataplane.v1alpha1.KafkaConnectService +// service. +type KafkaConnectServiceClient interface { + // ListConnectClusters implements the list clusters method, list connect + // clusters available in the console configuration + ListConnectClusters(context.Context, *connect.Request[v1alpha1.ListConnectClustersRequest]) (*connect.Response[v1alpha1.ListConnectClustersResponse], error) + // GetConnectCluster implements the get cluster info method, exposes a Kafka + // Connect equivalent REST endpoint + GetConnectCluster(context.Context, *connect.Request[v1alpha1.GetConnectClusterRequest]) (*connect.Response[v1alpha1.GetConnectClusterResponse], error) + // ListConnectors implements the list connectors method, exposes a Kafka + // Connect equivalent REST endpoint + ListConnectors(context.Context, *connect.Request[v1alpha1.ListConnectorsRequest]) (*connect.Response[v1alpha1.ListConnectorsResponse], error) + // CreateConnector implements the create connector method, and exposes an + // equivalent REST endpoint as the Kafka connect API endpoint + CreateConnector(context.Context, *connect.Request[v1alpha1.CreateConnectorRequest]) (*connect.Response[v1alpha1.CreateConnectorResponse], error) + // RestartConnector implements the restart connector method, exposes a Kafka + // Connect equivalent REST endpoint + RestartConnector(context.Context, *connect.Request[v1alpha1.RestartConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // GetConnector implements the get connector method, exposes a Kafka + // Connect equivalent REST endpoint + GetConnector(context.Context, *connect.Request[v1alpha1.GetConnectorRequest]) (*connect.Response[v1alpha1.GetConnectorResponse], error) + // GetConnectorStatus implement the get status method, Gets the current status of the connector, including: + // Whether it is running or restarting, or if it has failed or paused + // Which worker it is assigned to + // Error information if it has failed + // The state of all its tasks + GetConnectorStatus(context.Context, *connect.Request[v1alpha1.GetConnectorStatusRequest]) (*connect.Response[v1alpha1.GetConnectorStatusResponse], error) + // PauseConnector implements the pause connector method, exposes a Kafka + // connect equivalent REST endpoint + PauseConnector(context.Context, *connect.Request[v1alpha1.PauseConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // ResumeConnector implements the resume connector method, exposes a Kafka + // connect equivalent REST endpoint + ResumeConnector(context.Context, *connect.Request[v1alpha1.ResumeConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // StopConnector implements the stop connector method, exposes a Kafka + // connect equivalent REST endpoint it stops the connector but does not + // delete the connector. All tasks for the connector are shut down completely + StopConnector(context.Context, *connect.Request[v1alpha1.StopConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // DeleteConnector implements the delete connector method, exposes a Kafka + // connect equivalent REST endpoint + DeleteConnector(context.Context, *connect.Request[v1alpha1.DeleteConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // UpsertConector implements the update or create connector method, it + // exposes a kafka connect equivalent REST endpoint + UpsertConnector(context.Context, *connect.Request[v1alpha1.UpsertConnectorRequest]) (*connect.Response[v1alpha1.UpsertConnectorResponse], error) + // GetConnectorConfig implements the get connector configuration method, expose a kafka connect equivalent REST endpoint + GetConnectorConfig(context.Context, *connect.Request[v1alpha1.GetConnectorConfigRequest]) (*connect.Response[v1alpha1.GetConnectorConfigResponse], error) + // ListConnectorTopics implements the list connector topics method, expose a kafka connect equivalent REST endpoint + ListConnectorTopics(context.Context, *connect.Request[v1alpha1.ListConnectorTopicsRequest]) (*connect.Response[v1alpha1.ListConnectorTopicsResponse], error) + // ResetConnectorTopics implements the reset connector topics method, expose a kafka connect equivalent REST endpoint + // the request body is empty. + ResetConnectorTopics(context.Context, *connect.Request[v1alpha1.ResetConnectorTopicsRequest]) (*connect.Response[emptypb.Empty], error) +} + +// NewKafkaConnectServiceClient constructs a client for the +// redpanda.api.dataplane.v1alpha1.KafkaConnectService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewKafkaConnectServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) KafkaConnectServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &kafkaConnectServiceClient{ + listConnectClusters: connect.NewClient[v1alpha1.ListConnectClustersRequest, v1alpha1.ListConnectClustersResponse]( + httpClient, + baseURL+KafkaConnectServiceListConnectClustersProcedure, + connect.WithSchema(kafkaConnectServiceListConnectClustersMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getConnectCluster: connect.NewClient[v1alpha1.GetConnectClusterRequest, v1alpha1.GetConnectClusterResponse]( + httpClient, + baseURL+KafkaConnectServiceGetConnectClusterProcedure, + connect.WithSchema(kafkaConnectServiceGetConnectClusterMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listConnectors: connect.NewClient[v1alpha1.ListConnectorsRequest, v1alpha1.ListConnectorsResponse]( + httpClient, + baseURL+KafkaConnectServiceListConnectorsProcedure, + connect.WithSchema(kafkaConnectServiceListConnectorsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + createConnector: connect.NewClient[v1alpha1.CreateConnectorRequest, v1alpha1.CreateConnectorResponse]( + httpClient, + baseURL+KafkaConnectServiceCreateConnectorProcedure, + connect.WithSchema(kafkaConnectServiceCreateConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + restartConnector: connect.NewClient[v1alpha1.RestartConnectorRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServiceRestartConnectorProcedure, + connect.WithSchema(kafkaConnectServiceRestartConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getConnector: connect.NewClient[v1alpha1.GetConnectorRequest, v1alpha1.GetConnectorResponse]( + httpClient, + baseURL+KafkaConnectServiceGetConnectorProcedure, + connect.WithSchema(kafkaConnectServiceGetConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getConnectorStatus: connect.NewClient[v1alpha1.GetConnectorStatusRequest, v1alpha1.GetConnectorStatusResponse]( + httpClient, + baseURL+KafkaConnectServiceGetConnectorStatusProcedure, + connect.WithSchema(kafkaConnectServiceGetConnectorStatusMethodDescriptor), + connect.WithClientOptions(opts...), + ), + pauseConnector: connect.NewClient[v1alpha1.PauseConnectorRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServicePauseConnectorProcedure, + connect.WithSchema(kafkaConnectServicePauseConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + resumeConnector: connect.NewClient[v1alpha1.ResumeConnectorRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServiceResumeConnectorProcedure, + connect.WithSchema(kafkaConnectServiceResumeConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + stopConnector: connect.NewClient[v1alpha1.StopConnectorRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServiceStopConnectorProcedure, + connect.WithSchema(kafkaConnectServiceStopConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteConnector: connect.NewClient[v1alpha1.DeleteConnectorRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServiceDeleteConnectorProcedure, + connect.WithSchema(kafkaConnectServiceDeleteConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + upsertConnector: connect.NewClient[v1alpha1.UpsertConnectorRequest, v1alpha1.UpsertConnectorResponse]( + httpClient, + baseURL+KafkaConnectServiceUpsertConnectorProcedure, + connect.WithSchema(kafkaConnectServiceUpsertConnectorMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getConnectorConfig: connect.NewClient[v1alpha1.GetConnectorConfigRequest, v1alpha1.GetConnectorConfigResponse]( + httpClient, + baseURL+KafkaConnectServiceGetConnectorConfigProcedure, + connect.WithSchema(kafkaConnectServiceGetConnectorConfigMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listConnectorTopics: connect.NewClient[v1alpha1.ListConnectorTopicsRequest, v1alpha1.ListConnectorTopicsResponse]( + httpClient, + baseURL+KafkaConnectServiceListConnectorTopicsProcedure, + connect.WithSchema(kafkaConnectServiceListConnectorTopicsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + resetConnectorTopics: connect.NewClient[v1alpha1.ResetConnectorTopicsRequest, emptypb.Empty]( + httpClient, + baseURL+KafkaConnectServiceResetConnectorTopicsProcedure, + connect.WithSchema(kafkaConnectServiceResetConnectorTopicsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// kafkaConnectServiceClient implements KafkaConnectServiceClient. +type kafkaConnectServiceClient struct { + listConnectClusters *connect.Client[v1alpha1.ListConnectClustersRequest, v1alpha1.ListConnectClustersResponse] + getConnectCluster *connect.Client[v1alpha1.GetConnectClusterRequest, v1alpha1.GetConnectClusterResponse] + listConnectors *connect.Client[v1alpha1.ListConnectorsRequest, v1alpha1.ListConnectorsResponse] + createConnector *connect.Client[v1alpha1.CreateConnectorRequest, v1alpha1.CreateConnectorResponse] + restartConnector *connect.Client[v1alpha1.RestartConnectorRequest, emptypb.Empty] + getConnector *connect.Client[v1alpha1.GetConnectorRequest, v1alpha1.GetConnectorResponse] + getConnectorStatus *connect.Client[v1alpha1.GetConnectorStatusRequest, v1alpha1.GetConnectorStatusResponse] + pauseConnector *connect.Client[v1alpha1.PauseConnectorRequest, emptypb.Empty] + resumeConnector *connect.Client[v1alpha1.ResumeConnectorRequest, emptypb.Empty] + stopConnector *connect.Client[v1alpha1.StopConnectorRequest, emptypb.Empty] + deleteConnector *connect.Client[v1alpha1.DeleteConnectorRequest, emptypb.Empty] + upsertConnector *connect.Client[v1alpha1.UpsertConnectorRequest, v1alpha1.UpsertConnectorResponse] + getConnectorConfig *connect.Client[v1alpha1.GetConnectorConfigRequest, v1alpha1.GetConnectorConfigResponse] + listConnectorTopics *connect.Client[v1alpha1.ListConnectorTopicsRequest, v1alpha1.ListConnectorTopicsResponse] + resetConnectorTopics *connect.Client[v1alpha1.ResetConnectorTopicsRequest, emptypb.Empty] +} + +// ListConnectClusters calls +// redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectClusters. +func (c *kafkaConnectServiceClient) ListConnectClusters(ctx context.Context, req *connect.Request[v1alpha1.ListConnectClustersRequest]) (*connect.Response[v1alpha1.ListConnectClustersResponse], error) { + return c.listConnectClusters.CallUnary(ctx, req) +} + +// GetConnectCluster calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectCluster. +func (c *kafkaConnectServiceClient) GetConnectCluster(ctx context.Context, req *connect.Request[v1alpha1.GetConnectClusterRequest]) (*connect.Response[v1alpha1.GetConnectClusterResponse], error) { + return c.getConnectCluster.CallUnary(ctx, req) +} + +// ListConnectors calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectors. +func (c *kafkaConnectServiceClient) ListConnectors(ctx context.Context, req *connect.Request[v1alpha1.ListConnectorsRequest]) (*connect.Response[v1alpha1.ListConnectorsResponse], error) { + return c.listConnectors.CallUnary(ctx, req) +} + +// CreateConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.CreateConnector. +func (c *kafkaConnectServiceClient) CreateConnector(ctx context.Context, req *connect.Request[v1alpha1.CreateConnectorRequest]) (*connect.Response[v1alpha1.CreateConnectorResponse], error) { + return c.createConnector.CallUnary(ctx, req) +} + +// RestartConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.RestartConnector. +func (c *kafkaConnectServiceClient) RestartConnector(ctx context.Context, req *connect.Request[v1alpha1.RestartConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return c.restartConnector.CallUnary(ctx, req) +} + +// GetConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnector. +func (c *kafkaConnectServiceClient) GetConnector(ctx context.Context, req *connect.Request[v1alpha1.GetConnectorRequest]) (*connect.Response[v1alpha1.GetConnectorResponse], error) { + return c.getConnector.CallUnary(ctx, req) +} + +// GetConnectorStatus calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorStatus. +func (c *kafkaConnectServiceClient) GetConnectorStatus(ctx context.Context, req *connect.Request[v1alpha1.GetConnectorStatusRequest]) (*connect.Response[v1alpha1.GetConnectorStatusResponse], error) { + return c.getConnectorStatus.CallUnary(ctx, req) +} + +// PauseConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.PauseConnector. +func (c *kafkaConnectServiceClient) PauseConnector(ctx context.Context, req *connect.Request[v1alpha1.PauseConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return c.pauseConnector.CallUnary(ctx, req) +} + +// ResumeConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResumeConnector. +func (c *kafkaConnectServiceClient) ResumeConnector(ctx context.Context, req *connect.Request[v1alpha1.ResumeConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return c.resumeConnector.CallUnary(ctx, req) +} + +// StopConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.StopConnector. +func (c *kafkaConnectServiceClient) StopConnector(ctx context.Context, req *connect.Request[v1alpha1.StopConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return c.stopConnector.CallUnary(ctx, req) +} + +// DeleteConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.DeleteConnector. +func (c *kafkaConnectServiceClient) DeleteConnector(ctx context.Context, req *connect.Request[v1alpha1.DeleteConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return c.deleteConnector.CallUnary(ctx, req) +} + +// UpsertConnector calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.UpsertConnector. +func (c *kafkaConnectServiceClient) UpsertConnector(ctx context.Context, req *connect.Request[v1alpha1.UpsertConnectorRequest]) (*connect.Response[v1alpha1.UpsertConnectorResponse], error) { + return c.upsertConnector.CallUnary(ctx, req) +} + +// GetConnectorConfig calls redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorConfig. +func (c *kafkaConnectServiceClient) GetConnectorConfig(ctx context.Context, req *connect.Request[v1alpha1.GetConnectorConfigRequest]) (*connect.Response[v1alpha1.GetConnectorConfigResponse], error) { + return c.getConnectorConfig.CallUnary(ctx, req) +} + +// ListConnectorTopics calls +// redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectorTopics. +func (c *kafkaConnectServiceClient) ListConnectorTopics(ctx context.Context, req *connect.Request[v1alpha1.ListConnectorTopicsRequest]) (*connect.Response[v1alpha1.ListConnectorTopicsResponse], error) { + return c.listConnectorTopics.CallUnary(ctx, req) +} + +// ResetConnectorTopics calls +// redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResetConnectorTopics. +func (c *kafkaConnectServiceClient) ResetConnectorTopics(ctx context.Context, req *connect.Request[v1alpha1.ResetConnectorTopicsRequest]) (*connect.Response[emptypb.Empty], error) { + return c.resetConnectorTopics.CallUnary(ctx, req) +} + +// KafkaConnectServiceHandler is an implementation of the +// redpanda.api.dataplane.v1alpha1.KafkaConnectService service. +type KafkaConnectServiceHandler interface { + // ListConnectClusters implements the list clusters method, list connect + // clusters available in the console configuration + ListConnectClusters(context.Context, *connect.Request[v1alpha1.ListConnectClustersRequest]) (*connect.Response[v1alpha1.ListConnectClustersResponse], error) + // GetConnectCluster implements the get cluster info method, exposes a Kafka + // Connect equivalent REST endpoint + GetConnectCluster(context.Context, *connect.Request[v1alpha1.GetConnectClusterRequest]) (*connect.Response[v1alpha1.GetConnectClusterResponse], error) + // ListConnectors implements the list connectors method, exposes a Kafka + // Connect equivalent REST endpoint + ListConnectors(context.Context, *connect.Request[v1alpha1.ListConnectorsRequest]) (*connect.Response[v1alpha1.ListConnectorsResponse], error) + // CreateConnector implements the create connector method, and exposes an + // equivalent REST endpoint as the Kafka connect API endpoint + CreateConnector(context.Context, *connect.Request[v1alpha1.CreateConnectorRequest]) (*connect.Response[v1alpha1.CreateConnectorResponse], error) + // RestartConnector implements the restart connector method, exposes a Kafka + // Connect equivalent REST endpoint + RestartConnector(context.Context, *connect.Request[v1alpha1.RestartConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // GetConnector implements the get connector method, exposes a Kafka + // Connect equivalent REST endpoint + GetConnector(context.Context, *connect.Request[v1alpha1.GetConnectorRequest]) (*connect.Response[v1alpha1.GetConnectorResponse], error) + // GetConnectorStatus implement the get status method, Gets the current status of the connector, including: + // Whether it is running or restarting, or if it has failed or paused + // Which worker it is assigned to + // Error information if it has failed + // The state of all its tasks + GetConnectorStatus(context.Context, *connect.Request[v1alpha1.GetConnectorStatusRequest]) (*connect.Response[v1alpha1.GetConnectorStatusResponse], error) + // PauseConnector implements the pause connector method, exposes a Kafka + // connect equivalent REST endpoint + PauseConnector(context.Context, *connect.Request[v1alpha1.PauseConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // ResumeConnector implements the resume connector method, exposes a Kafka + // connect equivalent REST endpoint + ResumeConnector(context.Context, *connect.Request[v1alpha1.ResumeConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // StopConnector implements the stop connector method, exposes a Kafka + // connect equivalent REST endpoint it stops the connector but does not + // delete the connector. All tasks for the connector are shut down completely + StopConnector(context.Context, *connect.Request[v1alpha1.StopConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // DeleteConnector implements the delete connector method, exposes a Kafka + // connect equivalent REST endpoint + DeleteConnector(context.Context, *connect.Request[v1alpha1.DeleteConnectorRequest]) (*connect.Response[emptypb.Empty], error) + // UpsertConector implements the update or create connector method, it + // exposes a kafka connect equivalent REST endpoint + UpsertConnector(context.Context, *connect.Request[v1alpha1.UpsertConnectorRequest]) (*connect.Response[v1alpha1.UpsertConnectorResponse], error) + // GetConnectorConfig implements the get connector configuration method, expose a kafka connect equivalent REST endpoint + GetConnectorConfig(context.Context, *connect.Request[v1alpha1.GetConnectorConfigRequest]) (*connect.Response[v1alpha1.GetConnectorConfigResponse], error) + // ListConnectorTopics implements the list connector topics method, expose a kafka connect equivalent REST endpoint + ListConnectorTopics(context.Context, *connect.Request[v1alpha1.ListConnectorTopicsRequest]) (*connect.Response[v1alpha1.ListConnectorTopicsResponse], error) + // ResetConnectorTopics implements the reset connector topics method, expose a kafka connect equivalent REST endpoint + // the request body is empty. + ResetConnectorTopics(context.Context, *connect.Request[v1alpha1.ResetConnectorTopicsRequest]) (*connect.Response[emptypb.Empty], error) +} + +// NewKafkaConnectServiceHandler builds an HTTP handler from the service implementation. It returns +// the path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewKafkaConnectServiceHandler(svc KafkaConnectServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + kafkaConnectServiceListConnectClustersHandler := connect.NewUnaryHandler( + KafkaConnectServiceListConnectClustersProcedure, + svc.ListConnectClusters, + connect.WithSchema(kafkaConnectServiceListConnectClustersMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceGetConnectClusterHandler := connect.NewUnaryHandler( + KafkaConnectServiceGetConnectClusterProcedure, + svc.GetConnectCluster, + connect.WithSchema(kafkaConnectServiceGetConnectClusterMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceListConnectorsHandler := connect.NewUnaryHandler( + KafkaConnectServiceListConnectorsProcedure, + svc.ListConnectors, + connect.WithSchema(kafkaConnectServiceListConnectorsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceCreateConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceCreateConnectorProcedure, + svc.CreateConnector, + connect.WithSchema(kafkaConnectServiceCreateConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceRestartConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceRestartConnectorProcedure, + svc.RestartConnector, + connect.WithSchema(kafkaConnectServiceRestartConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceGetConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceGetConnectorProcedure, + svc.GetConnector, + connect.WithSchema(kafkaConnectServiceGetConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceGetConnectorStatusHandler := connect.NewUnaryHandler( + KafkaConnectServiceGetConnectorStatusProcedure, + svc.GetConnectorStatus, + connect.WithSchema(kafkaConnectServiceGetConnectorStatusMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServicePauseConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServicePauseConnectorProcedure, + svc.PauseConnector, + connect.WithSchema(kafkaConnectServicePauseConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceResumeConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceResumeConnectorProcedure, + svc.ResumeConnector, + connect.WithSchema(kafkaConnectServiceResumeConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceStopConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceStopConnectorProcedure, + svc.StopConnector, + connect.WithSchema(kafkaConnectServiceStopConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceDeleteConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceDeleteConnectorProcedure, + svc.DeleteConnector, + connect.WithSchema(kafkaConnectServiceDeleteConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceUpsertConnectorHandler := connect.NewUnaryHandler( + KafkaConnectServiceUpsertConnectorProcedure, + svc.UpsertConnector, + connect.WithSchema(kafkaConnectServiceUpsertConnectorMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceGetConnectorConfigHandler := connect.NewUnaryHandler( + KafkaConnectServiceGetConnectorConfigProcedure, + svc.GetConnectorConfig, + connect.WithSchema(kafkaConnectServiceGetConnectorConfigMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceListConnectorTopicsHandler := connect.NewUnaryHandler( + KafkaConnectServiceListConnectorTopicsProcedure, + svc.ListConnectorTopics, + connect.WithSchema(kafkaConnectServiceListConnectorTopicsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + kafkaConnectServiceResetConnectorTopicsHandler := connect.NewUnaryHandler( + KafkaConnectServiceResetConnectorTopicsProcedure, + svc.ResetConnectorTopics, + connect.WithSchema(kafkaConnectServiceResetConnectorTopicsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.KafkaConnectService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case KafkaConnectServiceListConnectClustersProcedure: + kafkaConnectServiceListConnectClustersHandler.ServeHTTP(w, r) + case KafkaConnectServiceGetConnectClusterProcedure: + kafkaConnectServiceGetConnectClusterHandler.ServeHTTP(w, r) + case KafkaConnectServiceListConnectorsProcedure: + kafkaConnectServiceListConnectorsHandler.ServeHTTP(w, r) + case KafkaConnectServiceCreateConnectorProcedure: + kafkaConnectServiceCreateConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceRestartConnectorProcedure: + kafkaConnectServiceRestartConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceGetConnectorProcedure: + kafkaConnectServiceGetConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceGetConnectorStatusProcedure: + kafkaConnectServiceGetConnectorStatusHandler.ServeHTTP(w, r) + case KafkaConnectServicePauseConnectorProcedure: + kafkaConnectServicePauseConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceResumeConnectorProcedure: + kafkaConnectServiceResumeConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceStopConnectorProcedure: + kafkaConnectServiceStopConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceDeleteConnectorProcedure: + kafkaConnectServiceDeleteConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceUpsertConnectorProcedure: + kafkaConnectServiceUpsertConnectorHandler.ServeHTTP(w, r) + case KafkaConnectServiceGetConnectorConfigProcedure: + kafkaConnectServiceGetConnectorConfigHandler.ServeHTTP(w, r) + case KafkaConnectServiceListConnectorTopicsProcedure: + kafkaConnectServiceListConnectorTopicsHandler.ServeHTTP(w, r) + case KafkaConnectServiceResetConnectorTopicsProcedure: + kafkaConnectServiceResetConnectorTopicsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedKafkaConnectServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedKafkaConnectServiceHandler struct{} + +func (UnimplementedKafkaConnectServiceHandler) ListConnectClusters(context.Context, *connect.Request[v1alpha1.ListConnectClustersRequest]) (*connect.Response[v1alpha1.ListConnectClustersResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectClusters is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) GetConnectCluster(context.Context, *connect.Request[v1alpha1.GetConnectClusterRequest]) (*connect.Response[v1alpha1.GetConnectClusterResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectCluster is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) ListConnectors(context.Context, *connect.Request[v1alpha1.ListConnectorsRequest]) (*connect.Response[v1alpha1.ListConnectorsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectors is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) CreateConnector(context.Context, *connect.Request[v1alpha1.CreateConnectorRequest]) (*connect.Response[v1alpha1.CreateConnectorResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.CreateConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) RestartConnector(context.Context, *connect.Request[v1alpha1.RestartConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.RestartConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) GetConnector(context.Context, *connect.Request[v1alpha1.GetConnectorRequest]) (*connect.Response[v1alpha1.GetConnectorResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) GetConnectorStatus(context.Context, *connect.Request[v1alpha1.GetConnectorStatusRequest]) (*connect.Response[v1alpha1.GetConnectorStatusResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorStatus is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) PauseConnector(context.Context, *connect.Request[v1alpha1.PauseConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.PauseConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) ResumeConnector(context.Context, *connect.Request[v1alpha1.ResumeConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResumeConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) StopConnector(context.Context, *connect.Request[v1alpha1.StopConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.StopConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) DeleteConnector(context.Context, *connect.Request[v1alpha1.DeleteConnectorRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.DeleteConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) UpsertConnector(context.Context, *connect.Request[v1alpha1.UpsertConnectorRequest]) (*connect.Response[v1alpha1.UpsertConnectorResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.UpsertConnector is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) GetConnectorConfig(context.Context, *connect.Request[v1alpha1.GetConnectorConfigRequest]) (*connect.Response[v1alpha1.GetConnectorConfigResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorConfig is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) ListConnectorTopics(context.Context, *connect.Request[v1alpha1.ListConnectorTopicsRequest]) (*connect.Response[v1alpha1.ListConnectorTopicsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectorTopics is not implemented")) +} + +func (UnimplementedKafkaConnectServiceHandler) ResetConnectorTopics(context.Context, *connect.Request[v1alpha1.ResetConnectorTopicsRequest]) (*connect.Response[emptypb.Empty], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResetConnectorTopics is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/secret.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/secret.connect.go new file mode 100644 index 0000000000000..249d8f8184bd1 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/secret.connect.go @@ -0,0 +1,403 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/secret.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // SecretServiceName is the fully-qualified name of the SecretService service. + SecretServiceName = "redpanda.api.dataplane.v1alpha1.SecretService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // SecretServiceGetSecretProcedure is the fully-qualified name of the SecretService's GetSecret RPC. + SecretServiceGetSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/GetSecret" + // SecretServiceListSecretsProcedure is the fully-qualified name of the SecretService's ListSecrets + // RPC. + SecretServiceListSecretsProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/ListSecrets" + // SecretServiceCreateSecretProcedure is the fully-qualified name of the SecretService's + // CreateSecret RPC. + SecretServiceCreateSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/CreateSecret" + // SecretServiceUpdateSecretProcedure is the fully-qualified name of the SecretService's + // UpdateSecret RPC. + SecretServiceUpdateSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/UpdateSecret" + // SecretServiceDeleteSecretProcedure is the fully-qualified name of the SecretService's + // DeleteSecret RPC. + SecretServiceDeleteSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/DeleteSecret" + // SecretServiceGetConnectSecretProcedure is the fully-qualified name of the SecretService's + // GetConnectSecret RPC. + SecretServiceGetConnectSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/GetConnectSecret" + // SecretServiceListConnectSecretsProcedure is the fully-qualified name of the SecretService's + // ListConnectSecrets RPC. + SecretServiceListConnectSecretsProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/ListConnectSecrets" + // SecretServiceCreateConnectSecretProcedure is the fully-qualified name of the SecretService's + // CreateConnectSecret RPC. + SecretServiceCreateConnectSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/CreateConnectSecret" + // SecretServiceUpdateConnectSecretProcedure is the fully-qualified name of the SecretService's + // UpdateConnectSecret RPC. + SecretServiceUpdateConnectSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/UpdateConnectSecret" + // SecretServiceDeleteConnectSecretProcedure is the fully-qualified name of the SecretService's + // DeleteConnectSecret RPC. + SecretServiceDeleteConnectSecretProcedure = "/redpanda.api.dataplane.v1alpha1.SecretService/DeleteConnectSecret" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + secretServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_secret_proto.Services().ByName("SecretService") + secretServiceGetSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("GetSecret") + secretServiceListSecretsMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("ListSecrets") + secretServiceCreateSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("CreateSecret") + secretServiceUpdateSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("UpdateSecret") + secretServiceDeleteSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("DeleteSecret") + secretServiceGetConnectSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("GetConnectSecret") + secretServiceListConnectSecretsMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("ListConnectSecrets") + secretServiceCreateConnectSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("CreateConnectSecret") + secretServiceUpdateConnectSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("UpdateConnectSecret") + secretServiceDeleteConnectSecretMethodDescriptor = secretServiceServiceDescriptor.Methods().ByName("DeleteConnectSecret") +) + +// SecretServiceClient is a client for the redpanda.api.dataplane.v1alpha1.SecretService service. +type SecretServiceClient interface { + // GetSecret retrieves the specific secret. + GetSecret(context.Context, *connect.Request[v1alpha1.GetSecretRequest]) (*connect.Response[v1alpha1.GetSecretResponse], error) + // ListSecrets lists the secrets based on optional filter. + ListSecrets(context.Context, *connect.Request[v1alpha1.ListSecretsRequest]) (*connect.Response[v1alpha1.ListSecretsResponse], error) + // CreateSecret creates the secret. + CreateSecret(context.Context, *connect.Request[v1alpha1.CreateSecretRequest]) (*connect.Response[v1alpha1.CreateSecretResponse], error) + // UpdateSecret updates the secret. + UpdateSecret(context.Context, *connect.Request[v1alpha1.UpdateSecretRequest]) (*connect.Response[v1alpha1.UpdateSecretResponse], error) + // DeleteSecret deletes the secret. + DeleteSecret(context.Context, *connect.Request[v1alpha1.DeleteSecretRequest]) (*connect.Response[v1alpha1.DeleteSecretResponse], error) + // GetConnectSecret retrieves the specific secret for a specific Connect. + GetConnectSecret(context.Context, *connect.Request[v1alpha1.GetConnectSecretRequest]) (*connect.Response[v1alpha1.GetConnectSecretResponse], error) + // ListConnectSecrets lists the Connect secrets based on optional filter. + ListConnectSecrets(context.Context, *connect.Request[v1alpha1.ListConnectSecretsRequest]) (*connect.Response[v1alpha1.ListConnectSecretsResponse], error) + // CreateConnectSecret creates the secret for a Connect. + CreateConnectSecret(context.Context, *connect.Request[v1alpha1.CreateConnectSecretRequest]) (*connect.Response[v1alpha1.CreateConnectSecretResponse], error) + // UpdateConnectSecret updates the Connect secret. + UpdateConnectSecret(context.Context, *connect.Request[v1alpha1.UpdateConnectSecretRequest]) (*connect.Response[v1alpha1.UpdateConnectSecretResponse], error) + // DeleteSecret deletes the secret. + DeleteConnectSecret(context.Context, *connect.Request[v1alpha1.DeleteConnectSecretRequest]) (*connect.Response[v1alpha1.DeleteConnectSecretResponse], error) +} + +// NewSecretServiceClient constructs a client for the redpanda.api.dataplane.v1alpha1.SecretService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewSecretServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SecretServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &secretServiceClient{ + getSecret: connect.NewClient[v1alpha1.GetSecretRequest, v1alpha1.GetSecretResponse]( + httpClient, + baseURL+SecretServiceGetSecretProcedure, + connect.WithSchema(secretServiceGetSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listSecrets: connect.NewClient[v1alpha1.ListSecretsRequest, v1alpha1.ListSecretsResponse]( + httpClient, + baseURL+SecretServiceListSecretsProcedure, + connect.WithSchema(secretServiceListSecretsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + createSecret: connect.NewClient[v1alpha1.CreateSecretRequest, v1alpha1.CreateSecretResponse]( + httpClient, + baseURL+SecretServiceCreateSecretProcedure, + connect.WithSchema(secretServiceCreateSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateSecret: connect.NewClient[v1alpha1.UpdateSecretRequest, v1alpha1.UpdateSecretResponse]( + httpClient, + baseURL+SecretServiceUpdateSecretProcedure, + connect.WithSchema(secretServiceUpdateSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteSecret: connect.NewClient[v1alpha1.DeleteSecretRequest, v1alpha1.DeleteSecretResponse]( + httpClient, + baseURL+SecretServiceDeleteSecretProcedure, + connect.WithSchema(secretServiceDeleteSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getConnectSecret: connect.NewClient[v1alpha1.GetConnectSecretRequest, v1alpha1.GetConnectSecretResponse]( + httpClient, + baseURL+SecretServiceGetConnectSecretProcedure, + connect.WithSchema(secretServiceGetConnectSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listConnectSecrets: connect.NewClient[v1alpha1.ListConnectSecretsRequest, v1alpha1.ListConnectSecretsResponse]( + httpClient, + baseURL+SecretServiceListConnectSecretsProcedure, + connect.WithSchema(secretServiceListConnectSecretsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + createConnectSecret: connect.NewClient[v1alpha1.CreateConnectSecretRequest, v1alpha1.CreateConnectSecretResponse]( + httpClient, + baseURL+SecretServiceCreateConnectSecretProcedure, + connect.WithSchema(secretServiceCreateConnectSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateConnectSecret: connect.NewClient[v1alpha1.UpdateConnectSecretRequest, v1alpha1.UpdateConnectSecretResponse]( + httpClient, + baseURL+SecretServiceUpdateConnectSecretProcedure, + connect.WithSchema(secretServiceUpdateConnectSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteConnectSecret: connect.NewClient[v1alpha1.DeleteConnectSecretRequest, v1alpha1.DeleteConnectSecretResponse]( + httpClient, + baseURL+SecretServiceDeleteConnectSecretProcedure, + connect.WithSchema(secretServiceDeleteConnectSecretMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// secretServiceClient implements SecretServiceClient. +type secretServiceClient struct { + getSecret *connect.Client[v1alpha1.GetSecretRequest, v1alpha1.GetSecretResponse] + listSecrets *connect.Client[v1alpha1.ListSecretsRequest, v1alpha1.ListSecretsResponse] + createSecret *connect.Client[v1alpha1.CreateSecretRequest, v1alpha1.CreateSecretResponse] + updateSecret *connect.Client[v1alpha1.UpdateSecretRequest, v1alpha1.UpdateSecretResponse] + deleteSecret *connect.Client[v1alpha1.DeleteSecretRequest, v1alpha1.DeleteSecretResponse] + getConnectSecret *connect.Client[v1alpha1.GetConnectSecretRequest, v1alpha1.GetConnectSecretResponse] + listConnectSecrets *connect.Client[v1alpha1.ListConnectSecretsRequest, v1alpha1.ListConnectSecretsResponse] + createConnectSecret *connect.Client[v1alpha1.CreateConnectSecretRequest, v1alpha1.CreateConnectSecretResponse] + updateConnectSecret *connect.Client[v1alpha1.UpdateConnectSecretRequest, v1alpha1.UpdateConnectSecretResponse] + deleteConnectSecret *connect.Client[v1alpha1.DeleteConnectSecretRequest, v1alpha1.DeleteConnectSecretResponse] +} + +// GetSecret calls redpanda.api.dataplane.v1alpha1.SecretService.GetSecret. +func (c *secretServiceClient) GetSecret(ctx context.Context, req *connect.Request[v1alpha1.GetSecretRequest]) (*connect.Response[v1alpha1.GetSecretResponse], error) { + return c.getSecret.CallUnary(ctx, req) +} + +// ListSecrets calls redpanda.api.dataplane.v1alpha1.SecretService.ListSecrets. +func (c *secretServiceClient) ListSecrets(ctx context.Context, req *connect.Request[v1alpha1.ListSecretsRequest]) (*connect.Response[v1alpha1.ListSecretsResponse], error) { + return c.listSecrets.CallUnary(ctx, req) +} + +// CreateSecret calls redpanda.api.dataplane.v1alpha1.SecretService.CreateSecret. +func (c *secretServiceClient) CreateSecret(ctx context.Context, req *connect.Request[v1alpha1.CreateSecretRequest]) (*connect.Response[v1alpha1.CreateSecretResponse], error) { + return c.createSecret.CallUnary(ctx, req) +} + +// UpdateSecret calls redpanda.api.dataplane.v1alpha1.SecretService.UpdateSecret. +func (c *secretServiceClient) UpdateSecret(ctx context.Context, req *connect.Request[v1alpha1.UpdateSecretRequest]) (*connect.Response[v1alpha1.UpdateSecretResponse], error) { + return c.updateSecret.CallUnary(ctx, req) +} + +// DeleteSecret calls redpanda.api.dataplane.v1alpha1.SecretService.DeleteSecret. +func (c *secretServiceClient) DeleteSecret(ctx context.Context, req *connect.Request[v1alpha1.DeleteSecretRequest]) (*connect.Response[v1alpha1.DeleteSecretResponse], error) { + return c.deleteSecret.CallUnary(ctx, req) +} + +// GetConnectSecret calls redpanda.api.dataplane.v1alpha1.SecretService.GetConnectSecret. +func (c *secretServiceClient) GetConnectSecret(ctx context.Context, req *connect.Request[v1alpha1.GetConnectSecretRequest]) (*connect.Response[v1alpha1.GetConnectSecretResponse], error) { + return c.getConnectSecret.CallUnary(ctx, req) +} + +// ListConnectSecrets calls redpanda.api.dataplane.v1alpha1.SecretService.ListConnectSecrets. +func (c *secretServiceClient) ListConnectSecrets(ctx context.Context, req *connect.Request[v1alpha1.ListConnectSecretsRequest]) (*connect.Response[v1alpha1.ListConnectSecretsResponse], error) { + return c.listConnectSecrets.CallUnary(ctx, req) +} + +// CreateConnectSecret calls redpanda.api.dataplane.v1alpha1.SecretService.CreateConnectSecret. +func (c *secretServiceClient) CreateConnectSecret(ctx context.Context, req *connect.Request[v1alpha1.CreateConnectSecretRequest]) (*connect.Response[v1alpha1.CreateConnectSecretResponse], error) { + return c.createConnectSecret.CallUnary(ctx, req) +} + +// UpdateConnectSecret calls redpanda.api.dataplane.v1alpha1.SecretService.UpdateConnectSecret. +func (c *secretServiceClient) UpdateConnectSecret(ctx context.Context, req *connect.Request[v1alpha1.UpdateConnectSecretRequest]) (*connect.Response[v1alpha1.UpdateConnectSecretResponse], error) { + return c.updateConnectSecret.CallUnary(ctx, req) +} + +// DeleteConnectSecret calls redpanda.api.dataplane.v1alpha1.SecretService.DeleteConnectSecret. +func (c *secretServiceClient) DeleteConnectSecret(ctx context.Context, req *connect.Request[v1alpha1.DeleteConnectSecretRequest]) (*connect.Response[v1alpha1.DeleteConnectSecretResponse], error) { + return c.deleteConnectSecret.CallUnary(ctx, req) +} + +// SecretServiceHandler is an implementation of the redpanda.api.dataplane.v1alpha1.SecretService +// service. +type SecretServiceHandler interface { + // GetSecret retrieves the specific secret. + GetSecret(context.Context, *connect.Request[v1alpha1.GetSecretRequest]) (*connect.Response[v1alpha1.GetSecretResponse], error) + // ListSecrets lists the secrets based on optional filter. + ListSecrets(context.Context, *connect.Request[v1alpha1.ListSecretsRequest]) (*connect.Response[v1alpha1.ListSecretsResponse], error) + // CreateSecret creates the secret. + CreateSecret(context.Context, *connect.Request[v1alpha1.CreateSecretRequest]) (*connect.Response[v1alpha1.CreateSecretResponse], error) + // UpdateSecret updates the secret. + UpdateSecret(context.Context, *connect.Request[v1alpha1.UpdateSecretRequest]) (*connect.Response[v1alpha1.UpdateSecretResponse], error) + // DeleteSecret deletes the secret. + DeleteSecret(context.Context, *connect.Request[v1alpha1.DeleteSecretRequest]) (*connect.Response[v1alpha1.DeleteSecretResponse], error) + // GetConnectSecret retrieves the specific secret for a specific Connect. + GetConnectSecret(context.Context, *connect.Request[v1alpha1.GetConnectSecretRequest]) (*connect.Response[v1alpha1.GetConnectSecretResponse], error) + // ListConnectSecrets lists the Connect secrets based on optional filter. + ListConnectSecrets(context.Context, *connect.Request[v1alpha1.ListConnectSecretsRequest]) (*connect.Response[v1alpha1.ListConnectSecretsResponse], error) + // CreateConnectSecret creates the secret for a Connect. + CreateConnectSecret(context.Context, *connect.Request[v1alpha1.CreateConnectSecretRequest]) (*connect.Response[v1alpha1.CreateConnectSecretResponse], error) + // UpdateConnectSecret updates the Connect secret. + UpdateConnectSecret(context.Context, *connect.Request[v1alpha1.UpdateConnectSecretRequest]) (*connect.Response[v1alpha1.UpdateConnectSecretResponse], error) + // DeleteSecret deletes the secret. + DeleteConnectSecret(context.Context, *connect.Request[v1alpha1.DeleteConnectSecretRequest]) (*connect.Response[v1alpha1.DeleteConnectSecretResponse], error) +} + +// NewSecretServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewSecretServiceHandler(svc SecretServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + secretServiceGetSecretHandler := connect.NewUnaryHandler( + SecretServiceGetSecretProcedure, + svc.GetSecret, + connect.WithSchema(secretServiceGetSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceListSecretsHandler := connect.NewUnaryHandler( + SecretServiceListSecretsProcedure, + svc.ListSecrets, + connect.WithSchema(secretServiceListSecretsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceCreateSecretHandler := connect.NewUnaryHandler( + SecretServiceCreateSecretProcedure, + svc.CreateSecret, + connect.WithSchema(secretServiceCreateSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceUpdateSecretHandler := connect.NewUnaryHandler( + SecretServiceUpdateSecretProcedure, + svc.UpdateSecret, + connect.WithSchema(secretServiceUpdateSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceDeleteSecretHandler := connect.NewUnaryHandler( + SecretServiceDeleteSecretProcedure, + svc.DeleteSecret, + connect.WithSchema(secretServiceDeleteSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceGetConnectSecretHandler := connect.NewUnaryHandler( + SecretServiceGetConnectSecretProcedure, + svc.GetConnectSecret, + connect.WithSchema(secretServiceGetConnectSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceListConnectSecretsHandler := connect.NewUnaryHandler( + SecretServiceListConnectSecretsProcedure, + svc.ListConnectSecrets, + connect.WithSchema(secretServiceListConnectSecretsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceCreateConnectSecretHandler := connect.NewUnaryHandler( + SecretServiceCreateConnectSecretProcedure, + svc.CreateConnectSecret, + connect.WithSchema(secretServiceCreateConnectSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceUpdateConnectSecretHandler := connect.NewUnaryHandler( + SecretServiceUpdateConnectSecretProcedure, + svc.UpdateConnectSecret, + connect.WithSchema(secretServiceUpdateConnectSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + secretServiceDeleteConnectSecretHandler := connect.NewUnaryHandler( + SecretServiceDeleteConnectSecretProcedure, + svc.DeleteConnectSecret, + connect.WithSchema(secretServiceDeleteConnectSecretMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.SecretService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case SecretServiceGetSecretProcedure: + secretServiceGetSecretHandler.ServeHTTP(w, r) + case SecretServiceListSecretsProcedure: + secretServiceListSecretsHandler.ServeHTTP(w, r) + case SecretServiceCreateSecretProcedure: + secretServiceCreateSecretHandler.ServeHTTP(w, r) + case SecretServiceUpdateSecretProcedure: + secretServiceUpdateSecretHandler.ServeHTTP(w, r) + case SecretServiceDeleteSecretProcedure: + secretServiceDeleteSecretHandler.ServeHTTP(w, r) + case SecretServiceGetConnectSecretProcedure: + secretServiceGetConnectSecretHandler.ServeHTTP(w, r) + case SecretServiceListConnectSecretsProcedure: + secretServiceListConnectSecretsHandler.ServeHTTP(w, r) + case SecretServiceCreateConnectSecretProcedure: + secretServiceCreateConnectSecretHandler.ServeHTTP(w, r) + case SecretServiceUpdateConnectSecretProcedure: + secretServiceUpdateConnectSecretHandler.ServeHTTP(w, r) + case SecretServiceDeleteConnectSecretProcedure: + secretServiceDeleteConnectSecretHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedSecretServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedSecretServiceHandler struct{} + +func (UnimplementedSecretServiceHandler) GetSecret(context.Context, *connect.Request[v1alpha1.GetSecretRequest]) (*connect.Response[v1alpha1.GetSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.GetSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) ListSecrets(context.Context, *connect.Request[v1alpha1.ListSecretsRequest]) (*connect.Response[v1alpha1.ListSecretsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.ListSecrets is not implemented")) +} + +func (UnimplementedSecretServiceHandler) CreateSecret(context.Context, *connect.Request[v1alpha1.CreateSecretRequest]) (*connect.Response[v1alpha1.CreateSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.CreateSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) UpdateSecret(context.Context, *connect.Request[v1alpha1.UpdateSecretRequest]) (*connect.Response[v1alpha1.UpdateSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.UpdateSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) DeleteSecret(context.Context, *connect.Request[v1alpha1.DeleteSecretRequest]) (*connect.Response[v1alpha1.DeleteSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.DeleteSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) GetConnectSecret(context.Context, *connect.Request[v1alpha1.GetConnectSecretRequest]) (*connect.Response[v1alpha1.GetConnectSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.GetConnectSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) ListConnectSecrets(context.Context, *connect.Request[v1alpha1.ListConnectSecretsRequest]) (*connect.Response[v1alpha1.ListConnectSecretsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.ListConnectSecrets is not implemented")) +} + +func (UnimplementedSecretServiceHandler) CreateConnectSecret(context.Context, *connect.Request[v1alpha1.CreateConnectSecretRequest]) (*connect.Response[v1alpha1.CreateConnectSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.CreateConnectSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) UpdateConnectSecret(context.Context, *connect.Request[v1alpha1.UpdateConnectSecretRequest]) (*connect.Response[v1alpha1.UpdateConnectSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.UpdateConnectSecret is not implemented")) +} + +func (UnimplementedSecretServiceHandler) DeleteConnectSecret(context.Context, *connect.Request[v1alpha1.DeleteConnectSecretRequest]) (*connect.Response[v1alpha1.DeleteConnectSecretResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.SecretService.DeleteConnectSecret is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/topic.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/topic.connect.go new file mode 100644 index 0000000000000..728ee71a6a23a --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/topic.connect.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/topic.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // TopicServiceName is the fully-qualified name of the TopicService service. + TopicServiceName = "redpanda.api.dataplane.v1alpha1.TopicService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // TopicServiceCreateTopicProcedure is the fully-qualified name of the TopicService's CreateTopic + // RPC. + TopicServiceCreateTopicProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/CreateTopic" + // TopicServiceListTopicsProcedure is the fully-qualified name of the TopicService's ListTopics RPC. + TopicServiceListTopicsProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/ListTopics" + // TopicServiceDeleteTopicProcedure is the fully-qualified name of the TopicService's DeleteTopic + // RPC. + TopicServiceDeleteTopicProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/DeleteTopic" + // TopicServiceGetTopicConfigurationsProcedure is the fully-qualified name of the TopicService's + // GetTopicConfigurations RPC. + TopicServiceGetTopicConfigurationsProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/GetTopicConfigurations" + // TopicServiceUpdateTopicConfigurationsProcedure is the fully-qualified name of the TopicService's + // UpdateTopicConfigurations RPC. + TopicServiceUpdateTopicConfigurationsProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/UpdateTopicConfigurations" + // TopicServiceSetTopicConfigurationsProcedure is the fully-qualified name of the TopicService's + // SetTopicConfigurations RPC. + TopicServiceSetTopicConfigurationsProcedure = "/redpanda.api.dataplane.v1alpha1.TopicService/SetTopicConfigurations" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + topicServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_topic_proto.Services().ByName("TopicService") + topicServiceCreateTopicMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("CreateTopic") + topicServiceListTopicsMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("ListTopics") + topicServiceDeleteTopicMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("DeleteTopic") + topicServiceGetTopicConfigurationsMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("GetTopicConfigurations") + topicServiceUpdateTopicConfigurationsMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("UpdateTopicConfigurations") + topicServiceSetTopicConfigurationsMethodDescriptor = topicServiceServiceDescriptor.Methods().ByName("SetTopicConfigurations") +) + +// TopicServiceClient is a client for the redpanda.api.dataplane.v1alpha1.TopicService service. +type TopicServiceClient interface { + CreateTopic(context.Context, *connect.Request[v1alpha1.CreateTopicRequest]) (*connect.Response[v1alpha1.CreateTopicResponse], error) + ListTopics(context.Context, *connect.Request[v1alpha1.ListTopicsRequest]) (*connect.Response[v1alpha1.ListTopicsResponse], error) + DeleteTopic(context.Context, *connect.Request[v1alpha1.DeleteTopicRequest]) (*connect.Response[v1alpha1.DeleteTopicResponse], error) + GetTopicConfigurations(context.Context, *connect.Request[v1alpha1.GetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.GetTopicConfigurationsResponse], error) + UpdateTopicConfigurations(context.Context, *connect.Request[v1alpha1.UpdateTopicConfigurationsRequest]) (*connect.Response[v1alpha1.UpdateTopicConfigurationsResponse], error) + SetTopicConfigurations(context.Context, *connect.Request[v1alpha1.SetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.SetTopicConfigurationsResponse], error) +} + +// NewTopicServiceClient constructs a client for the redpanda.api.dataplane.v1alpha1.TopicService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewTopicServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) TopicServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &topicServiceClient{ + createTopic: connect.NewClient[v1alpha1.CreateTopicRequest, v1alpha1.CreateTopicResponse]( + httpClient, + baseURL+TopicServiceCreateTopicProcedure, + connect.WithSchema(topicServiceCreateTopicMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listTopics: connect.NewClient[v1alpha1.ListTopicsRequest, v1alpha1.ListTopicsResponse]( + httpClient, + baseURL+TopicServiceListTopicsProcedure, + connect.WithSchema(topicServiceListTopicsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteTopic: connect.NewClient[v1alpha1.DeleteTopicRequest, v1alpha1.DeleteTopicResponse]( + httpClient, + baseURL+TopicServiceDeleteTopicProcedure, + connect.WithSchema(topicServiceDeleteTopicMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getTopicConfigurations: connect.NewClient[v1alpha1.GetTopicConfigurationsRequest, v1alpha1.GetTopicConfigurationsResponse]( + httpClient, + baseURL+TopicServiceGetTopicConfigurationsProcedure, + connect.WithSchema(topicServiceGetTopicConfigurationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateTopicConfigurations: connect.NewClient[v1alpha1.UpdateTopicConfigurationsRequest, v1alpha1.UpdateTopicConfigurationsResponse]( + httpClient, + baseURL+TopicServiceUpdateTopicConfigurationsProcedure, + connect.WithSchema(topicServiceUpdateTopicConfigurationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + setTopicConfigurations: connect.NewClient[v1alpha1.SetTopicConfigurationsRequest, v1alpha1.SetTopicConfigurationsResponse]( + httpClient, + baseURL+TopicServiceSetTopicConfigurationsProcedure, + connect.WithSchema(topicServiceSetTopicConfigurationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// topicServiceClient implements TopicServiceClient. +type topicServiceClient struct { + createTopic *connect.Client[v1alpha1.CreateTopicRequest, v1alpha1.CreateTopicResponse] + listTopics *connect.Client[v1alpha1.ListTopicsRequest, v1alpha1.ListTopicsResponse] + deleteTopic *connect.Client[v1alpha1.DeleteTopicRequest, v1alpha1.DeleteTopicResponse] + getTopicConfigurations *connect.Client[v1alpha1.GetTopicConfigurationsRequest, v1alpha1.GetTopicConfigurationsResponse] + updateTopicConfigurations *connect.Client[v1alpha1.UpdateTopicConfigurationsRequest, v1alpha1.UpdateTopicConfigurationsResponse] + setTopicConfigurations *connect.Client[v1alpha1.SetTopicConfigurationsRequest, v1alpha1.SetTopicConfigurationsResponse] +} + +// CreateTopic calls redpanda.api.dataplane.v1alpha1.TopicService.CreateTopic. +func (c *topicServiceClient) CreateTopic(ctx context.Context, req *connect.Request[v1alpha1.CreateTopicRequest]) (*connect.Response[v1alpha1.CreateTopicResponse], error) { + return c.createTopic.CallUnary(ctx, req) +} + +// ListTopics calls redpanda.api.dataplane.v1alpha1.TopicService.ListTopics. +func (c *topicServiceClient) ListTopics(ctx context.Context, req *connect.Request[v1alpha1.ListTopicsRequest]) (*connect.Response[v1alpha1.ListTopicsResponse], error) { + return c.listTopics.CallUnary(ctx, req) +} + +// DeleteTopic calls redpanda.api.dataplane.v1alpha1.TopicService.DeleteTopic. +func (c *topicServiceClient) DeleteTopic(ctx context.Context, req *connect.Request[v1alpha1.DeleteTopicRequest]) (*connect.Response[v1alpha1.DeleteTopicResponse], error) { + return c.deleteTopic.CallUnary(ctx, req) +} + +// GetTopicConfigurations calls redpanda.api.dataplane.v1alpha1.TopicService.GetTopicConfigurations. +func (c *topicServiceClient) GetTopicConfigurations(ctx context.Context, req *connect.Request[v1alpha1.GetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.GetTopicConfigurationsResponse], error) { + return c.getTopicConfigurations.CallUnary(ctx, req) +} + +// UpdateTopicConfigurations calls +// redpanda.api.dataplane.v1alpha1.TopicService.UpdateTopicConfigurations. +func (c *topicServiceClient) UpdateTopicConfigurations(ctx context.Context, req *connect.Request[v1alpha1.UpdateTopicConfigurationsRequest]) (*connect.Response[v1alpha1.UpdateTopicConfigurationsResponse], error) { + return c.updateTopicConfigurations.CallUnary(ctx, req) +} + +// SetTopicConfigurations calls redpanda.api.dataplane.v1alpha1.TopicService.SetTopicConfigurations. +func (c *topicServiceClient) SetTopicConfigurations(ctx context.Context, req *connect.Request[v1alpha1.SetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.SetTopicConfigurationsResponse], error) { + return c.setTopicConfigurations.CallUnary(ctx, req) +} + +// TopicServiceHandler is an implementation of the redpanda.api.dataplane.v1alpha1.TopicService +// service. +type TopicServiceHandler interface { + CreateTopic(context.Context, *connect.Request[v1alpha1.CreateTopicRequest]) (*connect.Response[v1alpha1.CreateTopicResponse], error) + ListTopics(context.Context, *connect.Request[v1alpha1.ListTopicsRequest]) (*connect.Response[v1alpha1.ListTopicsResponse], error) + DeleteTopic(context.Context, *connect.Request[v1alpha1.DeleteTopicRequest]) (*connect.Response[v1alpha1.DeleteTopicResponse], error) + GetTopicConfigurations(context.Context, *connect.Request[v1alpha1.GetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.GetTopicConfigurationsResponse], error) + UpdateTopicConfigurations(context.Context, *connect.Request[v1alpha1.UpdateTopicConfigurationsRequest]) (*connect.Response[v1alpha1.UpdateTopicConfigurationsResponse], error) + SetTopicConfigurations(context.Context, *connect.Request[v1alpha1.SetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.SetTopicConfigurationsResponse], error) +} + +// NewTopicServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewTopicServiceHandler(svc TopicServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + topicServiceCreateTopicHandler := connect.NewUnaryHandler( + TopicServiceCreateTopicProcedure, + svc.CreateTopic, + connect.WithSchema(topicServiceCreateTopicMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + topicServiceListTopicsHandler := connect.NewUnaryHandler( + TopicServiceListTopicsProcedure, + svc.ListTopics, + connect.WithSchema(topicServiceListTopicsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + topicServiceDeleteTopicHandler := connect.NewUnaryHandler( + TopicServiceDeleteTopicProcedure, + svc.DeleteTopic, + connect.WithSchema(topicServiceDeleteTopicMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + topicServiceGetTopicConfigurationsHandler := connect.NewUnaryHandler( + TopicServiceGetTopicConfigurationsProcedure, + svc.GetTopicConfigurations, + connect.WithSchema(topicServiceGetTopicConfigurationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + topicServiceUpdateTopicConfigurationsHandler := connect.NewUnaryHandler( + TopicServiceUpdateTopicConfigurationsProcedure, + svc.UpdateTopicConfigurations, + connect.WithSchema(topicServiceUpdateTopicConfigurationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + topicServiceSetTopicConfigurationsHandler := connect.NewUnaryHandler( + TopicServiceSetTopicConfigurationsProcedure, + svc.SetTopicConfigurations, + connect.WithSchema(topicServiceSetTopicConfigurationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.TopicService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case TopicServiceCreateTopicProcedure: + topicServiceCreateTopicHandler.ServeHTTP(w, r) + case TopicServiceListTopicsProcedure: + topicServiceListTopicsHandler.ServeHTTP(w, r) + case TopicServiceDeleteTopicProcedure: + topicServiceDeleteTopicHandler.ServeHTTP(w, r) + case TopicServiceGetTopicConfigurationsProcedure: + topicServiceGetTopicConfigurationsHandler.ServeHTTP(w, r) + case TopicServiceUpdateTopicConfigurationsProcedure: + topicServiceUpdateTopicConfigurationsHandler.ServeHTTP(w, r) + case TopicServiceSetTopicConfigurationsProcedure: + topicServiceSetTopicConfigurationsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedTopicServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedTopicServiceHandler struct{} + +func (UnimplementedTopicServiceHandler) CreateTopic(context.Context, *connect.Request[v1alpha1.CreateTopicRequest]) (*connect.Response[v1alpha1.CreateTopicResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.CreateTopic is not implemented")) +} + +func (UnimplementedTopicServiceHandler) ListTopics(context.Context, *connect.Request[v1alpha1.ListTopicsRequest]) (*connect.Response[v1alpha1.ListTopicsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.ListTopics is not implemented")) +} + +func (UnimplementedTopicServiceHandler) DeleteTopic(context.Context, *connect.Request[v1alpha1.DeleteTopicRequest]) (*connect.Response[v1alpha1.DeleteTopicResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.DeleteTopic is not implemented")) +} + +func (UnimplementedTopicServiceHandler) GetTopicConfigurations(context.Context, *connect.Request[v1alpha1.GetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.GetTopicConfigurationsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.GetTopicConfigurations is not implemented")) +} + +func (UnimplementedTopicServiceHandler) UpdateTopicConfigurations(context.Context, *connect.Request[v1alpha1.UpdateTopicConfigurationsRequest]) (*connect.Response[v1alpha1.UpdateTopicConfigurationsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.UpdateTopicConfigurations is not implemented")) +} + +func (UnimplementedTopicServiceHandler) SetTopicConfigurations(context.Context, *connect.Request[v1alpha1.SetTopicConfigurationsRequest]) (*connect.Response[v1alpha1.SetTopicConfigurationsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TopicService.SetTopicConfigurations is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/transform.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/transform.connect.go new file mode 100644 index 0000000000000..00314415869bc --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/transform.connect.go @@ -0,0 +1,176 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/transform.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // TransformServiceName is the fully-qualified name of the TransformService service. + TransformServiceName = "redpanda.api.dataplane.v1alpha1.TransformService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // TransformServiceListTransformsProcedure is the fully-qualified name of the TransformService's + // ListTransforms RPC. + TransformServiceListTransformsProcedure = "/redpanda.api.dataplane.v1alpha1.TransformService/ListTransforms" + // TransformServiceGetTransformProcedure is the fully-qualified name of the TransformService's + // GetTransform RPC. + TransformServiceGetTransformProcedure = "/redpanda.api.dataplane.v1alpha1.TransformService/GetTransform" + // TransformServiceDeleteTransformProcedure is the fully-qualified name of the TransformService's + // DeleteTransform RPC. + TransformServiceDeleteTransformProcedure = "/redpanda.api.dataplane.v1alpha1.TransformService/DeleteTransform" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + transformServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_transform_proto.Services().ByName("TransformService") + transformServiceListTransformsMethodDescriptor = transformServiceServiceDescriptor.Methods().ByName("ListTransforms") + transformServiceGetTransformMethodDescriptor = transformServiceServiceDescriptor.Methods().ByName("GetTransform") + transformServiceDeleteTransformMethodDescriptor = transformServiceServiceDescriptor.Methods().ByName("DeleteTransform") +) + +// TransformServiceClient is a client for the redpanda.api.dataplane.v1alpha1.TransformService +// service. +type TransformServiceClient interface { + ListTransforms(context.Context, *connect.Request[v1alpha1.ListTransformsRequest]) (*connect.Response[v1alpha1.ListTransformsResponse], error) + GetTransform(context.Context, *connect.Request[v1alpha1.GetTransformRequest]) (*connect.Response[v1alpha1.GetTransformResponse], error) + DeleteTransform(context.Context, *connect.Request[v1alpha1.DeleteTransformRequest]) (*connect.Response[v1alpha1.DeleteTransformResponse], error) +} + +// NewTransformServiceClient constructs a client for the +// redpanda.api.dataplane.v1alpha1.TransformService service. By default, it uses the Connect +// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed +// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewTransformServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) TransformServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &transformServiceClient{ + listTransforms: connect.NewClient[v1alpha1.ListTransformsRequest, v1alpha1.ListTransformsResponse]( + httpClient, + baseURL+TransformServiceListTransformsProcedure, + connect.WithSchema(transformServiceListTransformsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getTransform: connect.NewClient[v1alpha1.GetTransformRequest, v1alpha1.GetTransformResponse]( + httpClient, + baseURL+TransformServiceGetTransformProcedure, + connect.WithSchema(transformServiceGetTransformMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteTransform: connect.NewClient[v1alpha1.DeleteTransformRequest, v1alpha1.DeleteTransformResponse]( + httpClient, + baseURL+TransformServiceDeleteTransformProcedure, + connect.WithSchema(transformServiceDeleteTransformMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// transformServiceClient implements TransformServiceClient. +type transformServiceClient struct { + listTransforms *connect.Client[v1alpha1.ListTransformsRequest, v1alpha1.ListTransformsResponse] + getTransform *connect.Client[v1alpha1.GetTransformRequest, v1alpha1.GetTransformResponse] + deleteTransform *connect.Client[v1alpha1.DeleteTransformRequest, v1alpha1.DeleteTransformResponse] +} + +// ListTransforms calls redpanda.api.dataplane.v1alpha1.TransformService.ListTransforms. +func (c *transformServiceClient) ListTransforms(ctx context.Context, req *connect.Request[v1alpha1.ListTransformsRequest]) (*connect.Response[v1alpha1.ListTransformsResponse], error) { + return c.listTransforms.CallUnary(ctx, req) +} + +// GetTransform calls redpanda.api.dataplane.v1alpha1.TransformService.GetTransform. +func (c *transformServiceClient) GetTransform(ctx context.Context, req *connect.Request[v1alpha1.GetTransformRequest]) (*connect.Response[v1alpha1.GetTransformResponse], error) { + return c.getTransform.CallUnary(ctx, req) +} + +// DeleteTransform calls redpanda.api.dataplane.v1alpha1.TransformService.DeleteTransform. +func (c *transformServiceClient) DeleteTransform(ctx context.Context, req *connect.Request[v1alpha1.DeleteTransformRequest]) (*connect.Response[v1alpha1.DeleteTransformResponse], error) { + return c.deleteTransform.CallUnary(ctx, req) +} + +// TransformServiceHandler is an implementation of the +// redpanda.api.dataplane.v1alpha1.TransformService service. +type TransformServiceHandler interface { + ListTransforms(context.Context, *connect.Request[v1alpha1.ListTransformsRequest]) (*connect.Response[v1alpha1.ListTransformsResponse], error) + GetTransform(context.Context, *connect.Request[v1alpha1.GetTransformRequest]) (*connect.Response[v1alpha1.GetTransformResponse], error) + DeleteTransform(context.Context, *connect.Request[v1alpha1.DeleteTransformRequest]) (*connect.Response[v1alpha1.DeleteTransformResponse], error) +} + +// NewTransformServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewTransformServiceHandler(svc TransformServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + transformServiceListTransformsHandler := connect.NewUnaryHandler( + TransformServiceListTransformsProcedure, + svc.ListTransforms, + connect.WithSchema(transformServiceListTransformsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + transformServiceGetTransformHandler := connect.NewUnaryHandler( + TransformServiceGetTransformProcedure, + svc.GetTransform, + connect.WithSchema(transformServiceGetTransformMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + transformServiceDeleteTransformHandler := connect.NewUnaryHandler( + TransformServiceDeleteTransformProcedure, + svc.DeleteTransform, + connect.WithSchema(transformServiceDeleteTransformMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.TransformService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case TransformServiceListTransformsProcedure: + transformServiceListTransformsHandler.ServeHTTP(w, r) + case TransformServiceGetTransformProcedure: + transformServiceGetTransformHandler.ServeHTTP(w, r) + case TransformServiceDeleteTransformProcedure: + transformServiceDeleteTransformHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedTransformServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedTransformServiceHandler struct{} + +func (UnimplementedTransformServiceHandler) ListTransforms(context.Context, *connect.Request[v1alpha1.ListTransformsRequest]) (*connect.Response[v1alpha1.ListTransformsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TransformService.ListTransforms is not implemented")) +} + +func (UnimplementedTransformServiceHandler) GetTransform(context.Context, *connect.Request[v1alpha1.GetTransformRequest]) (*connect.Response[v1alpha1.GetTransformResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TransformService.GetTransform is not implemented")) +} + +func (UnimplementedTransformServiceHandler) DeleteTransform(context.Context, *connect.Request[v1alpha1.DeleteTransformRequest]) (*connect.Response[v1alpha1.DeleteTransformResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.TransformService.DeleteTransform is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/user.connect.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/user.connect.go new file mode 100644 index 0000000000000..c49423bdb608f --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dataplanev1alpha1connect/user.connect.go @@ -0,0 +1,200 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: redpanda/api/dataplane/v1alpha1/user.proto + +package dataplanev1alpha1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1alpha1 "github.com/redpanda-data/redpanda/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // UserServiceName is the fully-qualified name of the UserService service. + UserServiceName = "redpanda.api.dataplane.v1alpha1.UserService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // UserServiceCreateUserProcedure is the fully-qualified name of the UserService's CreateUser RPC. + UserServiceCreateUserProcedure = "/redpanda.api.dataplane.v1alpha1.UserService/CreateUser" + // UserServiceUpdateUserProcedure is the fully-qualified name of the UserService's UpdateUser RPC. + UserServiceUpdateUserProcedure = "/redpanda.api.dataplane.v1alpha1.UserService/UpdateUser" + // UserServiceListUsersProcedure is the fully-qualified name of the UserService's ListUsers RPC. + UserServiceListUsersProcedure = "/redpanda.api.dataplane.v1alpha1.UserService/ListUsers" + // UserServiceDeleteUserProcedure is the fully-qualified name of the UserService's DeleteUser RPC. + UserServiceDeleteUserProcedure = "/redpanda.api.dataplane.v1alpha1.UserService/DeleteUser" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + userServiceServiceDescriptor = v1alpha1.File_redpanda_api_dataplane_v1alpha1_user_proto.Services().ByName("UserService") + userServiceCreateUserMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("CreateUser") + userServiceUpdateUserMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("UpdateUser") + userServiceListUsersMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListUsers") + userServiceDeleteUserMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("DeleteUser") +) + +// UserServiceClient is a client for the redpanda.api.dataplane.v1alpha1.UserService service. +type UserServiceClient interface { + CreateUser(context.Context, *connect.Request[v1alpha1.CreateUserRequest]) (*connect.Response[v1alpha1.CreateUserResponse], error) + UpdateUser(context.Context, *connect.Request[v1alpha1.UpdateUserRequest]) (*connect.Response[v1alpha1.UpdateUserResponse], error) + ListUsers(context.Context, *connect.Request[v1alpha1.ListUsersRequest]) (*connect.Response[v1alpha1.ListUsersResponse], error) + DeleteUser(context.Context, *connect.Request[v1alpha1.DeleteUserRequest]) (*connect.Response[v1alpha1.DeleteUserResponse], error) +} + +// NewUserServiceClient constructs a client for the redpanda.api.dataplane.v1alpha1.UserService +// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for +// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply +// the connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) UserServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &userServiceClient{ + createUser: connect.NewClient[v1alpha1.CreateUserRequest, v1alpha1.CreateUserResponse]( + httpClient, + baseURL+UserServiceCreateUserProcedure, + connect.WithSchema(userServiceCreateUserMethodDescriptor), + connect.WithClientOptions(opts...), + ), + updateUser: connect.NewClient[v1alpha1.UpdateUserRequest, v1alpha1.UpdateUserResponse]( + httpClient, + baseURL+UserServiceUpdateUserProcedure, + connect.WithSchema(userServiceUpdateUserMethodDescriptor), + connect.WithClientOptions(opts...), + ), + listUsers: connect.NewClient[v1alpha1.ListUsersRequest, v1alpha1.ListUsersResponse]( + httpClient, + baseURL+UserServiceListUsersProcedure, + connect.WithSchema(userServiceListUsersMethodDescriptor), + connect.WithClientOptions(opts...), + ), + deleteUser: connect.NewClient[v1alpha1.DeleteUserRequest, v1alpha1.DeleteUserResponse]( + httpClient, + baseURL+UserServiceDeleteUserProcedure, + connect.WithSchema(userServiceDeleteUserMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// userServiceClient implements UserServiceClient. +type userServiceClient struct { + createUser *connect.Client[v1alpha1.CreateUserRequest, v1alpha1.CreateUserResponse] + updateUser *connect.Client[v1alpha1.UpdateUserRequest, v1alpha1.UpdateUserResponse] + listUsers *connect.Client[v1alpha1.ListUsersRequest, v1alpha1.ListUsersResponse] + deleteUser *connect.Client[v1alpha1.DeleteUserRequest, v1alpha1.DeleteUserResponse] +} + +// CreateUser calls redpanda.api.dataplane.v1alpha1.UserService.CreateUser. +func (c *userServiceClient) CreateUser(ctx context.Context, req *connect.Request[v1alpha1.CreateUserRequest]) (*connect.Response[v1alpha1.CreateUserResponse], error) { + return c.createUser.CallUnary(ctx, req) +} + +// UpdateUser calls redpanda.api.dataplane.v1alpha1.UserService.UpdateUser. +func (c *userServiceClient) UpdateUser(ctx context.Context, req *connect.Request[v1alpha1.UpdateUserRequest]) (*connect.Response[v1alpha1.UpdateUserResponse], error) { + return c.updateUser.CallUnary(ctx, req) +} + +// ListUsers calls redpanda.api.dataplane.v1alpha1.UserService.ListUsers. +func (c *userServiceClient) ListUsers(ctx context.Context, req *connect.Request[v1alpha1.ListUsersRequest]) (*connect.Response[v1alpha1.ListUsersResponse], error) { + return c.listUsers.CallUnary(ctx, req) +} + +// DeleteUser calls redpanda.api.dataplane.v1alpha1.UserService.DeleteUser. +func (c *userServiceClient) DeleteUser(ctx context.Context, req *connect.Request[v1alpha1.DeleteUserRequest]) (*connect.Response[v1alpha1.DeleteUserResponse], error) { + return c.deleteUser.CallUnary(ctx, req) +} + +// UserServiceHandler is an implementation of the redpanda.api.dataplane.v1alpha1.UserService +// service. +type UserServiceHandler interface { + CreateUser(context.Context, *connect.Request[v1alpha1.CreateUserRequest]) (*connect.Response[v1alpha1.CreateUserResponse], error) + UpdateUser(context.Context, *connect.Request[v1alpha1.UpdateUserRequest]) (*connect.Response[v1alpha1.UpdateUserResponse], error) + ListUsers(context.Context, *connect.Request[v1alpha1.ListUsersRequest]) (*connect.Response[v1alpha1.ListUsersResponse], error) + DeleteUser(context.Context, *connect.Request[v1alpha1.DeleteUserRequest]) (*connect.Response[v1alpha1.DeleteUserResponse], error) +} + +// NewUserServiceHandler builds an HTTP handler from the service implementation. It returns the path +// on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + userServiceCreateUserHandler := connect.NewUnaryHandler( + UserServiceCreateUserProcedure, + svc.CreateUser, + connect.WithSchema(userServiceCreateUserMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + userServiceUpdateUserHandler := connect.NewUnaryHandler( + UserServiceUpdateUserProcedure, + svc.UpdateUser, + connect.WithSchema(userServiceUpdateUserMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + userServiceListUsersHandler := connect.NewUnaryHandler( + UserServiceListUsersProcedure, + svc.ListUsers, + connect.WithSchema(userServiceListUsersMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + userServiceDeleteUserHandler := connect.NewUnaryHandler( + UserServiceDeleteUserProcedure, + svc.DeleteUser, + connect.WithSchema(userServiceDeleteUserMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/redpanda.api.dataplane.v1alpha1.UserService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case UserServiceCreateUserProcedure: + userServiceCreateUserHandler.ServeHTTP(w, r) + case UserServiceUpdateUserProcedure: + userServiceUpdateUserHandler.ServeHTTP(w, r) + case UserServiceListUsersProcedure: + userServiceListUsersHandler.ServeHTTP(w, r) + case UserServiceDeleteUserProcedure: + userServiceDeleteUserHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedUserServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedUserServiceHandler struct{} + +func (UnimplementedUserServiceHandler) CreateUser(context.Context, *connect.Request[v1alpha1.CreateUserRequest]) (*connect.Response[v1alpha1.CreateUserResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.UserService.CreateUser is not implemented")) +} + +func (UnimplementedUserServiceHandler) UpdateUser(context.Context, *connect.Request[v1alpha1.UpdateUserRequest]) (*connect.Response[v1alpha1.UpdateUserResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.UserService.UpdateUser is not implemented")) +} + +func (UnimplementedUserServiceHandler) ListUsers(context.Context, *connect.Request[v1alpha1.ListUsersRequest]) (*connect.Response[v1alpha1.ListUsersResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.UserService.ListUsers is not implemented")) +} + +func (UnimplementedUserServiceHandler) DeleteUser(context.Context, *connect.Request[v1alpha1.DeleteUserRequest]) (*connect.Response[v1alpha1.DeleteUserResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("redpanda.api.dataplane.v1alpha1.UserService.DeleteUser is not implemented")) +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dummy.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dummy.pb.go new file mode 100644 index 0000000000000..8268d001c0fb2 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/dummy.pb.go @@ -0,0 +1,247 @@ +// This file is a trick to force protoc-gen-openapiv2 into including the types used here into the openapi spec. They are not normally included, because they are not explicitly referenced in any proto (as protobuf ANY is used in errordetails). + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/dummy.proto + +package dataplanev1alpha1 + +import ( + errdetails "google.golang.org/genproto/googleapis/rpc/errdetails" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DummyMethodResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BadRequest *errdetails.BadRequest `protobuf:"bytes,1,opt,name=bad_request,json=badRequest,proto3" json:"bad_request,omitempty"` + ErrorInfo *errdetails.ErrorInfo `protobuf:"bytes,2,opt,name=error_info,json=errorInfo,proto3" json:"error_info,omitempty"` + QuotaFailure *errdetails.QuotaFailure `protobuf:"bytes,3,opt,name=quota_failure,json=quotaFailure,proto3" json:"quota_failure,omitempty"` + Help *errdetails.Help `protobuf:"bytes,4,opt,name=help,proto3" json:"help,omitempty"` + DeployTransformRequest *DeployTransformRequest `protobuf:"bytes,5,opt,name=deploy_transform_request,json=deployTransformRequest,proto3" json:"deploy_transform_request,omitempty"` +} + +func (x *DummyMethodResponse) Reset() { + *x = DummyMethodResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_dummy_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DummyMethodResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DummyMethodResponse) ProtoMessage() {} + +func (x *DummyMethodResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_dummy_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DummyMethodResponse.ProtoReflect.Descriptor instead. +func (*DummyMethodResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescGZIP(), []int{0} +} + +func (x *DummyMethodResponse) GetBadRequest() *errdetails.BadRequest { + if x != nil { + return x.BadRequest + } + return nil +} + +func (x *DummyMethodResponse) GetErrorInfo() *errdetails.ErrorInfo { + if x != nil { + return x.ErrorInfo + } + return nil +} + +func (x *DummyMethodResponse) GetQuotaFailure() *errdetails.QuotaFailure { + if x != nil { + return x.QuotaFailure + } + return nil +} + +func (x *DummyMethodResponse) GetHelp() *errdetails.Help { + if x != nil { + return x.Help + } + return nil +} + +func (x *DummyMethodResponse) GetDeployTransformRequest() *DeployTransformRequest { + if x != nil { + return x.DeployTransformRequest + } + return nil +} + +var File_redpanda_api_dataplane_v1alpha1_dummy_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdc, 0x02, 0x0a, + 0x13, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x62, 0x61, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x0a, 0x62, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, + 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0d, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x5f, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x52, 0x0c, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x46, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x65, + 0x6c, 0x70, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, 0x71, 0x0a, 0x18, 0x64, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x16, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x32, 0x6b, 0x0a, 0x0c, 0x44, + 0x75, 0x6d, 0x6d, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0b, 0x44, + 0x75, 0x6d, 0x6d, 0x79, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xbd, 0x02, 0x0a, 0x23, 0x63, 0x6f, 0x6d, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x42, 0x0a, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, + 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, + 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, + 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_dummy_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_redpanda_api_dataplane_v1alpha1_dummy_proto_goTypes = []interface{}{ + (*DummyMethodResponse)(nil), // 0: redpanda.api.dataplane.v1alpha1.DummyMethodResponse + (*errdetails.BadRequest)(nil), // 1: google.rpc.BadRequest + (*errdetails.ErrorInfo)(nil), // 2: google.rpc.ErrorInfo + (*errdetails.QuotaFailure)(nil), // 3: google.rpc.QuotaFailure + (*errdetails.Help)(nil), // 4: google.rpc.Help + (*DeployTransformRequest)(nil), // 5: redpanda.api.dataplane.v1alpha1.DeployTransformRequest + (*emptypb.Empty)(nil), // 6: google.protobuf.Empty +} +var file_redpanda_api_dataplane_v1alpha1_dummy_proto_depIdxs = []int32{ + 1, // 0: redpanda.api.dataplane.v1alpha1.DummyMethodResponse.bad_request:type_name -> google.rpc.BadRequest + 2, // 1: redpanda.api.dataplane.v1alpha1.DummyMethodResponse.error_info:type_name -> google.rpc.ErrorInfo + 3, // 2: redpanda.api.dataplane.v1alpha1.DummyMethodResponse.quota_failure:type_name -> google.rpc.QuotaFailure + 4, // 3: redpanda.api.dataplane.v1alpha1.DummyMethodResponse.help:type_name -> google.rpc.Help + 5, // 4: redpanda.api.dataplane.v1alpha1.DummyMethodResponse.deploy_transform_request:type_name -> redpanda.api.dataplane.v1alpha1.DeployTransformRequest + 6, // 5: redpanda.api.dataplane.v1alpha1.DummyService.DummyMethod:input_type -> google.protobuf.Empty + 0, // 6: redpanda.api.dataplane.v1alpha1.DummyService.DummyMethod:output_type -> redpanda.api.dataplane.v1alpha1.DummyMethodResponse + 6, // [6:7] is the sub-list for method output_type + 5, // [5:6] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_dummy_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_dummy_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_dummy_proto != nil { + return + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_init() + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_dummy_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DummyMethodResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_dummy_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_dummy_proto_depIdxs, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_dummy_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_dummy_proto = out.File + file_redpanda_api_dataplane_v1alpha1_dummy_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_dummy_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_dummy_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/error.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/error.pb.go new file mode 100644 index 0000000000000..6ffd8164ff77a --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/error.pb.go @@ -0,0 +1,181 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/error.proto + +package dataplanev1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Reason int32 + +const ( + Reason_REASON_UNSPECIFIED Reason = 0 + Reason_REASON_FEATURE_NOT_CONFIGURED Reason = 1 + Reason_REASON_CONSOLE_ERROR Reason = 2 + Reason_REASON_REDPANDA_ADMIN_API_ERROR Reason = 3 + Reason_REASON_KAFKA_API_ERROR Reason = 4 + Reason_REASON_KAFKA_CONNECT_API_ERROR Reason = 5 + Reason_REASON_TYPE_MAPPING_ERROR Reason = 6 + Reason_REASON_SECRET_STORE_ERROR Reason = 7 +) + +// Enum value maps for Reason. +var ( + Reason_name = map[int32]string{ + 0: "REASON_UNSPECIFIED", + 1: "REASON_FEATURE_NOT_CONFIGURED", + 2: "REASON_CONSOLE_ERROR", + 3: "REASON_REDPANDA_ADMIN_API_ERROR", + 4: "REASON_KAFKA_API_ERROR", + 5: "REASON_KAFKA_CONNECT_API_ERROR", + 6: "REASON_TYPE_MAPPING_ERROR", + 7: "REASON_SECRET_STORE_ERROR", + } + Reason_value = map[string]int32{ + "REASON_UNSPECIFIED": 0, + "REASON_FEATURE_NOT_CONFIGURED": 1, + "REASON_CONSOLE_ERROR": 2, + "REASON_REDPANDA_ADMIN_API_ERROR": 3, + "REASON_KAFKA_API_ERROR": 4, + "REASON_KAFKA_CONNECT_API_ERROR": 5, + "REASON_TYPE_MAPPING_ERROR": 6, + "REASON_SECRET_STORE_ERROR": 7, + } +) + +func (x Reason) Enum() *Reason { + p := new(Reason) + *p = x + return p +} + +func (x Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Reason) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_error_proto_enumTypes[0].Descriptor() +} + +func (Reason) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_error_proto_enumTypes[0] +} + +func (x Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Reason.Descriptor instead. +func (Reason) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescGZIP(), []int{0} +} + +var File_redpanda_api_dataplane_v1alpha1_error_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_error_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2a, 0x80, + 0x02, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x41, 0x54, + 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, + 0x4f, 0x4e, 0x53, 0x4f, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x23, + 0x0a, 0x1f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x44, 0x50, 0x41, 0x4e, 0x44, + 0x41, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4b, 0x41, + 0x46, 0x4b, 0x41, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, + 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4b, 0x41, 0x46, 0x4b, 0x41, 0x5f, + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x05, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x06, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x43, + 0x52, 0x45, 0x54, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x07, 0x42, 0xbd, 0x02, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0a, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, + 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, + 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, + 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_error_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_error_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_error_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_redpanda_api_dataplane_v1alpha1_error_proto_goTypes = []interface{}{ + (Reason)(0), // 0: redpanda.api.dataplane.v1alpha1.Reason +} +var file_redpanda_api_dataplane_v1alpha1_error_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_error_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_error_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_error_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_error_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_error_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_error_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_error_proto_enumTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_error_proto = out.File + file_redpanda_api_dataplane_v1alpha1_error_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_error_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_error_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/kafka_connect.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/kafka_connect.pb.go new file mode 100644 index 0000000000000..d89e8d2791672 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/kafka_connect.pb.go @@ -0,0 +1,3694 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/kafka_connect.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The following states are possible for a connector or one of its tasks +// implement the state interface described in the Kafka connect API @see +// https://docs.confluent.io/platform/current/connect/monitoring.html#connector-and-task-status +// this includes holistic unified connector status that takes into account not +// just the connector instance state, but also state of all the tasks within the +// connector +type ConnectorHolisticState int32 + +const ( + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_UNSPECIFIED ConnectorHolisticState = 0 + // PAUSED: The connector/task has been administratively paused. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_PAUSED ConnectorHolisticState = 1 + // RESTARTING: he connector/task is restarting. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_RESTARTING ConnectorHolisticState = 2 + // DESTROYED: Connector is in destroyed state, regardless of any tasks. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_DESTROYED ConnectorHolisticState = 3 + // STOPPED: The connector/task has been stopped. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_STOPPED ConnectorHolisticState = 4 + // The connector/task has not yet been assigned to a worker + // UNASSIGNED: Connector is in unassigned state. + // + // Or Connector is in running state, and there are unassigned tasks. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_UNASSIGNED ConnectorHolisticState = 5 + // HEALTHY: Connector is in running state, > 0 tasks, all of them in running state. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_HEALTHY ConnectorHolisticState = 6 + // UNHEALTHY: Connector is failed state. + // + // Or Connector is in running state but has 0 tasks. + // Or Connector is in running state, has > 0 tasks, and all tasks are in failed state. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_UNHEALTHY ConnectorHolisticState = 7 + // DEGRADED: Connector is in running state, has > 0 tasks, but has at least one state in failed state, but not all tasks are failed. + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_DEGRADED ConnectorHolisticState = 8 + // UNKNOWN: The connector/task could no be determined + ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_UNKNOWN ConnectorHolisticState = 9 +) + +// Enum value maps for ConnectorHolisticState. +var ( + ConnectorHolisticState_name = map[int32]string{ + 0: "CONNECTOR_HOLISTIC_STATE_UNSPECIFIED", + 1: "CONNECTOR_HOLISTIC_STATE_PAUSED", + 2: "CONNECTOR_HOLISTIC_STATE_RESTARTING", + 3: "CONNECTOR_HOLISTIC_STATE_DESTROYED", + 4: "CONNECTOR_HOLISTIC_STATE_STOPPED", + 5: "CONNECTOR_HOLISTIC_STATE_UNASSIGNED", + 6: "CONNECTOR_HOLISTIC_STATE_HEALTHY", + 7: "CONNECTOR_HOLISTIC_STATE_UNHEALTHY", + 8: "CONNECTOR_HOLISTIC_STATE_DEGRADED", + 9: "CONNECTOR_HOLISTIC_STATE_UNKNOWN", + } + ConnectorHolisticState_value = map[string]int32{ + "CONNECTOR_HOLISTIC_STATE_UNSPECIFIED": 0, + "CONNECTOR_HOLISTIC_STATE_PAUSED": 1, + "CONNECTOR_HOLISTIC_STATE_RESTARTING": 2, + "CONNECTOR_HOLISTIC_STATE_DESTROYED": 3, + "CONNECTOR_HOLISTIC_STATE_STOPPED": 4, + "CONNECTOR_HOLISTIC_STATE_UNASSIGNED": 5, + "CONNECTOR_HOLISTIC_STATE_HEALTHY": 6, + "CONNECTOR_HOLISTIC_STATE_UNHEALTHY": 7, + "CONNECTOR_HOLISTIC_STATE_DEGRADED": 8, + "CONNECTOR_HOLISTIC_STATE_UNKNOWN": 9, + } +) + +func (x ConnectorHolisticState) Enum() *ConnectorHolisticState { + p := new(ConnectorHolisticState) + *p = x + return p +} + +func (x ConnectorHolisticState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectorHolisticState) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes[0].Descriptor() +} + +func (ConnectorHolisticState) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes[0] +} + +func (x ConnectorHolisticState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectorHolisticState.Descriptor instead. +func (ConnectorHolisticState) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{0} +} + +type ConnectorError_Type int32 + +const ( + ConnectorError_TYPE_UNSPECIFIED ConnectorError_Type = 0 + ConnectorError_TYPE_ERROR ConnectorError_Type = 1 + ConnectorError_TYPE_WARNING ConnectorError_Type = 2 +) + +// Enum value maps for ConnectorError_Type. +var ( + ConnectorError_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "TYPE_ERROR", + 2: "TYPE_WARNING", + } + ConnectorError_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "TYPE_ERROR": 1, + "TYPE_WARNING": 2, + } +) + +func (x ConnectorError_Type) Enum() *ConnectorError_Type { + p := new(ConnectorError_Type) + *p = x + return p +} + +func (x ConnectorError_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectorError_Type) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes[1].Descriptor() +} + +func (ConnectorError_Type) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes[1] +} + +func (x ConnectorError_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectorError_Type.Descriptor instead. +func (ConnectorError_Type) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{5, 0} +} + +type ConnectorPlugin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Class string `protobuf:"bytes,3,opt,name=class,proto3" json:"class,omitempty"` +} + +func (x *ConnectorPlugin) Reset() { + *x = ConnectorPlugin{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectorPlugin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorPlugin) ProtoMessage() {} + +func (x *ConnectorPlugin) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorPlugin.ProtoReflect.Descriptor instead. +func (*ConnectorPlugin) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{0} +} + +func (x *ConnectorPlugin) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ConnectorPlugin) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *ConnectorPlugin) GetClass() string { + if x != nil { + return x.Class + } + return "" +} + +type ConnectCluster struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Info *ConnectCluster_Info `protobuf:"bytes,3,opt,name=info,proto3" json:"info,omitempty"` + Plugins []*ConnectorPlugin `protobuf:"bytes,4,rep,name=plugins,proto3" json:"plugins,omitempty"` +} + +func (x *ConnectCluster) Reset() { + *x = ConnectCluster{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectCluster) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectCluster) ProtoMessage() {} + +func (x *ConnectCluster) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectCluster.ProtoReflect.Descriptor instead. +func (*ConnectCluster) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{1} +} + +func (x *ConnectCluster) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ConnectCluster) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *ConnectCluster) GetInfo() *ConnectCluster_Info { + if x != nil { + return x.Info + } + return nil +} + +func (x *ConnectCluster) GetPlugins() []*ConnectorPlugin { + if x != nil { + return x.Plugins + } + return nil +} + +type ConnectorStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Connector *ConnectorStatus_Connector `protobuf:"bytes,2,opt,name=connector,proto3" json:"connector,omitempty"` + Tasks []*TaskStatus `protobuf:"bytes,3,rep,name=tasks,proto3" json:"tasks,omitempty"` + Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` + // holistic_state of all the tasks within the connector this is our internal + // holistic state concept + HolisticState ConnectorHolisticState `protobuf:"varint,5,opt,name=holistic_state,json=holisticState,proto3,enum=redpanda.api.dataplane.v1alpha1.ConnectorHolisticState" json:"holistic_state,omitempty"` + // Errors is list of parsed connectors' and tasks' errors + Errors []*ConnectorError `protobuf:"bytes,6,rep,name=errors,proto3" json:"errors,omitempty"` +} + +func (x *ConnectorStatus) Reset() { + *x = ConnectorStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectorStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorStatus) ProtoMessage() {} + +func (x *ConnectorStatus) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorStatus.ProtoReflect.Descriptor instead. +func (*ConnectorStatus) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{2} +} + +func (x *ConnectorStatus) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ConnectorStatus) GetConnector() *ConnectorStatus_Connector { + if x != nil { + return x.Connector + } + return nil +} + +func (x *ConnectorStatus) GetTasks() []*TaskStatus { + if x != nil { + return x.Tasks + } + return nil +} + +func (x *ConnectorStatus) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ConnectorStatus) GetHolisticState() ConnectorHolisticState { + if x != nil { + return x.HolisticState + } + return ConnectorHolisticState_CONNECTOR_HOLISTIC_STATE_UNSPECIFIED +} + +func (x *ConnectorStatus) GetErrors() []*ConnectorError { + if x != nil { + return x.Errors + } + return nil +} + +type TaskStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + WorkerId string `protobuf:"bytes,3,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty"` + Trace string `protobuf:"bytes,4,opt,name=trace,proto3" json:"trace,omitempty"` +} + +func (x *TaskStatus) Reset() { + *x = TaskStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TaskStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskStatus) ProtoMessage() {} + +func (x *TaskStatus) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskStatus.ProtoReflect.Descriptor instead. +func (*TaskStatus) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{3} +} + +func (x *TaskStatus) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *TaskStatus) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *TaskStatus) GetWorkerId() string { + if x != nil { + return x.WorkerId + } + return "" +} + +func (x *TaskStatus) GetTrace() string { + if x != nil { + return x.Trace + } + return "" +} + +type TaskInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connector string `protobuf:"bytes,1,opt,name=connector,proto3" json:"connector,omitempty"` + Task int32 `protobuf:"varint,2,opt,name=task,proto3" json:"task,omitempty"` +} + +func (x *TaskInfo) Reset() { + *x = TaskInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TaskInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskInfo) ProtoMessage() {} + +func (x *TaskInfo) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskInfo.ProtoReflect.Descriptor instead. +func (*TaskInfo) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{4} +} + +func (x *TaskInfo) GetConnector() string { + if x != nil { + return x.Connector + } + return "" +} + +func (x *TaskInfo) GetTask() int32 { + if x != nil { + return x.Task + } + return 0 +} + +// ConnectorError is the error of a connector, this is holistic error +// abstraction, made parsing the error trace of connector or Task +type ConnectorError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type ConnectorError_Type `protobuf:"varint,1,opt,name=type,proto3,enum=redpanda.api.dataplane.v1alpha1.ConnectorError_Type" json:"type,omitempty"` + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *ConnectorError) Reset() { + *x = ConnectorError{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectorError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorError) ProtoMessage() {} + +func (x *ConnectorError) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorError.ProtoReflect.Descriptor instead. +func (*ConnectorError) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{5} +} + +func (x *ConnectorError) GetType() ConnectorError_Type { + if x != nil { + return x.Type + } + return ConnectorError_TYPE_UNSPECIFIED +} + +func (x *ConnectorError) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *ConnectorError) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +// ConectorInfo is the spec of the connector, as defined in the Kafka connect +// API, it can be used as input of the connector creation or output of the +// connectors +type ConnectorSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Config map[string]string `protobuf:"bytes,2,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Tasks []*TaskInfo `protobuf:"bytes,3,rep,name=tasks,proto3" json:"tasks,omitempty"` + Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *ConnectorSpec) Reset() { + *x = ConnectorSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectorSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorSpec) ProtoMessage() {} + +func (x *ConnectorSpec) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorSpec.ProtoReflect.Descriptor instead. +func (*ConnectorSpec) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{6} +} + +func (x *ConnectorSpec) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ConnectorSpec) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +func (x *ConnectorSpec) GetTasks() []*TaskInfo { + if x != nil { + return x.Tasks + } + return nil +} + +func (x *ConnectorSpec) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +type ListConnectorsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListConnectorsRequest) Reset() { + *x = ListConnectorsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectorsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectorsRequest) ProtoMessage() {} + +func (x *ListConnectorsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectorsRequest.ProtoReflect.Descriptor instead. +func (*ListConnectorsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{7} +} + +func (x *ListConnectorsRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *ListConnectorsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type RestartConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Options *RestartConnectorRequest_Options `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *RestartConnectorRequest) Reset() { + *x = RestartConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartConnectorRequest) ProtoMessage() {} + +func (x *RestartConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartConnectorRequest.ProtoReflect.Descriptor instead. +func (*RestartConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{8} +} + +func (x *RestartConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *RestartConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RestartConnectorRequest) GetOptions() *RestartConnectorRequest_Options { + if x != nil { + return x.Options + } + return nil +} + +type DeleteConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteConnectorRequest) Reset() { + *x = DeleteConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConnectorRequest) ProtoMessage() {} + +func (x *DeleteConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConnectorRequest.ProtoReflect.Descriptor instead. +func (*DeleteConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{9} +} + +func (x *DeleteConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *DeleteConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type PauseConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *PauseConnectorRequest) Reset() { + *x = PauseConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PauseConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseConnectorRequest) ProtoMessage() {} + +func (x *PauseConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseConnectorRequest.ProtoReflect.Descriptor instead. +func (*PauseConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{10} +} + +func (x *PauseConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *PauseConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ResumeConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ResumeConnectorRequest) Reset() { + *x = ResumeConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResumeConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeConnectorRequest) ProtoMessage() {} + +func (x *ResumeConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeConnectorRequest.ProtoReflect.Descriptor instead. +func (*ResumeConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{11} +} + +func (x *ResumeConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *ResumeConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type StopConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *StopConnectorRequest) Reset() { + *x = StopConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StopConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopConnectorRequest) ProtoMessage() {} + +func (x *StopConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopConnectorRequest.ProtoReflect.Descriptor instead. +func (*StopConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{12} +} + +func (x *StopConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *StopConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetConnectorRequest) Reset() { + *x = GetConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorRequest) ProtoMessage() {} + +func (x *GetConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorRequest.ProtoReflect.Descriptor instead. +func (*GetConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{13} +} + +func (x *GetConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *GetConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type CreateConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Connector *ConnectorSpec `protobuf:"bytes,2,opt,name=connector,proto3" json:"connector,omitempty"` +} + +func (x *CreateConnectorRequest) Reset() { + *x = CreateConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateConnectorRequest) ProtoMessage() {} + +func (x *CreateConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateConnectorRequest.ProtoReflect.Descriptor instead. +func (*CreateConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{14} +} + +func (x *CreateConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *CreateConnectorRequest) GetConnector() *ConnectorSpec { + if x != nil { + return x.Connector + } + return nil +} + +type GetConnectorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connector *ConnectorSpec `protobuf:"bytes,1,opt,name=connector,proto3" json:"connector,omitempty"` +} + +func (x *GetConnectorResponse) Reset() { + *x = GetConnectorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorResponse) ProtoMessage() {} + +func (x *GetConnectorResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorResponse.ProtoReflect.Descriptor instead. +func (*GetConnectorResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{15} +} + +func (x *GetConnectorResponse) GetConnector() *ConnectorSpec { + if x != nil { + return x.Connector + } + return nil +} + +type CreateConnectorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connector *ConnectorSpec `protobuf:"bytes,1,opt,name=connector,proto3" json:"connector,omitempty"` +} + +func (x *CreateConnectorResponse) Reset() { + *x = CreateConnectorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateConnectorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateConnectorResponse) ProtoMessage() {} + +func (x *CreateConnectorResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateConnectorResponse.ProtoReflect.Descriptor instead. +func (*CreateConnectorResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{16} +} + +func (x *CreateConnectorResponse) GetConnector() *ConnectorSpec { + if x != nil { + return x.Connector + } + return nil +} + +type ListConnectorsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // connectors is the list of connectors the key is the connector name + Connectors []*ListConnectorsResponse_ConnectorInfoStatus `protobuf:"bytes,1,rep,name=connectors,proto3" json:"connectors,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListConnectorsResponse) Reset() { + *x = ListConnectorsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectorsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectorsResponse) ProtoMessage() {} + +func (x *ListConnectorsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectorsResponse.ProtoReflect.Descriptor instead. +func (*ListConnectorsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{17} +} + +func (x *ListConnectorsResponse) GetConnectors() []*ListConnectorsResponse_ConnectorInfoStatus { + if x != nil { + return x.Connectors + } + return nil +} + +func (x *ListConnectorsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type GetConnectClusterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` +} + +func (x *GetConnectClusterRequest) Reset() { + *x = GetConnectClusterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectClusterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectClusterRequest) ProtoMessage() {} + +func (x *GetConnectClusterRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectClusterRequest.ProtoReflect.Descriptor instead. +func (*GetConnectClusterRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{18} +} + +func (x *GetConnectClusterRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +type GetConnectClusterResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Cluster *ConnectCluster `protobuf:"bytes,1,opt,name=cluster,proto3" json:"cluster,omitempty"` +} + +func (x *GetConnectClusterResponse) Reset() { + *x = GetConnectClusterResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectClusterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectClusterResponse) ProtoMessage() {} + +func (x *GetConnectClusterResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectClusterResponse.ProtoReflect.Descriptor instead. +func (*GetConnectClusterResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{19} +} + +func (x *GetConnectClusterResponse) GetCluster() *ConnectCluster { + if x != nil { + return x.Cluster + } + return nil +} + +type ListConnectClustersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListConnectClustersRequest) Reset() { + *x = ListConnectClustersRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectClustersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectClustersRequest) ProtoMessage() {} + +func (x *ListConnectClustersRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectClustersRequest.ProtoReflect.Descriptor instead. +func (*ListConnectClustersRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{20} +} + +type ListConnectClustersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Clusters []*ConnectCluster `protobuf:"bytes,1,rep,name=clusters,proto3" json:"clusters,omitempty"` +} + +func (x *ListConnectClustersResponse) Reset() { + *x = ListConnectClustersResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectClustersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectClustersResponse) ProtoMessage() {} + +func (x *ListConnectClustersResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectClustersResponse.ProtoReflect.Descriptor instead. +func (*ListConnectClustersResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{21} +} + +func (x *ListConnectClustersResponse) GetClusters() []*ConnectCluster { + if x != nil { + return x.Clusters + } + return nil +} + +type UpsertConnectorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Config map[string]string `protobuf:"bytes,3,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *UpsertConnectorRequest) Reset() { + *x = UpsertConnectorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpsertConnectorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertConnectorRequest) ProtoMessage() {} + +func (x *UpsertConnectorRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertConnectorRequest.ProtoReflect.Descriptor instead. +func (*UpsertConnectorRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{22} +} + +func (x *UpsertConnectorRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *UpsertConnectorRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpsertConnectorRequest) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +type UpsertConnectorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Connector *ConnectorSpec `protobuf:"bytes,1,opt,name=connector,proto3" json:"connector,omitempty"` +} + +func (x *UpsertConnectorResponse) Reset() { + *x = UpsertConnectorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpsertConnectorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertConnectorResponse) ProtoMessage() {} + +func (x *UpsertConnectorResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertConnectorResponse.ProtoReflect.Descriptor instead. +func (*UpsertConnectorResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{23} +} + +func (x *UpsertConnectorResponse) GetConnector() *ConnectorSpec { + if x != nil { + return x.Connector + } + return nil +} + +type GetConnectorConfigRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetConnectorConfigRequest) Reset() { + *x = GetConnectorConfigRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorConfigRequest) ProtoMessage() {} + +func (x *GetConnectorConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorConfigRequest.ProtoReflect.Descriptor instead. +func (*GetConnectorConfigRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{24} +} + +func (x *GetConnectorConfigRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *GetConnectorConfigRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetConnectorConfigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *GetConnectorConfigResponse) Reset() { + *x = GetConnectorConfigResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorConfigResponse) ProtoMessage() {} + +func (x *GetConnectorConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorConfigResponse.ProtoReflect.Descriptor instead. +func (*GetConnectorConfigResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{25} +} + +func (x *GetConnectorConfigResponse) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +type GetConnectorStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetConnectorStatusRequest) Reset() { + *x = GetConnectorStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorStatusRequest) ProtoMessage() {} + +func (x *GetConnectorStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorStatusRequest.ProtoReflect.Descriptor instead. +func (*GetConnectorStatusRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{26} +} + +func (x *GetConnectorStatusRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *GetConnectorStatusRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetConnectorStatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *ConnectorStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *GetConnectorStatusResponse) Reset() { + *x = GetConnectorStatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectorStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectorStatusResponse) ProtoMessage() {} + +func (x *GetConnectorStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectorStatusResponse.ProtoReflect.Descriptor instead. +func (*GetConnectorStatusResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{27} +} + +func (x *GetConnectorStatusResponse) GetStatus() *ConnectorStatus { + if x != nil { + return x.Status + } + return nil +} + +type ListConnectorTopicsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ListConnectorTopicsRequest) Reset() { + *x = ListConnectorTopicsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectorTopicsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectorTopicsRequest) ProtoMessage() {} + +func (x *ListConnectorTopicsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectorTopicsRequest.ProtoReflect.Descriptor instead. +func (*ListConnectorTopicsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{28} +} + +func (x *ListConnectorTopicsRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *ListConnectorTopicsRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ListConnectorTopicsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Topics []string `protobuf:"bytes,1,rep,name=topics,proto3" json:"topics,omitempty"` +} + +func (x *ListConnectorTopicsResponse) Reset() { + *x = ListConnectorTopicsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectorTopicsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectorTopicsResponse) ProtoMessage() {} + +func (x *ListConnectorTopicsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectorTopicsResponse.ProtoReflect.Descriptor instead. +func (*ListConnectorTopicsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{29} +} + +func (x *ListConnectorTopicsResponse) GetTopics() []string { + if x != nil { + return x.Topics + } + return nil +} + +type ResetConnectorTopicsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ResetConnectorTopicsRequest) Reset() { + *x = ResetConnectorTopicsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResetConnectorTopicsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetConnectorTopicsRequest) ProtoMessage() {} + +func (x *ResetConnectorTopicsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetConnectorTopicsRequest.ProtoReflect.Descriptor instead. +func (*ResetConnectorTopicsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{30} +} + +func (x *ResetConnectorTopicsRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *ResetConnectorTopicsRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ConnectCluster_Info struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Commit string `protobuf:"bytes,2,opt,name=commit,proto3" json:"commit,omitempty"` + KafkaClusterId string `protobuf:"bytes,3,opt,name=kafka_cluster_id,json=kafkaClusterId,proto3" json:"kafka_cluster_id,omitempty"` +} + +func (x *ConnectCluster_Info) Reset() { + *x = ConnectCluster_Info{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectCluster_Info) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectCluster_Info) ProtoMessage() {} + +func (x *ConnectCluster_Info) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectCluster_Info.ProtoReflect.Descriptor instead. +func (*ConnectCluster_Info) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *ConnectCluster_Info) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *ConnectCluster_Info) GetCommit() string { + if x != nil { + return x.Commit + } + return "" +} + +func (x *ConnectCluster_Info) GetKafkaClusterId() string { + if x != nil { + return x.KafkaClusterId + } + return "" +} + +type ConnectorStatus_Connector struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + WorkerId string `protobuf:"bytes,2,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty"` + Trace string `protobuf:"bytes,3,opt,name=trace,proto3" json:"trace,omitempty"` +} + +func (x *ConnectorStatus_Connector) Reset() { + *x = ConnectorStatus_Connector{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectorStatus_Connector) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorStatus_Connector) ProtoMessage() {} + +func (x *ConnectorStatus_Connector) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorStatus_Connector.ProtoReflect.Descriptor instead. +func (*ConnectorStatus_Connector) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *ConnectorStatus_Connector) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ConnectorStatus_Connector) GetWorkerId() string { + if x != nil { + return x.WorkerId + } + return "" +} + +func (x *ConnectorStatus_Connector) GetTrace() string { + if x != nil { + return x.Trace + } + return "" +} + +type RestartConnectorRequest_Options struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IncludeTasks bool `protobuf:"varint,1,opt,name=include_tasks,json=includeTasks,proto3" json:"include_tasks,omitempty"` + OnlyFailed bool `protobuf:"varint,2,opt,name=only_failed,json=onlyFailed,proto3" json:"only_failed,omitempty"` +} + +func (x *RestartConnectorRequest_Options) Reset() { + *x = RestartConnectorRequest_Options{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartConnectorRequest_Options) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartConnectorRequest_Options) ProtoMessage() {} + +func (x *RestartConnectorRequest_Options) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartConnectorRequest_Options.ProtoReflect.Descriptor instead. +func (*RestartConnectorRequest_Options) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{8, 0} +} + +func (x *RestartConnectorRequest_Options) GetIncludeTasks() bool { + if x != nil { + return x.IncludeTasks + } + return false +} + +func (x *RestartConnectorRequest_Options) GetOnlyFailed() bool { + if x != nil { + return x.OnlyFailed + } + return false +} + +type ListConnectorsResponse_ConnectorInfoStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // name is the connector name + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Info *ConnectorSpec `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Status *ConnectorStatus `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) Reset() { + *x = ListConnectorsResponse_ConnectorInfoStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectorsResponse_ConnectorInfoStatus) ProtoMessage() {} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectorsResponse_ConnectorInfoStatus.ProtoReflect.Descriptor instead. +func (*ListConnectorsResponse_ConnectorInfoStatus) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP(), []int{17, 0} +} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) GetInfo() *ConnectorSpec { + if x != nil { + return x.Info + } + return nil +} + +func (x *ListConnectorsResponse_ConnectorInfoStatus) GetStatus() *ConnectorStatus { + if x != nil { + return x.Status + } + return nil +} + +var File_redpanda_api_dataplane_v1alpha1_kafka_connect_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDesc = []byte{ + 0x0a, 0x33, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, + 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x55, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0xb8, 0x02, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x48, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, + 0x6f, 0x12, 0x4a, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x1a, 0x62, 0x0a, + 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x61, 0x66, 0x6b, 0x61, + 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, + 0x64, 0x22, 0xd5, 0x03, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x09, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x41, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x5e, 0x0a, 0x0e, 0x68, 0x6f, + 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x6f, + 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x68, 0x6f, 0x6c, + 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x73, 0x1a, 0x54, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x22, 0x65, 0x0a, 0x0a, 0x54, 0x61, 0x73, + 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x22, 0x3c, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, + 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x22, 0xca, + 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x48, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3e, 0x0a, 0x04, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x22, 0xa7, 0x02, 0x0a, 0x0d, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1d, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xe0, 0x41, 0x02, + 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x5d, 0x0a, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x09, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x03, + 0xc8, 0x01, 0x01, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x44, 0x0a, 0x05, 0x74, + 0x61, 0x73, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x61, 0x73, + 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x03, 0xe0, 0x41, 0x03, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x03, 0xe0, 0x41, 0x03, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd3, 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, + 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, + 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, + 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, + 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, + 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xb6, 0x04, 0x0a, 0x17, + 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, + 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, + 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, + 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, + 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, + 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, + 0x01, 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, + 0x39, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, + 0x3a, 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, + 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x5a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x1a, 0x4f, 0x0a, 0x07, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x22, 0x88, 0x03, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, + 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, + 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, + 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, + 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, + 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, + 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, + 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, + 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, + 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x87, 0x03, 0x0a, 0x15, 0x50, 0x61, 0x75, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, + 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, + 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, + 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, + 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, + 0x32, 0x10, 0x01, 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, + 0x30, 0x2d, 0x39, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, + 0x2b, 0x3b, 0x3a, 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, + 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x88, 0x03, 0x0a, 0x16, 0x52, 0x65, + 0x73, 0x75, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, + 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, + 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, + 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, + 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, 0x18, 0x80, + 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x21, 0x40, + 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, 0x27, 0x22, + 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x86, 0x03, 0x0a, 0x14, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, + 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, + 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, + 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, + 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, + 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, + 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, + 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, + 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, + 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, + 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, + 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x85, 0x03, + 0x0a, 0x13, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, + 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, + 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, + 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, + 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, + 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, + 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, 0x18, + 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x21, + 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, 0x27, + 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x83, 0x03, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, + 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, + 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, + 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, + 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, + 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, + 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4c, 0x0a, + 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x64, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x22, 0x67, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, + 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xd8, 0x03, 0x0a, 0x16, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x73, 0x12, 0x96, 0x01, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6e, 0x92, 0x41, + 0x6b, 0x32, 0x69, 0x50, 0x61, 0x67, 0x65, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, + 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x20, + 0x70, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, + 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x65, 0x78, 0x74, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0xb7, 0x01, 0x0a, 0x13, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xb7, 0x02, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, + 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, + 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, + 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, + 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, + 0x66, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x07, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x07, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x1c, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6a, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x22, 0x8d, 0x04, 0x0a, 0x16, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, + 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, + 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, + 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, + 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, + 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, + 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, + 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1f, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, + 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, + 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x66, + 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x42, 0x09, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x67, 0x0a, 0x17, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, + 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xed, 0x02, 0x0a, 0x19, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, + 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, + 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, + 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x1f, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, + 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, + 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xb8, 0x01, 0x0a, 0x1a, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8b, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, + 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, + 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, + 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, + 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, + 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3d, + 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, 0x18, 0x80, 0x08, + 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x21, 0x40, 0x23, + 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, 0x27, 0x22, 0x60, + 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x66, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x48, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x8c, 0x03, 0x0a, 0x1a, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0xf6, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, + 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, + 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x19, 0xc8, 0x01, 0x01, 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, + 0x72, 0x32, 0x10, 0x01, 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, + 0x7a, 0x30, 0x2d, 0x39, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, + 0x3d, 0x2b, 0x3b, 0x3a, 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, + 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x0a, 0x1b, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x06, 0x52, 0x06, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x22, 0x8d, 0x03, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9a, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf6, 0x01, + 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, + 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, + 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, + 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x19, 0xc8, 0x01, 0x01, + 0x72, 0x14, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x0d, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, + 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x3d, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x37, 0xc8, 0x01, 0x01, 0x72, 0x32, 0x10, 0x01, + 0x18, 0x80, 0x08, 0x32, 0x2b, 0x5e, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, + 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29, 0x2d, 0x5f, 0x3d, 0x2b, 0x3b, 0x3a, + 0x27, 0x22, 0x60, 0x7e, 0x2c, 0x3c, 0x2e, 0x3e, 0x2f, 0x3f, 0x7c, 0x5c, 0x2d, 0x5d, 0x2b, 0x24, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x2a, 0xa2, 0x03, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x48, 0x6f, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x28, 0x0a, 0x24, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, + 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, + 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x27, 0x0a, 0x23, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, + 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x53, + 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x24, 0x0a, 0x20, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, + 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x54, + 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x10, 0x05, + 0x12, 0x24, 0x0a, 0x20, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, + 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x48, 0x45, 0x41, + 0x4c, 0x54, 0x48, 0x59, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x07, 0x12, 0x25, + 0x0a, 0x21, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, + 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x47, 0x52, 0x41, + 0x44, 0x45, 0x44, 0x10, 0x08, 0x12, 0x24, 0x0a, 0x20, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, + 0x4f, 0x52, 0x5f, 0x48, 0x4f, 0x4c, 0x49, 0x53, 0x54, 0x49, 0x43, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x09, 0x32, 0xef, 0x2e, 0x0a, 0x13, + 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0xfb, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x01, 0x92, 0x41, 0xc2, 0x01, 0x12, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x1a, 0x5a, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x20, 0x61, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x2d, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x4a, 0x4d, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x46, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x40, 0x0a, + 0x3e, 0x1a, 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x73, 0x12, 0xa6, 0x04, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x99, + 0x03, 0x92, 0x41, 0xdb, 0x02, 0x12, 0x13, 0x47, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0xc5, 0x01, 0x47, 0x65, 0x74, + 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, + 0x75, 0x74, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, + 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, + 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x60, 0x2e, 0x4a, 0x40, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x39, 0x0a, 0x02, 0x4f, 0x4b, 0x12, + 0x33, 0x0a, 0x31, 0x1a, 0x2f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x4a, 0x3a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x33, 0x0a, 0x19, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, 0x62, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, + 0x29, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xd6, 0x02, 0x0a, 0x0e, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x36, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd2, + 0x01, 0x92, 0x41, 0x92, 0x01, 0x12, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x35, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x20, + 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4a, 0x48, 0x0a, + 0x03, 0x32, 0x30, 0x30, 0x12, 0x41, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x3b, 0x0a, 0x39, 0x1a, 0x37, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x36, 0x12, 0x34, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x12, 0xeb, 0x02, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe4, 0x01, 0x92, 0x41, 0x8e, + 0x01, 0x12, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x1a, 0x34, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x44, 0x0a, 0x03, 0x32, 0x30, 0x31, + 0x12, 0x3d, 0x0a, 0x07, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x30, 0x1a, + 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x3a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x62, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x34, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x12, 0xf7, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x90, 0x02, 0x92, 0x41, 0xb8, 0x01, 0x12, + 0x11, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x1a, 0x75, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x20, 0x61, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2c, 0x20, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, + 0x72, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x72, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, + 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x20, 0x6f, 0x72, 0x2f, 0x61, 0x6e, 0x64, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x2c, 0x0a, 0x03, 0x32, 0x30, 0x34, + 0x12, 0x25, 0x0a, 0x21, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4e, 0x3a, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x43, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0xda, 0x02, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x34, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xdc, 0x01, 0x92, 0x41, 0x8a, 0x01, + 0x12, 0x0d, 0x47, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x1a, + 0x38, 0x47, 0x65, 0x74, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4a, 0x3f, 0x0a, 0x03, 0x32, 0x30, 0x30, + 0x12, 0x38, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x32, 0x0a, 0x30, 0x1a, 0x2e, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x48, + 0x62, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xad, 0x03, 0x0a, 0x12, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x3a, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9d, 0x02, 0x92, 0x41, 0xc7, 0x01, 0x12, + 0x14, 0x47, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x6c, 0x47, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2c, + 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x66, + 0x20, 0x69, 0x74, 0x73, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x65, + 0x74, 0x63, 0x2e, 0x4a, 0x41, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3a, 0x0a, 0x02, 0x4f, 0x4b, + 0x12, 0x34, 0x0a, 0x32, 0x1a, 0x30, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x62, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x42, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, + 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x8b, 0x03, 0x0a, 0x0e, 0x50, 0x61, 0x75, + 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x36, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x61, + 0x75, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x02, 0x92, 0x41, + 0xdb, 0x01, 0x12, 0x0f, 0x50, 0x61, 0x75, 0x73, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x1a, 0xa4, 0x01, 0x50, 0x61, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, + 0x73, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x73, + 0x74, 0x6f, 0x70, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x75, 0x6e, + 0x74, 0x69, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x2e, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x73, 0x20, 0x61, 0x73, 0x79, 0x6e, 0x63, + 0x68, 0x72, 0x6f, 0x6e, 0x6f, 0x75, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x79, 0x20, + 0x74, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x74, + 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x4a, 0x21, 0x0a, 0x03, 0x32, 0x30, + 0x32, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x61, 0x75, 0x73, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x00, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x43, 0x1a, 0x41, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, + 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x70, 0x61, 0x75, 0x73, 0x65, 0x12, 0xb7, 0x03, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x75, 0x6d, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x75, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xd2, 0x02, 0x92, 0x41, + 0x84, 0x02, 0x12, 0x10, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x1a, 0xcb, 0x01, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x20, 0x61, 0x20, + 0x70, 0x61, 0x75, 0x73, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x73, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2c, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x69, 0x73, 0x20, 0x61, 0x73, 0x79, + 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x6f, 0x75, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, + 0x79, 0x20, 0x74, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, + 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x49, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x77, 0x61, + 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x70, 0x61, 0x75, 0x73, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x6f, 0x65, + 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x2e, 0x4a, 0x22, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x65, 0x73, + 0x75, 0x6d, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x12, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x1a, 0x42, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, + 0x12, 0x85, 0x03, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0xa4, 0x02, 0x92, 0x41, 0xd8, 0x01, 0x12, 0x0e, 0x53, 0x74, 0x6f, 0x70, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x1a, 0xa8, 0x01, 0x53, 0x74, 0x6f, 0x70, 0x73, + 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2c, 0x20, 0x62, 0x75, + 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, + 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x73, 0x68, 0x75, 0x74, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x6c, 0x79, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6c, 0x6c, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x6f, 0x75, 0x73, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x74, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x6f, + 0x6d, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, + 0x73, 0x73, 0x2e, 0x4a, 0x1b, 0x0a, 0x03, 0x32, 0x30, 0x32, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x00, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, 0x1a, 0x40, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x12, 0xba, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xd5, 0x01, + 0x92, 0x41, 0x8e, 0x01, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x1a, 0x66, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, + 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x20, 0x73, 0x74, 0x6f, 0x70, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x12, + 0x0a, 0x03, 0x32, 0x30, 0x34, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, + 0x12, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x2a, 0x3b, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xcb, 0x04, 0x0a, 0x0f, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc4, 0x03, 0x92, + 0x41, 0xe3, 0x02, 0x12, 0x1e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x1a, 0xb4, 0x01, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x6e, + 0x65, 0x77, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x65, + 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x2e, 0x20, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, + 0x62, 0x65, 0x65, 0x6e, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x2e, 0x4a, 0x44, 0x0a, 0x03, 0x32, 0x30, + 0x30, 0x12, 0x3d, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x30, + 0x1a, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, + 0x4a, 0x44, 0x0a, 0x03, 0x32, 0x30, 0x31, 0x12, 0x3d, 0x0a, 0x07, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x12, 0x32, 0x0a, 0x30, 0x1a, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x57, 0x3a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x62, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x1a, + 0x42, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0xac, 0x02, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x9c, 0x01, 0x92, 0x41, 0x47, 0x12, 0x1b, 0x47, 0x65, 0x74, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x28, 0x47, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x62, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0xb0, 0x03, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0x3b, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9d, 0x02, 0x92, 0x41, 0xcf, 0x01, 0x12, 0x15, 0x4c, 0x69, + 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x1a, 0x67, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x20, 0x49, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x69, + 0x73, 0x20, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x6e, + 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x4a, 0x4d, 0x0a, 0x03, + 0x32, 0x30, 0x30, 0x12, 0x46, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x40, 0x0a, 0x3e, 0x1a, 0x3c, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x44, 0x12, 0x42, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0xa5, 0x02, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0x3c, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0xb6, 0x01, 0x92, 0x41, 0x63, 0x12, 0x16, 0x52, 0x65, 0x73, 0x65, + 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x70, 0x69, + 0x63, 0x73, 0x1a, 0x3a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x4a, 0x0d, + 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x00, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x4a, 0x1a, 0x48, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, + 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x2f, 0x72, 0x65, 0x73, 0x65, 0x74, 0x42, 0xc4, 0x02, + 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x11, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, + 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, + 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, + 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, + 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, + 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes = make([]protoimpl.MessageInfo, 38) +var file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_goTypes = []interface{}{ + (ConnectorHolisticState)(0), // 0: redpanda.api.dataplane.v1alpha1.ConnectorHolisticState + (ConnectorError_Type)(0), // 1: redpanda.api.dataplane.v1alpha1.ConnectorError.Type + (*ConnectorPlugin)(nil), // 2: redpanda.api.dataplane.v1alpha1.ConnectorPlugin + (*ConnectCluster)(nil), // 3: redpanda.api.dataplane.v1alpha1.ConnectCluster + (*ConnectorStatus)(nil), // 4: redpanda.api.dataplane.v1alpha1.ConnectorStatus + (*TaskStatus)(nil), // 5: redpanda.api.dataplane.v1alpha1.TaskStatus + (*TaskInfo)(nil), // 6: redpanda.api.dataplane.v1alpha1.TaskInfo + (*ConnectorError)(nil), // 7: redpanda.api.dataplane.v1alpha1.ConnectorError + (*ConnectorSpec)(nil), // 8: redpanda.api.dataplane.v1alpha1.ConnectorSpec + (*ListConnectorsRequest)(nil), // 9: redpanda.api.dataplane.v1alpha1.ListConnectorsRequest + (*RestartConnectorRequest)(nil), // 10: redpanda.api.dataplane.v1alpha1.RestartConnectorRequest + (*DeleteConnectorRequest)(nil), // 11: redpanda.api.dataplane.v1alpha1.DeleteConnectorRequest + (*PauseConnectorRequest)(nil), // 12: redpanda.api.dataplane.v1alpha1.PauseConnectorRequest + (*ResumeConnectorRequest)(nil), // 13: redpanda.api.dataplane.v1alpha1.ResumeConnectorRequest + (*StopConnectorRequest)(nil), // 14: redpanda.api.dataplane.v1alpha1.StopConnectorRequest + (*GetConnectorRequest)(nil), // 15: redpanda.api.dataplane.v1alpha1.GetConnectorRequest + (*CreateConnectorRequest)(nil), // 16: redpanda.api.dataplane.v1alpha1.CreateConnectorRequest + (*GetConnectorResponse)(nil), // 17: redpanda.api.dataplane.v1alpha1.GetConnectorResponse + (*CreateConnectorResponse)(nil), // 18: redpanda.api.dataplane.v1alpha1.CreateConnectorResponse + (*ListConnectorsResponse)(nil), // 19: redpanda.api.dataplane.v1alpha1.ListConnectorsResponse + (*GetConnectClusterRequest)(nil), // 20: redpanda.api.dataplane.v1alpha1.GetConnectClusterRequest + (*GetConnectClusterResponse)(nil), // 21: redpanda.api.dataplane.v1alpha1.GetConnectClusterResponse + (*ListConnectClustersRequest)(nil), // 22: redpanda.api.dataplane.v1alpha1.ListConnectClustersRequest + (*ListConnectClustersResponse)(nil), // 23: redpanda.api.dataplane.v1alpha1.ListConnectClustersResponse + (*UpsertConnectorRequest)(nil), // 24: redpanda.api.dataplane.v1alpha1.UpsertConnectorRequest + (*UpsertConnectorResponse)(nil), // 25: redpanda.api.dataplane.v1alpha1.UpsertConnectorResponse + (*GetConnectorConfigRequest)(nil), // 26: redpanda.api.dataplane.v1alpha1.GetConnectorConfigRequest + (*GetConnectorConfigResponse)(nil), // 27: redpanda.api.dataplane.v1alpha1.GetConnectorConfigResponse + (*GetConnectorStatusRequest)(nil), // 28: redpanda.api.dataplane.v1alpha1.GetConnectorStatusRequest + (*GetConnectorStatusResponse)(nil), // 29: redpanda.api.dataplane.v1alpha1.GetConnectorStatusResponse + (*ListConnectorTopicsRequest)(nil), // 30: redpanda.api.dataplane.v1alpha1.ListConnectorTopicsRequest + (*ListConnectorTopicsResponse)(nil), // 31: redpanda.api.dataplane.v1alpha1.ListConnectorTopicsResponse + (*ResetConnectorTopicsRequest)(nil), // 32: redpanda.api.dataplane.v1alpha1.ResetConnectorTopicsRequest + (*ConnectCluster_Info)(nil), // 33: redpanda.api.dataplane.v1alpha1.ConnectCluster.Info + (*ConnectorStatus_Connector)(nil), // 34: redpanda.api.dataplane.v1alpha1.ConnectorStatus.Connector + nil, // 35: redpanda.api.dataplane.v1alpha1.ConnectorSpec.ConfigEntry + (*RestartConnectorRequest_Options)(nil), // 36: redpanda.api.dataplane.v1alpha1.RestartConnectorRequest.Options + (*ListConnectorsResponse_ConnectorInfoStatus)(nil), // 37: redpanda.api.dataplane.v1alpha1.ListConnectorsResponse.ConnectorInfoStatus + nil, // 38: redpanda.api.dataplane.v1alpha1.UpsertConnectorRequest.ConfigEntry + nil, // 39: redpanda.api.dataplane.v1alpha1.GetConnectorConfigResponse.ConfigEntry + (*emptypb.Empty)(nil), // 40: google.protobuf.Empty +} +var file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_depIdxs = []int32{ + 33, // 0: redpanda.api.dataplane.v1alpha1.ConnectCluster.info:type_name -> redpanda.api.dataplane.v1alpha1.ConnectCluster.Info + 2, // 1: redpanda.api.dataplane.v1alpha1.ConnectCluster.plugins:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorPlugin + 34, // 2: redpanda.api.dataplane.v1alpha1.ConnectorStatus.connector:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorStatus.Connector + 5, // 3: redpanda.api.dataplane.v1alpha1.ConnectorStatus.tasks:type_name -> redpanda.api.dataplane.v1alpha1.TaskStatus + 0, // 4: redpanda.api.dataplane.v1alpha1.ConnectorStatus.holistic_state:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorHolisticState + 7, // 5: redpanda.api.dataplane.v1alpha1.ConnectorStatus.errors:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorError + 1, // 6: redpanda.api.dataplane.v1alpha1.ConnectorError.type:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorError.Type + 35, // 7: redpanda.api.dataplane.v1alpha1.ConnectorSpec.config:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec.ConfigEntry + 6, // 8: redpanda.api.dataplane.v1alpha1.ConnectorSpec.tasks:type_name -> redpanda.api.dataplane.v1alpha1.TaskInfo + 36, // 9: redpanda.api.dataplane.v1alpha1.RestartConnectorRequest.options:type_name -> redpanda.api.dataplane.v1alpha1.RestartConnectorRequest.Options + 8, // 10: redpanda.api.dataplane.v1alpha1.CreateConnectorRequest.connector:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec + 8, // 11: redpanda.api.dataplane.v1alpha1.GetConnectorResponse.connector:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec + 8, // 12: redpanda.api.dataplane.v1alpha1.CreateConnectorResponse.connector:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec + 37, // 13: redpanda.api.dataplane.v1alpha1.ListConnectorsResponse.connectors:type_name -> redpanda.api.dataplane.v1alpha1.ListConnectorsResponse.ConnectorInfoStatus + 3, // 14: redpanda.api.dataplane.v1alpha1.GetConnectClusterResponse.cluster:type_name -> redpanda.api.dataplane.v1alpha1.ConnectCluster + 3, // 15: redpanda.api.dataplane.v1alpha1.ListConnectClustersResponse.clusters:type_name -> redpanda.api.dataplane.v1alpha1.ConnectCluster + 38, // 16: redpanda.api.dataplane.v1alpha1.UpsertConnectorRequest.config:type_name -> redpanda.api.dataplane.v1alpha1.UpsertConnectorRequest.ConfigEntry + 8, // 17: redpanda.api.dataplane.v1alpha1.UpsertConnectorResponse.connector:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec + 39, // 18: redpanda.api.dataplane.v1alpha1.GetConnectorConfigResponse.config:type_name -> redpanda.api.dataplane.v1alpha1.GetConnectorConfigResponse.ConfigEntry + 4, // 19: redpanda.api.dataplane.v1alpha1.GetConnectorStatusResponse.status:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorStatus + 8, // 20: redpanda.api.dataplane.v1alpha1.ListConnectorsResponse.ConnectorInfoStatus.info:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorSpec + 4, // 21: redpanda.api.dataplane.v1alpha1.ListConnectorsResponse.ConnectorInfoStatus.status:type_name -> redpanda.api.dataplane.v1alpha1.ConnectorStatus + 22, // 22: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectClusters:input_type -> redpanda.api.dataplane.v1alpha1.ListConnectClustersRequest + 20, // 23: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectCluster:input_type -> redpanda.api.dataplane.v1alpha1.GetConnectClusterRequest + 9, // 24: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectors:input_type -> redpanda.api.dataplane.v1alpha1.ListConnectorsRequest + 16, // 25: redpanda.api.dataplane.v1alpha1.KafkaConnectService.CreateConnector:input_type -> redpanda.api.dataplane.v1alpha1.CreateConnectorRequest + 10, // 26: redpanda.api.dataplane.v1alpha1.KafkaConnectService.RestartConnector:input_type -> redpanda.api.dataplane.v1alpha1.RestartConnectorRequest + 15, // 27: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnector:input_type -> redpanda.api.dataplane.v1alpha1.GetConnectorRequest + 28, // 28: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorStatus:input_type -> redpanda.api.dataplane.v1alpha1.GetConnectorStatusRequest + 12, // 29: redpanda.api.dataplane.v1alpha1.KafkaConnectService.PauseConnector:input_type -> redpanda.api.dataplane.v1alpha1.PauseConnectorRequest + 13, // 30: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResumeConnector:input_type -> redpanda.api.dataplane.v1alpha1.ResumeConnectorRequest + 14, // 31: redpanda.api.dataplane.v1alpha1.KafkaConnectService.StopConnector:input_type -> redpanda.api.dataplane.v1alpha1.StopConnectorRequest + 11, // 32: redpanda.api.dataplane.v1alpha1.KafkaConnectService.DeleteConnector:input_type -> redpanda.api.dataplane.v1alpha1.DeleteConnectorRequest + 24, // 33: redpanda.api.dataplane.v1alpha1.KafkaConnectService.UpsertConnector:input_type -> redpanda.api.dataplane.v1alpha1.UpsertConnectorRequest + 26, // 34: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorConfig:input_type -> redpanda.api.dataplane.v1alpha1.GetConnectorConfigRequest + 30, // 35: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectorTopics:input_type -> redpanda.api.dataplane.v1alpha1.ListConnectorTopicsRequest + 32, // 36: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResetConnectorTopics:input_type -> redpanda.api.dataplane.v1alpha1.ResetConnectorTopicsRequest + 23, // 37: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectClusters:output_type -> redpanda.api.dataplane.v1alpha1.ListConnectClustersResponse + 21, // 38: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectCluster:output_type -> redpanda.api.dataplane.v1alpha1.GetConnectClusterResponse + 19, // 39: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectors:output_type -> redpanda.api.dataplane.v1alpha1.ListConnectorsResponse + 18, // 40: redpanda.api.dataplane.v1alpha1.KafkaConnectService.CreateConnector:output_type -> redpanda.api.dataplane.v1alpha1.CreateConnectorResponse + 40, // 41: redpanda.api.dataplane.v1alpha1.KafkaConnectService.RestartConnector:output_type -> google.protobuf.Empty + 17, // 42: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnector:output_type -> redpanda.api.dataplane.v1alpha1.GetConnectorResponse + 29, // 43: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorStatus:output_type -> redpanda.api.dataplane.v1alpha1.GetConnectorStatusResponse + 40, // 44: redpanda.api.dataplane.v1alpha1.KafkaConnectService.PauseConnector:output_type -> google.protobuf.Empty + 40, // 45: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResumeConnector:output_type -> google.protobuf.Empty + 40, // 46: redpanda.api.dataplane.v1alpha1.KafkaConnectService.StopConnector:output_type -> google.protobuf.Empty + 40, // 47: redpanda.api.dataplane.v1alpha1.KafkaConnectService.DeleteConnector:output_type -> google.protobuf.Empty + 25, // 48: redpanda.api.dataplane.v1alpha1.KafkaConnectService.UpsertConnector:output_type -> redpanda.api.dataplane.v1alpha1.UpsertConnectorResponse + 27, // 49: redpanda.api.dataplane.v1alpha1.KafkaConnectService.GetConnectorConfig:output_type -> redpanda.api.dataplane.v1alpha1.GetConnectorConfigResponse + 31, // 50: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ListConnectorTopics:output_type -> redpanda.api.dataplane.v1alpha1.ListConnectorTopicsResponse + 40, // 51: redpanda.api.dataplane.v1alpha1.KafkaConnectService.ResetConnectorTopics:output_type -> google.protobuf.Empty + 37, // [37:52] is the sub-list for method output_type + 22, // [22:37] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_kafka_connect_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectorPlugin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectCluster); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectorStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaskStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaskInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectorError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectorSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectorsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestartConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PauseConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResumeConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateConnectorResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectorsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectClusterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectClusterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectClustersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectClustersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpsertConnectorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpsertConnectorResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorConfigRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorConfigResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectorStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectorTopicsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectorTopicsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResetConnectorTopicsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectCluster_Info); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectorStatus_Connector); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestartConnectorRequest_Options); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectorsResponse_ConnectorInfoStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDesc, + NumEnums: 2, + NumMessages: 38, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_enumTypes, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_kafka_connect_proto = out.File + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_kafka_connect_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/secret.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/secret.pb.go new file mode 100644 index 0000000000000..8e53b4dd932bf --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/secret.pb.go @@ -0,0 +1,2209 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/secret.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Secret defienes the secret resource. +type Secret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secret identifier. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Secret labels. + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Secret) Reset() { + *x = Secret{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secret) ProtoMessage() {} + +func (x *Secret) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secret.ProtoReflect.Descriptor instead. +func (*Secret) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{0} +} + +func (x *Secret) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Secret) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +// SecretInput defienes the secret input for secret API actions. +type SecretInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secret labels. + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // The secret data. + SecretData []byte `protobuf:"bytes,3,opt,name=secret_data,json=secretData,proto3" json:"secret_data,omitempty"` +} + +func (x *SecretInput) Reset() { + *x = SecretInput{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SecretInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretInput) ProtoMessage() {} + +func (x *SecretInput) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretInput.ProtoReflect.Descriptor instead. +func (*SecretInput) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{1} +} + +func (x *SecretInput) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *SecretInput) GetSecretData() []byte { + if x != nil { + return x.SecretData + } + return nil +} + +// ListSecretsResponse is the response of ListSecrets. +type ListSecretsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secrets retrieved. + Secrets []*Secret `protobuf:"bytes,1,rep,name=secrets,proto3" json:"secrets,omitempty"` + // Token to retrieve the next page. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListSecretsResponse) Reset() { + *x = ListSecretsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSecretsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSecretsResponse) ProtoMessage() {} + +func (x *ListSecretsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSecretsResponse.ProtoReflect.Descriptor instead. +func (*ListSecretsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{2} +} + +func (x *ListSecretsResponse) GetSecrets() []*Secret { + if x != nil { + return x.Secrets + } + return nil +} + +func (x *ListSecretsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// ListSecretsFilter are the filter options for listing secrets. +type ListSecretsFilter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The secret ID or name to search for. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The secret labels to search for. + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ListSecretsFilter) Reset() { + *x = ListSecretsFilter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSecretsFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSecretsFilter) ProtoMessage() {} + +func (x *ListSecretsFilter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSecretsFilter.ProtoReflect.Descriptor instead. +func (*ListSecretsFilter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{3} +} + +func (x *ListSecretsFilter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListSecretsFilter) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +// ListSecretsRequest is the request of ListSecrets. +type ListSecretsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List filter. + Filter *ListSecretsFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Limit the paginated response to a number of items. + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` +} + +func (x *ListSecretsRequest) Reset() { + *x = ListSecretsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSecretsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSecretsRequest) ProtoMessage() {} + +func (x *ListSecretsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSecretsRequest.ProtoReflect.Descriptor instead. +func (*ListSecretsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{4} +} + +func (x *ListSecretsRequest) GetFilter() *ListSecretsFilter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListSecretsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListSecretsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +// GetSecretRequest is the request of GetSecret. +type GetSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the secret to retrieve. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetSecretRequest) Reset() { + *x = GetSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSecretRequest) ProtoMessage() {} + +func (x *GetSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSecretRequest.ProtoReflect.Descriptor instead. +func (*GetSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{5} +} + +func (x *GetSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// GetSecretResponse is the response of GetSecret. +type GetSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The created secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *GetSecretResponse) Reset() { + *x = GetSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSecretResponse) ProtoMessage() {} + +func (x *GetSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSecretResponse.ProtoReflect.Descriptor instead. +func (*GetSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{6} +} + +func (x *GetSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// CreateSecretRequest is the request of CreateSecret. +type CreateSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secret identifier. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The input for the secret to create. + Secret *SecretInput `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *CreateSecretRequest) Reset() { + *x = CreateSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSecretRequest) ProtoMessage() {} + +func (x *CreateSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSecretRequest.ProtoReflect.Descriptor instead. +func (*CreateSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CreateSecretRequest) GetSecret() *SecretInput { + if x != nil { + return x.Secret + } + return nil +} + +// CreateSecretResponse is the response of CreateSecret. +type CreateSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The created secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *CreateSecretResponse) Reset() { + *x = CreateSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSecretResponse) ProtoMessage() {} + +func (x *CreateSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSecretResponse.ProtoReflect.Descriptor instead. +func (*CreateSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{8} +} + +func (x *CreateSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// UpdateSecretRequest is the request of UpdateSecret. +type UpdateSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secret identifier. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The input for the secret to update. + Secret *SecretInput `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *UpdateSecretRequest) Reset() { + *x = UpdateSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSecretRequest) ProtoMessage() {} + +func (x *UpdateSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSecretRequest.ProtoReflect.Descriptor instead. +func (*UpdateSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateSecretRequest) GetSecret() *SecretInput { + if x != nil { + return x.Secret + } + return nil +} + +// UpdateSecretResponse is the response of UpdateSecret. +type UpdateSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The updated secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *UpdateSecretResponse) Reset() { + *x = UpdateSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSecretResponse) ProtoMessage() {} + +func (x *UpdateSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSecretResponse.ProtoReflect.Descriptor instead. +func (*UpdateSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// DeleteSecretRequest is the request of DeleteSecret. +type DeleteSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the secret to delete. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteSecretRequest) Reset() { + *x = DeleteSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSecretRequest) ProtoMessage() {} + +func (x *DeleteSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSecretRequest.ProtoReflect.Descriptor instead. +func (*DeleteSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{11} +} + +func (x *DeleteSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// DeleteSecretResponse is the response of DeleteSecret. +type DeleteSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteSecretResponse) Reset() { + *x = DeleteSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSecretResponse) ProtoMessage() {} + +func (x *DeleteSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSecretResponse.ProtoReflect.Descriptor instead. +func (*DeleteSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{12} +} + +// GetConnectSecretRequest is the request of GetSecret. +type GetConnectSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique name of target connect cluster. + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // The id of the secret to retrieve. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetConnectSecretRequest) Reset() { + *x = GetConnectSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectSecretRequest) ProtoMessage() {} + +func (x *GetConnectSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectSecretRequest.ProtoReflect.Descriptor instead. +func (*GetConnectSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{13} +} + +func (x *GetConnectSecretRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *GetConnectSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// GetConnectSecretResponse is the response of GetConnectSecret. +type GetConnectSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The created secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *GetConnectSecretResponse) Reset() { + *x = GetConnectSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConnectSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConnectSecretResponse) ProtoMessage() {} + +func (x *GetConnectSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConnectSecretResponse.ProtoReflect.Descriptor instead. +func (*GetConnectSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{14} +} + +func (x *GetConnectSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// CreateConnectSecretRequest is the request of CreateConnectSecret. +type CreateConnectSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique name of target connect cluster. + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // The Connect name. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // The input for the secret to create. + Secret *SecretInput `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *CreateConnectSecretRequest) Reset() { + *x = CreateConnectSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateConnectSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateConnectSecretRequest) ProtoMessage() {} + +func (x *CreateConnectSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateConnectSecretRequest.ProtoReflect.Descriptor instead. +func (*CreateConnectSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{15} +} + +func (x *CreateConnectSecretRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *CreateConnectSecretRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateConnectSecretRequest) GetSecret() *SecretInput { + if x != nil { + return x.Secret + } + return nil +} + +// CreateConnectSecretResponse is the response of CreateConnectSecret. +type CreateConnectSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The created secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *CreateConnectSecretResponse) Reset() { + *x = CreateConnectSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateConnectSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateConnectSecretResponse) ProtoMessage() {} + +func (x *CreateConnectSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateConnectSecretResponse.ProtoReflect.Descriptor instead. +func (*CreateConnectSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{16} +} + +func (x *CreateConnectSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// ListConnectSecretRequest is the request of ListConnectSecrets. +type ListConnectSecretsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique name of target connect cluster. + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // List filter. + Filter *ListSecretsFilter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` + // Value of the next_page_token field returned by the previous response. + // If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Limit the paginated response to a number of items. + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` +} + +func (x *ListConnectSecretsRequest) Reset() { + *x = ListConnectSecretsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectSecretsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectSecretsRequest) ProtoMessage() {} + +func (x *ListConnectSecretsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectSecretsRequest.ProtoReflect.Descriptor instead. +func (*ListConnectSecretsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{17} +} + +func (x *ListConnectSecretsRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *ListConnectSecretsRequest) GetFilter() *ListSecretsFilter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListConnectSecretsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListConnectSecretsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +// ListConnectSecretsResponse is the response of ListConnectSecrets. +type ListConnectSecretsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secrets retrieved. + Secrets []*Secret `protobuf:"bytes,1,rep,name=secrets,proto3" json:"secrets,omitempty"` + // Token to retrieve the next page. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListConnectSecretsResponse) Reset() { + *x = ListConnectSecretsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConnectSecretsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConnectSecretsResponse) ProtoMessage() {} + +func (x *ListConnectSecretsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConnectSecretsResponse.ProtoReflect.Descriptor instead. +func (*ListConnectSecretsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{18} +} + +func (x *ListConnectSecretsResponse) GetSecrets() []*Secret { + if x != nil { + return x.Secrets + } + return nil +} + +func (x *ListConnectSecretsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// UpdateConnectSecretRequest is the request of UpdateConnectSecret. +type UpdateConnectSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique name of target connect cluster. + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // The id of the secret to retrieve. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // The input for the secret to update. + Secret *SecretInput `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *UpdateConnectSecretRequest) Reset() { + *x = UpdateConnectSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateConnectSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateConnectSecretRequest) ProtoMessage() {} + +func (x *UpdateConnectSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateConnectSecretRequest.ProtoReflect.Descriptor instead. +func (*UpdateConnectSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{19} +} + +func (x *UpdateConnectSecretRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *UpdateConnectSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateConnectSecretRequest) GetSecret() *SecretInput { + if x != nil { + return x.Secret + } + return nil +} + +// UpdateConnectSecretResponse is the response of UpdateConnectSecret. +type UpdateConnectSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The updated secret. + Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *UpdateConnectSecretResponse) Reset() { + *x = UpdateConnectSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateConnectSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateConnectSecretResponse) ProtoMessage() {} + +func (x *UpdateConnectSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateConnectSecretResponse.ProtoReflect.Descriptor instead. +func (*UpdateConnectSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{20} +} + +func (x *UpdateConnectSecretResponse) GetSecret() *Secret { + if x != nil { + return x.Secret + } + return nil +} + +// DeleteConnectSecretRequest is the request of DeleteConnectSecret. +type DeleteConnectSecretRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique name of target connect cluster. + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + // The id of the secret to delete. + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteConnectSecretRequest) Reset() { + *x = DeleteConnectSecretRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConnectSecretRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConnectSecretRequest) ProtoMessage() {} + +func (x *DeleteConnectSecretRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConnectSecretRequest.ProtoReflect.Descriptor instead. +func (*DeleteConnectSecretRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{21} +} + +func (x *DeleteConnectSecretRequest) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *DeleteConnectSecretRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// DeleteConnectSecretResponse is the response of DeleteConnectSecret. +type DeleteConnectSecretResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteConnectSecretResponse) Reset() { + *x = DeleteConnectSecretResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConnectSecretResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConnectSecretResponse) ProtoMessage() {} + +func (x *DeleteConnectSecretResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConnectSecretResponse.ProtoReflect.Descriptor instead. +func (*DeleteConnectSecretResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP(), []int{22} +} + +var File_redpanda_api_dataplane_v1alpha1_secret_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, + 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, + 0x61, 0x76, 0x69, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, + 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd8, 0x01, 0x0a, 0x06, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x06, 0xe0, 0x41, 0x05, 0xe0, 0x41, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x7b, + 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x42, 0x2e, 0xe0, 0x41, 0x05, 0xba, 0x48, 0x28, 0x9a, 0x01, 0x25, 0x2a, 0x23, + 0x72, 0x21, 0x32, 0x1f, 0x5e, 0x28, 0x5b, 0x5c, 0x70, 0x7b, 0x4c, 0x7d, 0x5c, 0x70, 0x7b, 0x5a, + 0x7d, 0x5c, 0x70, 0x7b, 0x4e, 0x7d, 0x5f, 0x2e, 0x3a, 0x2f, 0x3d, 0x2b, 0x5c, 0x2d, 0x40, 0x5d, + 0x2a, 0x29, 0x24, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf4, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x80, 0x01, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x42, 0x2e, 0xe0, 0x41, 0x05, 0xba, 0x48, 0x28, 0x9a, 0x01, 0x25, 0x2a, 0x23, 0x72, 0x21, + 0x32, 0x1f, 0x5e, 0x28, 0x5b, 0x5c, 0x70, 0x7b, 0x4c, 0x7d, 0x5c, 0x70, 0x7b, 0x5a, 0x7d, 0x5c, + 0x70, 0x7b, 0x4e, 0x7d, 0x5f, 0x2e, 0x3a, 0x2f, 0x3d, 0x2b, 0x5c, 0x2d, 0x40, 0x5d, 0x2a, 0x29, + 0x24, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x27, 0x0a, 0x0b, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, + 0xe0, 0x41, 0x04, 0xe0, 0x41, 0x02, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x80, 0x01, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, + 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, + 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0xe8, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x06, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x2b, 0xba, 0x48, 0x28, + 0x9a, 0x01, 0x25, 0x2a, 0x23, 0x72, 0x21, 0x32, 0x1f, 0x5e, 0x28, 0x5b, 0x5c, 0x70, 0x7b, 0x4c, + 0x7d, 0x5c, 0x70, 0x7b, 0x5a, 0x7d, 0x5c, 0x70, 0x7b, 0x4e, 0x7d, 0x5f, 0x2e, 0x3a, 0x2f, 0x3d, + 0x2b, 0x5c, 0x2d, 0x40, 0x5d, 0x2a, 0x29, 0x24, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe7, 0x01, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x66, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x42, 0x49, 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, + 0x6f, 0x66, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, 0xff, + 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, 0x5f, + 0x2b, 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x22, 0x54, 0x0a, + 0x11, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xba, 0x48, 0x1e, 0x72, 0x1c, 0x10, 0x01, + 0x18, 0xff, 0x01, 0x32, 0x15, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x2f, 0x5f, 0x2b, 0x3d, 0x2e, 0x40, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x12, 0x49, + 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x57, 0x0a, 0x14, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, + 0xff, 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, + 0x5f, 0x2b, 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x12, 0x49, + 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x57, 0x0a, 0x14, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0x49, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, 0xff, + 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, 0x5f, + 0x2b, 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x22, 0x16, 0x0a, + 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xed, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x9d, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf9, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, + 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, + 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, + 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, + 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, + 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, + 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, + 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, + 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, 0xff, 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, + 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, 0x5f, 0x2b, 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, + 0x24, 0x52, 0x02, 0x69, 0x64, 0x22, 0x5b, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0xad, 0x03, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x8b, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xe7, 0x01, 0x92, 0x41, 0xc1, 0x01, 0x32, + 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, + 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, + 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, + 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, + 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, + 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, + 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x36, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xe0, + 0x41, 0x02, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, + 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, + 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x49, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0x5e, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0x8e, 0x04, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x9d, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xf9, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, + 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, + 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, + 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, + 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, + 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, + 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x4a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x66, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x49, + 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, + 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x40, + 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x07, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa9, 0x03, + 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x8b, 0x02, 0x0a, + 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0xe7, 0x01, 0x92, 0x41, 0xc1, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, + 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, + 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, + 0x0a, 0x22, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xe0, 0x41, 0x02, 0xba, 0x48, + 0x1c, 0xc8, 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, + 0xff, 0x01, 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, + 0x5f, 0x2b, 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x12, 0x49, + 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x03, 0xe0, 0x41, + 0x02, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x5e, 0x0a, 0x1b, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x1a, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x9d, 0x02, 0x0a, 0x0c, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0xf9, 0x01, 0x92, 0x41, 0xd3, 0x01, 0x32, 0xb2, 0x01, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x20, 0x73, 0x65, 0x6c, + 0x66, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x60, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x60, 0x2e, 0x4a, 0x0a, 0x22, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x22, 0xca, 0x3e, 0x0f, 0xfa, 0x02, 0x0c, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1c, 0xc8, + 0x01, 0x01, 0x72, 0x17, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, + 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x5f, 0x5d, 0x2b, 0x24, 0x52, 0x0b, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x32, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xba, 0x48, 0x1f, 0x72, 0x1d, 0x10, 0x01, 0x18, 0xff, 0x01, + 0x32, 0x16, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2f, 0x5f, 0x2b, + 0x3d, 0x2e, 0x40, 0x25, 0x2d, 0x5d, 0x2b, 0x24, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb5, 0x1a, 0x0a, 0x0d, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa9, 0x02, + 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x31, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0xb4, 0x01, 0x92, 0x41, 0x8a, 0x01, 0x12, 0x0a, 0x47, 0x65, 0x74, 0x20, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x16, 0x47, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x38, 0x0a, + 0x03, 0x32, 0x30, 0x30, 0x12, 0x31, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x2b, 0x0a, 0x29, 0x1a, 0x27, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, + 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, + 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x62, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x94, 0x02, 0x0a, 0x0b, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x99, 0x01, 0x92, 0x41, 0x7d, 0x12, 0x0c, 0x4c, 0x69, 0x73, 0x74, + 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x26, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, + 0x4a, 0x45, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3e, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x38, 0x0a, + 0x36, 0x1a, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x12, 0x8d, 0x02, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x12, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8f, + 0x01, 0x92, 0x41, 0x68, 0x12, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x1a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x45, 0x0a, 0x03, 0x32, 0x30, 0x31, 0x12, 0x3e, 0x0a, 0x0f, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x12, + 0x2b, 0x0a, 0x29, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x62, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x11, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x12, 0xb7, 0x02, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x12, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb9, + 0x01, 0x92, 0x41, 0x87, 0x01, 0x12, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x1a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x38, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x31, 0x0a, + 0x02, 0x4f, 0x6b, 0x12, 0x2b, 0x0a, 0x29, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, + 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x28, 0x3a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x62, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x1a, 0x16, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xa3, 0x02, 0x0a, 0x0c, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x34, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa5, 0x01, 0x92, 0x41, 0x83, 0x01, 0x12, + 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x19, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x2b, 0x0a, 0x03, 0x32, 0x30, 0x34, + 0x12, 0x24, 0x0a, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, + 0x6c, 0x6c, 0x79, 0x2e, 0x12, 0x00, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, + 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x12, 0x84, 0x03, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xfa, 0x01, 0x92, 0x41, 0xb0, + 0x01, 0x12, 0x1a, 0x47, 0x65, 0x74, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x2c, 0x47, + 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x4b, 0x61, + 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x38, 0x0a, 0x03, 0x32, + 0x30, 0x30, 0x12, 0x31, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x2b, 0x0a, 0x29, 0x1a, 0x27, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, + 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x40, 0x62, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, + 0x36, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xf0, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x3a, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe0, 0x01, 0x92, 0x41, 0xa3, 0x01, 0x12, 0x1c, + 0x4c, 0x69, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x3c, 0x4c, 0x69, + 0x73, 0x74, 0x20, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, + 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x4a, 0x45, 0x0a, 0x03, 0x32, 0x30, + 0x30, 0x12, 0x3e, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x38, 0x0a, 0x36, 0x1a, 0x34, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x12, 0x31, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x7d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0xe9, 0x02, 0x0a, 0x13, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x12, 0x3b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd6, 0x01, + 0x92, 0x41, 0x8e, 0x01, 0x12, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x1a, 0x26, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x4b, 0x61, + 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x45, 0x0a, 0x03, 0x32, + 0x30, 0x31, 0x12, 0x3e, 0x0a, 0x0f, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x2e, 0x12, 0x2b, 0x0a, 0x29, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x3a, 0x01, 0x2a, 0x62, 0x06, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x22, 0x31, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, + 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x92, 0x03, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x3b, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xff, 0x01, 0x92, 0x41, 0xad, 0x01, + 0x12, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, + 0x26, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x38, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x31, + 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x2b, 0x0a, 0x29, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, + 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x48, 0x3a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x62, 0x06, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x1a, 0x36, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, + 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xf5, 0x02, 0x0a, 0x13, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x12, 0x3b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3c, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe2, + 0x01, 0x92, 0x41, 0xa0, 0x01, 0x12, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x1a, 0x26, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x4b, + 0x61, 0x66, 0x6b, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2e, 0x4a, 0x2b, 0x0a, 0x03, + 0x32, 0x30, 0x34, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x77, 0x61, + 0x73, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x2e, 0x12, 0x00, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, + 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, + 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x38, 0x2a, 0x36, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2f, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x2f, 0x7b, + 0x69, 0x64, 0x7d, 0x42, 0xbe, 0x02, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0b, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, + 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, + 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, + 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, + 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, + 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, + 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_redpanda_api_dataplane_v1alpha1_secret_proto_goTypes = []interface{}{ + (*Secret)(nil), // 0: redpanda.api.dataplane.v1alpha1.Secret + (*SecretInput)(nil), // 1: redpanda.api.dataplane.v1alpha1.SecretInput + (*ListSecretsResponse)(nil), // 2: redpanda.api.dataplane.v1alpha1.ListSecretsResponse + (*ListSecretsFilter)(nil), // 3: redpanda.api.dataplane.v1alpha1.ListSecretsFilter + (*ListSecretsRequest)(nil), // 4: redpanda.api.dataplane.v1alpha1.ListSecretsRequest + (*GetSecretRequest)(nil), // 5: redpanda.api.dataplane.v1alpha1.GetSecretRequest + (*GetSecretResponse)(nil), // 6: redpanda.api.dataplane.v1alpha1.GetSecretResponse + (*CreateSecretRequest)(nil), // 7: redpanda.api.dataplane.v1alpha1.CreateSecretRequest + (*CreateSecretResponse)(nil), // 8: redpanda.api.dataplane.v1alpha1.CreateSecretResponse + (*UpdateSecretRequest)(nil), // 9: redpanda.api.dataplane.v1alpha1.UpdateSecretRequest + (*UpdateSecretResponse)(nil), // 10: redpanda.api.dataplane.v1alpha1.UpdateSecretResponse + (*DeleteSecretRequest)(nil), // 11: redpanda.api.dataplane.v1alpha1.DeleteSecretRequest + (*DeleteSecretResponse)(nil), // 12: redpanda.api.dataplane.v1alpha1.DeleteSecretResponse + (*GetConnectSecretRequest)(nil), // 13: redpanda.api.dataplane.v1alpha1.GetConnectSecretRequest + (*GetConnectSecretResponse)(nil), // 14: redpanda.api.dataplane.v1alpha1.GetConnectSecretResponse + (*CreateConnectSecretRequest)(nil), // 15: redpanda.api.dataplane.v1alpha1.CreateConnectSecretRequest + (*CreateConnectSecretResponse)(nil), // 16: redpanda.api.dataplane.v1alpha1.CreateConnectSecretResponse + (*ListConnectSecretsRequest)(nil), // 17: redpanda.api.dataplane.v1alpha1.ListConnectSecretsRequest + (*ListConnectSecretsResponse)(nil), // 18: redpanda.api.dataplane.v1alpha1.ListConnectSecretsResponse + (*UpdateConnectSecretRequest)(nil), // 19: redpanda.api.dataplane.v1alpha1.UpdateConnectSecretRequest + (*UpdateConnectSecretResponse)(nil), // 20: redpanda.api.dataplane.v1alpha1.UpdateConnectSecretResponse + (*DeleteConnectSecretRequest)(nil), // 21: redpanda.api.dataplane.v1alpha1.DeleteConnectSecretRequest + (*DeleteConnectSecretResponse)(nil), // 22: redpanda.api.dataplane.v1alpha1.DeleteConnectSecretResponse + nil, // 23: redpanda.api.dataplane.v1alpha1.Secret.LabelsEntry + nil, // 24: redpanda.api.dataplane.v1alpha1.SecretInput.LabelsEntry + nil, // 25: redpanda.api.dataplane.v1alpha1.ListSecretsFilter.LabelsEntry +} +var file_redpanda_api_dataplane_v1alpha1_secret_proto_depIdxs = []int32{ + 23, // 0: redpanda.api.dataplane.v1alpha1.Secret.labels:type_name -> redpanda.api.dataplane.v1alpha1.Secret.LabelsEntry + 24, // 1: redpanda.api.dataplane.v1alpha1.SecretInput.labels:type_name -> redpanda.api.dataplane.v1alpha1.SecretInput.LabelsEntry + 0, // 2: redpanda.api.dataplane.v1alpha1.ListSecretsResponse.secrets:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 25, // 3: redpanda.api.dataplane.v1alpha1.ListSecretsFilter.labels:type_name -> redpanda.api.dataplane.v1alpha1.ListSecretsFilter.LabelsEntry + 3, // 4: redpanda.api.dataplane.v1alpha1.ListSecretsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListSecretsFilter + 0, // 5: redpanda.api.dataplane.v1alpha1.GetSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 1, // 6: redpanda.api.dataplane.v1alpha1.CreateSecretRequest.secret:type_name -> redpanda.api.dataplane.v1alpha1.SecretInput + 0, // 7: redpanda.api.dataplane.v1alpha1.CreateSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 1, // 8: redpanda.api.dataplane.v1alpha1.UpdateSecretRequest.secret:type_name -> redpanda.api.dataplane.v1alpha1.SecretInput + 0, // 9: redpanda.api.dataplane.v1alpha1.UpdateSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 0, // 10: redpanda.api.dataplane.v1alpha1.GetConnectSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 1, // 11: redpanda.api.dataplane.v1alpha1.CreateConnectSecretRequest.secret:type_name -> redpanda.api.dataplane.v1alpha1.SecretInput + 0, // 12: redpanda.api.dataplane.v1alpha1.CreateConnectSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 3, // 13: redpanda.api.dataplane.v1alpha1.ListConnectSecretsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListSecretsFilter + 0, // 14: redpanda.api.dataplane.v1alpha1.ListConnectSecretsResponse.secrets:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 1, // 15: redpanda.api.dataplane.v1alpha1.UpdateConnectSecretRequest.secret:type_name -> redpanda.api.dataplane.v1alpha1.SecretInput + 0, // 16: redpanda.api.dataplane.v1alpha1.UpdateConnectSecretResponse.secret:type_name -> redpanda.api.dataplane.v1alpha1.Secret + 5, // 17: redpanda.api.dataplane.v1alpha1.SecretService.GetSecret:input_type -> redpanda.api.dataplane.v1alpha1.GetSecretRequest + 4, // 18: redpanda.api.dataplane.v1alpha1.SecretService.ListSecrets:input_type -> redpanda.api.dataplane.v1alpha1.ListSecretsRequest + 7, // 19: redpanda.api.dataplane.v1alpha1.SecretService.CreateSecret:input_type -> redpanda.api.dataplane.v1alpha1.CreateSecretRequest + 9, // 20: redpanda.api.dataplane.v1alpha1.SecretService.UpdateSecret:input_type -> redpanda.api.dataplane.v1alpha1.UpdateSecretRequest + 11, // 21: redpanda.api.dataplane.v1alpha1.SecretService.DeleteSecret:input_type -> redpanda.api.dataplane.v1alpha1.DeleteSecretRequest + 13, // 22: redpanda.api.dataplane.v1alpha1.SecretService.GetConnectSecret:input_type -> redpanda.api.dataplane.v1alpha1.GetConnectSecretRequest + 17, // 23: redpanda.api.dataplane.v1alpha1.SecretService.ListConnectSecrets:input_type -> redpanda.api.dataplane.v1alpha1.ListConnectSecretsRequest + 15, // 24: redpanda.api.dataplane.v1alpha1.SecretService.CreateConnectSecret:input_type -> redpanda.api.dataplane.v1alpha1.CreateConnectSecretRequest + 19, // 25: redpanda.api.dataplane.v1alpha1.SecretService.UpdateConnectSecret:input_type -> redpanda.api.dataplane.v1alpha1.UpdateConnectSecretRequest + 21, // 26: redpanda.api.dataplane.v1alpha1.SecretService.DeleteConnectSecret:input_type -> redpanda.api.dataplane.v1alpha1.DeleteConnectSecretRequest + 6, // 27: redpanda.api.dataplane.v1alpha1.SecretService.GetSecret:output_type -> redpanda.api.dataplane.v1alpha1.GetSecretResponse + 2, // 28: redpanda.api.dataplane.v1alpha1.SecretService.ListSecrets:output_type -> redpanda.api.dataplane.v1alpha1.ListSecretsResponse + 8, // 29: redpanda.api.dataplane.v1alpha1.SecretService.CreateSecret:output_type -> redpanda.api.dataplane.v1alpha1.CreateSecretResponse + 10, // 30: redpanda.api.dataplane.v1alpha1.SecretService.UpdateSecret:output_type -> redpanda.api.dataplane.v1alpha1.UpdateSecretResponse + 12, // 31: redpanda.api.dataplane.v1alpha1.SecretService.DeleteSecret:output_type -> redpanda.api.dataplane.v1alpha1.DeleteSecretResponse + 14, // 32: redpanda.api.dataplane.v1alpha1.SecretService.GetConnectSecret:output_type -> redpanda.api.dataplane.v1alpha1.GetConnectSecretResponse + 18, // 33: redpanda.api.dataplane.v1alpha1.SecretService.ListConnectSecrets:output_type -> redpanda.api.dataplane.v1alpha1.ListConnectSecretsResponse + 16, // 34: redpanda.api.dataplane.v1alpha1.SecretService.CreateConnectSecret:output_type -> redpanda.api.dataplane.v1alpha1.CreateConnectSecretResponse + 20, // 35: redpanda.api.dataplane.v1alpha1.SecretService.UpdateConnectSecret:output_type -> redpanda.api.dataplane.v1alpha1.UpdateConnectSecretResponse + 22, // 36: redpanda.api.dataplane.v1alpha1.SecretService.DeleteConnectSecret:output_type -> redpanda.api.dataplane.v1alpha1.DeleteConnectSecretResponse + 27, // [27:37] is the sub-list for method output_type + 17, // [17:27] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_secret_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_secret_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_secret_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secret); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SecretInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSecretsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSecretsFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSecretsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConnectSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateConnectSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateConnectSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectSecretsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConnectSecretsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateConnectSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateConnectSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConnectSecretRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConnectSecretResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDesc, + NumEnums: 0, + NumMessages: 26, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_secret_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_secret_proto_depIdxs, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_secret_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_secret_proto = out.File + file_redpanda_api_dataplane_v1alpha1_secret_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_secret_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_secret_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/swagger.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/swagger.pb.go new file mode 100644 index 0000000000000..d6304631405ec --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/swagger.pb.go @@ -0,0 +1,96 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/swagger.proto + +package dataplanev1alpha1 + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var File_redpanda_api_dataplane_v1alpha1_swagger_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_swagger_proto_rawDesc = []byte{ + 0x0a, 0x2d, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x1f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, + 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x42, 0xc5, 0x03, 0x92, 0x41, 0x82, 0x01, 0x52, 0x31, 0x0a, 0x03, 0x34, 0x30, 0x31, 0x12, 0x2a, + 0x0a, 0x10, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x64, 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x4d, 0x0a, 0x03, 0x35, 0x30, + 0x30, 0x12, 0x46, 0x0a, 0x2c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x20, 0x52, 0x65, 0x61, 0x63, + 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0c, + 0x53, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, + 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, 0x69, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, + 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, 0x3a, + 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, 0x3a, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_redpanda_api_dataplane_v1alpha1_swagger_proto_goTypes = []interface{}{} +var file_redpanda_api_dataplane_v1alpha1_swagger_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_swagger_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_swagger_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_swagger_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_swagger_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_swagger_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_swagger_proto_depIdxs, + }.Build() + File_redpanda_api_dataplane_v1alpha1_swagger_proto = out.File + file_redpanda_api_dataplane_v1alpha1_swagger_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_swagger_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_swagger_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/topic.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/topic.pb.go new file mode 100644 index 0000000000000..2cb2b3550dbdb --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/topic.pb.go @@ -0,0 +1,1984 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/topic.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Topic struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Topic) Reset() { + *x = Topic{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Topic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Topic) ProtoMessage() {} + +func (x *Topic) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Topic.ProtoReflect.Descriptor instead. +func (*Topic) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{0} +} + +type CreateTopicRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Topic is the topic to attempt to create. + Topic *CreateTopicRequest_Topic `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // ValidateOnly makes this request a dry-run; everything is validated but + // no topics are actually created. + ValidateOnly bool `protobuf:"varint,2,opt,name=validate_only,json=validateOnly,proto3" json:"validate_only,omitempty"` +} + +func (x *CreateTopicRequest) Reset() { + *x = CreateTopicRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTopicRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTopicRequest) ProtoMessage() {} + +func (x *CreateTopicRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTopicRequest.ProtoReflect.Descriptor instead. +func (*CreateTopicRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTopicRequest) GetTopic() *CreateTopicRequest_Topic { + if x != nil { + return x.Topic + } + return nil +} + +func (x *CreateTopicRequest) GetValidateOnly() bool { + if x != nil { + return x.ValidateOnly + } + return false +} + +type CreateTopicResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the topic's name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // PartitionCount is how many partitions were created for this topic. + // This field has a default of -1, which may be returned if the broker + // does not support v5+ of this request which added support for returning + // this information. + PartitionCount int32 `protobuf:"varint,2,opt,name=partition_count,json=partitionCount,proto3" json:"partition_count,omitempty"` + // ReplicationFactor is how many replicas every partition has for this topic. + // This field has a default of -1, which may be returned if the broker + // does not support v5+ of this request which added support for returning + // this information. + ReplicationFactor int32 `protobuf:"varint,3,opt,name=replication_factor,json=replicationFactor,proto3" json:"replication_factor,omitempty"` +} + +func (x *CreateTopicResponse) Reset() { + *x = CreateTopicResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTopicResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTopicResponse) ProtoMessage() {} + +func (x *CreateTopicResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTopicResponse.ProtoReflect.Descriptor instead. +func (*CreateTopicResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateTopicResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTopicResponse) GetPartitionCount() int32 { + if x != nil { + return x.PartitionCount + } + return 0 +} + +func (x *CreateTopicResponse) GetReplicationFactor() int32 { + if x != nil { + return x.ReplicationFactor + } + return 0 +} + +type ListTopicsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListTopicsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListTopicsRequest) Reset() { + *x = ListTopicsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTopicsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicsRequest) ProtoMessage() {} + +func (x *ListTopicsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicsRequest.ProtoReflect.Descriptor instead. +func (*ListTopicsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{3} +} + +func (x *ListTopicsRequest) GetFilter() *ListTopicsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListTopicsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListTopicsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type ListTopicsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Topics []*ListTopicsResponse_Topic `protobuf:"bytes,1,rep,name=topics,proto3" json:"topics,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *ListTopicsResponse) Reset() { + *x = ListTopicsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTopicsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicsResponse) ProtoMessage() {} + +func (x *ListTopicsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicsResponse.ProtoReflect.Descriptor instead. +func (*ListTopicsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{4} +} + +func (x *ListTopicsResponse) GetTopics() []*ListTopicsResponse_Topic { + if x != nil { + return x.Topics + } + return nil +} + +func (x *ListTopicsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type DeleteTopicRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteTopicRequest) Reset() { + *x = DeleteTopicRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTopicRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTopicRequest) ProtoMessage() {} + +func (x *DeleteTopicRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTopicRequest.ProtoReflect.Descriptor instead. +func (*DeleteTopicRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteTopicRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTopicResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteTopicResponse) Reset() { + *x = DeleteTopicResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTopicResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTopicResponse) ProtoMessage() {} + +func (x *DeleteTopicResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTopicResponse.ProtoReflect.Descriptor instead. +func (*DeleteTopicResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{6} +} + +type GetTopicConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TopicName string `protobuf:"bytes,1,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"` +} + +func (x *GetTopicConfigurationsRequest) Reset() { + *x = GetTopicConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTopicConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTopicConfigurationsRequest) ProtoMessage() {} + +func (x *GetTopicConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTopicConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*GetTopicConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{7} +} + +func (x *GetTopicConfigurationsRequest) GetTopicName() string { + if x != nil { + return x.TopicName + } + return "" +} + +type GetTopicConfigurationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Configurations []*Topic_Configuration `protobuf:"bytes,1,rep,name=configurations,proto3" json:"configurations,omitempty"` +} + +func (x *GetTopicConfigurationsResponse) Reset() { + *x = GetTopicConfigurationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTopicConfigurationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTopicConfigurationsResponse) ProtoMessage() {} + +func (x *GetTopicConfigurationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTopicConfigurationsResponse.ProtoReflect.Descriptor instead. +func (*GetTopicConfigurationsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{8} +} + +func (x *GetTopicConfigurationsResponse) GetConfigurations() []*Topic_Configuration { + if x != nil { + return x.Configurations + } + return nil +} + +type UpdateTopicConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TopicName string `protobuf:"bytes,1,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"` + Configurations []*UpdateTopicConfigurationsRequest_UpdateConfiguration `protobuf:"bytes,2,rep,name=configurations,proto3" json:"configurations,omitempty"` +} + +func (x *UpdateTopicConfigurationsRequest) Reset() { + *x = UpdateTopicConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTopicConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTopicConfigurationsRequest) ProtoMessage() {} + +func (x *UpdateTopicConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTopicConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*UpdateTopicConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateTopicConfigurationsRequest) GetTopicName() string { + if x != nil { + return x.TopicName + } + return "" +} + +func (x *UpdateTopicConfigurationsRequest) GetConfigurations() []*UpdateTopicConfigurationsRequest_UpdateConfiguration { + if x != nil { + return x.Configurations + } + return nil +} + +type UpdateTopicConfigurationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Topic's complete set of configurations after this partial patch has been applied. + Configurations []*Topic_Configuration `protobuf:"bytes,1,rep,name=configurations,proto3" json:"configurations,omitempty"` +} + +func (x *UpdateTopicConfigurationsResponse) Reset() { + *x = UpdateTopicConfigurationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTopicConfigurationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTopicConfigurationsResponse) ProtoMessage() {} + +func (x *UpdateTopicConfigurationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTopicConfigurationsResponse.ProtoReflect.Descriptor instead. +func (*UpdateTopicConfigurationsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateTopicConfigurationsResponse) GetConfigurations() []*Topic_Configuration { + if x != nil { + return x.Configurations + } + return nil +} + +type SetTopicConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TopicName string `protobuf:"bytes,1,opt,name=topic_name,json=topicName,proto3" json:"topic_name,omitempty"` + Configurations []*SetTopicConfigurationsRequest_SetConfiguration `protobuf:"bytes,2,rep,name=configurations,proto3" json:"configurations,omitempty"` +} + +func (x *SetTopicConfigurationsRequest) Reset() { + *x = SetTopicConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetTopicConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTopicConfigurationsRequest) ProtoMessage() {} + +func (x *SetTopicConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTopicConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*SetTopicConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{11} +} + +func (x *SetTopicConfigurationsRequest) GetTopicName() string { + if x != nil { + return x.TopicName + } + return "" +} + +func (x *SetTopicConfigurationsRequest) GetConfigurations() []*SetTopicConfigurationsRequest_SetConfiguration { + if x != nil { + return x.Configurations + } + return nil +} + +type SetTopicConfigurationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Configurations []*Topic_Configuration `protobuf:"bytes,1,rep,name=configurations,proto3" json:"configurations,omitempty"` +} + +func (x *SetTopicConfigurationsResponse) Reset() { + *x = SetTopicConfigurationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetTopicConfigurationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTopicConfigurationsResponse) ProtoMessage() {} + +func (x *SetTopicConfigurationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTopicConfigurationsResponse.ProtoReflect.Descriptor instead. +func (*SetTopicConfigurationsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{12} +} + +func (x *SetTopicConfigurationsResponse) GetConfigurations() []*Topic_Configuration { + if x != nil { + return x.Configurations + } + return nil +} + +type Topic_Configuration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type ConfigType `protobuf:"varint,2,opt,name=type,proto3,enum=redpanda.api.dataplane.v1alpha1.ConfigType" json:"type,omitempty"` + Value *string `protobuf:"bytes,3,opt,name=value,proto3,oneof" json:"value,omitempty"` + Source ConfigSource `protobuf:"varint,4,opt,name=source,proto3,enum=redpanda.api.dataplane.v1alpha1.ConfigSource" json:"source,omitempty"` + ReadOnly bool `protobuf:"varint,5,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + Sensitive bool `protobuf:"varint,6,opt,name=sensitive,proto3" json:"sensitive,omitempty"` + ConfigSynonyms []*ConfigSynonym `protobuf:"bytes,7,rep,name=config_synonyms,json=configSynonyms,proto3" json:"config_synonyms,omitempty"` + Documentation *string `protobuf:"bytes,8,opt,name=documentation,proto3,oneof" json:"documentation,omitempty"` +} + +func (x *Topic_Configuration) Reset() { + *x = Topic_Configuration{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Topic_Configuration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Topic_Configuration) ProtoMessage() {} + +func (x *Topic_Configuration) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Topic_Configuration.ProtoReflect.Descriptor instead. +func (*Topic_Configuration) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Topic_Configuration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Topic_Configuration) GetType() ConfigType { + if x != nil { + return x.Type + } + return ConfigType_CONFIG_TYPE_UNSPECIFIED +} + +func (x *Topic_Configuration) GetValue() string { + if x != nil && x.Value != nil { + return *x.Value + } + return "" +} + +func (x *Topic_Configuration) GetSource() ConfigSource { + if x != nil { + return x.Source + } + return ConfigSource_CONFIG_SOURCE_UNSPECIFIED +} + +func (x *Topic_Configuration) GetReadOnly() bool { + if x != nil { + return x.ReadOnly + } + return false +} + +func (x *Topic_Configuration) GetSensitive() bool { + if x != nil { + return x.Sensitive + } + return false +} + +func (x *Topic_Configuration) GetConfigSynonyms() []*ConfigSynonym { + if x != nil { + return x.ConfigSynonyms + } + return nil +} + +func (x *Topic_Configuration) GetDocumentation() string { + if x != nil && x.Documentation != nil { + return *x.Documentation + } + return "" +} + +type CreateTopicRequest_Topic struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the topic's name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // PartitionCount is how many partitions to give a topic. This must + // be null if specifying partitions manually (see ReplicaAssignment) + // or, to use the cluster default partitions. + PartitionCount *int32 `protobuf:"varint,2,opt,name=partition_count,json=partitionCount,proto3,oneof" json:"partition_count,omitempty"` + // ReplicationFactor is how many replicas every partition must have. + // This must be null if specifying partitions manually (see ReplicaAssignment) + // or, to use the cluster default replication factor. + ReplicationFactor *int32 `protobuf:"varint,3,opt,name=replication_factor,json=replicationFactor,proto3,oneof" json:"replication_factor,omitempty"` + // ReplicaAssignment is an array to manually dictate replicas and their + // partitions for a topic. If using this, both ReplicationFactor and + // NumPartitions must be -1. + ReplicaAssignments []*CreateTopicRequest_Topic_ReplicaAssignment `protobuf:"bytes,4,rep,name=replica_assignments,json=replicaAssignments,proto3" json:"replica_assignments,omitempty"` + // Configs is an array of key value config pairs for a topic. + // These correspond to Kafka Topic-Level Configs. + Configs []*CreateTopicRequest_Topic_Config `protobuf:"bytes,5,rep,name=configs,proto3" json:"configs,omitempty"` +} + +func (x *CreateTopicRequest_Topic) Reset() { + *x = CreateTopicRequest_Topic{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTopicRequest_Topic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTopicRequest_Topic) ProtoMessage() {} + +func (x *CreateTopicRequest_Topic) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTopicRequest_Topic.ProtoReflect.Descriptor instead. +func (*CreateTopicRequest_Topic) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *CreateTopicRequest_Topic) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTopicRequest_Topic) GetPartitionCount() int32 { + if x != nil && x.PartitionCount != nil { + return *x.PartitionCount + } + return 0 +} + +func (x *CreateTopicRequest_Topic) GetReplicationFactor() int32 { + if x != nil && x.ReplicationFactor != nil { + return *x.ReplicationFactor + } + return 0 +} + +func (x *CreateTopicRequest_Topic) GetReplicaAssignments() []*CreateTopicRequest_Topic_ReplicaAssignment { + if x != nil { + return x.ReplicaAssignments + } + return nil +} + +func (x *CreateTopicRequest_Topic) GetConfigs() []*CreateTopicRequest_Topic_Config { + if x != nil { + return x.Configs + } + return nil +} + +type CreateTopicRequest_Topic_Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is a topic level config key (e.g. segment.bytes). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Value is a topic level config value (e.g. 1073741824) + Value *string `protobuf:"bytes,2,opt,name=value,proto3,oneof" json:"value,omitempty"` +} + +func (x *CreateTopicRequest_Topic_Config) Reset() { + *x = CreateTopicRequest_Topic_Config{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTopicRequest_Topic_Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTopicRequest_Topic_Config) ProtoMessage() {} + +func (x *CreateTopicRequest_Topic_Config) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTopicRequest_Topic_Config.ProtoReflect.Descriptor instead. +func (*CreateTopicRequest_Topic_Config) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{1, 0, 0} +} + +func (x *CreateTopicRequest_Topic_Config) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTopicRequest_Topic_Config) GetValue() string { + if x != nil && x.Value != nil { + return *x.Value + } + return "" +} + +type CreateTopicRequest_Topic_ReplicaAssignment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Partition is a partition to create. + PartitionId int32 `protobuf:"varint,1,opt,name=partition_id,json=partitionId,proto3" json:"partition_id,omitempty"` + // Replicas are the broker IDs the partition must exist on. + ReplicaIds []int32 `protobuf:"varint,2,rep,packed,name=replica_ids,json=replicaIds,proto3" json:"replica_ids,omitempty"` +} + +func (x *CreateTopicRequest_Topic_ReplicaAssignment) Reset() { + *x = CreateTopicRequest_Topic_ReplicaAssignment{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTopicRequest_Topic_ReplicaAssignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTopicRequest_Topic_ReplicaAssignment) ProtoMessage() {} + +func (x *CreateTopicRequest_Topic_ReplicaAssignment) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTopicRequest_Topic_ReplicaAssignment.ProtoReflect.Descriptor instead. +func (*CreateTopicRequest_Topic_ReplicaAssignment) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{1, 0, 1} +} + +func (x *CreateTopicRequest_Topic_ReplicaAssignment) GetPartitionId() int32 { + if x != nil { + return x.PartitionId + } + return 0 +} + +func (x *CreateTopicRequest_Topic_ReplicaAssignment) GetReplicaIds() []int32 { + if x != nil { + return x.ReplicaIds + } + return nil +} + +type ListTopicsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NameContains string `protobuf:"bytes,1,opt,name=name_contains,json=nameContains,proto3" json:"name_contains,omitempty"` +} + +func (x *ListTopicsRequest_Filter) Reset() { + *x = ListTopicsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTopicsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicsRequest_Filter) ProtoMessage() {} + +func (x *ListTopicsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListTopicsRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *ListTopicsRequest_Filter) GetNameContains() string { + if x != nil { + return x.NameContains + } + return "" +} + +type ListTopicsResponse_Topic struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Internal bool `protobuf:"varint,2,opt,name=internal,proto3" json:"internal,omitempty"` + PartitionCount int32 `protobuf:"varint,3,opt,name=partition_count,json=partitionCount,proto3" json:"partition_count,omitempty"` + ReplicationFactor int32 `protobuf:"varint,4,opt,name=replication_factor,json=replicationFactor,proto3" json:"replication_factor,omitempty"` +} + +func (x *ListTopicsResponse_Topic) Reset() { + *x = ListTopicsResponse_Topic{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTopicsResponse_Topic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTopicsResponse_Topic) ProtoMessage() {} + +func (x *ListTopicsResponse_Topic) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTopicsResponse_Topic.ProtoReflect.Descriptor instead. +func (*ListTopicsResponse_Topic) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *ListTopicsResponse_Topic) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListTopicsResponse_Topic) GetInternal() bool { + if x != nil { + return x.Internal + } + return false +} + +func (x *ListTopicsResponse_Topic) GetPartitionCount() int32 { + if x != nil { + return x.PartitionCount + } + return 0 +} + +func (x *ListTopicsResponse_Topic) GetReplicationFactor() int32 { + if x != nil { + return x.ReplicationFactor + } + return 0 +} + +type UpdateTopicConfigurationsRequest_UpdateConfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value *string `protobuf:"bytes,2,opt,name=value,proto3,oneof" json:"value,omitempty"` + Operation ConfigAlterOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=redpanda.api.dataplane.v1alpha1.ConfigAlterOperation" json:"operation,omitempty"` +} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) Reset() { + *x = UpdateTopicConfigurationsRequest_UpdateConfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTopicConfigurationsRequest_UpdateConfiguration) ProtoMessage() {} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTopicConfigurationsRequest_UpdateConfiguration.ProtoReflect.Descriptor instead. +func (*UpdateTopicConfigurationsRequest_UpdateConfiguration) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{9, 0} +} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) GetValue() string { + if x != nil && x.Value != nil { + return *x.Value + } + return "" +} + +func (x *UpdateTopicConfigurationsRequest_UpdateConfiguration) GetOperation() ConfigAlterOperation { + if x != nil { + return x.Operation + } + return ConfigAlterOperation_CONFIG_ALTER_OPERATION_UNSPECIFIED +} + +type SetTopicConfigurationsRequest_SetConfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value *string `protobuf:"bytes,2,opt,name=value,proto3,oneof" json:"value,omitempty"` +} + +func (x *SetTopicConfigurationsRequest_SetConfiguration) Reset() { + *x = SetTopicConfigurationsRequest_SetConfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetTopicConfigurationsRequest_SetConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTopicConfigurationsRequest_SetConfiguration) ProtoMessage() {} + +func (x *SetTopicConfigurationsRequest_SetConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTopicConfigurationsRequest_SetConfiguration.ProtoReflect.Descriptor instead. +func (*SetTopicConfigurationsRequest_SetConfiguration) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP(), []int{11, 0} +} + +func (x *SetTopicConfigurationsRequest_SetConfiguration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SetTopicConfigurationsRequest_SetConfiguration) GetValue() string { + if x != nil && x.Value != nil { + return *x.Value + } + return "" +} + +var File_redpanda_api_dataplane_v1alpha1_topic_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, + 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xab, 0x03, 0x0a, 0x05, 0x54, 0x6f, 0x70, 0x69, + 0x63, 0x1a, 0xa1, 0x03, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x88, 0x01, 0x01, 0x12, 0x45, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, + 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, + 0x74, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x57, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, + 0x73, 0x79, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x79, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x52, 0x0e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x79, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x73, 0x12, 0x29, + 0x0a, 0x0d, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0d, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfb, 0x05, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x57, 0x0a, 0x05, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x05, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x1a, 0xe6, 0x04, 0x0a, 0x05, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x12, 0x35, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x21, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, + 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, + 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x0f, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x12, 0x72, 0x65, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xba, 0x48, 0x06, 0x1a, 0x04, 0x18, 0x05, 0x28, 0x01, + 0x48, 0x01, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x7c, 0x0a, 0x13, 0x72, 0x65, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x12, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x5a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x73, 0x1a, 0x5b, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xba, 0x48, 0x15, 0x72, + 0x13, 0x10, 0x01, 0x18, 0xff, 0x01, 0x32, 0x0c, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, + 0x2e, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, + 0x57, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x49, 0x64, 0x73, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x15, 0x0a, 0x13, + 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xba, 0x02, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x51, 0x0a, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x12, 0x66, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x42, 0x49, 0x92, 0x41, 0x46, 0x32, 0x32, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x20, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x2e, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x59, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x52, 0x08, + 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x4b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x41, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xba, 0x48, 0x19, 0x72, 0x17, 0x18, + 0xf9, 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, + 0x5f, 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x06, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x06, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x4b, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xba, 0x48, + 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x61, 0x0a, 0x1d, + 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, + 0x0a, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x21, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, 0x01, + 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, + 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x09, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x22, + 0x7e, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5c, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x69, + 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0xb6, 0x03, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x0a, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, + 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x09, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x55, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0xba, 0x48, 0x08, 0xc8, 0x01, 0x01, 0x92, 0x01, + 0x02, 0x08, 0x01, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x1a, 0xc2, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, + 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0xf9, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x63, 0x0a, 0x09, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x35, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x42, 0x0e, 0xba, 0x48, 0x0b, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, + 0x22, 0x01, 0x00, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x21, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, + 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x84, 0x02, 0x0a, + 0x1d, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x77, 0x0a, + 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x4b, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x7e, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x54, 0x6f, 0x70, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x32, 0xf3, 0x11, 0x0a, 0x0c, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x80, 0x02, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x12, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x85, 0x01, 0x92, 0x41, 0x63, 0x12, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x1a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x2e, 0x4a, 0x42, 0x0a, 0x03, 0x32, 0x30, 0x31, 0x12, 0x3b, 0x0a, 0x0d, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x28, + 0x1a, 0x26, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x05, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0xf2, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, + 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x7b, 0x92, 0x41, 0x60, 0x12, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x73, 0x1a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x4a, 0x44, + 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3d, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x37, 0x0a, 0x35, 0x1a, + 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0xd6, 0x02, 0x0a, + 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x33, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xdb, 0x01, 0x92, 0x41, 0xb8, 0x01, 0x12, 0x14, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x1a, 0x30, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x4a, 0x29, 0x0a, 0x03, 0x32, 0x30, 0x34, 0x12, 0x22, 0x0a, + 0x1e, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x12, + 0x00, 0x4a, 0x43, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x3c, 0x0a, 0x22, 0x54, 0x68, 0x65, 0x20, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, + 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x12, 0x16, + 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x2f, 0x7b, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x97, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, + 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x3f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0xfb, 0x01, 0x92, 0x41, 0xb3, 0x01, 0x12, 0x18, 0x47, 0x65, 0x74, 0x20, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x1a, 0x19, 0x47, 0x65, 0x74, 0x20, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x50, 0x0a, + 0x03, 0x32, 0x30, 0x30, 0x12, 0x49, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x43, 0x0a, 0x41, 0x1a, 0x3f, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, + 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, + 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x3e, 0x62, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x2c, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x2f, 0x7b, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0xc8, 0x03, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x42, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa3, 0x02, 0x92, 0x41, 0xcb, 0x01, 0x12, 0x1a, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x20, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x2c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x53, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x4c, 0x0a, 0x02, + 0x4f, 0x6b, 0x12, 0x46, 0x0a, 0x44, 0x1a, 0x42, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, + 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, + 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4e, 0x3a, 0x0e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x62, 0x0e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x2c, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x2f, 0x7b, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xac, 0x04, 0x0a, 0x16, 0x53, + 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x90, 0x03, 0x92, 0x41, 0xb8, 0x02, 0x12, 0x18, 0x53, + 0x65, 0x74, 0x20, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x9d, 0x01, 0x53, 0x65, 0x74, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x2e, 0x20, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x2d, 0x62, 0x61, 0x63, 0x6b, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x4a, 0x50, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x49, + 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x43, 0x0a, 0x41, 0x1a, 0x3f, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x6f, + 0x70, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, + 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, + 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4e, 0x3a, 0x0e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x62, 0x0e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x2c, 0x2f, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x2f, 0x7b, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0xbd, 0x02, 0x0a, 0x23, 0x63, 0x6f, + 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x42, 0x0a, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x6b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x52, + 0x41, 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, 0x70, + 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5c, + 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x3a, + 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x3a, + 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_redpanda_api_dataplane_v1alpha1_topic_proto_goTypes = []interface{}{ + (*Topic)(nil), // 0: redpanda.api.dataplane.v1alpha1.Topic + (*CreateTopicRequest)(nil), // 1: redpanda.api.dataplane.v1alpha1.CreateTopicRequest + (*CreateTopicResponse)(nil), // 2: redpanda.api.dataplane.v1alpha1.CreateTopicResponse + (*ListTopicsRequest)(nil), // 3: redpanda.api.dataplane.v1alpha1.ListTopicsRequest + (*ListTopicsResponse)(nil), // 4: redpanda.api.dataplane.v1alpha1.ListTopicsResponse + (*DeleteTopicRequest)(nil), // 5: redpanda.api.dataplane.v1alpha1.DeleteTopicRequest + (*DeleteTopicResponse)(nil), // 6: redpanda.api.dataplane.v1alpha1.DeleteTopicResponse + (*GetTopicConfigurationsRequest)(nil), // 7: redpanda.api.dataplane.v1alpha1.GetTopicConfigurationsRequest + (*GetTopicConfigurationsResponse)(nil), // 8: redpanda.api.dataplane.v1alpha1.GetTopicConfigurationsResponse + (*UpdateTopicConfigurationsRequest)(nil), // 9: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest + (*UpdateTopicConfigurationsResponse)(nil), // 10: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsResponse + (*SetTopicConfigurationsRequest)(nil), // 11: redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsRequest + (*SetTopicConfigurationsResponse)(nil), // 12: redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsResponse + (*Topic_Configuration)(nil), // 13: redpanda.api.dataplane.v1alpha1.Topic.Configuration + (*CreateTopicRequest_Topic)(nil), // 14: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic + (*CreateTopicRequest_Topic_Config)(nil), // 15: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.Config + (*CreateTopicRequest_Topic_ReplicaAssignment)(nil), // 16: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.ReplicaAssignment + (*ListTopicsRequest_Filter)(nil), // 17: redpanda.api.dataplane.v1alpha1.ListTopicsRequest.Filter + (*ListTopicsResponse_Topic)(nil), // 18: redpanda.api.dataplane.v1alpha1.ListTopicsResponse.Topic + (*UpdateTopicConfigurationsRequest_UpdateConfiguration)(nil), // 19: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest.UpdateConfiguration + (*SetTopicConfigurationsRequest_SetConfiguration)(nil), // 20: redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsRequest.SetConfiguration + (ConfigType)(0), // 21: redpanda.api.dataplane.v1alpha1.ConfigType + (ConfigSource)(0), // 22: redpanda.api.dataplane.v1alpha1.ConfigSource + (*ConfigSynonym)(nil), // 23: redpanda.api.dataplane.v1alpha1.ConfigSynonym + (ConfigAlterOperation)(0), // 24: redpanda.api.dataplane.v1alpha1.ConfigAlterOperation +} +var file_redpanda_api_dataplane_v1alpha1_topic_proto_depIdxs = []int32{ + 14, // 0: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.topic:type_name -> redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic + 17, // 1: redpanda.api.dataplane.v1alpha1.ListTopicsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListTopicsRequest.Filter + 18, // 2: redpanda.api.dataplane.v1alpha1.ListTopicsResponse.topics:type_name -> redpanda.api.dataplane.v1alpha1.ListTopicsResponse.Topic + 13, // 3: redpanda.api.dataplane.v1alpha1.GetTopicConfigurationsResponse.configurations:type_name -> redpanda.api.dataplane.v1alpha1.Topic.Configuration + 19, // 4: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest.configurations:type_name -> redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest.UpdateConfiguration + 13, // 5: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsResponse.configurations:type_name -> redpanda.api.dataplane.v1alpha1.Topic.Configuration + 20, // 6: redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsRequest.configurations:type_name -> redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsRequest.SetConfiguration + 13, // 7: redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsResponse.configurations:type_name -> redpanda.api.dataplane.v1alpha1.Topic.Configuration + 21, // 8: redpanda.api.dataplane.v1alpha1.Topic.Configuration.type:type_name -> redpanda.api.dataplane.v1alpha1.ConfigType + 22, // 9: redpanda.api.dataplane.v1alpha1.Topic.Configuration.source:type_name -> redpanda.api.dataplane.v1alpha1.ConfigSource + 23, // 10: redpanda.api.dataplane.v1alpha1.Topic.Configuration.config_synonyms:type_name -> redpanda.api.dataplane.v1alpha1.ConfigSynonym + 16, // 11: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.replica_assignments:type_name -> redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.ReplicaAssignment + 15, // 12: redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.configs:type_name -> redpanda.api.dataplane.v1alpha1.CreateTopicRequest.Topic.Config + 24, // 13: redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest.UpdateConfiguration.operation:type_name -> redpanda.api.dataplane.v1alpha1.ConfigAlterOperation + 1, // 14: redpanda.api.dataplane.v1alpha1.TopicService.CreateTopic:input_type -> redpanda.api.dataplane.v1alpha1.CreateTopicRequest + 3, // 15: redpanda.api.dataplane.v1alpha1.TopicService.ListTopics:input_type -> redpanda.api.dataplane.v1alpha1.ListTopicsRequest + 5, // 16: redpanda.api.dataplane.v1alpha1.TopicService.DeleteTopic:input_type -> redpanda.api.dataplane.v1alpha1.DeleteTopicRequest + 7, // 17: redpanda.api.dataplane.v1alpha1.TopicService.GetTopicConfigurations:input_type -> redpanda.api.dataplane.v1alpha1.GetTopicConfigurationsRequest + 9, // 18: redpanda.api.dataplane.v1alpha1.TopicService.UpdateTopicConfigurations:input_type -> redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsRequest + 11, // 19: redpanda.api.dataplane.v1alpha1.TopicService.SetTopicConfigurations:input_type -> redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsRequest + 2, // 20: redpanda.api.dataplane.v1alpha1.TopicService.CreateTopic:output_type -> redpanda.api.dataplane.v1alpha1.CreateTopicResponse + 4, // 21: redpanda.api.dataplane.v1alpha1.TopicService.ListTopics:output_type -> redpanda.api.dataplane.v1alpha1.ListTopicsResponse + 6, // 22: redpanda.api.dataplane.v1alpha1.TopicService.DeleteTopic:output_type -> redpanda.api.dataplane.v1alpha1.DeleteTopicResponse + 8, // 23: redpanda.api.dataplane.v1alpha1.TopicService.GetTopicConfigurations:output_type -> redpanda.api.dataplane.v1alpha1.GetTopicConfigurationsResponse + 10, // 24: redpanda.api.dataplane.v1alpha1.TopicService.UpdateTopicConfigurations:output_type -> redpanda.api.dataplane.v1alpha1.UpdateTopicConfigurationsResponse + 12, // 25: redpanda.api.dataplane.v1alpha1.TopicService.SetTopicConfigurations:output_type -> redpanda.api.dataplane.v1alpha1.SetTopicConfigurationsResponse + 20, // [20:26] is the sub-list for method output_type + 14, // [14:20] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_topic_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_topic_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_topic_proto != nil { + return + } + file_redpanda_api_dataplane_v1alpha1_common_proto_init() + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Topic); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTopicRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTopicResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTopicsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTopicsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTopicRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTopicResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTopicConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTopicConfigurationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTopicConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTopicConfigurationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetTopicConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetTopicConfigurationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Topic_Configuration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTopicRequest_Topic); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTopicRequest_Topic_Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTopicRequest_Topic_ReplicaAssignment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTopicsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTopicsResponse_Topic); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTopicConfigurationsRequest_UpdateConfiguration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetTopicConfigurationsRequest_SetConfiguration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[13].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[14].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[15].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[19].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes[20].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDesc, + NumEnums: 0, + NumMessages: 21, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_topic_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_topic_proto_depIdxs, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_topic_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_topic_proto = out.File + file_redpanda_api_dataplane_v1alpha1_topic_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_topic_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_topic_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/transform.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/transform.pb.go new file mode 100644 index 0000000000000..a4cf07b192387 --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/transform.pb.go @@ -0,0 +1,1163 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/transform.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PartitionTransformStatus_PartitionStatus int32 + +const ( + PartitionTransformStatus_PARTITION_STATUS_UNSPECIFIED PartitionTransformStatus_PartitionStatus = 0 + PartitionTransformStatus_PARTITION_STATUS_RUNNING PartitionTransformStatus_PartitionStatus = 1 + PartitionTransformStatus_PARTITION_STATUS_INACTIVE PartitionTransformStatus_PartitionStatus = 2 + PartitionTransformStatus_PARTITION_STATUS_ERRORED PartitionTransformStatus_PartitionStatus = 3 + PartitionTransformStatus_PARTITION_STATUS_UNKNOWN PartitionTransformStatus_PartitionStatus = 4 +) + +// Enum value maps for PartitionTransformStatus_PartitionStatus. +var ( + PartitionTransformStatus_PartitionStatus_name = map[int32]string{ + 0: "PARTITION_STATUS_UNSPECIFIED", + 1: "PARTITION_STATUS_RUNNING", + 2: "PARTITION_STATUS_INACTIVE", + 3: "PARTITION_STATUS_ERRORED", + 4: "PARTITION_STATUS_UNKNOWN", + } + PartitionTransformStatus_PartitionStatus_value = map[string]int32{ + "PARTITION_STATUS_UNSPECIFIED": 0, + "PARTITION_STATUS_RUNNING": 1, + "PARTITION_STATUS_INACTIVE": 2, + "PARTITION_STATUS_ERRORED": 3, + "PARTITION_STATUS_UNKNOWN": 4, + } +) + +func (x PartitionTransformStatus_PartitionStatus) Enum() *PartitionTransformStatus_PartitionStatus { + p := new(PartitionTransformStatus_PartitionStatus) + *p = x + return p +} + +func (x PartitionTransformStatus_PartitionStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PartitionTransformStatus_PartitionStatus) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_enumTypes[0].Descriptor() +} + +func (PartitionTransformStatus_PartitionStatus) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_transform_proto_enumTypes[0] +} + +func (x PartitionTransformStatus_PartitionStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PartitionTransformStatus_PartitionStatus.Descriptor instead. +func (PartitionTransformStatus_PartitionStatus) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{1, 0} +} + +type TransformMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + InputTopicName string `protobuf:"bytes,2,opt,name=input_topic_name,json=inputTopicName,proto3" json:"input_topic_name,omitempty"` + OutputTopicNames []string `protobuf:"bytes,3,rep,name=output_topic_names,json=outputTopicNames,proto3" json:"output_topic_names,omitempty"` + Statuses []*PartitionTransformStatus `protobuf:"bytes,4,rep,name=statuses,proto3" json:"statuses,omitempty"` + EnvironmentVariables []*TransformMetadata_EnvironmentVariable `protobuf:"bytes,5,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty"` +} + +func (x *TransformMetadata) Reset() { + *x = TransformMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransformMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransformMetadata) ProtoMessage() {} + +func (x *TransformMetadata) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransformMetadata.ProtoReflect.Descriptor instead. +func (*TransformMetadata) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{0} +} + +func (x *TransformMetadata) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TransformMetadata) GetInputTopicName() string { + if x != nil { + return x.InputTopicName + } + return "" +} + +func (x *TransformMetadata) GetOutputTopicNames() []string { + if x != nil { + return x.OutputTopicNames + } + return nil +} + +func (x *TransformMetadata) GetStatuses() []*PartitionTransformStatus { + if x != nil { + return x.Statuses + } + return nil +} + +func (x *TransformMetadata) GetEnvironmentVariables() []*TransformMetadata_EnvironmentVariable { + if x != nil { + return x.EnvironmentVariables + } + return nil +} + +type PartitionTransformStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BrokerId int32 `protobuf:"varint,1,opt,name=broker_id,json=brokerId,proto3" json:"broker_id,omitempty"` + PartitionId int32 `protobuf:"varint,2,opt,name=partition_id,json=partitionId,proto3" json:"partition_id,omitempty"` + Status PartitionTransformStatus_PartitionStatus `protobuf:"varint,3,opt,name=status,proto3,enum=redpanda.api.dataplane.v1alpha1.PartitionTransformStatus_PartitionStatus" json:"status,omitempty"` + Lag int32 `protobuf:"varint,4,opt,name=lag,proto3" json:"lag,omitempty"` +} + +func (x *PartitionTransformStatus) Reset() { + *x = PartitionTransformStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PartitionTransformStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PartitionTransformStatus) ProtoMessage() {} + +func (x *PartitionTransformStatus) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PartitionTransformStatus.ProtoReflect.Descriptor instead. +func (*PartitionTransformStatus) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{1} +} + +func (x *PartitionTransformStatus) GetBrokerId() int32 { + if x != nil { + return x.BrokerId + } + return 0 +} + +func (x *PartitionTransformStatus) GetPartitionId() int32 { + if x != nil { + return x.PartitionId + } + return 0 +} + +func (x *PartitionTransformStatus) GetStatus() PartitionTransformStatus_PartitionStatus { + if x != nil { + return x.Status + } + return PartitionTransformStatus_PARTITION_STATUS_UNSPECIFIED +} + +func (x *PartitionTransformStatus) GetLag() int32 { + if x != nil { + return x.Lag + } + return 0 +} + +// DeployTransformRequest is the metadata that is required to deploy a new WASM +// transform in a Redpanda cluster. +type DeployTransformRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + InputTopicName string `protobuf:"bytes,2,opt,name=input_topic_name,json=inputTopicName,proto3" json:"input_topic_name,omitempty"` + OutputTopicNames []string `protobuf:"bytes,3,rep,name=output_topic_names,json=outputTopicNames,proto3" json:"output_topic_names,omitempty"` + EnvironmentVariables []*TransformMetadata_EnvironmentVariable `protobuf:"bytes,4,rep,name=environment_variables,json=environmentVariables,proto3" json:"environment_variables,omitempty"` +} + +func (x *DeployTransformRequest) Reset() { + *x = DeployTransformRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeployTransformRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeployTransformRequest) ProtoMessage() {} + +func (x *DeployTransformRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeployTransformRequest.ProtoReflect.Descriptor instead. +func (*DeployTransformRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{2} +} + +func (x *DeployTransformRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeployTransformRequest) GetInputTopicName() string { + if x != nil { + return x.InputTopicName + } + return "" +} + +func (x *DeployTransformRequest) GetOutputTopicNames() []string { + if x != nil { + return x.OutputTopicNames + } + return nil +} + +func (x *DeployTransformRequest) GetEnvironmentVariables() []*TransformMetadata_EnvironmentVariable { + if x != nil { + return x.EnvironmentVariables + } + return nil +} + +type ListTransformsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListTransformsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *ListTransformsRequest) Reset() { + *x = ListTransformsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTransformsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTransformsRequest) ProtoMessage() {} + +func (x *ListTransformsRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTransformsRequest.ProtoReflect.Descriptor instead. +func (*ListTransformsRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{3} +} + +func (x *ListTransformsRequest) GetFilter() *ListTransformsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +func (x *ListTransformsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type ListTransformsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + Transforms []*TransformMetadata `protobuf:"bytes,2,rep,name=transforms,proto3" json:"transforms,omitempty"` +} + +func (x *ListTransformsResponse) Reset() { + *x = ListTransformsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTransformsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTransformsResponse) ProtoMessage() {} + +func (x *ListTransformsResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTransformsResponse.ProtoReflect.Descriptor instead. +func (*ListTransformsResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{4} +} + +func (x *ListTransformsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ListTransformsResponse) GetTransforms() []*TransformMetadata { + if x != nil { + return x.Transforms + } + return nil +} + +type GetTransformRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetTransformRequest) Reset() { + *x = GetTransformRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTransformRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransformRequest) ProtoMessage() {} + +func (x *GetTransformRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransformRequest.ProtoReflect.Descriptor instead. +func (*GetTransformRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{5} +} + +func (x *GetTransformRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetTransformResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Transform *TransformMetadata `protobuf:"bytes,1,opt,name=transform,proto3" json:"transform,omitempty"` +} + +func (x *GetTransformResponse) Reset() { + *x = GetTransformResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTransformResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransformResponse) ProtoMessage() {} + +func (x *GetTransformResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTransformResponse.ProtoReflect.Descriptor instead. +func (*GetTransformResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{6} +} + +func (x *GetTransformResponse) GetTransform() *TransformMetadata { + if x != nil { + return x.Transform + } + return nil +} + +type DeleteTransformRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteTransformRequest) Reset() { + *x = DeleteTransformRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTransformRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTransformRequest) ProtoMessage() {} + +func (x *DeleteTransformRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTransformRequest.ProtoReflect.Descriptor instead. +func (*DeleteTransformRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteTransformRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTransformResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteTransformResponse) Reset() { + *x = DeleteTransformResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTransformResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTransformResponse) ProtoMessage() {} + +func (x *DeleteTransformResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTransformResponse.ProtoReflect.Descriptor instead. +func (*DeleteTransformResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{8} +} + +type TransformMetadata_EnvironmentVariable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *TransformMetadata_EnvironmentVariable) Reset() { + *x = TransformMetadata_EnvironmentVariable{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransformMetadata_EnvironmentVariable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransformMetadata_EnvironmentVariable) ProtoMessage() {} + +func (x *TransformMetadata_EnvironmentVariable) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransformMetadata_EnvironmentVariable.ProtoReflect.Descriptor instead. +func (*TransformMetadata_EnvironmentVariable) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *TransformMetadata_EnvironmentVariable) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransformMetadata_EnvironmentVariable) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type ListTransformsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ListTransformsRequest_Filter) Reset() { + *x = ListTransformsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTransformsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTransformsRequest_Filter) ProtoMessage() {} + +func (x *ListTransformsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTransformsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListTransformsRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *ListTransformsRequest_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +var File_redpanda_api_dataplane_v1alpha1_transform_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDesc = []byte{ + 0x0a, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x1f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, + 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, + 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaf, + 0x06, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0x72, 0x18, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x11, + 0x5e, 0x5b, 0x5c, 0x50, 0x7b, 0x43, 0x63, 0x7d, 0x5c, 0x50, 0x7b, 0x43, 0x66, 0x7d, 0x5d, 0x2b, + 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, + 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, + 0x55, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0xcd, 0x01, 0x0a, 0x15, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, + 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x50, + 0x92, 0x41, 0x4d, 0x32, 0x4b, 0x54, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, + 0x72, 0x6d, 0x27, 0x73, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x14, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0xe7, 0x02, 0x0a, 0x13, 0x45, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0xf4, + 0x01, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0xe1, 0x01, 0x92, + 0x41, 0x34, 0x32, 0x25, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x79, + 0x6f, 0x75, 0x72, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x4a, 0x0b, 0x22, 0x4c, 0x4f, 0x47, 0x5f, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x22, 0xe0, 0x41, 0x02, 0xba, 0x48, 0xa3, 0x01, 0xba, 0x01, 0x95, + 0x01, 0x0a, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x5f, 0x65, 0x6e, + 0x76, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x6f, 0x65, 0x73, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x75, + 0x73, 0x65, 0x5f, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x5f, 0x70, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x12, 0x43, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x27, 0x52, + 0x45, 0x44, 0x50, 0x41, 0x4e, 0x44, 0x41, 0x5f, 0x27, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x2e, 0x1a, 0x1d, 0x21, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x28, 0x27, 0x52, 0x45, 0x44, 0x50, 0x41, + 0x4e, 0x44, 0x41, 0x5f, 0x27, 0x29, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x59, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x43, 0x92, 0x41, 0x30, 0x32, 0x25, 0x54, 0x68, 0x65, 0x20, 0x6b, + 0x65, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x4a, 0x07, 0x22, 0x44, 0x45, 0x42, 0x55, 0x47, 0x22, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x0a, 0xc8, + 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x10, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0xfe, 0x02, 0x0a, 0x18, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, + 0x09, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x61, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x49, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, + 0x72, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6c, + 0x61, 0x67, 0x22, 0xac, 0x01, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x41, 0x52, 0x54, 0x49, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x50, 0x41, 0x52, 0x54, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x55, 0x4e, + 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x41, 0x52, 0x54, 0x49, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, + 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18, 0x50, 0x41, 0x52, 0x54, 0x49, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x45, + 0x44, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x50, 0x41, 0x52, 0x54, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x04, 0x22, 0xa5, 0x05, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x7b, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x67, 0x92, 0x41, 0x40, 0x32, + 0x1a, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x4a, 0x22, 0x22, 0x72, 0x65, + 0x64, 0x61, 0x63, 0x74, 0x2d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x2d, 0x69, 0x6e, 0x2d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0xe0, + 0x41, 0x02, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, + 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, 0x2d, + 0x5d, 0x2a, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x10, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x5d, 0x92, 0x41, 0x36, 0x32, 0x2a, 0x54, 0x68, 0x65, 0x20, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x70, + 0x70, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x74, 0x6f, 0x2e, 0x4a, 0x08, 0x22, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0xe0, + 0x41, 0x02, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, 0x01, 0x32, + 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, 0x2d, + 0x5d, 0x2a, 0x24, 0x52, 0x0e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0xa8, 0x01, 0x0a, 0x12, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x42, 0x7a, 0x92, 0x41, 0x48, 0x32, 0x33, 0x54, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x20, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x20, 0x74, 0x6f, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x2e, 0x4a, 0x11, 0x22, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x2d, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64, 0x22, 0xe0, 0x41, 0x02, + 0xba, 0x48, 0x29, 0xc8, 0x01, 0x01, 0x92, 0x01, 0x23, 0x08, 0x01, 0x10, 0x01, 0x18, 0x01, 0x22, + 0x1b, 0x72, 0x19, 0x10, 0x01, 0x18, 0xf9, 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, + 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x10, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0xd8, + 0x01, 0x0a, 0x15, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x5b, 0x92, 0x41, 0x4d, 0x32, 0x4b, 0x54, 0x68, 0x65, + 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, + 0x74, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x72, + 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x27, 0x73, 0x20, 0x65, 0x6e, 0x76, + 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0xba, 0x48, 0x08, 0x92, 0x01, 0x05, 0x08, 0x00, + 0x10, 0x80, 0x01, 0x52, 0x14, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x22, 0xca, 0x01, 0x0a, 0x15, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x55, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x3b, 0x0a, 0x06, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x1d, 0xba, 0x48, 0x1a, 0x72, 0x18, 0x10, 0x01, 0x18, 0x80, 0x01, 0x32, 0x11, 0x5e, + 0x5b, 0x5c, 0x50, 0x7b, 0x43, 0x63, 0x7d, 0x5c, 0x50, 0x7b, 0x43, 0x66, 0x7d, 0x5d, 0x2b, 0x24, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x52, 0x0a, 0x0a, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x22, 0x4e, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x23, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1d, 0xc8, 0x01, 0x01, 0x72, 0x18, 0x10, + 0x01, 0x18, 0x80, 0x01, 0x32, 0x11, 0x5e, 0x5b, 0x5c, 0x50, 0x7b, 0x43, 0x63, 0x7d, 0x5c, 0x50, + 0x7b, 0x43, 0x66, 0x7d, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x68, 0x0a, + 0x14, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, + 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x09, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x52, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x38, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x24, 0xe0, 0x41, 0x02, 0xba, 0x48, 0x1e, 0xc8, 0x01, 0x01, 0x72, 0x19, 0x10, 0x01, 0x18, 0x80, + 0x01, 0x32, 0x12, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2e, 0x5f, + 0x5c, 0x2d, 0x5d, 0x2a, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xdd, 0x07, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xb6, 0x02, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x12, 0x36, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xb2, 0x01, 0x92, 0x41, 0x92, 0x01, 0x12, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, + 0x65, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4a, 0x48, + 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x41, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x3b, 0x0a, 0x39, 0x1a, + 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x6f, 0x72, 0x6d, 0x73, 0x12, 0xc3, 0x02, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x34, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0xc5, 0x01, 0x92, 0x41, 0x93, 0x01, 0x12, 0x0d, 0x47, 0x65, 0x74, 0x20, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x1a, 0x0e, 0x47, 0x65, 0x74, 0x20, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x21, 0x4a, 0x46, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, + 0x3f, 0x0a, 0x02, 0x4f, 0x4b, 0x12, 0x39, 0x0a, 0x37, 0x1a, 0x35, 0x2e, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, + 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x28, 0x62, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1b, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, + 0x72, 0x6d, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xc9, 0x02, 0x0a, 0x0f, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x37, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0xc2, 0x01, 0x92, 0x41, 0x9b, 0x01, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x20, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x1a, 0x30, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x20, 0x61, 0x20, 0x57, 0x41, 0x53, 0x4d, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x6f, 0x72, 0x6d, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x4a, 0x29, 0x0a, 0x03, + 0x32, 0x30, 0x34, 0x12, 0x22, 0x0a, 0x1e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x12, 0x00, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, + 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, + 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x2a, 0x1b, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2f, + 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x42, 0xc1, 0x02, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, + 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x41, + 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_transform_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_redpanda_api_dataplane_v1alpha1_transform_proto_goTypes = []interface{}{ + (PartitionTransformStatus_PartitionStatus)(0), // 0: redpanda.api.dataplane.v1alpha1.PartitionTransformStatus.PartitionStatus + (*TransformMetadata)(nil), // 1: redpanda.api.dataplane.v1alpha1.TransformMetadata + (*PartitionTransformStatus)(nil), // 2: redpanda.api.dataplane.v1alpha1.PartitionTransformStatus + (*DeployTransformRequest)(nil), // 3: redpanda.api.dataplane.v1alpha1.DeployTransformRequest + (*ListTransformsRequest)(nil), // 4: redpanda.api.dataplane.v1alpha1.ListTransformsRequest + (*ListTransformsResponse)(nil), // 5: redpanda.api.dataplane.v1alpha1.ListTransformsResponse + (*GetTransformRequest)(nil), // 6: redpanda.api.dataplane.v1alpha1.GetTransformRequest + (*GetTransformResponse)(nil), // 7: redpanda.api.dataplane.v1alpha1.GetTransformResponse + (*DeleteTransformRequest)(nil), // 8: redpanda.api.dataplane.v1alpha1.DeleteTransformRequest + (*DeleteTransformResponse)(nil), // 9: redpanda.api.dataplane.v1alpha1.DeleteTransformResponse + (*TransformMetadata_EnvironmentVariable)(nil), // 10: redpanda.api.dataplane.v1alpha1.TransformMetadata.EnvironmentVariable + (*ListTransformsRequest_Filter)(nil), // 11: redpanda.api.dataplane.v1alpha1.ListTransformsRequest.Filter +} +var file_redpanda_api_dataplane_v1alpha1_transform_proto_depIdxs = []int32{ + 2, // 0: redpanda.api.dataplane.v1alpha1.TransformMetadata.statuses:type_name -> redpanda.api.dataplane.v1alpha1.PartitionTransformStatus + 10, // 1: redpanda.api.dataplane.v1alpha1.TransformMetadata.environment_variables:type_name -> redpanda.api.dataplane.v1alpha1.TransformMetadata.EnvironmentVariable + 0, // 2: redpanda.api.dataplane.v1alpha1.PartitionTransformStatus.status:type_name -> redpanda.api.dataplane.v1alpha1.PartitionTransformStatus.PartitionStatus + 10, // 3: redpanda.api.dataplane.v1alpha1.DeployTransformRequest.environment_variables:type_name -> redpanda.api.dataplane.v1alpha1.TransformMetadata.EnvironmentVariable + 11, // 4: redpanda.api.dataplane.v1alpha1.ListTransformsRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListTransformsRequest.Filter + 1, // 5: redpanda.api.dataplane.v1alpha1.ListTransformsResponse.transforms:type_name -> redpanda.api.dataplane.v1alpha1.TransformMetadata + 1, // 6: redpanda.api.dataplane.v1alpha1.GetTransformResponse.transform:type_name -> redpanda.api.dataplane.v1alpha1.TransformMetadata + 4, // 7: redpanda.api.dataplane.v1alpha1.TransformService.ListTransforms:input_type -> redpanda.api.dataplane.v1alpha1.ListTransformsRequest + 6, // 8: redpanda.api.dataplane.v1alpha1.TransformService.GetTransform:input_type -> redpanda.api.dataplane.v1alpha1.GetTransformRequest + 8, // 9: redpanda.api.dataplane.v1alpha1.TransformService.DeleteTransform:input_type -> redpanda.api.dataplane.v1alpha1.DeleteTransformRequest + 5, // 10: redpanda.api.dataplane.v1alpha1.TransformService.ListTransforms:output_type -> redpanda.api.dataplane.v1alpha1.ListTransformsResponse + 7, // 11: redpanda.api.dataplane.v1alpha1.TransformService.GetTransform:output_type -> redpanda.api.dataplane.v1alpha1.GetTransformResponse + 9, // 12: redpanda.api.dataplane.v1alpha1.TransformService.DeleteTransform:output_type -> redpanda.api.dataplane.v1alpha1.DeleteTransformResponse + 10, // [10:13] is the sub-list for method output_type + 7, // [7:10] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_transform_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_transform_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_transform_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransformMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PartitionTransformStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeployTransformRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTransformsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTransformsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransformRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransformResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTransformRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTransformResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransformMetadata_EnvironmentVariable); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTransformsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDesc, + NumEnums: 1, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_transform_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_transform_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_transform_proto_enumTypes, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_transform_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_transform_proto = out.File + file_redpanda_api_dataplane_v1alpha1_transform_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_transform_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_transform_proto_depIdxs = nil +} diff --git a/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/user.pb.go b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/user.pb.go new file mode 100644 index 0000000000000..aba091454b50e --- /dev/null +++ b/src/go/rpk/proto/gen/go/redpanda/api/dataplane/v1alpha1/user.pb.go @@ -0,0 +1,1259 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: redpanda/api/dataplane/v1alpha1/user.proto + +package dataplanev1alpha1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SASLMechanism int32 + +const ( + SASLMechanism_SASL_MECHANISM_UNSPECIFIED SASLMechanism = 0 + SASLMechanism_SASL_MECHANISM_SCRAM_SHA_256 SASLMechanism = 1 + SASLMechanism_SASL_MECHANISM_SCRAM_SHA_512 SASLMechanism = 2 +) + +// Enum value maps for SASLMechanism. +var ( + SASLMechanism_name = map[int32]string{ + 0: "SASL_MECHANISM_UNSPECIFIED", + 1: "SASL_MECHANISM_SCRAM_SHA_256", + 2: "SASL_MECHANISM_SCRAM_SHA_512", + } + SASLMechanism_value = map[string]int32{ + "SASL_MECHANISM_UNSPECIFIED": 0, + "SASL_MECHANISM_SCRAM_SHA_256": 1, + "SASL_MECHANISM_SCRAM_SHA_512": 2, + } +) + +func (x SASLMechanism) Enum() *SASLMechanism { + p := new(SASLMechanism) + *p = x + return p +} + +func (x SASLMechanism) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SASLMechanism) Descriptor() protoreflect.EnumDescriptor { + return file_redpanda_api_dataplane_v1alpha1_user_proto_enumTypes[0].Descriptor() +} + +func (SASLMechanism) Type() protoreflect.EnumType { + return &file_redpanda_api_dataplane_v1alpha1_user_proto_enumTypes[0] +} + +func (x SASLMechanism) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SASLMechanism.Descriptor instead. +func (SASLMechanism) EnumDescriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{0} +} + +type ListUsersRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListUsersRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListUsersRequest) Reset() { + *x = ListUsersRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUsersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersRequest) ProtoMessage() {} + +func (x *ListUsersRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersRequest.ProtoReflect.Descriptor instead. +func (*ListUsersRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{0} +} + +func (x *ListUsersRequest) GetFilter() *ListUsersRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type ListUsersResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Users []*ListUsersResponse_User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` +} + +func (x *ListUsersResponse) Reset() { + *x = ListUsersResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUsersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersResponse) ProtoMessage() {} + +func (x *ListUsersResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersResponse.ProtoReflect.Descriptor instead. +func (*ListUsersResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{1} +} + +func (x *ListUsersResponse) GetUsers() []*ListUsersResponse_User { + if x != nil { + return x.Users + } + return nil +} + +type CreateUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *CreateUserRequest_User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *CreateUserRequest) Reset() { + *x = CreateUserRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserRequest) ProtoMessage() {} + +func (x *CreateUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead. +func (*CreateUserRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateUserRequest) GetUser() *CreateUserRequest_User { + if x != nil { + return x.User + } + return nil +} + +type CreateUserResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *CreateUserResponse_User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *CreateUserResponse) Reset() { + *x = CreateUserResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserResponse) ProtoMessage() {} + +func (x *CreateUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserResponse.ProtoReflect.Descriptor instead. +func (*CreateUserResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateUserResponse) GetUser() *CreateUserResponse_User { + if x != nil { + return x.User + } + return nil +} + +type UpdateUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *UpdateUserRequest_User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *UpdateUserRequest) Reset() { + *x = UpdateUserRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserRequest) ProtoMessage() {} + +func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserRequest.ProtoReflect.Descriptor instead. +func (*UpdateUserRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{4} +} + +func (x *UpdateUserRequest) GetUser() *UpdateUserRequest_User { + if x != nil { + return x.User + } + return nil +} + +type UpdateUserResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *UpdateUserResponse_User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *UpdateUserResponse) Reset() { + *x = UpdateUserResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserResponse) ProtoMessage() {} + +func (x *UpdateUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserResponse.ProtoReflect.Descriptor instead. +func (*UpdateUserResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdateUserResponse) GetUser() *UpdateUserResponse_User { + if x != nil { + return x.User + } + return nil +} + +type DeleteUserRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteUserRequest) Reset() { + *x = DeleteUserRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserRequest) ProtoMessage() {} + +func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead. +func (*DeleteUserRequest) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteUserRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteUserResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteUserResponse) Reset() { + *x = DeleteUserResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserResponse) ProtoMessage() {} + +func (x *DeleteUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserResponse.ProtoReflect.Descriptor instead. +func (*DeleteUserResponse) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{7} +} + +type ListUsersRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + NameContains string `protobuf:"bytes,2,opt,name=name_contains,json=nameContains,proto3" json:"name_contains,omitempty"` +} + +func (x *ListUsersRequest_Filter) Reset() { + *x = ListUsersRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUsersRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersRequest_Filter) ProtoMessage() {} + +func (x *ListUsersRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListUsersRequest_Filter) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *ListUsersRequest_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListUsersRequest_Filter) GetNameContains() string { + if x != nil { + return x.NameContains + } + return "" +} + +type ListUsersResponse_User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Mechanism *SASLMechanism `protobuf:"varint,2,opt,name=mechanism,proto3,enum=redpanda.api.dataplane.v1alpha1.SASLMechanism,oneof" json:"mechanism,omitempty"` +} + +func (x *ListUsersResponse_User) Reset() { + *x = ListUsersResponse_User{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUsersResponse_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersResponse_User) ProtoMessage() {} + +func (x *ListUsersResponse_User) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersResponse_User.ProtoReflect.Descriptor instead. +func (*ListUsersResponse_User) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *ListUsersResponse_User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListUsersResponse_User) GetMechanism() SASLMechanism { + if x != nil && x.Mechanism != nil { + return *x.Mechanism + } + return SASLMechanism_SASL_MECHANISM_UNSPECIFIED +} + +type CreateUserRequest_User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Mechanism SASLMechanism `protobuf:"varint,3,opt,name=mechanism,proto3,enum=redpanda.api.dataplane.v1alpha1.SASLMechanism" json:"mechanism,omitempty"` +} + +func (x *CreateUserRequest_User) Reset() { + *x = CreateUserRequest_User{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateUserRequest_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserRequest_User) ProtoMessage() {} + +func (x *CreateUserRequest_User) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserRequest_User.ProtoReflect.Descriptor instead. +func (*CreateUserRequest_User) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *CreateUserRequest_User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateUserRequest_User) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *CreateUserRequest_User) GetMechanism() SASLMechanism { + if x != nil { + return x.Mechanism + } + return SASLMechanism_SASL_MECHANISM_UNSPECIFIED +} + +type CreateUserResponse_User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Mechanism *SASLMechanism `protobuf:"varint,2,opt,name=mechanism,proto3,enum=redpanda.api.dataplane.v1alpha1.SASLMechanism,oneof" json:"mechanism,omitempty"` +} + +func (x *CreateUserResponse_User) Reset() { + *x = CreateUserResponse_User{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateUserResponse_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserResponse_User) ProtoMessage() {} + +func (x *CreateUserResponse_User) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserResponse_User.ProtoReflect.Descriptor instead. +func (*CreateUserResponse_User) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *CreateUserResponse_User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateUserResponse_User) GetMechanism() SASLMechanism { + if x != nil && x.Mechanism != nil { + return *x.Mechanism + } + return SASLMechanism_SASL_MECHANISM_UNSPECIFIED +} + +type UpdateUserRequest_User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Mechanism SASLMechanism `protobuf:"varint,3,opt,name=mechanism,proto3,enum=redpanda.api.dataplane.v1alpha1.SASLMechanism" json:"mechanism,omitempty"` +} + +func (x *UpdateUserRequest_User) Reset() { + *x = UpdateUserRequest_User{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserRequest_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserRequest_User) ProtoMessage() {} + +func (x *UpdateUserRequest_User) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserRequest_User.ProtoReflect.Descriptor instead. +func (*UpdateUserRequest_User) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *UpdateUserRequest_User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateUserRequest_User) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *UpdateUserRequest_User) GetMechanism() SASLMechanism { + if x != nil { + return x.Mechanism + } + return SASLMechanism_SASL_MECHANISM_UNSPECIFIED +} + +type UpdateUserResponse_User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Mechanism *SASLMechanism `protobuf:"varint,2,opt,name=mechanism,proto3,enum=redpanda.api.dataplane.v1alpha1.SASLMechanism,oneof" json:"mechanism,omitempty"` +} + +func (x *UpdateUserResponse_User) Reset() { + *x = UpdateUserResponse_User{} + if protoimpl.UnsafeEnabled { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserResponse_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserResponse_User) ProtoMessage() {} + +func (x *UpdateUserResponse_User) ProtoReflect() protoreflect.Message { + mi := &file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserResponse_User.ProtoReflect.Descriptor instead. +func (*UpdateUserResponse_User) Descriptor() ([]byte, []int) { + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *UpdateUserResponse_User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateUserResponse_User) GetMechanism() SASLMechanism { + if x != nil && x.Mechanism != nil { + return *x.Mechanism + } + return SASLMechanism_SASL_MECHANISM_UNSPECIFIED +} + +var File_redpanda_api_dataplane_v1alpha1_user_proto protoreflect.FileDescriptor + +var file_redpanda_api_dataplane_v1alpha1_user_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, + 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1b, 0x62, + 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, + 0x41, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x7b, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, + 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x41, 0x53, 0x4c, 0x4d, 0x65, 0x63, + 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, + 0x69, 0x73, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x63, 0x68, 0x61, + 0x6e, 0x69, 0x73, 0x6d, 0x22, 0x95, 0x02, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x1a, 0xb2, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, + 0x12, 0x21, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, + 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, + 0x03, 0x18, 0x80, 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x5c, + 0x0a, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x41, 0x53, 0x4c, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, + 0x6d, 0x42, 0x0e, 0xba, 0x48, 0x0b, 0xc8, 0x01, 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, 0x01, + 0x00, 0x52, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x22, 0xdf, 0x01, 0x0a, + 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, + 0x72, 0x1a, 0x7b, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, + 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x53, 0x41, 0x53, 0x4c, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, + 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x88, 0x01, 0x01, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x22, 0x95, + 0x02, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, + 0x72, 0x1a, 0xb2, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, + 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x03, 0x18, 0x80, 0x01, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x5c, 0x0a, 0x09, 0x6d, 0x65, 0x63, 0x68, + 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x41, + 0x53, 0x4c, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x42, 0x0e, 0xba, 0x48, 0x0b, + 0xc8, 0x01, 0x01, 0x82, 0x01, 0x05, 0x10, 0x01, 0x22, 0x01, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x63, + 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x22, 0xdf, 0x01, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, + 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x1a, 0x7b, 0x0a, 0x04, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x51, 0x0a, 0x09, 0x6d, 0x65, 0x63, 0x68, 0x61, + 0x6e, 0x69, 0x73, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x41, 0x53, + 0x4c, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, + 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, + 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x22, 0x36, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, + 0xc8, 0x01, 0x01, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x73, 0x0a, 0x0d, 0x53, 0x41, 0x53, 0x4c, 0x4d, 0x65, + 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x41, 0x53, 0x4c, 0x5f, + 0x4d, 0x45, 0x43, 0x48, 0x41, 0x4e, 0x49, 0x53, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x41, 0x53, 0x4c, 0x5f, + 0x4d, 0x45, 0x43, 0x48, 0x41, 0x4e, 0x49, 0x53, 0x4d, 0x5f, 0x53, 0x43, 0x52, 0x41, 0x4d, 0x5f, + 0x53, 0x48, 0x41, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x41, 0x53, + 0x4c, 0x5f, 0x4d, 0x45, 0x43, 0x48, 0x41, 0x4e, 0x49, 0x53, 0x4d, 0x5f, 0x53, 0x43, 0x52, 0x41, + 0x4d, 0x5f, 0x53, 0x48, 0x41, 0x5f, 0x35, 0x31, 0x32, 0x10, 0x02, 0x32, 0xdf, 0x09, 0x0a, 0x0b, + 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xe7, 0x02, 0x0a, 0x0a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x32, 0x2e, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xef, 0x01, 0x92, 0x41, 0xce, 0x01, 0x12, 0x0b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x1a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, + 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x4a, 0x54, 0x0a, 0x03, 0x32, 0x30, 0x31, 0x12, 0x4d, + 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x75, 0x73, 0x65, 0x72, + 0x12, 0x3b, 0x0a, 0x39, 0x1a, 0x37, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x59, 0x0a, + 0x03, 0x34, 0x30, 0x30, 0x12, 0x52, 0x0a, 0x38, 0x42, 0x61, 0x64, 0x20, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x41, 0x50, 0x49, 0x20, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x04, + 0x75, 0x73, 0x65, 0x72, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, + 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xe8, 0x02, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x55, 0x73, 0x65, 0x72, 0x12, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, + 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf0, 0x01, + 0x92, 0x41, 0xc3, 0x01, 0x12, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x55, 0x73, 0x65, + 0x72, 0x1a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, + 0x2e, 0x4a, 0x49, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x42, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x3c, + 0x0a, 0x3a, 0x1a, 0x38, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x59, 0x0a, 0x03, + 0x34, 0x30, 0x30, 0x12, 0x52, 0x0a, 0x38, 0x42, 0x61, 0x64, 0x20, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x41, 0x50, 0x49, 0x20, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x12, + 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x04, 0x75, + 0x73, 0x65, 0x72, 0x1a, 0x1b, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x75, + 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x12, 0xeb, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x31, + 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x77, 0x92, 0x41, 0x5d, 0x12, 0x0a, 0x4c, 0x69, 0x73, 0x74, + 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x55, 0x73, 0x65, + 0x72, 0x73, 0x4a, 0x43, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x3c, 0x0a, 0x02, 0x4f, 0x4b, 0x12, + 0x36, 0x0a, 0x34, 0x1a, 0x32, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x8c, + 0x02, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x32, 0x2e, + 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x33, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x01, 0x92, 0x41, 0x73, 0x12, 0x0c, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x0c, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x4a, 0x29, 0x0a, 0x03, 0x32, 0x30, 0x34, 0x12, + 0x22, 0x0a, 0x1e, 0x55, 0x73, 0x65, 0x72, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x6c, 0x79, + 0x2e, 0x12, 0x00, 0x4a, 0x2a, 0x0a, 0x03, 0x34, 0x30, 0x34, 0x12, 0x23, 0x0a, 0x09, 0x4e, 0x6f, + 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x14, 0x1a, 0x12, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x42, 0xbc, 0x02, + 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x09, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x6b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, + 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x64, + 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x6b, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x65, + 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, + 0x02, 0x03, 0x52, 0x41, 0x44, 0xaa, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, 0x64, 0x61, + 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x1f, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2b, 0x52, 0x65, 0x64, 0x70, + 0x61, 0x6e, 0x64, 0x61, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x22, 0x52, 0x65, 0x64, 0x70, 0x61, 0x6e, + 0x64, 0x61, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6c, 0x61, + 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescOnce sync.Once + file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescData = file_redpanda_api_dataplane_v1alpha1_user_proto_rawDesc +) + +func file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescGZIP() []byte { + file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescOnce.Do(func() { + file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescData = protoimpl.X.CompressGZIP(file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescData) + }) + return file_redpanda_api_dataplane_v1alpha1_user_proto_rawDescData +} + +var file_redpanda_api_dataplane_v1alpha1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_redpanda_api_dataplane_v1alpha1_user_proto_goTypes = []interface{}{ + (SASLMechanism)(0), // 0: redpanda.api.dataplane.v1alpha1.SASLMechanism + (*ListUsersRequest)(nil), // 1: redpanda.api.dataplane.v1alpha1.ListUsersRequest + (*ListUsersResponse)(nil), // 2: redpanda.api.dataplane.v1alpha1.ListUsersResponse + (*CreateUserRequest)(nil), // 3: redpanda.api.dataplane.v1alpha1.CreateUserRequest + (*CreateUserResponse)(nil), // 4: redpanda.api.dataplane.v1alpha1.CreateUserResponse + (*UpdateUserRequest)(nil), // 5: redpanda.api.dataplane.v1alpha1.UpdateUserRequest + (*UpdateUserResponse)(nil), // 6: redpanda.api.dataplane.v1alpha1.UpdateUserResponse + (*DeleteUserRequest)(nil), // 7: redpanda.api.dataplane.v1alpha1.DeleteUserRequest + (*DeleteUserResponse)(nil), // 8: redpanda.api.dataplane.v1alpha1.DeleteUserResponse + (*ListUsersRequest_Filter)(nil), // 9: redpanda.api.dataplane.v1alpha1.ListUsersRequest.Filter + (*ListUsersResponse_User)(nil), // 10: redpanda.api.dataplane.v1alpha1.ListUsersResponse.User + (*CreateUserRequest_User)(nil), // 11: redpanda.api.dataplane.v1alpha1.CreateUserRequest.User + (*CreateUserResponse_User)(nil), // 12: redpanda.api.dataplane.v1alpha1.CreateUserResponse.User + (*UpdateUserRequest_User)(nil), // 13: redpanda.api.dataplane.v1alpha1.UpdateUserRequest.User + (*UpdateUserResponse_User)(nil), // 14: redpanda.api.dataplane.v1alpha1.UpdateUserResponse.User +} +var file_redpanda_api_dataplane_v1alpha1_user_proto_depIdxs = []int32{ + 9, // 0: redpanda.api.dataplane.v1alpha1.ListUsersRequest.filter:type_name -> redpanda.api.dataplane.v1alpha1.ListUsersRequest.Filter + 10, // 1: redpanda.api.dataplane.v1alpha1.ListUsersResponse.users:type_name -> redpanda.api.dataplane.v1alpha1.ListUsersResponse.User + 11, // 2: redpanda.api.dataplane.v1alpha1.CreateUserRequest.user:type_name -> redpanda.api.dataplane.v1alpha1.CreateUserRequest.User + 12, // 3: redpanda.api.dataplane.v1alpha1.CreateUserResponse.user:type_name -> redpanda.api.dataplane.v1alpha1.CreateUserResponse.User + 13, // 4: redpanda.api.dataplane.v1alpha1.UpdateUserRequest.user:type_name -> redpanda.api.dataplane.v1alpha1.UpdateUserRequest.User + 14, // 5: redpanda.api.dataplane.v1alpha1.UpdateUserResponse.user:type_name -> redpanda.api.dataplane.v1alpha1.UpdateUserResponse.User + 0, // 6: redpanda.api.dataplane.v1alpha1.ListUsersResponse.User.mechanism:type_name -> redpanda.api.dataplane.v1alpha1.SASLMechanism + 0, // 7: redpanda.api.dataplane.v1alpha1.CreateUserRequest.User.mechanism:type_name -> redpanda.api.dataplane.v1alpha1.SASLMechanism + 0, // 8: redpanda.api.dataplane.v1alpha1.CreateUserResponse.User.mechanism:type_name -> redpanda.api.dataplane.v1alpha1.SASLMechanism + 0, // 9: redpanda.api.dataplane.v1alpha1.UpdateUserRequest.User.mechanism:type_name -> redpanda.api.dataplane.v1alpha1.SASLMechanism + 0, // 10: redpanda.api.dataplane.v1alpha1.UpdateUserResponse.User.mechanism:type_name -> redpanda.api.dataplane.v1alpha1.SASLMechanism + 3, // 11: redpanda.api.dataplane.v1alpha1.UserService.CreateUser:input_type -> redpanda.api.dataplane.v1alpha1.CreateUserRequest + 5, // 12: redpanda.api.dataplane.v1alpha1.UserService.UpdateUser:input_type -> redpanda.api.dataplane.v1alpha1.UpdateUserRequest + 1, // 13: redpanda.api.dataplane.v1alpha1.UserService.ListUsers:input_type -> redpanda.api.dataplane.v1alpha1.ListUsersRequest + 7, // 14: redpanda.api.dataplane.v1alpha1.UserService.DeleteUser:input_type -> redpanda.api.dataplane.v1alpha1.DeleteUserRequest + 4, // 15: redpanda.api.dataplane.v1alpha1.UserService.CreateUser:output_type -> redpanda.api.dataplane.v1alpha1.CreateUserResponse + 6, // 16: redpanda.api.dataplane.v1alpha1.UserService.UpdateUser:output_type -> redpanda.api.dataplane.v1alpha1.UpdateUserResponse + 2, // 17: redpanda.api.dataplane.v1alpha1.UserService.ListUsers:output_type -> redpanda.api.dataplane.v1alpha1.ListUsersResponse + 8, // 18: redpanda.api.dataplane.v1alpha1.UserService.DeleteUser:output_type -> redpanda.api.dataplane.v1alpha1.DeleteUserResponse + 15, // [15:19] is the sub-list for method output_type + 11, // [11:15] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_redpanda_api_dataplane_v1alpha1_user_proto_init() } +func file_redpanda_api_dataplane_v1alpha1_user_proto_init() { + if File_redpanda_api_dataplane_v1alpha1_user_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUsersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUsersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateUserRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateUserResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteUserRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteUserResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUsersRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUsersResponse_User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateUserRequest_User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateUserResponse_User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserRequest_User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserResponse_User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[9].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[11].OneofWrappers = []interface{}{} + file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes[13].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_redpanda_api_dataplane_v1alpha1_user_proto_rawDesc, + NumEnums: 1, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_redpanda_api_dataplane_v1alpha1_user_proto_goTypes, + DependencyIndexes: file_redpanda_api_dataplane_v1alpha1_user_proto_depIdxs, + EnumInfos: file_redpanda_api_dataplane_v1alpha1_user_proto_enumTypes, + MessageInfos: file_redpanda_api_dataplane_v1alpha1_user_proto_msgTypes, + }.Build() + File_redpanda_api_dataplane_v1alpha1_user_proto = out.File + file_redpanda_api_dataplane_v1alpha1_user_proto_rawDesc = nil + file_redpanda_api_dataplane_v1alpha1_user_proto_goTypes = nil + file_redpanda_api_dataplane_v1alpha1_user_proto_depIdxs = nil +} diff --git a/src/transform-sdk/go/transform/internal/testdata/CMakeLists.txt b/src/transform-sdk/go/transform/internal/testdata/CMakeLists.txt index fda5e629bfcbb..4c3d5a99f3792 100644 --- a/src/transform-sdk/go/transform/internal/testdata/CMakeLists.txt +++ b/src/transform-sdk/go/transform/internal/testdata/CMakeLists.txt @@ -7,7 +7,7 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter) function(add_wasm_transform NAME) find_program(TINYGO_BIN "tinygo") set(wasm_output "${CMAKE_CURRENT_BINARY_DIR}/${NAME}.wasm") - set(tinygo_cmd ${TINYGO_BIN} build -o ${wasm_output} -quiet -target wasi -scheduler none "${NAME}/transform.go") + set(tinygo_cmd ${TINYGO_BIN} build -o ${wasm_output} -target wasi -scheduler none "${NAME}/transform.go") set(gopath ${CMAKE_CURRENT_BINARY_DIR}/${NAME}) add_custom_command(OUTPUT ${wasm_output} COMMAND Python3::Interpreter ${CMAKE_CURRENT_SOURCE_DIR}/retry.py diff --git a/src/v/archival/ntp_archiver_service.cc b/src/v/archival/ntp_archiver_service.cc index f75b8ad483409..99739d4eebae1 100644 --- a/src/v/archival/ntp_archiver_service.cc +++ b/src/v/archival/ntp_archiver_service.cc @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -225,9 +226,9 @@ ntp_archiver::ntp_archiver( , _next_housekeeping(_housekeeping_jitter()) , _feature_table(parent.feature_table()) , _local_segment_merger( - maybe_make_adjacent_segment_merger(*this, parent.log()->config())) + maybe_make_adjacent_segment_merger(*this, _parent.log()->config())) , _scrubber(maybe_make_scrubber( - *this, _remote, _feature_table.local(), parent.log()->config())) + *this, _remote, _feature_table.local(), _parent.log()->config())) , _manifest_upload_interval( config::shard_local_cfg() .cloud_storage_manifest_max_upload_interval_sec.bind()) @@ -238,6 +239,13 @@ ntp_archiver::ntp_archiver( _next_housekeeping = _housekeeping_jitter(); }); + if (_local_segment_merger) { + _local_segment_merger->set_enabled(false); + } + if (_scrubber) { + _scrubber->set_enabled(false); + } + _start_term = _parent.term(); // Override bucket for read-replica if (_parent.is_read_replica_mode_enabled()) { @@ -248,7 +256,7 @@ ntp_archiver::ntp_archiver( archival_log.debug, "created ntp_archiver {} in term {}", _ntp, - _start_term); + _parent.term()); } const cloud_storage::partition_manifest& ntp_archiver::manifest() const { @@ -260,20 +268,6 @@ const cloud_storage::partition_manifest& ntp_archiver::manifest() const { } ss::future<> ntp_archiver::start() { - // Pre-sync the ntp_archiver to make sure that the adjacent segment merger - // can only see up to date manifest. - auto sync_timeout = config::shard_local_cfg() - .cloud_storage_metadata_sync_timeout_ms.value(); - co_await _parent.archival_meta_stm()->sync(sync_timeout); - - bool is_leader = _parent.is_leader(); - if (_local_segment_merger) { - _local_segment_merger->set_enabled(is_leader); - } - if (_scrubber) { - _scrubber->set_enabled(is_leader); - } - if (_parent.get_ntp_config().is_read_replica_mode_enabled()) { ssx::spawn_with_gate(_gate, [this] { return sync_manifest_until_abort().then([this] { @@ -315,13 +309,6 @@ void ntp_archiver::notify_leadership(std::optional leader_id) { if (is_leader) { _leader_cond.signal(); } - if (_local_segment_merger) { - _local_segment_merger->set_enabled(is_leader); - } - - if (_scrubber) { - _scrubber->set_enabled(is_leader); - } } ss::future<> ntp_archiver::upload_until_abort() { @@ -367,7 +354,36 @@ ss::future<> ntp_archiver::upload_until_abort() { if (!is_synced) { continue; } + vlog(_rtclog.debug, "upload loop synced in term {}", _start_term); + if (!may_begin_uploads()) { + continue; + } + + if (_local_segment_merger) { + vlog( + _rtclog.debug, + "Enable adjacent segment merger in term {}", + _start_term); + _local_segment_merger->set_enabled(true); + } + if (_scrubber) { + vlog(_rtclog.debug, "Enable scrubber in term {}", _start_term); + _scrubber->set_enabled(true); + } + auto disable_hk_jobs = ss::defer([this] { + if (_local_segment_merger) { + vlog( + _rtclog.debug, + "Disable adjacent segment merger in term {}", + _start_term); + _local_segment_merger->set_enabled(false); + } + if (_scrubber) { + vlog(_rtclog.debug, "Disable scrubber in term {}", _start_term); + _scrubber->set_enabled(false); + } + }); co_await ss::with_scheduling_group( _conf->upload_scheduling_group, @@ -501,7 +517,7 @@ ss::future<> ntp_archiver::upload_topic_manifest() { try { retry_chain_node fib( - _conf->manifest_upload_timeout, + _conf->manifest_upload_timeout(), _conf->cloud_storage_initial_backoff, &_rtcnode); retry_chain_logger ctxlog(archival_log, fib); @@ -913,7 +929,7 @@ ss::future< ntp_archiver::download_manifest() { auto guard = _gate.hold(); retry_chain_node fib( - _conf->manifest_upload_timeout, + _conf->manifest_upload_timeout(), _conf->cloud_storage_initial_backoff, &_rtcnode); cloud_storage::partition_manifest tmp(_ntp, _rev); @@ -1067,7 +1083,7 @@ ss::future ntp_archiver::upload_manifest( auto guard = _gate.hold(); auto rtc = source_rtc.value_or(std::ref(_rtcnode)); retry_chain_node fib( - _conf->manifest_upload_timeout, + _conf->manifest_upload_timeout(), _conf->cloud_storage_initial_backoff, &rtc.get()); retry_chain_logger ctxlog(archival_log, fib, _ntp.path()); @@ -1541,7 +1557,7 @@ ntp_archiver::do_schedule_single_upload( ss::future ntp_archiver::schedule_single_upload(const upload_context& upload_ctx) { auto start_upload_offset = upload_ctx.start_offset; - auto last_stable_offset = upload_ctx.last_offset; + auto last_stable_offset = upload_ctx.end_offset_exclusive; auto log = _parent.log(); @@ -1616,7 +1632,7 @@ ntp_archiver::schedule_single_upload(const upload_context& upload_ctx) { } ss::future> -ntp_archiver::schedule_uploads(model::offset last_stable_offset) { +ntp_archiver::schedule_uploads(model::offset max_offset_exclusive) { // We have to increment last offset to guarantee progress. // The manifest's last offset contains dirty_offset of the // latest uploaded segment but '_policy' requires offset that @@ -1640,7 +1656,7 @@ ntp_archiver::schedule_uploads(model::offset last_stable_offset) { params.push_back({ .upload_kind = segment_upload_kind::non_compacted, .start_offset = start_upload_offset, - .last_offset = last_stable_offset, + .end_offset_exclusive = max_offset_exclusive, .allow_reuploads = allow_reuploads_t::no, .archiver_term = _start_term, }); @@ -1651,7 +1667,7 @@ ntp_archiver::schedule_uploads(model::offset last_stable_offset) { params.push_back({ .upload_kind = segment_upload_kind::compacted, .start_offset = compacted_segments_upload_start, - .last_offset = model::offset::max(), + .end_offset_exclusive = model::offset::max(), .allow_reuploads = allow_reuploads_t::yes, .archiver_term = _start_term, }); @@ -1672,7 +1688,7 @@ ntp_archiver::schedule_uploads(std::vector loop_contexts) { "offset: {}, last offset: {}, uploads remaining: {}", ctx.upload_kind, ctx.start_offset, - ctx.last_offset, + ctx.end_offset_exclusive, uploads_remaining); break; } @@ -1682,13 +1698,13 @@ ntp_archiver::schedule_uploads(std::vector loop_contexts) { "scheduling uploads, start offset: {}, last offset: {}, upload kind: " "{}, uploads remaining: {}", ctx.start_offset, - ctx.last_offset, + ctx.end_offset_exclusive, ctx.upload_kind, uploads_remaining); // this metric is only relevant for non compacted uploads. if (ctx.upload_kind == segment_upload_kind::non_compacted) { - _probe->upload_lag(ctx.last_offset - ctx.start_offset); + _probe->upload_lag(ctx.end_offset_exclusive - ctx.start_offset); } std::exception_ptr ep; @@ -1898,7 +1914,7 @@ ss::future ntp_archiver::wait_uploads( _ntp.path()); auto deadline = ss::lowres_clock::now() - + _conf->manifest_upload_timeout; + + _conf->manifest_upload_timeout(); std::optional manifest_clean_offset; if ( @@ -2024,15 +2040,30 @@ ss::future ntp_archiver::wait_all_scheduled_uploads( .compacted_upload_result = compacted_result}; } +model::offset ntp_archiver::max_uploadable_offset_exclusive() const { + // We impose an additional (LSO) constraint on the uploadable offset to + // as we need to have a complete index of aborted transactions if any + // before we can upload a segment. + return std::min( + _parent.last_stable_offset(), + model::next_offset(_parent.committed_offset())); +} + ss::future ntp_archiver::upload_next_candidates( - std::optional lso_override) { - vlog(_rtclog.debug, "Uploading next candidates called for {}", _ntp); - auto last_stable_offset = lso_override ? *lso_override - : _parent.last_stable_offset(); + std::optional unsafe_max_offset_override_exclusive) { + auto max_offset_exclusive = unsafe_max_offset_override_exclusive + ? *unsafe_max_offset_override_exclusive + : max_uploadable_offset_exclusive(); + vlog( + _rtclog.debug, + "Uploading next candidates called for {} with max_offset_exclusive={}", + _ntp, + max_offset_exclusive); ss::gate::holder holder(_gate); try { auto units = co_await ss::get_units(_mutex, 1, _as); - auto scheduled_uploads = co_await schedule_uploads(last_stable_offset); + auto scheduled_uploads = co_await schedule_uploads( + max_offset_exclusive); co_return co_await wait_all_scheduled_uploads( std::move(scheduled_uploads)); } catch (const ss::gate_closed_exception&) { @@ -2072,7 +2103,7 @@ ntp_archiver::maybe_truncate_manifest() { const auto& m = manifest(); for (const auto& meta : m) { retry_chain_node fib( - _conf->manifest_upload_timeout, + _conf->manifest_upload_timeout(), _conf->upload_loop_initial_backoff, &rtc); auto sname = cloud_storage::generate_local_segment_name( @@ -2101,12 +2132,12 @@ ntp_archiver::maybe_truncate_manifest() { "manifest, start offset before cleanup: {}", manifest().get_start_offset()); retry_chain_node rc_node( - _conf->manifest_upload_timeout, + _conf->manifest_upload_timeout(), _conf->upload_loop_initial_backoff, &rtc); auto error = co_await _parent.archival_meta_stm()->truncate( adjusted_start_offset, - ss::lowres_clock::now() + _conf->manifest_upload_timeout, + ss::lowres_clock::now() + _conf->manifest_upload_timeout(), _as); if (error != cluster::errc::success) { vlog( @@ -2187,6 +2218,10 @@ ss::future<> ntp_archiver::apply_archive_retention() { } const auto& ntp_conf = _parent.get_ntp_config(); + if (!ntp_conf.is_collectable()) { + vlog(_rtclog.trace, "NTP is not collectable"); + co_return; + } std::optional retention_bytes = ntp_conf.retention_bytes(); std::optional retention_ms = ntp_conf.retention_duration(); @@ -3005,7 +3040,7 @@ ss::future ntp_archiver::do_upload_local( features::feature::cloud_metadata_cluster_recovery) ? _parent.highest_producer_id() : model::producer_id{}; - auto deadline = ss::lowres_clock::now() + _conf->manifest_upload_timeout; + auto deadline = ss::lowres_clock::now() + _conf->manifest_upload_timeout(); auto error = co_await _parent.archival_meta_stm()->add_segments( {meta}, std::nullopt, diff --git a/src/v/archival/ntp_archiver_service.h b/src/v/archival/ntp_archiver_service.h index f9e569fff45e1..f1a6e4bb5eece 100644 --- a/src/v/archival/ntp_archiver_service.h +++ b/src/v/archival/ntp_archiver_service.h @@ -178,15 +178,27 @@ class ntp_archiver { auto operator<=>(const batch_result&) const = default; }; + /// Compute the maximum offset that is safe to be uploaded to the cloud. + /// + /// It must be guaranteed that this offset is monotonically increasing/ + /// can never go backwards. Otherwise, the local and cloud logs will + /// diverge leading to undefined behavior. + model::offset max_uploadable_offset_exclusive() const; + /// \brief Upload next set of segments to S3 (if any) /// The semaphore is used to track number of parallel uploads. The method /// will pick not more than '_concurrency' candidates and start /// uploading them. /// - /// \param lso_override last stable offset override + /// \param unsafe_max_offset_override_exclusive Overrides the maximum offset + /// that can be uploaded. ONLY FOR TESTING. It is not clamped to a + /// safe value/committed offset as some tests work directly with + /// segments bypassing the raft thus not advancing the committed + /// offset. /// \return future that returns number of uploaded/failed segments virtual ss::future upload_next_candidates( - std::optional last_stable_offset_override = std::nullopt); + std::optional unsafe_max_offset_override_exclusive + = std::nullopt); ss::future sync_manifest(); @@ -410,7 +422,7 @@ class ntp_archiver { /// The next scheduled upload will start from this offset model::offset start_offset; /// Uploads will stop at this offset - model::offset last_offset; + model::offset end_offset_exclusive; /// Controls checks for reuploads, compacted segments have this /// check disabled allow_reuploads_t allow_reuploads; @@ -427,7 +439,7 @@ class ntp_archiver { /// Start all uploads ss::future> - schedule_uploads(model::offset last_stable_offset); + schedule_uploads(model::offset max_offset_exclusive); ss::future> schedule_uploads(std::vector loop_contexts); diff --git a/src/v/archival/purger.cc b/src/v/archival/purger.cc index baded574ee91e..4ffbf38ea6b05 100644 --- a/src/v/archival/purger.cc +++ b/src/v/archival/purger.cc @@ -230,7 +230,24 @@ purger::collect_manifest_paths( continue; } - collected.spillover.push_back(std::move(item.key)); + // The spillover manifest path is of the form + // "{prefix}/{manifest.bin().x.x.x.x.x.x}" Find the index of the last + // '/' in the path, so we can check just the filename (starting from the + // first character after '/'). + const size_t filename_idx = path.rfind('/'); + if (filename_idx == std::string_view::npos) { + continue; + } + + // File should start with "manifest.bin()", but it should have + // additional spillover components as well. + std::string_view file = path.substr(filename_idx + 1); + static const ss::sstring partition_manifest_filename = "manifest.bin"; + if ( + file.starts_with(partition_manifest_filename) + && !file.ends_with(partition_manifest_filename)) { + collected.spillover.push_back(std::move(item.key)); + } } co_return collected; diff --git a/src/v/archival/tests/archival_metadata_stm_gtest.cc b/src/v/archival/tests/archival_metadata_stm_gtest.cc index 6c3b45773394c..56f1605629367 100644 --- a/src/v/archival/tests/archival_metadata_stm_gtest.cc +++ b/src/v/archival/tests/archival_metadata_stm_gtest.cc @@ -215,17 +215,17 @@ TEST_F_CORO( 10s, [&reached_dispatch_append, &may_resume_append](raft::raft_node_instance& node) { - node.on_dispatch( - [&reached_dispatch_append, &may_resume_append](raft::msg_type t) { - if (t == raft::msg_type::append_entries) { - if (!reached_dispatch_append.available()) { - reached_dispatch_append.set_value(true); - } - return may_resume_append.get_shared_future(); - } - - return ss::now(); - }); + node.on_dispatch([&reached_dispatch_append, &may_resume_append]( + model::node_id, raft::msg_type t) { + if (t == raft::msg_type::append_entries) { + if (!reached_dispatch_append.available()) { + reached_dispatch_append.set_value(true); + } + return may_resume_append.get_shared_future(); + } + + return ss::now(); + }); return node.get_vnode(); }); diff --git a/src/v/archival/tests/service_fixture.cc b/src/v/archival/tests/service_fixture.cc index 9b54f2ff2421b..331d1a444a0c2 100644 --- a/src/v/archival/tests/service_fixture.cc +++ b/src/v/archival/tests/service_fixture.cc @@ -186,13 +186,14 @@ archiver_fixture::get_configurations() { cloud_storage_clients::endpoint_url{}); s3conf.server_addr = server_addr; - archival::configuration aconf; + archival::configuration aconf{ + .manifest_upload_timeout = config::mock_binding(1000ms), + }; aconf.bucket_name = cloud_storage_clients::bucket_name("test-bucket"); aconf.ntp_metrics_disabled = archival::per_ntp_metrics_disabled::yes; aconf.svc_metrics_disabled = archival::service_metrics_disabled::yes; aconf.cloud_storage_initial_backoff = 100ms; aconf.segment_upload_timeout = 1s; - aconf.manifest_upload_timeout = 1s; aconf.garbage_collect_timeout = 1s; aconf.upload_loop_initial_backoff = 100ms; aconf.upload_loop_max_backoff = 5s; diff --git a/src/v/archival/types.cc b/src/v/archival/types.cc index b822fc16e07da..106afcde30665 100644 --- a/src/v/archival/types.cc +++ b/src/v/archival/types.cc @@ -45,7 +45,7 @@ std::ostream& operator<<(std::ostream& o, const configuration& cfg) { std::chrono::duration_cast( cfg.segment_upload_timeout), std::chrono::duration_cast( - cfg.manifest_upload_timeout), + cfg.manifest_upload_timeout()), cfg.time_limit); return o; } @@ -97,7 +97,7 @@ get_archival_service_config(ss::scheduling_group sg, ss::io_priority_class p) { .cloud_storage_segment_upload_timeout_ms.value(), .manifest_upload_timeout = config::shard_local_cfg() - .cloud_storage_manifest_upload_timeout_ms.value(), + .cloud_storage_manifest_upload_timeout_ms.bind(), .garbage_collect_timeout = config::shard_local_cfg() .cloud_storage_garbage_collect_timeout_ms.value(), diff --git a/src/v/archival/types.h b/src/v/archival/types.h index 41001931b6acb..7c8288e42f269 100644 --- a/src/v/archival/types.h +++ b/src/v/archival/types.h @@ -48,7 +48,7 @@ struct configuration { /// Long upload timeout ss::lowres_clock::duration segment_upload_timeout; /// Shor upload timeout - ss::lowres_clock::duration manifest_upload_timeout; + config::binding manifest_upload_timeout; /// Timeout for running delete operations during the GC phase ss::lowres_clock::duration garbage_collect_timeout; /// Initial backoff for upload loop in case there is nothing to upload diff --git a/src/v/bytes/iobuf.h b/src/v/bytes/iobuf.h index 567e0365cb155..dc004fdbb4ab8 100644 --- a/src/v/bytes/iobuf.h +++ b/src/v/bytes/iobuf.h @@ -76,6 +76,12 @@ class iobuf { using byte_iterator = details::io_byte_iterator; using placeholder = details::io_placeholder; + static iobuf from(std::string_view view) { + iobuf i; + i.append(view.data(), view.size()); + return i; + } + // NOLINTNEXTLINE iobuf() noexcept { // nothing allocates memory, but boost intrusive list is not marked as diff --git a/src/v/bytes/streambuf.h b/src/v/bytes/streambuf.h index 31be5e11669bb..eecfb5431fcfd 100644 --- a/src/v/bytes/streambuf.h +++ b/src/v/bytes/streambuf.h @@ -91,3 +91,43 @@ class iobuf_ostreambuf final : public std::streambuf { private: iobuf* _buf; }; + +///\brief Wrap a std::istream around an iobuf +/// +/// iobuf buf; +/// iobuf_istream is(std::move(buf)); +/// std::string out; +/// is.istream() >> out; +class iobuf_istream { +public: + explicit iobuf_istream(iobuf buf) + : _buf(std::move(buf)) + , _isb(_buf) + , _sis{&_isb} {} + std::istream& istream() { return _sis; } + +private: + iobuf _buf; + iobuf_istreambuf _isb; + std::istream _sis; +}; + +///\brief Wrap a std::ostream around an iobuf +/// +/// iobuf_ostream os; +/// os.ostream() << "Hello World"; +/// iobuf buf = std::move(os).buf(); +class iobuf_ostream { +public: + iobuf_ostream() + : _buf() + , _osb(_buf) + , _sos{&_osb} {} + std::ostream& ostream() { return _sos; } + iobuf buf() && { return std::move(_buf); } + +private: + iobuf _buf; + iobuf_ostreambuf _osb; + std::ostream _sos; +}; diff --git a/src/v/cloud_storage/async_manifest_view.cc b/src/v/cloud_storage/async_manifest_view.cc index dc8adb58fa7b2..46f72f2612b13 100644 --- a/src/v/cloud_storage/async_manifest_view.cc +++ b/src/v/cloud_storage/async_manifest_view.cc @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -62,7 +63,9 @@ static ss::sstring to_string(const async_view_search_query_t& t) { t, [&](model::offset ro) { return ssx::sformat("[offset: {}]", ro); }, [&](kafka::offset ko) { return ssx::sformat("[kafka offset: {}]", ko); }, - [&](model::timestamp ts) { return ssx::sformat("[timestamp: {}]", ts); }); + [&](const async_view_timestamp_query& ts) { + return ssx::sformat("{}", ts); + }); } std::ostream& operator<<(std::ostream& s, const async_view_search_query_t& q) { @@ -82,9 +85,27 @@ contains(const partition_manifest& m, const async_view_search_query_t& query) { return k >= m.get_start_kafka_offset() && k < m.get_next_kafka_offset(); }, - [&](model::timestamp t) { - return m.size() > 0 && t >= m.begin()->base_timestamp - && t <= m.last_segment()->max_timestamp; + [&](const async_view_timestamp_query& ts_query) { + if (m.size() == 0) { + return false; + } + + auto kafka_start_offset = m.get_start_kafka_offset(); + if (!kafka_start_offset.has_value()) { + return false; + } + + auto kafka_last_offset = m.get_last_kafka_offset(); + if (!kafka_last_offset.has_value()) { + return false; + } + + auto range_overlaps = ts_query.min_offset <= kafka_last_offset.value() + && ts_query.max_offset + >= kafka_start_offset.value(); + + return range_overlaps && ts_query.ts >= m.begin()->base_timestamp + && ts_query.ts <= m.last_segment()->max_timestamp; }); } @@ -560,18 +581,11 @@ ss::future<> async_manifest_view::run_bg_loop() { ss::future, error_outcome>> async_manifest_view::get_cursor( async_view_search_query_t query, - std::optional end_inclusive) noexcept { + std::optional end_inclusive, + cursor_base_t cursor_base) noexcept { try { ss::gate::holder h(_gate); - if ( - !in_archive(query) && !in_stm(query) - && !std::holds_alternative(query)) { - // The view should contain manifest below archive start in - // order to be able to perform retention and advance metadata. - vlog( - _ctxlog.debug, - "query {} is out of valid range", - to_string(query)); + if (!in_archive(query) && !in_stm(query)) { co_return error_outcome::out_of_range; } model::offset begin; @@ -580,7 +594,14 @@ async_manifest_view::get_cursor( if (_stm_manifest.get_archive_start_offset() == model::offset{}) { begin = _stm_manifest.get_start_offset().value_or(begin); } else { - begin = _stm_manifest.get_archive_clean_offset(); + switch (cursor_base) { + case cursor_base_t::archive_start_offset: + begin = _stm_manifest.get_archive_start_offset(); + break; + case cursor_base_t::archive_clean_offset: + begin = _stm_manifest.get_archive_clean_offset(); + break; + } } if (end < begin) { @@ -843,7 +864,23 @@ bool async_manifest_view::in_archive(async_view_search_query_t o) { && ko < _stm_manifest.get_start_kafka_offset().value_or( kafka::offset::min()); }, - [this](model::timestamp ts) { + [this](async_view_timestamp_query ts_query) { + // For a query to be satisfiable by the archive the min offset must be + // in the archive. The same condition can be stated as: min offset + // must be before the start of the STM manifest. + // + // Otherwise, even though the last timestamp in the archive could + // satisfy the query, it can't be used because offset-wise it is + // outside of the queried range. + kafka::offset archive_end_offset = kafka::prev_offset( + _stm_manifest.get_start_kafka_offset().value_or( + kafka::offset::min())); + + bool range_overlaps + = ts_query.min_offset <= archive_end_offset + && ts_query.max_offset + >= _stm_manifest.get_archive_start_kafka_offset(); + // The condition for timequery is tricky. With offsets there is a // clear pivot point. The start_offset of the STM manifest separates // the STM region from the archive. With timestamps it's not as @@ -851,8 +888,11 @@ bool async_manifest_view::in_archive(async_view_search_query_t o) { // and the first segment in the STM manifest. We need in_stm and // in_archive to be consistent with each other. To do this we can use // last timestamp in the archive as a pivot point. - return _stm_manifest.get_spillover_map().last_segment()->max_timestamp - >= ts; + return range_overlaps + && _stm_manifest.get_spillover_map() + .last_segment() + ->max_timestamp + >= ts_query.ts; }); } @@ -869,20 +909,32 @@ bool async_manifest_view::in_stm(async_view_search_query_t o) { kafka::offset::max()); return ko >= sko; }, - [this](model::timestamp ts) { - vlog(_ctxlog.debug, "Checking timestamp {} using timequery", ts); + [this](async_view_timestamp_query ts_query) { + vlog( + _ctxlog.debug, "Checking timestamp {} using timequery", ts_query); if (_stm_manifest.get_spillover_map().empty()) { - // The STM manifest is empty, so the timestamp has to be directed - // to the STM manifest. - // Implicitly, this case also handles the empty manifest case - // because the STM manifest with spillover segments is never - // empty. + // The spillover manifest is empty, so the timestamp query has to + // be directed to the STM manifest. Otherwise, we can safely + // direct the query either to spillover or stm because the + // STM manifest with spillover segments is never empty. return true; } + + bool range_overlaps + = ts_query.min_offset + <= _stm_manifest.get_last_kafka_offset().value_or( + kafka::offset::min()) + && ts_query.max_offset + >= _stm_manifest.get_start_kafka_offset().value_or( + kafka::offset::max()); + // The last timestamp in the archive is used as a pivot point. See // description in in_archive. - return _stm_manifest.get_spillover_map().last_segment()->max_timestamp - < ts; + return range_overlaps + && _stm_manifest.get_spillover_map() + .last_segment() + ->max_timestamp + < ts_query.ts; }); } @@ -958,7 +1010,8 @@ async_manifest_view::offset_based_retention() noexcept { archive_start_offset_advance result; try { auto boundary = _stm_manifest.get_start_kafka_offset_override(); - auto res = co_await get_cursor(boundary); + auto res = co_await get_cursor( + boundary, std::nullopt, cursor_base_t::archive_clean_offset); if (res.has_failure()) { if (res.error() == error_outcome::out_of_range) { vlog( @@ -1022,7 +1075,8 @@ async_manifest_view::time_based_retention( auto res = co_await get_cursor( _stm_manifest.get_archive_start_offset(), - model::prev_offset(_stm_manifest.get_start_offset().value())); + model::prev_offset(_stm_manifest.get_start_offset().value()), + cursor_base_t::archive_clean_offset); if (res.has_failure()) { if (res.error() == error_outcome::out_of_range) { // The cutoff point is outside of the offset range, no need to @@ -1148,7 +1202,8 @@ async_manifest_view::size_based_retention(size_t size_limit) noexcept { auto res = co_await get_cursor( _stm_manifest.get_archive_clean_offset(), - model::prev_offset(_stm_manifest.get_start_offset().value())); + model::prev_offset(_stm_manifest.get_start_offset().value()), + cursor_base_t::archive_clean_offset); if (res.has_failure()) { vlogl( _ctxlog, @@ -1299,7 +1354,7 @@ async_manifest_view::get_materialized_manifest( } // query in not in the stm region if ( - std::holds_alternative(q) + std::holds_alternative(q) && _stm_manifest.get_archive_start_offset() == model::offset{}) { vlog(_ctxlog.debug, "Using STM manifest for timequery {}", q); co_return std::ref(_stm_manifest); @@ -1459,7 +1514,7 @@ std::optional async_manifest_view::search_spillover_manifests( } return -1; }, - [&](model::timestamp t) { + [&](const async_view_timestamp_query& ts_query) { if (manifests.empty()) { return -1; } @@ -1469,35 +1524,56 @@ std::optional async_manifest_view::search_spillover_manifests( "{}, last: {}", query, manifests.size(), - manifests.begin()->base_timestamp, - manifests.last_segment()->max_timestamp); + *manifests.begin(), + *manifests.last_segment()); - auto first_manifest = manifests.begin(); - auto base_t = first_manifest->base_timestamp; auto max_t = manifests.last_segment()->max_timestamp; // Edge cases - if (t < base_t || max_t == base_t) { - return 0; - } else if (t > max_t) { + if (ts_query.ts > max_t) { return -1; } + const auto& bo_col = manifests.get_base_offset_column(); + const auto& co_col = manifests.get_committed_offset_column(); + const auto& do_col = manifests.get_delta_offset_column(); + const auto& de_col = manifests.get_delta_offset_end_column(); const auto& bt_col = manifests.get_base_timestamp_column(); const auto& mt_col = manifests.get_max_timestamp_column(); - auto mt_it = mt_col.begin(); - auto bt_it = bt_col.begin(); + + auto bo_it = bo_col.begin(); + auto co_it = co_col.begin(); + auto do_it = do_col.begin(); + auto de_it = de_col.begin(); + auto max_ts_it = mt_col.begin(); + auto base_ts_it = bt_col.begin(); + int target_ix = -1; - while (!bt_it.is_end()) { - if (*mt_it >= t.value() || *bt_it > t.value()) { + while (!base_ts_it.is_end()) { + static constexpr int64_t min_delta = model::offset::min()(); + auto d_begin = *do_it == min_delta ? 0 : *do_it; + auto d_end = *de_it == min_delta ? d_begin : *de_it; + auto bko = kafka::offset(*bo_it - d_begin); + auto cko = kafka::offset(*co_it - d_end); + + auto range_overlaps = ts_query.min_offset <= cko + && ts_query.max_offset >= bko; + + if ( + range_overlaps + && (*max_ts_it >= ts_query.ts() || *base_ts_it > ts_query.ts())) { // Handle case when we're overshooting the target // (base_timestamp > t) or the case when the target is in the // middle of the manifest (max_timestamp >= t) - target_ix = static_cast(bt_it.index()); + target_ix = static_cast(base_ts_it.index()); break; } - ++bt_it; - ++mt_it; + ++bo_it; + ++co_it; + ++do_it; + ++de_it; + ++base_ts_it; + ++max_ts_it; } return target_ix; }); diff --git a/src/v/cloud_storage/async_manifest_view.h b/src/v/cloud_storage/async_manifest_view.h index 04ba1fdada0c8..8922119cb1e5d 100644 --- a/src/v/cloud_storage/async_manifest_view.h +++ b/src/v/cloud_storage/async_manifest_view.h @@ -37,9 +37,32 @@ namespace cloud_storage { +struct async_view_timestamp_query { + async_view_timestamp_query( + kafka::offset min_offset, model::timestamp ts, kafka::offset max_offset) + : min_offset(min_offset) + , ts(ts) + , max_offset(max_offset) {} + + friend std::ostream& + operator<<(std::ostream& o, const async_view_timestamp_query& q) { + fmt::print( + o, + "async_view_timestamp_query{{min_offset:{}, ts:{}, max_offset:{}}}", + q.min_offset, + q.ts, + q.max_offset); + return o; + } + + kafka::offset min_offset; + model::timestamp ts; + kafka::offset max_offset; +}; + /// Search query type using async_view_search_query_t - = std::variant; + = std::variant; std::ostream& operator<<(std::ostream&, const async_view_search_query_t&); @@ -80,6 +103,17 @@ class async_manifest_view { ss::future<> start(); ss::future<> stop(); + enum class cursor_base_t { + archive_start_offset, + + /// Special case that is used when computing retention. + /// + /// For details, see: + /// GitHub: https://github.com/redpanda-data/redpanda/pull/12177 + /// Commit: 1b6ab7be8818e3878a32f9037694ae5c4cf4fea2 + archive_clean_offset, + }; + /// Get active spillover manifests asynchronously /// /// \note the method may hydrate manifests in the cache or @@ -89,7 +123,8 @@ class async_manifest_view { result, error_outcome>> get_cursor( async_view_search_query_t q, - std::optional end_inclusive = std::nullopt) noexcept; + std::optional end_inclusive = std::nullopt, + cursor_base_t cursor_base = cursor_base_t::archive_start_offset) noexcept; /// Get inactive spillover manifests which are waiting for /// retention diff --git a/src/v/cloud_storage/cache_probe.cc b/src/v/cloud_storage/cache_probe.cc index a1e7915ec5d80..5bd06b72fb7c2 100644 --- a/src/v/cloud_storage/cache_probe.cc +++ b/src/v/cloud_storage/cache_probe.cc @@ -102,6 +102,11 @@ cache_probe::cache_probe() { "Number of times we couldn't free enough space with a fast " "trim and had to fall back to a slower exhaustive trim.")) .aggregate(aggregate_labels), + sm::make_counter( + "carryover_trims", + [this] { return _carryover_trims; }, + sm::description("Number of times we invoked carryover trim.")) + .aggregate(aggregate_labels), sm::make_counter( "failed_trims", [this] { return _failed_trims; }, diff --git a/src/v/cloud_storage/cache_probe.h b/src/v/cloud_storage/cache_probe.h index 3cc46f602e768..791d211b5f031 100644 --- a/src/v/cloud_storage/cache_probe.h +++ b/src/v/cloud_storage/cache_probe.h @@ -39,6 +39,7 @@ class cache_probe { void fast_trim() { ++_fast_trims; } void exhaustive_trim() { ++_exhaustive_trims; } + void carryover_trim() { ++_carryover_trims; } void failed_trim() { ++_failed_trims; } private: @@ -55,6 +56,7 @@ class cache_probe { int64_t _fast_trims{0}; int64_t _exhaustive_trims{0}; + int64_t _carryover_trims{0}; int64_t _failed_trims{0}; metrics::internal_metric_groups _metrics; diff --git a/src/v/cloud_storage/cache_service.cc b/src/v/cloud_storage/cache_service.cc index 0f4d4a25071b8..d6c34ca2ed916 100644 --- a/src/v/cloud_storage/cache_service.cc +++ b/src/v/cloud_storage/cache_service.cc @@ -11,9 +11,13 @@ #include "bytes/iostream.h" #include "cloud_storage/access_time_tracker.h" #include "cloud_storage/logger.h" +#include "cloud_storage/recursive_directory_walker.h" +#include "config/configuration.h" #include "seastar/util/file.hh" #include "ssx/future-util.h" +#include "ssx/sformat.h" #include "storage/segment.h" +#include "utils/human.h" #include "vassert.h" #include "vlog.h" @@ -31,6 +35,7 @@ #include #include #include +#include #include #include @@ -71,7 +76,8 @@ cache::cache( config::binding disk_reservation, config::binding max_bytes_cfg, config::binding> max_percent, - config::binding max_objects) noexcept + config::binding max_objects, + config::binding walk_concurrency) noexcept : _cache_dir(std::move(cache_dir)) , _disk_size(disk_size) , _disk_reservation(std::move(disk_reservation)) @@ -79,6 +85,7 @@ cache::cache( , _max_percent(std::move(max_percent)) , _max_bytes(_max_bytes_cfg()) , _max_objects(std::move(max_objects)) + , _walk_concurrency(std::move(walk_concurrency)) , _cnt(0) , _total_cleaned(0) { if (ss::this_shard_id() == ss::shard_id{0}) { @@ -133,10 +140,7 @@ void cache::update_max_bytes() { _max_percent()); if (_current_cache_size > _max_bytes) { - ssx::spawn_with_gate(_gate, [this]() { - return ss::with_semaphore( - _cleanup_sm, 1, [this]() { return trim_throttled(); }); - }); + ssx::spawn_with_gate(_gate, [this]() { return trim_throttled(); }); } } @@ -187,7 +191,8 @@ uint64_t cache::get_total_cleaned() { return _total_cleaned; } ss::future<> cache::clean_up_at_start() { auto guard = _gate.hold(); auto [walked_size, filtered_out_files, candidates_for_deletion, empty_dirs] - = co_await _walker.walk(_cache_dir.native(), _access_time_tracker); + = co_await _walker.walk( + _cache_dir.native(), _access_time_tracker, _walk_concurrency()); vassert( filtered_out_files == 0, @@ -208,7 +213,7 @@ ss::future<> cache::clean_up_at_start() { try { co_await delete_file_and_empty_parents(filepath_to_remove); deleted_bytes += file_item.size; - deleted_bytes++; + deleted_count++; } catch (std::exception& e) { vlog( cst_log.error, @@ -262,7 +267,9 @@ std::optional cache::get_trim_delay() const { } } -ss::future<> cache::trim_throttled() { +ss::future<> cache::trim_throttled_unlocked( + std::optional size_limit_override, + std::optional object_limit_override) { // If we trimmed very recently then do not do it immediately: // this reduces load and improves chance of currently promoted // segments finishing their read work before we demote their @@ -278,7 +285,15 @@ ss::future<> cache::trim_throttled() { co_await ss::sleep_abortable(*trim_delay, _as); } - co_await trim(); + co_await trim(size_limit_override, object_limit_override); +} + +ss::future<> cache::trim_throttled( + std::optional size_limit_override, + std::optional object_limit_override) { + auto units = co_await ss::get_units(_cleanup_sm, 1); + co_await trim_throttled_unlocked( + size_limit_override, object_limit_override); } ss::future<> cache::trim_manually( @@ -286,6 +301,12 @@ ss::future<> cache::trim_manually( std::optional object_limit_override) { vassert(ss::this_shard_id() == 0, "Method can only be invoked on shard 0"); auto units = co_await ss::get_units(_cleanup_sm, 1); + vlog( + cst_log.info, + "Beginning manual trim, requested bytes limit: {}, requested object " + "limit: {}", + size_limit_override, + object_limit_override); co_return co_await trim(size_limit_override, object_limit_override); } @@ -296,7 +317,10 @@ ss::future<> cache::trim( auto guard = _gate.hold(); auto [walked_cache_size, filtered_out_files, candidates_for_deletion, _] = co_await _walker.walk( - _cache_dir.native(), _access_time_tracker, [](std::string_view path) { + _cache_dir.native(), + _access_time_tracker, + _walk_concurrency(), + [](std::string_view path) { return !( std::string_view(path).ends_with(".tx") || std::string_view(path).ends_with(".index")); @@ -529,6 +553,99 @@ ss::future<> cache::trim( _last_trim_failed = false; } +ss::future +cache::remove_segment_full(const file_list_item& file_stat) { + trim_result result; + try { + uint64_t this_segment_deleted_bytes{0}; + + auto deleted_parents = co_await delete_file_and_empty_parents( + file_stat.path); + result.deleted_size += file_stat.size; + this_segment_deleted_bytes += file_stat.size; + _current_cache_size -= file_stat.size; + _current_cache_objects -= 1; + result.deleted_count += 1; + + // Determine whether we should delete indices along with the + // object we have just deleted + std::optional tx_file; + std::optional index_file; + + if (RE2::FullMatch(file_stat.path.data(), segment_expr)) { + // If this was a legacy whole-segment item, delete the index + // and tx file along with the segment + tx_file = fmt::format("{}.tx", file_stat.path); + index_file = fmt::format("{}.index", file_stat.path); + } else if (deleted_parents) { + auto immediate_parent = std::string( + std::filesystem::path(file_stat.path).parent_path()); + static constexpr std::string_view chunks_suffix{"_chunks"}; + if (immediate_parent.ends_with(chunks_suffix)) { + // We just deleted the last chunk from a _chunks segment + // directory. We may delete the index + tx state for + // that segment. + auto base_segment_path = immediate_parent.substr( + 0, immediate_parent.size() - chunks_suffix.size()); + tx_file = fmt::format("{}.tx", base_segment_path); + index_file = fmt::format("{}.index", base_segment_path); + } + } + + if (tx_file.has_value()) { + try { + auto sz = co_await ss::file_size(tx_file.value()); + co_await ss::remove_file(tx_file.value()); + result.deleted_size += sz; + this_segment_deleted_bytes += sz; + result.deleted_count += 1; + _current_cache_size -= sz; + _current_cache_objects -= 1; + } catch (std::filesystem::filesystem_error& e) { + if (e.code() != std::errc::no_such_file_or_directory) { + throw; + } + } + } + + if (index_file.has_value()) { + try { + auto sz = co_await ss::file_size(index_file.value()); + co_await ss::remove_file(index_file.value()); + result.deleted_size += sz; + this_segment_deleted_bytes += sz; + result.deleted_count += 1; + _current_cache_size -= sz; + _current_cache_objects -= 1; + } catch (std::filesystem::filesystem_error& e) { + if (e.code() != std::errc::no_such_file_or_directory) { + throw; + } + } + } + + // Remove key if possible to make sure there is no resource + // leak + _access_time_tracker.remove_timestamp(std::string_view(file_stat.path)); + + vlog( + cst_log.trace, + "trim: reclaimed(fast) {} bytes from {}", + this_segment_deleted_bytes, + file_stat.path); + } catch (const ss::gate_closed_exception&) { + // We are shutting down, stop iterating and propagate + throw; + } catch (const std::exception& e) { + vlog( + cst_log.error, + "trim: couldn't delete {}: {}.", + file_stat.path, + e.what()); + } + co_return result; +} + ss::future cache::trim_fast( const fragmented_vector& candidates, uint64_t size_to_delete, @@ -537,19 +654,17 @@ ss::future cache::trim_fast( trim_result result; - size_t candidate_i = 0; - while ( - candidate_i < candidates.size() - && (result.deleted_size < size_to_delete || result.deleted_count < objects_to_delete)) { - auto& file_stat = candidates[candidate_i++]; + // Reset carryover list + _last_trim_carryover = std::nullopt; + auto need_to_skip = [this](const file_list_item& file_stat) { if (is_trim_exempt(file_stat.path)) { - continue; + return true; } // skip tmp files since someone may be writing to it if (std::string_view(file_stat.path).ends_with(tmp_extension)) { - continue; + return true; } // Doesn't make sense to demote these independent of the segment @@ -558,97 +673,46 @@ ss::future cache::trim_fast( if ( std::string_view(file_stat.path).ends_with(".tx") || std::string_view(file_stat.path).ends_with(".index")) { - continue; + return true; } + return false; + }; - try { - uint64_t this_segment_deleted_bytes{0}; - - auto deleted_parents = co_await delete_file_and_empty_parents( - file_stat.path); - result.deleted_size += file_stat.size; - this_segment_deleted_bytes += file_stat.size; - _current_cache_size -= file_stat.size; - _current_cache_objects -= 1; - result.deleted_count += 1; - - // Determine whether we should delete indices along with the - // object we have just deleted - std::optional tx_file; - std::optional index_file; - - if (RE2::FullMatch(file_stat.path.data(), segment_expr)) { - // If this was a legacy whole-segment item, delete the index - // and tx file along with the segment - tx_file = fmt::format("{}.tx", file_stat.path); - index_file = fmt::format("{}.index", file_stat.path); - } else if (deleted_parents) { - auto immediate_parent = std::string( - std::filesystem::path(file_stat.path).parent_path()); - static constexpr std::string_view chunks_suffix{"_chunks"}; - if (immediate_parent.ends_with(chunks_suffix)) { - // We just deleted the last chunk from a _chunks segment - // directory. We may delete the index + tx state for - // that segment. - auto base_segment_path = immediate_parent.substr( - 0, immediate_parent.size() - chunks_suffix.size()); - tx_file = fmt::format("{}.tx", base_segment_path); - index_file = fmt::format("{}.index", base_segment_path); - } - } - - if (tx_file.has_value()) { - try { - auto sz = co_await ss::file_size(tx_file.value()); - co_await ss::remove_file(tx_file.value()); - result.deleted_size += sz; - this_segment_deleted_bytes += sz; - result.deleted_count += 1; - _current_cache_size -= sz; - _current_cache_objects -= 1; - } catch (std::filesystem::filesystem_error& e) { - if (e.code() != std::errc::no_such_file_or_directory) { - throw; - } - } - } + size_t candidate_i = 0; + while ( + candidate_i < candidates.size() + && (result.deleted_size < size_to_delete || result.deleted_count < objects_to_delete)) { + auto& file_stat = candidates[candidate_i++]; - if (index_file.has_value()) { - try { - auto sz = co_await ss::file_size(index_file.value()); - co_await ss::remove_file(index_file.value()); - result.deleted_size += sz; - this_segment_deleted_bytes += sz; - result.deleted_count += 1; - _current_cache_size -= sz; - _current_cache_objects -= 1; - } catch (std::filesystem::filesystem_error& e) { - if (e.code() != std::errc::no_such_file_or_directory) { - throw; - } - } - } + if (need_to_skip(file_stat)) { + continue; + } - // Remove key if possible to make sure there is no resource - // leak - _access_time_tracker.remove_timestamp( - std::string_view(file_stat.path)); + auto op_res = co_await this->remove_segment_full(file_stat); + result.deleted_count += op_res.deleted_count; + result.deleted_size += op_res.deleted_size; + } - vlog( - cst_log.trace, - "trim: reclaimed(fast) {} bytes from {}", - this_segment_deleted_bytes, - file_stat.path); - } catch (const ss::gate_closed_exception&) { - // We are shutting down, stop iterating and propagate - throw; - } catch (const std::exception& e) { - vlog( - cst_log.error, - "trim: couldn't delete {}: {}.", - file_stat.path, - e.what()); + ssize_t max_carryover_bytes + = config::shard_local_cfg() + .cloud_storage_cache_trim_carryover_bytes.value(); + fragmented_vector tmp; + auto estimated_size = std::min( + static_cast(max_carryover_bytes), + candidates.size() - candidate_i); + tmp.reserve(estimated_size); + while (max_carryover_bytes > 0 && candidate_i < candidates.size()) { + const auto& fs = candidates[candidate_i++]; + if (need_to_skip(fs)) { + continue; } + max_carryover_bytes -= static_cast( + sizeof(fs) + fs.path.size()); + tmp.push_back(fs); + } + + if (!tmp.empty()) { + _last_trim_carryover = std::move(tmp); } co_return result; @@ -669,10 +733,13 @@ cache::trim_exhaustive(uint64_t size_to_delete, size_t objects_to_delete) { probe.exhaustive_trim(); trim_result result; + _last_trim_carryover = std::nullopt; + // Enumerate ALL files in the cache (as opposed to trim_fast that strips out // indices/tx/tmp files) auto [walked_cache_size, _filtered_out, candidates, _] - = co_await _walker.walk(_cache_dir.native(), _access_time_tracker); + = co_await _walker.walk( + _cache_dir.native(), _access_time_tracker, _walk_concurrency()); vlog( cst_log.debug, @@ -1043,10 +1110,7 @@ ss::future<> cache::put( // Trim proactively: if many fibers hit this concurrently, // they'll contend for cleanup_sm and the losers will skip // trim due to throttling. - { - auto units = co_await ss::get_units(_cleanup_sm, 1); - co_await trim_throttled(); - } + co_await trim_throttled(); throw disk_full_error; } @@ -1223,9 +1287,164 @@ bool cache::may_exceed_limits(uint64_t bytes, size_t objects) { && !would_fit_in_cache; } +ss::future +cache::trim_carryover(uint64_t delete_bytes, uint64_t delete_objects) { + // During the normal trim we're doing the recursive directory walk to + // generate a exhaustive list of files stored in the cache. If we store very + // large number of files in the cache this operation could take long time. + // We have a limit for number of objects that the cache could support but + // it's often set to relatively high value. Also, when we reach the object + // count limit the cache blocks all new 'put' operations because it doesn't + // allow any overallocation in this case. + // + // This creates a corner case when every trim is caused by the object count + // limit being reached. In this case the trim is blocking readers every + // time. + // + // The solution is to quickly delete objects without doing the full + // recursive directory walk and unblock the readers proactively allowing + // them object count to overshoot for very brief period of time. In order to + // be able to do this we need to have the list of candidates for deletion. + // Such list is stored in the _last_trim_carryover field. This is a list of + // files with oldest access times from the last directory walk. The + // carryover trim compares the access times from the carryover list to their + // actual access times from the access time tracker. All objects with + // matching access times wasn't accessed since the last trim and can be + // deleted. This doesn't change the LRU behavior since the + // _last_trim_carryover stores objects in LRU order. + trim_result result; + vlog( + cst_log.trace, + "trim carryover: list available {}", + _last_trim_carryover.has_value()); + + if (!_last_trim_carryover.has_value()) { + co_return result; + } + probe.carryover_trim(); + auto it = _last_trim_carryover->begin(); + for (; it < _last_trim_carryover->end(); it++) { + vlog( + cst_log.trace, + "carryover trim: check object {} ({})", + it->path, + it->size); + if ( + result.deleted_size >= delete_bytes + && result.deleted_count >= delete_objects) { + vlog( + cst_log.trace, + "carryover trim: stop, deleted {} / {}, requested to delete {} / " + "{}", + human::bytes(result.deleted_size), + result.deleted_count, + human::bytes(delete_bytes), + delete_objects); + break; + } + auto& file_stat = *it; + // Don't hit access time tracker file/tmp + if ( + is_trim_exempt(file_stat.path) + || std::string_view(file_stat.path).ends_with(tmp_extension)) { + continue; + } + // Both tx and index files are handled as part of the segment + // deletion. + if ( + std::string_view(file_stat.path).ends_with(".tx") + || std::string_view(file_stat.path).ends_with(".index")) { + continue; + } + // Check that access time didn't change + auto rel_path = _cache_dir + / std::filesystem::relative( + std::filesystem::path(file_stat.path), _cache_dir); + auto estimate = _access_time_tracker.estimate_timestamp( + rel_path.native()); + if (estimate != file_stat.access_time) { + vlog( + cst_log.trace, + "carryover file {} was accessed ({}) since the last trim ({}), " + "ignoring", + rel_path.native(), + estimate->time_since_epoch().count(), + file_stat.access_time.time_since_epoch().count()); + // The file was accessed since we get the stats + continue; + } + auto op_res = co_await this->remove_segment_full(file_stat); + result.deleted_count += op_res.deleted_count; + result.deleted_size += op_res.deleted_size; + } + vlog( + cst_log.debug, + "carryover trim reclaimed {} bytes from {} files", + result.deleted_size, + result.deleted_count); + + if (it == _last_trim_carryover->end()) { + _last_trim_carryover = std::nullopt; + } else { + fragmented_vector tmp; + size_t estimate = _last_trim_carryover->end() - it; + tmp.reserve(estimate); + std::copy(it, _last_trim_carryover->end(), std::back_inserter(tmp)); + _last_trim_carryover = std::move(tmp); + } + + co_return result; +} + +void cache::maybe_background_trim() { + auto& trim_threshold_pct_objects + = config::shard_local_cfg() + .cloud_storage_cache_trim_threshold_percent_objects; + auto& trim_threshold_pct_size + = config::shard_local_cfg() + .cloud_storage_cache_trim_threshold_percent_size; + if ( + !trim_threshold_pct_size.value().has_value() + && !trim_threshold_pct_objects.value().has_value()) { + return; + } + + uint64_t target_bytes = uint64_t( + _max_bytes * trim_threshold_pct_size.value().value_or(100.0) / 100.0); + uint32_t target_objects = uint32_t( + _max_objects() * trim_threshold_pct_objects.value().value_or(100.0) + / 100.0); + + bool bytes_over_limit = _current_cache_size + _reserved_cache_size + > target_bytes; + bool objects_over_limit = _current_cache_objects + _reserved_cache_objects + > target_objects; + + if (bytes_over_limit || objects_over_limit) { + auto units = ss::try_get_units(_cleanup_sm, 1); + if (units.has_value()) { + vlog(cst_log.debug, "Spawning background trim"); + ssx::spawn_with_gate( + _gate, + [this, + target_bytes, + target_objects, + u = std::move(units)]() mutable { + return trim_throttled_unlocked(target_bytes, target_objects) + .finally([u = std::move(u)] {}); + }); + } else { + vlog( + cst_log.debug, "Not spawning background trim: already started"); + } + } +} + ss::future<> cache::do_reserve_space(uint64_t bytes, size_t objects) { vassert(ss::this_shard_id() == ss::shard_id{0}, "Only call on shard 0"); + maybe_background_trim(); + if (may_reserve_space(bytes, objects)) { // Fast path: space was available. _reserved_cache_size += bytes; @@ -1233,12 +1452,6 @@ ss::future<> cache::do_reserve_space(uint64_t bytes, size_t objects) { co_return; } - // Slow path: register a pending need for bytes that will be used in - // clean_up_cache to make space available, and then proceed to cooperatively - // call clean_up_cache along with anyone else who is waiting. - _reservations_pending += bytes; - _reservations_pending_objects += objects; - vlog( cst_log.trace, "Out of space reserving {} bytes (size={}/{} " @@ -1251,8 +1464,77 @@ ss::future<> cache::do_reserve_space(uint64_t bytes, size_t objects) { _reservations_pending, _reservations_pending_objects); + auto units = co_await ss::get_units(_cleanup_sm, 1); + + // Situation may change after a scheduling point. Another fiber could + // trigger carryover trim which released some resources. Exit early in this + // case. + if (may_reserve_space(bytes, objects)) { + _reserved_cache_size += bytes; + _reserved_cache_objects += objects; + co_return; + } + + // Do not increment _reservations_pending* before carryover trim is + // completed. + if (_last_trim_carryover.has_value()) { + // Slow path: try to run carryover trim if we have data + // from the previous trim. + + auto short_term_hydrations_estimate + = config::shard_local_cfg().cloud_storage_max_connections() + * ss::smp::count; + + // Here we're trying to estimate how much space do we need to + // free to allow all TS resources to be used again to download + // data from S3. This is only a crude estimate. + auto trim_bytes = std::min( + config::shard_local_cfg().log_segment_size() + * short_term_hydrations_estimate / 3, + _max_bytes); + auto trim_objects = std::min( + short_term_hydrations_estimate * 3, _max_objects()); + + vlog( + cst_log.debug, + "Carryover trim list has {} elements, trying to remove {} bytes " + "and {} objects", + _last_trim_carryover->size(), + human::bytes(trim_bytes), + trim_objects); + + co_await trim_carryover(trim_bytes, trim_objects); + } else { + vlog(cst_log.debug, "Carryover trim list is empty"); + } + + if (may_reserve_space(bytes, objects)) { + _reserved_cache_size += bytes; + _reserved_cache_objects += objects; + // Carryover trim released enough space for this fiber to continue. But + // we are starting the trim in the background to release more space and + // refresh the carryover list. Without this subsequent 'reserve_space' + // calls will be removing elements from the carryover list until it's + // empty. After that the blocking trim will be forced and the readers + // will be blocked for the duration of the trim. To avoid this we need + // to run trim in the background even if the fiber is unblocked. + // We want number of full trims to match number of carryover trims. + vlog(cst_log.debug, "Spawning background trim_throttled"); + ssx::spawn_with_gate(_gate, [this, u = std::move(units)]() mutable { + return trim_throttled_unlocked(std::nullopt, std::nullopt) + .finally([u = std::move(u)] {}); + }); + co_return; + } + + // Slowest path: register a pending need for bytes that will be used in + // clean_up_cache to make space available, and then proceed to + // cooperatively call clean_up_cache along with anyone else who is + // waiting. try { - auto units = co_await ss::get_units(_cleanup_sm, 1); + _reservations_pending += bytes; + _reservations_pending_objects += objects; + while (!may_reserve_space(bytes, objects)) { bool may_exceed = may_exceed_limits(bytes, objects) && _last_trim_failed; @@ -1273,7 +1555,7 @@ ss::future<> cache::do_reserve_space(uint64_t bytes, size_t objects) { // After taking lock, there still isn't space: means someone // else didn't take it and free space for us already, so we will // do the trim. - co_await trim_throttled(); + co_await trim_throttled_unlocked(); did_trim = true; } @@ -1421,5 +1703,4 @@ ss::future<> cache::initialize(std::filesystem::path cache_dir) { co_await ss::recursive_touch_directory(cache_dir.string()); } } - } // namespace cloud_storage diff --git a/src/v/cloud_storage/cache_service.h b/src/v/cloud_storage/cache_service.h index cdae7cd94acf7..007a8e17efda0 100644 --- a/src/v/cloud_storage/cache_service.h +++ b/src/v/cloud_storage/cache_service.h @@ -24,8 +24,11 @@ #include #include #include +#include #include +#include +#include #include #include @@ -108,7 +111,8 @@ class cache : public ss::peering_sharded_service { config::binding, config::binding, config::binding>, - config::binding) noexcept; + config::binding, + config::binding) noexcept; cache(const cache&) = delete; cache(cache&& rhs) = delete; @@ -177,6 +181,10 @@ class cache : public ss::peering_sharded_service { uint64_t get_usage_bytes() { return _current_cache_size; } + uint64_t get_max_bytes() { return _max_bytes; } + + uint64_t get_max_objects() { return _max_objects(); } + /// Administrative trim, that specifies its own limits instead of using /// the configured limits (skips throttling, and can e.g. trim to zero bytes /// if they want to) @@ -230,7 +238,16 @@ class cache : public ss::peering_sharded_service { std::optional get_trim_delay() const; /// Invoke trim, waiting if not enough time passed since the last trim - ss::future<> trim_throttled(); + ss::future<> trim_throttled_unlocked( + std::optional size_limit_override = std::nullopt, + std::optional object_limit_override = std::nullopt); + + // Take the cleanup semaphore before calling trim_throttled + ss::future<> trim_throttled( + std::optional size_limit_override = std::nullopt, + std::optional object_limit_override = std::nullopt); + + void maybe_background_trim(); /// Whether an objects path makes it impervious to pinning, like /// the access time tracker. @@ -253,6 +270,10 @@ class cache : public ss::peering_sharded_service { /// (only runs on shard 0) ss::future<> do_reserve_space(uint64_t, size_t); + /// Trim cache using results from the previous recursive directory walk + ss::future + trim_carryover(uint64_t delete_bytes, uint64_t delete_objects); + /// Return true if the sum of used space and reserved space is far enough /// below max size to accommodate a new reservation of `bytes` /// (only runs on shard 0) @@ -273,6 +294,11 @@ class cache : public ss::peering_sharded_service { /// update. void set_block_puts(bool); + /// Remove segment or chunk subdirectory with all its auxilary files (tx, + /// index) + ss::future + remove_segment_full(const file_list_item& file_stat); + std::filesystem::path _cache_dir; size_t _disk_size; config::binding _disk_reservation; @@ -280,6 +306,7 @@ class cache : public ss::peering_sharded_service { config::binding> _max_percent; uint64_t _max_bytes; config::binding _max_objects; + config::binding _walk_concurrency; void update_max_bytes(); ss::abort_source _as; @@ -337,6 +364,9 @@ class cache : public ss::peering_sharded_service { ss::condition_variable _block_puts_cond; friend class cache_test_fixture; + + // List of probable deletion candidates from the last trim. + std::optional> _last_trim_carryover; }; } // namespace cloud_storage diff --git a/src/v/cloud_storage/materialized_resources.cc b/src/v/cloud_storage/materialized_resources.cc index 82be9d881850b..24a694ad33711 100644 --- a/src/v/cloud_storage/materialized_resources.cc +++ b/src/v/cloud_storage/materialized_resources.cc @@ -177,7 +177,9 @@ materialized_resources::materialized_resources() , _throughput_shard_limit_config( config::shard_local_cfg().cloud_storage_max_throughput_per_shard.bind()) , _relative_throughput( - config::shard_local_cfg().cloud_storage_throughput_limit_percent.bind()) { + config::shard_local_cfg().cloud_storage_throughput_limit_percent.bind()) + , _cache_carryover_bytes(config::shard_local_cfg() + .cloud_storage_cache_trim_carryover_bytes.bind()) { auto update_max_mem = [this]() { // Update memory capacity to accommodate new max number of segment // readers @@ -203,6 +205,52 @@ materialized_resources::materialized_resources() }); }); + if (ss::this_shard_id() == 0) { + // Take into account number of bytes used by cache carryover mechanism. + // The cache doesn't have access to 'materialized_resources' because + // otherwise it'd create a dependency cycle. + _carryover_units = _mem_units.try_get_units(_cache_carryover_bytes()); + vlog( + cst_log.info, + "{} units reserved for cache trim carryover mechanism", + _carryover_units.has_value() ? _carryover_units->count() : 0); + + _cache_carryover_bytes.watch([this] { + // We're using best effort approach here. Under memory pressure we + // might not be able to change reservation + auto current_units = _carryover_units.has_value() + ? _carryover_units->count() + : 0; + auto upd = _cache_carryover_bytes(); + if (upd < current_units) { + // Free units that represent memory used by carryover cache + // trim. It's guaranteed that optional is not null. + _carryover_units->return_units(current_units - upd); + } else { + // Acquire new units + auto tmp = _mem_units.try_get_units(upd - current_units); + if (tmp.has_value()) { + if (_carryover_units.has_value()) { + _carryover_units->adopt(std::move(tmp.value())); + } else { + _carryover_units = std::move(tmp.value()); + } + } else { + vlog( + cst_log.info, + "Failed to reserve {} units for the cache carryover " + "mechanism because tiered-storage is likely under memory " + "pressure", + upd - current_units); + } + } + vlog( + cst_log.info, + "{} units reserved for cache trim carryover mechanism", + _carryover_units.has_value() ? _carryover_units->count() : 0); + }); + } + auto reset_tp = [this] { ssx::spawn_with_gate(_gate, [this] { return update_throughput(); }); }; diff --git a/src/v/cloud_storage/materialized_resources.h b/src/v/cloud_storage/materialized_resources.h index e3a69bdc5cc25..10ce03f1f96b9 100644 --- a/src/v/cloud_storage/materialized_resources.h +++ b/src/v/cloud_storage/materialized_resources.h @@ -179,6 +179,9 @@ class materialized_resources { config::binding> _relative_throughput; bool _throttling_disabled{false}; std::optional _device_throughput; + config::binding _cache_carryover_bytes; + // Memory reserved for cache carryover mechanism + std::optional _carryover_units; }; } // namespace cloud_storage diff --git a/src/v/cloud_storage/recursive_directory_walker.cc b/src/v/cloud_storage/recursive_directory_walker.cc index cbf99cf615e7c..01fa1ba4dc112 100644 --- a/src/v/cloud_storage/recursive_directory_walker.cc +++ b/src/v/cloud_storage/recursive_directory_walker.cc @@ -12,12 +12,15 @@ #include "cloud_storage/access_time_tracker.h" #include "cloud_storage/logger.h" +#include "ssx/watchdog.h" #include "vassert.h" #include "vlog.h" #include #include +#include #include +#include #include #include @@ -38,7 +41,6 @@ struct walk_accumulator { , filter(std::move(collect_filter)) {} ss::future<> visit(ss::sstring const& target, ss::directory_entry entry) { - seen_dentries = true; auto entry_path = fmt::format("{}/{}", target, entry.name); if (entry.type && entry.type == ss::directory_entry_type::regular) { auto file_stats = co_await ss::file_stat(entry_path); @@ -65,69 +67,124 @@ struct walk_accumulator { } else if ( entry.type && entry.type == ss::directory_entry_type::directory) { vlog(cst_log.debug, "Dir found {}", entry_path); - dirlist.push_front(entry_path); + dirlist.emplace_front(entry_path); } } bool empty() const { return dirlist.empty(); } ss::sstring pop() { - auto r = dirlist.back(); + auto r = std::move(dirlist.back()); dirlist.pop_back(); return r; } - void reset_seen_dentries() { seen_dentries = false; } - const access_time_tracker& tracker; - bool seen_dentries{false}; std::deque dirlist; std::optional filter; fragmented_vector files; uint64_t current_cache_size{0}; size_t filtered_out_files{0}; }; +} // namespace cloud_storage + +namespace { +ss::future<> walker_process_directory( + const ss::sstring& start_dir, + ss::sstring target, + cloud_storage::walk_accumulator& state, + fragmented_vector& empty_dirs) { + try { + ss::file target_dir = co_await open_directory(target); + + bool seen_dentries = false; + co_await target_dir + .list_directory( + [&state, &target, &seen_dentries](ss::directory_entry entry) { + seen_dentries = true; + return state.visit(target, std::move(entry)); + }) + .done() + .finally([target_dir]() mutable { return target_dir.close(); }); + + if (unlikely(!seen_dentries) && target != start_dir) { + empty_dirs.push_back(target); + } + } catch (std::filesystem::filesystem_error& e) { + if (e.code() == std::errc::no_such_file_or_directory) { + // skip this directory, move to the next one + } else { + throw; + } + } +} +} // namespace +namespace cloud_storage { ss::future recursive_directory_walker::walk( ss::sstring start_dir, const access_time_tracker& tracker, + uint16_t max_concurrency, std::optional collect_filter) { + vassert(max_concurrency > 0, "Max concurrency must be greater than 0"); + auto guard = _gate.hold(); + watchdog wd1m(std::chrono::seconds(60), [] { + vlog(cst_log.info, "Directory walk is taking more than 1 min"); + }); + watchdog wd10m(std::chrono::seconds(600), [] { + vlog(cst_log.warn, "Directory walk is taking more than 10 min"); + }); + // Object to accumulate data as we walk directories walk_accumulator state(start_dir, tracker, std::move(collect_filter)); fragmented_vector empty_dirs; + // Listing directories involves blocking I/O which in Seastar is serviced by + // a dedicated thread pool and workqueue. When listing directories with + // large number of sub-directories this leads to a large queue of work items + // for each of which we have to incur the cost of task switching. Even for a + // lightly loaded reactor, it can take up to 1ms for a full round-trip. The + // round-trip time is ~100x the syscall duration. With 2 level directory + // structure, 100K files, 2 getdents64 calls per directory this adds up to + // 400K syscalls and 6m of wall time. + // + // By running the directory listing in parallel we also parallelize the + // round-trip overhead. This leads to a significant speedup in the + // directory listing phase. + // + // Limit the number of concurrent directory reads to avoid running out of + // file descriptors. + // + // Empirical testing shows that this value is a good balance between + // performance and resource usage. + std::vector targets; + targets.reserve(max_concurrency); + while (!state.empty()) { - auto target = state.pop(); - vassert( - std::string_view(target).starts_with(start_dir), - "Looking at directory {}, which is outside of initial dir {}.", - target, - start_dir); - - try { - ss::file target_dir = co_await open_directory(target); - - state.reset_seen_dentries(); - co_await target_dir - .list_directory([&state, &target](ss::directory_entry entry) { - return state.visit(target, std::move(entry)); - }) - .done() - .finally([target_dir]() mutable { return target_dir.close(); }); - - if (unlikely(!state.seen_dentries) && target != start_dir) { - empty_dirs.push_back(target); - } - } catch (std::filesystem::filesystem_error& e) { - if (e.code() == std::errc::no_such_file_or_directory) { - // skip this directory, move to the ext one - } else { - throw; - } + targets.clear(); + + auto concurrency = std::min( + state.dirlist.size(), size_t(max_concurrency)); + + for (size_t i = 0; i < concurrency; ++i) { + auto target = state.pop(); + vassert( + std::string_view(target).starts_with(start_dir), + "Looking at directory {}, which is outside of initial dir " + "{}.", + target, + start_dir); + targets.push_back(std::move(target)); } + + co_await ss::parallel_for_each( + targets, [&start_dir, &state, &empty_dirs](ss::sstring target) { + return walker_process_directory( + start_dir, std::move(target), state, empty_dirs); + }); } co_return walk_result{ diff --git a/src/v/cloud_storage/recursive_directory_walker.h b/src/v/cloud_storage/recursive_directory_walker.h index 3a16bee87399b..d81f829d34d4a 100644 --- a/src/v/cloud_storage/recursive_directory_walker.h +++ b/src/v/cloud_storage/recursive_directory_walker.h @@ -46,6 +46,7 @@ class recursive_directory_walker { ss::future walk( ss::sstring start_dir, const access_time_tracker& tracker, + uint16_t max_concurrency, std::optional collect_filter = std::nullopt); private: diff --git a/src/v/cloud_storage/remote.cc b/src/v/cloud_storage/remote.cc index ec25377a7ca08..9e1859d18a654 100644 --- a/src/v/cloud_storage/remote.cc +++ b/src/v/cloud_storage/remote.cc @@ -327,6 +327,8 @@ ss::future remote::do_download_manifest( retry_permit.delay, fib.root_abort_source()); retry_permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = download_result::failed; vlog( @@ -425,6 +427,8 @@ ss::future remote::upload_manifest( co_await ss::sleep_abortable(permit.delay, fib.root_abort_source()); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::key_not_found: // not expected during upload [[fallthrough]]; @@ -593,6 +597,8 @@ ss::future remote::upload_stream( } permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::key_not_found: // not expected during upload [[fallthrough]]; @@ -773,6 +779,8 @@ ss::future remote::download_stream( co_await ss::sleep_abortable(permit.delay, fib.root_abort_source()); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = download_result::failed; break; @@ -851,6 +859,8 @@ ss::future remote::download_index( co_await ss::sleep_abortable(permit.delay, fib.root_abort_source()); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = download_result::failed; break; @@ -918,6 +928,8 @@ ss::future remote::segment_exists( co_await ss::sleep_abortable(permit.delay, fib.root_abort_source()); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = download_result::failed; break; @@ -987,6 +999,8 @@ ss::future remote::delete_object( co_await ss::sleep_abortable(permit.delay, fib.root_abort_source()); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = upload_result::failed; break; @@ -1146,6 +1160,8 @@ ss::future remote::delete_object_batch( co_await ss::sleep_abortable(permit.delay, _as); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = upload_result::failed; break; @@ -1271,7 +1287,9 @@ ss::future remote::list_objects( retry_chain_node& parent, std::optional prefix, std::optional delimiter, - std::optional item_filter) { + std::optional item_filter, + std::optional max_keys, + std::optional continuation_token) { ss::gate::holder gh{_gate}; retry_chain_node fib(&parent); retry_chain_logger ctxlog(cst_log, fib); @@ -1281,18 +1299,23 @@ ss::future remote::list_objects( std::optional result; bool items_remaining = true; - std::optional continuation_token = std::nullopt; // Gathers the items from a series of successful ListObjectsV2 calls cloud_storage_clients::client::list_bucket_result list_bucket_result; - // Keep iterating until the ListObjectsV2 calls has more items to return + const auto caller_handle_truncation = max_keys.has_value(); + + if (caller_handle_truncation) { + vassert(max_keys.value() > 0, "Max keys must be greater than 0."); + } + + // Keep iterating while the ListObjectsV2 calls has more items to return while (!_gate.is_closed() && permit.is_allowed && !result) { auto res = co_await lease.client->list_objects( bucket, prefix, std::nullopt, - std::nullopt, + max_keys, continuation_token, fib.get_timeout(), delimiter, @@ -1325,6 +1348,14 @@ ss::future remote::list_objects( // Continue to list the remaining items if (items_remaining) { + // But, return early if max_keys was specified (caller will + // handle truncation) + if (caller_handle_truncation) { + list_bucket_result.is_truncated = true; + list_bucket_result.next_continuation_token + = continuation_token.value(); + co_return list_bucket_result; + } continue; } @@ -1344,6 +1375,8 @@ ss::future remote::list_objects( co_await ss::sleep_abortable(permit.delay, _as); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: result = cloud_storage_clients::error_outcome::fail; break; @@ -1421,6 +1454,8 @@ ss::future remote::upload_object( co_await ss::sleep_abortable(permit.delay, _as); permit = fib.retry(); break; + case cloud_storage_clients::error_outcome::operation_not_supported: + [[fallthrough]]; case cloud_storage_clients::error_outcome::key_not_found: [[fallthrough]]; case cloud_storage_clients::error_outcome::fail: diff --git a/src/v/cloud_storage/remote.h b/src/v/cloud_storage/remote.h index 592b5bf099225..4824823fa2cb1 100644 --- a/src/v/cloud_storage/remote.h +++ b/src/v/cloud_storage/remote.h @@ -384,15 +384,29 @@ class remote : public ss::peering_sharded_service { /// \param prefix Optional prefix to restrict listing of objects /// \param delimiter A character to use as a delimiter when grouping list /// results - /// \param item_filter Optional filter to apply to items before - /// collecting + /// \param max_keys The maximum number of keys to return. If left + /// unspecified, all object keys that fulfill the request will be collected, + /// and the result will not be truncated (truncation not allowed). If + /// specified, it will be up to the user to deal with a possibly-truncated + /// result (using list_result.is_truncated) at the call site, most likely in + /// a while loop. The continuation-token generated by that request will be + /// available through list_result.next_continuation_token for future + /// requests. It is also important to note that the value for max_keys will + /// be capped by the cloud provider default (which may vary between + /// providers, e.g AWS has a limit of 1000 keys per ListObjects request). + /// \param continuation_token The token hopefully passed back to the user + /// from a prior list_objects() request, in the case that they are handling + /// a truncated result manually. + /// \param item_filter Optional filter to apply to items before collecting ss::future list_objects( const cloud_storage_clients::bucket_name& name, retry_chain_node& parent, std::optional prefix = std::nullopt, std::optional delimiter = std::nullopt, std::optional item_filter - = std::nullopt); + = std::nullopt, + std::optional max_keys = std::nullopt, + std::optional continuation_token = std::nullopt); /// \brief Upload small objects to bucket. Suitable for uploading simple /// strings, does not check for leadership before upload like the segment diff --git a/src/v/cloud_storage/remote_partition.cc b/src/v/cloud_storage/remote_partition.cc index c9f9d3409a03d..bc2fc7685facf 100644 --- a/src/v/cloud_storage/remote_partition.cc +++ b/src/v/cloud_storage/remote_partition.cc @@ -20,6 +20,8 @@ #include "cloud_storage/tx_range_manifest.h" #include "cloud_storage/types.h" #include "model/fundamental.h" +#include "model/timestamp.h" +#include "net/connection.h" #include "ssx/future-util.h" #include "ssx/watchdog.h" #include "storage/log_reader.h" @@ -402,69 +404,58 @@ class partition_record_batch_reader_impl final "{}", _seg_reader->config()); - try { - auto result = co_await _seg_reader->read_some( - deadline, *_ot_state); - throw_on_external_abort(); + auto result = co_await _seg_reader->read_some( + deadline, *_ot_state); + throw_on_external_abort(); - if (!result) { - vlog( - _ctxlog.debug, - "Error while reading from stream '{}'", - result.error()); - co_await set_end_of_stream(); - throw std::system_error(result.error()); - } - data_t d = std::move(result.value()); - for (const auto& batch : d) { - _partition->_probe.add_bytes_read( - batch.header().size_bytes); - _partition->_probe.add_records_read( - batch.record_count()); - } - if ( - _first_produced_offset == model::offset{} && !d.empty()) { - _first_produced_offset = d.front().base_offset(); - } - co_return storage_t{std::move(d)}; - } catch (const stuck_reader_exception& ex) { - throw_on_external_abort(); + if (!result) { vlog( - _ctxlog.warn, - "stuck reader: current rp offset: {}, max rp offset: {}", - ex.rp_offset, - _seg_reader->max_rp_offset()); - - // If the reader is stuck because of a mismatch between - // segment data and manifest entry, set reader to EOF and - // try to reset reader on the next loop iteration. We only - // do this when the reader has not reached eof. For example, - // the segment ends at offset 10 but the manifest has max - // offset at 11 for the segment, with offset 11 actually - // present in the next segment. When the reader is stuck, - // the current offset will be 10 which we will not be able - // to read from. Switching to the next segment should enable - // reads to proceed. - if ( - model::next_offset(ex.rp_offset) - >= _next_segment_base_offset - && !_seg_reader->is_eof()) { - vlog( - _ctxlog.info, - "mismatch between current segment end and manifest " - "data: current rp offset {}, manifest max rp offset " - "{}, next segment base offset {}, reader is EOF: {}. " - "set EOF on reader and try to " - "reset", - ex.rp_offset, - _seg_reader->max_rp_offset(), - _next_segment_base_offset, - _seg_reader->is_eof()); - _seg_reader->set_eof(); - continue; + _ctxlog.debug, + "Error while reading from stream '{}'", + result.error()); + co_await set_end_of_stream(); + throw std::system_error(result.error()); + } + data_t d = std::move(result.value()); + for (const auto& batch : d) { + _partition->_probe.add_bytes_read( + batch.header().size_bytes); + _partition->_probe.add_records_read(batch.record_count()); + } + if (_first_produced_offset == model::offset{} && !d.empty()) { + _first_produced_offset = d.front().base_offset(); + } else { + auto current_ko = _ot_state->from_log_offset( + _seg_reader->current_rp_offset()); + vlog( + _ctxlog.debug, + "No results, current rp offset: {}, current kafka " + "offset: {}, max rp offset: " + "{}", + _seg_reader->current_rp_offset(), + current_ko, + _seg_reader->config().max_offset); + if (current_ko > _seg_reader->config().max_offset) { + // Reader overshoot the offset. If we will not reset + // the stream the loop inside the + // record_batch_reader will keep calling this method + // again and again. We will be returning empty + // result every time because the current offset + // overshoot the max allowed offset. Resetting the + // segment reader fixes the issue. + // + // We can get into the situation when the current + // reader returns empty result in several cases: + // - we reached max_offset (covered here) + // - we reached end of stream (covered above right + // after the 'read_some' call) + // + // If we reached max-bytes then the result won't be + // empty. It will have at least one record batch. + co_await set_end_of_stream(); } - throw; } + co_return storage_t{std::move(d)}; } } catch (const ss::gate_closed_exception&) { vlog( @@ -522,7 +513,10 @@ class partition_record_batch_reader_impl final async_view_search_query_t query; if (config.first_timestamp.has_value()) { - query = config.first_timestamp.value(); + query = async_view_timestamp_query( + model::offset_cast(config.start_offset), + config.first_timestamp.value(), + model::offset_cast(config.max_offset)); } else { // NOTE: config.start_offset actually contains kafka offset // stored using model::offset type. @@ -535,66 +529,72 @@ class partition_record_batch_reader_impl final co_return; } - if ( - cur.error() == error_outcome::out_of_range - && ss::visit( - query, - [&](model::offset) { return false; }, - [&](kafka::offset query_offset) { - // Special case queries below the start offset of the log. - // The start offset may have advanced while the request was - // in progress. This is expected, so log at debug level. - const auto log_start_offset - = _partition->_manifest_view->stm_manifest() - .full_log_start_kafka_offset(); - - if (log_start_offset && query_offset < *log_start_offset) { - vlog( - _ctxlog.debug, - "Manifest query below the log's start Kafka offset: " - "{} < {}", - query_offset(), - log_start_offset.value()()); - return true; - } - return false; - }, - [&](model::timestamp query_ts) { - // Special case, it can happen when a timequery falls below - // the clean offset. Caused when the query races with - // retention/gc. log a warning, since the kafka client can - // handle a failed query - auto const& spillovers = _partition->_manifest_view - ->stm_manifest() - .get_spillover_map(); - if ( - spillovers.empty() - || spillovers.get_max_timestamp_column() - .last_value() - .value_or(model::timestamp::max()()) - >= query_ts()) { - vlog( - _ctxlog.debug, - "Manifest query raced with retention and the result " - "is below the clean/start offset for {}", - query_ts); - return true; - } - - // query was not meant for archive region. fallthrough and - // log an error - return false; - })) { - // error was handled - co_return; + // Out of range queries are unexpected. The caller must take care + // to send only valid queries to remote_partition. I.e. the fetch + // handler does such validation. Similar validation is done inside + // remote partition. + // + // Out of range at this point is due to a race condition or due to + // a bug. In both cases the only valid action is to throw an + // exception and let the caller deal with it. If the caller doesn't + // handle it it leads to a closed kafka connection which the + // end clients retry. + if (cur.error() == error_outcome::out_of_range) { + ss::visit( + query, + [&](model::offset) { + vassert( + false, + "Unreachable code. Remote partition doesn't know how " + "to " + "handle model::offset queries."); + }, + [&](kafka::offset query_offset) { + // Bug or retention racing with the query. + const auto log_start_offset + = _partition->_manifest_view->stm_manifest() + .full_log_start_kafka_offset(); + + if ( + log_start_offset && query_offset < *log_start_offset) { + vlog( + _ctxlog.warn, + "Manifest query below the log's start Kafka " + "offset: " + "{} < {}", + query_offset(), + log_start_offset.value()()); + } + }, + [&](const async_view_timestamp_query& query_ts) { + // Special case, it can happen when a timequery falls + // below the clean offset. Caused when the query races + // with retention/gc. + auto const& spillovers = _partition->_manifest_view + ->stm_manifest() + .get_spillover_map(); + + bool timestamp_inside_spillover + = query_ts.ts() + <= spillovers.get_max_timestamp_column() + .last_value() + .value_or(model::timestamp::min()()); + + if (timestamp_inside_spillover) { + vlog( + _ctxlog.debug, + "Manifest query raced with retention and the " + "result " + "is below the clean/start offset for {}", + query_ts); + } + }); } - vlog( - _ctxlog.error, + throw std::runtime_error(fmt::format( "Failed to query spillover manifests: {}, query: {}", cur.error(), - query); - co_return; + query)); } _view_cursor = std::move(cur.value()); co_await _view_cursor->with_manifest( @@ -824,8 +824,13 @@ class partition_record_batch_reader_impl final /// Transition reader to the completed state. Stop tracking state in /// the 'remote_partition' ss::future<> set_end_of_stream() { - co_await _seg_reader->stop(); - _seg_reader = {}; + if (!_seg_reader) { + co_return; + } + // It's critical that we swap out the reader before calling stop(). + // Otherwise, another fiber may swap it out while we're stopping! + auto reader = std::move(_seg_reader); + co_await reader->stop(); } retry_chain_node _rtc; @@ -1207,11 +1212,13 @@ remote_partition::timequery(storage::timequery_config cfg) { co_return std::nullopt; } - auto start_offset = stm_manifest.full_log_start_kafka_offset().value(); + auto start_offset = std::max( + cfg.min_offset, + kafka::offset_cast(stm_manifest.full_log_start_kafka_offset().value())); // Synthesize a log_reader_config from our timequery_config storage::log_reader_config config( - kafka::offset_cast(start_offset), + start_offset, cfg.max_offset, 0, 2048, // We just need one record batch @@ -1234,7 +1241,8 @@ remote_partition::timequery(storage::timequery_config cfg) { vlog(_ctxlog.debug, "timequery: {} batches", batches.size()); if (batches.size()) { - co_return storage::batch_timequery(*(batches.begin()), cfg.time); + co_return storage::batch_timequery( + *(batches.begin()), cfg.min_offset, cfg.time, cfg.max_offset); } else { co_return std::nullopt; } diff --git a/src/v/cloud_storage/remote_segment.cc b/src/v/cloud_storage/remote_segment.cc index 53867d799c1bc..9e19569c9454c 100644 --- a/src/v/cloud_storage/remote_segment.cc +++ b/src/v/cloud_storage/remote_segment.cc @@ -248,9 +248,10 @@ ss::future<> remote_segment::stop() { vlog(cst_log.error, "remote_segment {} stop operation stuck", path); }); - vlog(_ctxlog.debug, "remote segment stop"); + vlog(_ctxlog.debug, "remote segment stop: gate closing"); _bg_cvar.broken(); co_await _gate.close(); + vlog(_ctxlog.debug, "remote segment stop: gate closed"); if (_data_file) { co_await _data_file.close().handle_exception( [this](std::exception_ptr err) { @@ -260,7 +261,9 @@ ss::future<> remote_segment::stop() { } if (_chunks_api) { + vlog(_ctxlog.debug, "waiting for chunk api to stop"); co_await _chunks_api->stop(); + vlog(_ctxlog.debug, "chunk api stopped"); } _stopped = true; } @@ -765,12 +768,26 @@ void remote_segment::set_waiter_errors(const std::exception_ptr& err) { p.set_exception(err); _wait_list.pop_front(); } + + fragmented_vector chunk_waiters; + chunk_waiters.swap(_chunk_waiters); + for (auto& w : chunk_waiters) { + w.promise.set_exception(err); + } }; bool remote_segment::is_legacy_mode_engaged() const { return _fallback_mode || _sname_format <= segment_name_format::v2; } +void remote_segment::switch_to_legacy_mode() { + _fallback_mode = fallback_mode::yes; + for (auto& waiter : _chunk_waiters) { + waiter.promise.set_exception( + std::runtime_error{"chunk download aborted"}); + } +} + bool remote_segment::is_state_materialized() const { if (is_legacy_mode_engaged()) { return bool(_data_file); @@ -805,8 +822,10 @@ ss::future<> remote_segment::run_hydrate_bg() { while (!_gate.is_closed()) { try { - co_await _bg_cvar.wait( - [this] { return !_wait_list.empty() || _gate.is_closed(); }); + co_await _bg_cvar.wait([this] { + return !_wait_list.empty() || !_chunk_waiters.empty() + || _gate.is_closed(); + }); if (is_legacy_mode_engaged()) { vlog( @@ -865,6 +884,18 @@ ss::future<> remote_segment::run_hydrate_bg() { } _wait_list.pop_front(); } + + // Only download chunks if we are not in legacy mode and the index + // is available. + if ( + !is_legacy_mode_engaged() && is_state_materialized() + && !_chunk_waiters.empty()) { + vlog( + _ctxlog.debug, + "Processing {} chunk download request(s)", + _chunk_waiters.size()); + co_await service_chunk_requests(); + } } catch (...) { const auto err = std::current_exception(); set_waiter_errors(err); @@ -881,8 +912,82 @@ ss::future<> remote_segment::run_hydrate_bg() { _hydration_loop_running = false; } -namespace { +ss::future<> remote_segment::service_chunk_requests() { + fragmented_vector> chunk_op_results; + chunk_op_results.reserve(_chunk_waiters.size()); + + fragmented_vector requests; + requests.swap(_chunk_waiters); + + std::ranges::transform( + requests, + std::back_inserter(chunk_op_results), + [this](const auto& request) { + vlog( + _ctxlog.debug, + "Downloading chunk {} with prefetch {}", + request.start, + request.prefetch); + return hydrate_and_materialize_chunk( + {request.start, request.prefetch}); + }); + + auto results = co_await ss::when_all( + chunk_op_results.begin(), chunk_op_results.end()); + + for (size_t i = 0; i < results.size(); ++i) { + auto request = std::move(requests[i]); + auto current_result = std::move(results[i]); + + ss::file materialized_handle; + std::exception_ptr err; + + if (current_result.failed()) { + err = current_result.get_exception(); + } else { + materialized_handle = current_result.get(); + } + // Materialization may fail due to cache eviction. In this case the + // materialized handle is default initialized. Re-queue waiter which + // will be processed again on the next iteration of run_hydrate_bg loop. + // Continue with other requests in queue. + if (!err && !materialized_handle) { + vlog( + _ctxlog.debug, + "Failed to materialize chunk start {}, retrying", + request.start); + _chunk_waiters.emplace_back(std::move(request)); + continue; + } + + if (err) { + request.promise.set_exception(err); + } else { + request.promise.set_value(materialized_handle); + } + } +} + +ss::future remote_segment::hydrate_and_materialize_chunk( + start_and_prefetch_t start_and_prefetch) { + auto [chunk_start, prefetch] = start_and_prefetch; + vlog(_ctxlog.debug, "Hydrating chunk {}", chunk_start); + co_await hydrate_chunk({_chunks_api->chunk_map(), prefetch, chunk_start}); + vlog(_ctxlog.debug, "Materializing chunk {}", chunk_start); + co_return co_await materialize_chunk(chunk_start); +} + +ss::future remote_segment::download_chunk( + chunk_start_offset_t chunk_start, uint16_t prefetch) { + ss::promise p; + auto fut = p.get_future(); + _chunk_waiters.push_back({chunk_start, prefetch, std::move(p)}); + _bg_cvar.signal(); + return fut; +} + +namespace { void log_hydration_abort_cause( const retry_chain_logger& logger, const ss::lowres_clock::time_point& deadline, @@ -890,8 +995,8 @@ void log_hydration_abort_cause( if (ss::lowres_clock::now() > deadline) { vlog(logger.warn, "timed out while waiting for hydration"); } else if (as.has_value() && as->get().abort_requested()) { - // TODO it might be useful to be able to log the client info here from - // log reader config. + // TODO it might be useful to be able to log the client info here + // from log reader config. vlog(logger.debug, "consumer disconnected during hydration"); } } @@ -929,7 +1034,7 @@ ss::future<> remote_segment::do_hydrate( "failed to download index with error [{}], switching to " "fallback mode and retrying hydration.", ex); - _fallback_mode = fallback_mode::yes; + switch_to_legacy_mode(); return do_hydrate(as, deadline).then([] { // This is an empty file to match the type returned by // `fut`. The result is discarded immediately so it is diff --git a/src/v/cloud_storage/remote_segment.h b/src/v/cloud_storage/remote_segment.h index 7cde77af091c4..13693c9d7c7c8 100644 --- a/src/v/cloud_storage/remote_segment.h +++ b/src/v/cloud_storage/remote_segment.h @@ -37,19 +37,6 @@ namespace cloud_storage { -class stuck_reader_exception final : public std::runtime_error { -public: - stuck_reader_exception( - model::offset cur_rp_offset, - size_t cur_bytes_consumed, - ss::sstring context) - : std::runtime_error{context} - , rp_offset(cur_rp_offset) - , bytes_consumed(cur_bytes_consumed) {} - const model::offset rp_offset; - const size_t bytes_consumed; -}; - std::filesystem::path generate_index_path(const cloud_storage::remote_segment_path& p); @@ -191,6 +178,9 @@ class remote_segment final { // granularity, otherwise the size is chunk granularity. std::pair min_cache_cost() const; + ss::future + download_chunk(chunk_start_offset_t chunk_start, uint16_t prefetch); + private: /// get a file offset for the corresponding kafka offset /// if the index is available @@ -256,12 +246,29 @@ class remote_segment final { /// download chunks of the segment instead of the entire segment file. bool is_legacy_mode_engaged() const; + /// Switches to legacy mode while also aborting all pending chunk downloads. + /// The switch to legacy mode is performed when we cannot download the + /// segment index. Since segment index is required to download chunks, + /// aborting any pending downloads is necessary when switching to legacy + /// mode. + void switch_to_legacy_mode(); + /// Is the remote segment state materialized, IE do we need to hydrate or /// not. For segment format v0, v1 and v2, the data file handle should be /// opened. For newer formats, v3 or later, the index should be /// materialized. bool is_state_materialized() const; + /// Download all pending chunk requests, waiting until all chunks are + /// materialized. Any chunks which are downloaded successfully but fail to + /// materialize are added back to the waiter list. + ss::future<> service_chunk_requests(); + + using start_and_prefetch_t = std::pair; + + ss::future + hydrate_and_materialize_chunk(start_and_prefetch_t start_and_prefetch); + ss::gate _gate; remote& _api; cache& _cache; @@ -319,6 +326,22 @@ class remote_segment final { ts_read_path_probe& _ts_probe; friend class split_segment_into_chunk_range_consumer; + + /// Pending chunk download request. The start offset and prefetch are + /// supplied by the caller. The promise is created before adding a request + /// to the queue and the associated future is returned to the caller. + struct chunk_request { + chunk_start_offset_t start; + uint16_t prefetch; + ss::promise promise; + }; + + /// Waiters pending chunk downloads. Only the first hydration request for a + /// given chunk ends up here. All following requests for that chunk are + /// stored by the chunk API in its own wait list. This way only a single + /// file handle is created for a given chunk, and the chunk API distributes + /// shared ptrs to that handle to consumers. + fragmented_vector _chunk_waiters; }; class remote_segment_batch_consumer; diff --git a/src/v/cloud_storage/segment_chunk.cc b/src/v/cloud_storage/segment_chunk.cc index fb0f951cdf8c3..6594a9db19edb 100644 --- a/src/v/cloud_storage/segment_chunk.cc +++ b/src/v/cloud_storage/segment_chunk.cc @@ -12,6 +12,17 @@ namespace cloud_storage { +std::ostream& operator<<(std::ostream& os, chunk_state c) { + switch (c) { + case chunk_state::not_available: + return os << "not available"; + case chunk_state::download_in_progress: + return os << "download in progress"; + case chunk_state::hydrated: + return os << "hydrated"; + } +} + std::strong_ordering segment_chunk::operator<=>(const segment_chunk& chunk) const { const auto cmp = required_by_readers_in_future diff --git a/src/v/cloud_storage/segment_chunk.h b/src/v/cloud_storage/segment_chunk.h index e2ccff522fcbe..5bad7d9be89fb 100644 --- a/src/v/cloud_storage/segment_chunk.h +++ b/src/v/cloud_storage/segment_chunk.h @@ -30,6 +30,8 @@ enum class chunk_state { hydrated, }; +std::ostream& operator<<(std::ostream& os, chunk_state); + struct segment_chunk { chunk_state current_state; diff --git a/src/v/cloud_storage/segment_chunk_api.cc b/src/v/cloud_storage/segment_chunk_api.cc index 2b164840083c0..79dd3e23bd441 100644 --- a/src/v/cloud_storage/segment_chunk_api.cc +++ b/src/v/cloud_storage/segment_chunk_api.cc @@ -12,6 +12,7 @@ #include "cloud_storage/logger.h" #include "cloud_storage/remote_segment.h" +#include "ssx/watchdog.h" namespace { constexpr auto cache_backoff_duration = 5s; @@ -110,24 +111,6 @@ bool segment_chunks::downloads_in_progress() const { }); } -ss::future segment_chunks::do_hydrate_and_materialize( - chunk_start_offset_t chunk_start, std::optional prefetch_override) { - auto g = _gate.hold(); - vassert(_started, "chunk API is not started"); - - auto it = _chunks.find(chunk_start); - std::optional chunk_end = std::nullopt; - if (auto next = std::next(it); next != _chunks.end()) { - chunk_end = next->first - 1; - } - - const auto prefetch = prefetch_override.value_or( - config::shard_local_cfg().cloud_storage_chunk_prefetch); - co_await _segment.hydrate_chunk( - segment_chunk_range{_chunks, prefetch, chunk_start}); - co_return co_await _segment.materialize_chunk(chunk_start); -} - ss::future segment_chunks::hydrate_chunk( chunk_start_offset_t chunk_start, std::optional prefetch_override) { auto g = _gate.hold(); @@ -140,6 +123,12 @@ ss::future segment_chunks::hydrate_chunk( auto& chunk = _chunks[chunk_start]; auto curr_state = chunk.current_state; + + vlog( + _ctxlog.debug, + "hydrate_chunk for {}, current state: {}", + chunk_start, + curr_state); if (curr_state == chunk_state::hydrated) { vassert( chunk.handle, @@ -151,6 +140,11 @@ ss::future segment_chunks::hydrate_chunk( // If a download is already in progress, subsequent callers to hydrate are // added to a wait list, and notified when the download finishes. if (curr_state == chunk_state::download_in_progress) { + vlog( + _ctxlog.debug, + "adding waitor for {}, waiters before: {}", + chunk_start, + chunk.waiters.size()); co_return co_await add_waiter_to_chunk(chunk_start, chunk); } @@ -158,24 +152,27 @@ ss::future segment_chunks::hydrate_chunk( try { chunk.current_state = chunk_state::download_in_progress; - // Keep retrying if materialization fails. - bool done = false; - while (!done) { - auto handle = co_await do_hydrate_and_materialize( - chunk_start, prefetch_override); - if (handle) { - done = true; - chunk.handle = ss::make_lw_shared(std::move(handle)); - } else { - vlog( - _ctxlog.trace, - "do_hydrate_and_materialize failed for chunk start offset {}", - chunk_start); - co_await ss::sleep_abortable( - _cache_backoff_jitter.next_jitter_duration(), _as); - } - } + watchdog wd( + 300s, [path = _segment.get_segment_path(), start = chunk_start] { + vlog( + cst_log.error, + "Stuck during do_hydrate_and_materialize for segment path: {}, " + "chunk start: {}", + path(), + start); + }); + + auto handle = co_await _segment.download_chunk( + chunk_start, + prefetch_override.value_or( + config::shard_local_cfg().cloud_storage_chunk_prefetch)); + chunk.handle = ss::make_lw_shared(std::move(handle)); } catch (const std::exception& ex) { + vlog( + _ctxlog.debug, + "Failed to hydrate chunk start {}, error: {}", + chunk_start, + ex.what()); chunk.current_state = chunk_state::not_available; while (!chunk.waiters.empty()) { chunk.waiters.front().set_to_current_exception(); diff --git a/src/v/cloud_storage/segment_chunk_api.h b/src/v/cloud_storage/segment_chunk_api.h index 07de554866ddd..5fd46f76561cb 100644 --- a/src/v/cloud_storage/segment_chunk_api.h +++ b/src/v/cloud_storage/segment_chunk_api.h @@ -73,14 +73,13 @@ class segment_chunks { iterator_t begin(); iterator_t end(); -private: - // Attempts to download chunk into cache and return the file handle for - // segment_chunk. Should be retried if there is a failure due to cache - // eviction between download and opening the file handle. - ss::future do_hydrate_and_materialize( - chunk_start_offset_t chunk_start, - std::optional prefetch_override = std::nullopt); + /// Returns a map of chunk start offset to metadata. The map is initialized + /// once per remote segment, when the segment chunk API is started. The + /// contents of the map are fixed and contain one entry per chunk in the + /// segment. + const chunk_map_t& chunk_map() const { return _chunks; } +private: // Periodically closes chunk file handles for the space to be reclaimable by // cache eviction. The chunks are evicted when they are no longer opened for // reading by any readers. We also take into account any readers that may diff --git a/src/v/cloud_storage/segment_chunk_data_source.cc b/src/v/cloud_storage/segment_chunk_data_source.cc index b59c8863936c3..0770582e784dc 100644 --- a/src/v/cloud_storage/segment_chunk_data_source.cc +++ b/src/v/cloud_storage/segment_chunk_data_source.cc @@ -74,6 +74,11 @@ ss::future> chunk_data_source_impl::get() { ss::future<> chunk_data_source_impl::load_chunk_handle(chunk_start_offset_t chunk_start) { try { + vlog( + _ctxlog.debug, + "Hydrating chunk {} with prefetch {}", + chunk_start, + _prefetch_override); _current_data_file = co_await _chunks.hydrate_chunk( chunk_start, _prefetch_override); } catch (const ss::abort_requested_exception& ex) { @@ -100,10 +105,20 @@ ss::future<> chunk_data_source_impl::load_stream_for_chunk( co_await load_chunk_handle(chunk_start); } catch (...) { eptr = std::current_exception(); + vlog( + _ctxlog.debug, + "Hydrating chunk {} failed with error {}", + chunk_start, + eptr); } if (eptr) { co_await maybe_close_stream(); + vlog( + _ctxlog.debug, + "Closed stream after error {} while hydrating chunk start {}", + eptr, + chunk_start); std::rethrow_exception(eptr); } diff --git a/src/v/cloud_storage/tests/async_manifest_view_test.cc b/src/v/cloud_storage/tests/async_manifest_view_test.cc index cf430d09afb53..f82837218379b 100644 --- a/src/v/cloud_storage/tests/async_manifest_view_test.cc +++ b/src/v/cloud_storage/tests/async_manifest_view_test.cc @@ -455,8 +455,19 @@ FIXTURE_TEST(test_async_manifest_view_truncate, async_manifest_view_fixture) { model::offset so = model::offset{0}; auto maybe_cursor = view.get_cursor(so).get(); + BOOST_REQUIRE( + maybe_cursor.has_error() + && maybe_cursor.error() == cloud_storage::error_outcome::out_of_range); + // The clean offset should still be accesible such that retention // can operate above it. + maybe_cursor = view + .get_cursor( + so, + std::nullopt, + cloud_storage::async_manifest_view::cursor_base_t:: + archive_clean_offset) + .get(); BOOST_REQUIRE(!maybe_cursor.has_failure()); maybe_cursor = view.get_cursor(new_so).get(); @@ -1106,7 +1117,8 @@ FIXTURE_TEST(test_async_manifest_view_timequery, async_manifest_view_fixture) { // Find exact matches for all segments for (const auto& meta : expected) { - auto target = meta.base_timestamp; + auto target = async_view_timestamp_query( + kafka::offset(0), meta.base_timestamp, kafka::offset::max()); auto maybe_cursor = view.get_cursor(target).get(); BOOST_REQUIRE(!maybe_cursor.has_failure()); auto cursor = std::move(maybe_cursor.value()); @@ -1119,9 +1131,9 @@ FIXTURE_TEST(test_async_manifest_view_timequery, async_manifest_view_fixture) { m.last_segment()->max_timestamp, stm_manifest.begin()->base_timestamp, stm_manifest.last_segment()->max_timestamp); - auto res = m.timequery(target); + auto res = m.timequery(target.ts); BOOST_REQUIRE(res.has_value()); - BOOST_REQUIRE(res.value().base_timestamp == target); + BOOST_REQUIRE(res.value().base_timestamp == target.ts); }) .get(); } @@ -1148,7 +1160,10 @@ FIXTURE_TEST( // that there is a gap between any two segments. for (const auto& meta : expected) { - auto target = model::timestamp(meta.base_timestamp.value() - 1); + auto target = async_view_timestamp_query( + kafka::offset(0), + model::timestamp(meta.base_timestamp() - 1), + kafka::offset::max()); auto maybe_cursor = view.get_cursor(target).get(); BOOST_REQUIRE(!maybe_cursor.has_failure()); auto cursor = std::move(maybe_cursor.value()); @@ -1162,11 +1177,11 @@ FIXTURE_TEST( m.last_segment()->max_timestamp, stm_manifest.begin()->base_timestamp, stm_manifest.last_segment()->max_timestamp); - auto res = m.timequery(target); + auto res = m.timequery(target.ts); BOOST_REQUIRE(res.has_value()); BOOST_REQUIRE( model::timestamp(res.value().base_timestamp.value() - 1) - == target); + == target.ts); }) .get(); } diff --git a/src/v/cloud_storage/tests/cache_test.cc b/src/v/cloud_storage/tests/cache_test.cc index fa84f4eb6ee9d..d08ec2e5853e9 100644 --- a/src/v/cloud_storage/tests/cache_test.cc +++ b/src/v/cloud_storage/tests/cache_test.cc @@ -8,14 +8,18 @@ * https://github.com/redpanda-data/redpanda/blob/master/licenses/rcl.md */ +#include "bytes/bytes.h" #include "bytes/iobuf.h" #include "bytes/iostream.h" #include "cache_test_fixture.h" #include "cloud_storage/access_time_tracker.h" #include "cloud_storage/cache_service.h" +#include "random/generators.h" +#include "ssx/sformat.h" #include "test_utils/fixture.h" #include "units.h" #include "utils/file_io.h" +#include "utils/human.h" #include #include @@ -31,6 +35,8 @@ using namespace cloud_storage; +static ss::logger test_log("cache_test_logger"); + FIXTURE_TEST(put_creates_file, cache_test_fixture) { auto data_string = create_data_string('a', 1_MiB + 1_KiB); put_into_cache(data_string, KEY); @@ -455,6 +461,7 @@ FIXTURE_TEST(test_clean_up_on_start, cache_test_fixture) { BOOST_CHECK(!ss::file_exists((CACHE_DIR / tmp_key).native()).get()); BOOST_CHECK(!ss::file_exists(empty_dir_path.native()).get()); BOOST_CHECK(ss::file_exists(populated_dir_path.native()).get()); + BOOST_CHECK_EQUAL(get_object_count(), 2); } /** @@ -538,3 +545,119 @@ FIXTURE_TEST(test_log_segment_cleanup, cache_test_fixture) { return !std::filesystem::exists(path); })); } + +FIXTURE_TEST(test_cache_carryover_trim, cache_test_fixture) { + std::string write_buf(1_MiB, ' '); + random_generators::fill_buffer_randomchars( + write_buf.data(), write_buf.size()); + size_t bytes_used = 0; + size_t num_objects = 0; + std::vector object_keys; + for (int i = 0; i < 10; i++) { + object_keys.emplace_back(fmt::format("test_{}.log.1", i)); + std::ofstream segment{CACHE_DIR / object_keys.back()}; + segment.write( + write_buf.data(), static_cast(write_buf.size())); + segment.flush(); + bytes_used += write_buf.size(); + num_objects++; + object_keys.emplace_back(fmt::format("test_{}.log.1.index", i)); + std::ofstream index{CACHE_DIR / object_keys.back()}; + index.write( + write_buf.data(), static_cast(write_buf.size())); + index.flush(); + bytes_used += write_buf.size(); + num_objects++; + object_keys.emplace_back(fmt::format("test_{}.log.1.tx", i)); + std::ofstream tx{CACHE_DIR / object_keys.back()}; + tx.write( + write_buf.data(), static_cast(write_buf.size())); + tx.flush(); + bytes_used += write_buf.size(); + num_objects++; + } + // Account all files in the cache (30 MiB). + clean_up_at_start().get(); + for (const auto& key : object_keys) { + // Touch every object so they have access times assigned to them + auto item = sharded_cache.local().get(key).get(); + item->body.close().get(); + } + + // Force trim to create a carryover list. + // Nothing is deleted by the trim. + vlog(test_log.info, "Initial trim"); + auto cache_size_target_bytes = bytes_used; + auto cache_size_target_objects = num_objects; + trim_cache(cache_size_target_bytes, cache_size_target_objects); + + // Start trim using only carryover data + auto before_bytes = sharded_cache.local().get_usage_bytes(); + auto before_objects = sharded_cache.local().get_usage_objects(); + vlog( + test_log.info, + "Trim {}, {} bytes used, {} objects", + human::bytes(bytes_used), + human::bytes(before_bytes), + before_objects); + BOOST_REQUIRE(before_bytes > 0); + BOOST_REQUIRE(before_objects > 0); + + // Note that 'trim_carryover' accepts number of bytes/objects + // that has to be deleted. This is the opposite of how 'trim' + // method behaves which accepts target size for the cache. + // This behavior is similar to 'trim_fast'. + auto bytes_to_delete = before_bytes; + auto objects_to_delete = before_objects; + trim_carryover(bytes_to_delete, objects_to_delete); + + // At this point we should be able to delete all objects + auto after_bytes = sharded_cache.local().get_usage_bytes(); + auto after_objects = sharded_cache.local().get_usage_objects(); + vlog( + test_log.info, + "After trim {} bytes used, {} objects", + human::bytes(after_bytes), + after_objects); + BOOST_REQUIRE_EQUAL(after_bytes, 0); + BOOST_REQUIRE_EQUAL(after_objects, 0); +} + +FIXTURE_TEST(test_background_maybe_trim, cache_test_fixture) { + std::string write_buf(1_KiB, ' '); + random_generators::fill_buffer_randomchars( + write_buf.data(), write_buf.size()); + size_t num_objects = 100; + std::vector object_keys; + for (int i = 0; i < num_objects; i++) { + object_keys.emplace_back(fmt::format("test_{}.log.1", i)); + std::ofstream segment{CACHE_DIR / object_keys.back()}; + segment.write( + write_buf.data(), static_cast(write_buf.size())); + segment.flush(); + } + + // Account all files in the cache (100 MiB). + clean_up_at_start().get(); + BOOST_REQUIRE_EQUAL(get_object_count(), 100); + + for (const auto& key : object_keys) { + // Touch every object so they have access times assigned to them + auto item = sharded_cache.local().get(key).get(); + item->body.close().get(); + } + BOOST_REQUIRE_EQUAL(get_object_count(), 100); + + set_trim_thresholds(100.0, 50.0, 100); + + // Do a put which should trigger a background trim and reduce the + // object count to 40, followed by a put that will increase it to 41. + auto data_string = create_data_string('a', 1_KiB); + put_into_cache(data_string, KEY); + wait_for_trim(); + + // 41 because we set a trigger of 50. That means that trim is triggered at + // 50%, but the low water mark adjusts this by an additional 80%, 50% * 80% + // = 40%. +1 for the object we just added. + BOOST_REQUIRE_EQUAL(get_object_count(), 41); +} diff --git a/src/v/cloud_storage/tests/cache_test_fixture.h b/src/v/cloud_storage/tests/cache_test_fixture.h index 6562cfe4abcc6..6b7a683b0f5fa 100644 --- a/src/v/cloud_storage/tests/cache_test_fixture.h +++ b/src/v/cloud_storage/tests/cache_test_fixture.h @@ -13,18 +13,22 @@ #include "cloud_storage/cache_service.h" #include "config/property.h" #include "seastarx.h" +#include "test_utils/scoped_config.h" #include "test_utils/tmp_dir.h" #include "units.h" #include #include +#include #include #include #include #include +#include #include +#include using namespace std::chrono_literals; @@ -59,7 +63,8 @@ class cache_test_fixture { config::mock_binding(0.0), config::mock_binding(1_MiB + 500_KiB), config::mock_binding>(std::nullopt), - config::mock_binding(100000)) + config::mock_binding(100000), + config::mock_binding(3)) .get(); sharded_cache .invoke_on_all([](cloud_storage::cache& c) { return c.start(); }) @@ -119,6 +124,50 @@ class cache_test_fixture { }) .get(); } + + void trim_carryover(uint64_t size_limit, uint64_t object_limit) { + sharded_cache.local().trim_carryover(size_limit, object_limit).get(); + } + + void set_trim_thresholds( + double size_limit_percent, + double object_limit_percent, + uint32_t max_objects) { + cfg.get("cloud_storage_cache_trim_threshold_percent_size") + .set_value(std::make_optional(size_limit_percent)); + cfg.get("cloud_storage_cache_trim_threshold_percent_objects") + .set_value(std::make_optional(object_limit_percent)); + + sharded_cache + .invoke_on( + ss::shard_id{0}, + [max_objects](cloud_storage::cache& c) { + c._max_objects = config::mock_binding(max_objects); + }) + .get(); + } + + void wait_for_trim() { + // Waits for the cleanup semaphore to be available. This ensures that + // there are no trim operations in progress. + sharded_cache + .invoke_on( + ss::shard_id{0}, + [](cloud_storage::cache& c) { + auto units = ss::get_units(c._cleanup_sm, 1).get(); + }) + .get(); + } + + uint32_t get_object_count() { + return sharded_cache + .invoke_on( + ss::shard_id{0}, + [](cloud_storage::cache& c) { return c._current_cache_objects; }) + .get(); + } + + scoped_config cfg; }; } // namespace cloud_storage diff --git a/src/v/cloud_storage/tests/cloud_storage_e2e_test.cc b/src/v/cloud_storage/tests/cloud_storage_e2e_test.cc index b471e5b9bac1a..0a1ce5d007978 100644 --- a/src/v/cloud_storage/tests/cloud_storage_e2e_test.cc +++ b/src/v/cloud_storage/tests/cloud_storage_e2e_test.cc @@ -13,6 +13,7 @@ #include "cloud_storage/spillover_manifest.h" #include "cloud_storage/tests/manual_fixture.h" #include "cloud_storage/tests/produce_utils.h" +#include "cloud_storage/tests/read_replica_fixture.h" #include "cloud_storage/tests/s3_imposter.h" #include "cluster/cloud_metadata/tests/manual_mixin.h" #include "cluster/health_monitor_frontend.h" @@ -20,6 +21,7 @@ #include "kafka/server/tests/list_offsets_utils.h" #include "kafka/server/tests/produce_consume_utils.h" #include "model/fundamental.h" +#include "random/generators.h" #include "redpanda/tests/fixture.h" #include "storage/ntp_config.h" #include "test_utils/async.h" @@ -55,6 +57,51 @@ class e2e_fixture scoped_config test_local_cfg; }; +FIXTURE_TEST(test_spillover_retention_compacted_topic, e2e_fixture) { + test_local_cfg.get("cloud_storage_disable_upload_loop_for_tests") + .set_value(true); + test_local_cfg.get("cloud_storage_spillover_manifest_max_segments") + .set_value(std::make_optional(5)); + test_local_cfg.get("cloud_storage_spillover_manifest_size") + .set_value(std::optional{}); + test_local_cfg.get("log_retention_ms") + .set_value(std::make_optional(1ms)); + const model::topic topic_name("tapioca"); + model::ntp ntp(model::kafka_namespace, topic_name, 0); + + cluster::topic_properties props; + props.shadow_indexing = model::shadow_indexing_mode::full; + props.cleanup_policy_bitflags = model::cleanup_policy_bitflags::compaction; + add_topic({model::kafka_namespace, topic_name}, 1, props).get(); + wait_for_leader(ntp).get(); + + const auto records_per_seg = 5; + const auto num_segs = 100; + auto partition = app.partition_manager.local().get(ntp); + auto& archiver = partition->archiver().value().get(); + tests::remote_segment_generator gen(make_kafka_client().get(), *partition); + auto total_records = gen.num_segments(num_segs) + .batches_per_segment(records_per_seg) + .produce() + .get(); + BOOST_REQUIRE_GE(total_records, 500); + BOOST_REQUIRE(archiver.sync_for_tests().get()); + archiver.apply_spillover().get(); + ss::sleep(5s).get(); + archiver.apply_archive_retention().get(); + + tests::kafka_list_offsets_transport lister(make_kafka_client().get()); + lister.start().get(); + + auto offset + = lister.start_offset_for_partition(topic_name, model::partition_id(0)) + .get(); + BOOST_REQUIRE_EQUAL(offset(), 0); + BOOST_REQUIRE_EQUAL( + archiver.manifest().full_log_start_offset().value_or(model::offset{})(), + 0); +} + FIXTURE_TEST(test_produce_consume_from_cloud, e2e_fixture) { test_local_cfg.get("cloud_storage_disable_upload_loop_for_tests") .set_value(true); @@ -617,7 +664,7 @@ FIXTURE_TEST( if (report.has_value()) { std::vector sizes; for (auto& node_report : report.value().node_reports) { - for (auto& topic : node_report.topics) { + for (auto& topic : node_report->topics) { if ( topic.tp_ns != model::topic_namespace_view( @@ -671,3 +718,374 @@ FIXTURE_TEST( // health report never reported non-zero reclaimable sizes. bummer! BOOST_REQUIRE(false); } + +FIXTURE_TEST(test_local_timequery, e2e_fixture) { + const model::topic topic_name("tapioca"); + model::ntp ntp(model::kafka_namespace, topic_name, model::partition_id{0}); + + // Force local timequeries only through archival mode. + cluster::topic_properties props; + props.shadow_indexing = model::shadow_indexing_mode::archival; + + add_topic({model::kafka_namespace, topic_name}, 1, props).get(); + + wait_for_leader(ntp).get(); + + auto partition = app.partition_manager.local().get(ntp); + auto log = partition->log(); + auto& archiver = partition->archiver().value().get(); + BOOST_REQUIRE(archiver.sync_for_tests().get()); + + const auto batches_per_segment = 1; + const auto num_segs = 5; + const auto batch_time_delta_ms = 10; + const auto base_timestamp = model::timestamp{0}; + tests::remote_segment_generator gen(make_kafka_client().get(), *partition); + auto total_records = gen.num_segments(num_segs) + .batches_per_segment(batches_per_segment) + .base_timestamp(base_timestamp) + .batch_time_delta_ms(batch_time_delta_ms) + .produce() + .get(); + BOOST_REQUIRE_EQUAL(total_records, 5); + + auto make_and_verify_timequery = + [partition]( + model::timestamp t, + model::offset o, + bool expect_value = false, + std::optional expected_o = std::nullopt) { + auto timequery_conf = storage::timequery_config( + model::offset(0), t, o, ss::default_priority_class(), std::nullopt); + + auto result = partition->timequery(timequery_conf).get(); + + if (expect_value) { + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE_EQUAL(result.value().offset, expected_o.value()); + } else { + BOOST_REQUIRE(!result.has_value()); + } + }; + + make_and_verify_timequery( + base_timestamp, model::offset{0}, true, model::offset{0}); + + for (int i = 1; i < total_records; ++i) { + const auto min_timestamp = base_timestamp() + + batch_time_delta_ms * (i - 1); + const auto max_timestamp = min_timestamp + batch_time_delta_ms; + const auto query_timestamp = random_generators::get_int( + min_timestamp + 1, max_timestamp); + make_and_verify_timequery( + model::timestamp{query_timestamp}, + model::offset{i}, + true, + model::offset{i}); + } + + make_and_verify_timequery( + model::timestamp{ + base_timestamp() + (batch_time_delta_ms * total_records)}, + model::offset{total_records}, + false); +} + +FIXTURE_TEST(test_cloud_storage_timequery, e2e_fixture) { + const model::topic topic_name("tapioca"); + model::ntp ntp(model::kafka_namespace, topic_name, model::partition_id{0}); + + // Allow cloud storage timequeries with full shadow indexing mode. + cluster::topic_properties props; + props.shadow_indexing = model::shadow_indexing_mode::full; + props.retention_local_target_bytes = tristate(0); + + add_topic({model::kafka_namespace, topic_name}, 1, props).get(); + + wait_for_leader(ntp).get(); + + auto partition = app.partition_manager.local().get(ntp); + auto log = partition->log(); + auto& archiver = partition->archiver().value().get(); + BOOST_REQUIRE(archiver.sync_for_tests().get()); + + const auto batches_per_segment = 1; + const auto num_segs = 5; + const auto batch_time_delta_ms = 10; + const auto base_timestamp = model::timestamp{0}; + tests::remote_segment_generator gen(make_kafka_client().get(), *partition); + auto total_records = gen.num_segments(num_segs) + .batches_per_segment(batches_per_segment) + .base_timestamp(base_timestamp) + .batch_time_delta_ms(batch_time_delta_ms) + .produce() + .get(); + BOOST_REQUIRE_EQUAL(total_records, 5); + + // Force garbage collection of all local records, so that timequeries must + // go through cloud storage. + ss::abort_source as; + storage::housekeeping_config housekeeping_conf( + model::timestamp::max(), + 0, + log->stm_manager()->max_collectible_offset(), + ss::default_priority_class(), + as); + partition->log()->housekeeping(housekeeping_conf).get(); + + RPTEST_REQUIRE_EVENTUALLY( + 10s, [log = partition->log()] { return log->segments().size() == 1; }); + + auto make_and_verify_timequery = + [partition]( + model::timestamp t, + model::offset o, + bool expect_value = false, + std::optional expected_o = std::nullopt) { + auto timequery_conf = storage::timequery_config( + model::offset(0), t, o, ss::default_priority_class(), std::nullopt); + + auto result = partition->timequery(timequery_conf).get(); + + if (expect_value) { + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE_EQUAL(result.value().offset, expected_o.value()); + } else { + BOOST_REQUIRE(!result.has_value()); + } + }; + + make_and_verify_timequery( + base_timestamp, model::offset{0}, true, model::offset{0}); + + for (int i = 1; i < total_records; ++i) { + const auto min_timestamp = base_timestamp() + + batch_time_delta_ms * (i - 1); + const auto max_timestamp = min_timestamp + batch_time_delta_ms; + const auto query_timestamp = random_generators::get_int( + min_timestamp + 1, max_timestamp); + make_and_verify_timequery( + model::timestamp{query_timestamp}, + model::offset{i}, + true, + model::offset{i}); + } + + // This will attempt to timequery from local disk since cloud storage cannot + // answer it, but won't have a value anyways. + make_and_verify_timequery( + model::timestamp{ + base_timestamp() + (batch_time_delta_ms * total_records)}, + model::offset{total_records}, + false); +} + +FIXTURE_TEST( + test_cloud_storage_timequery_read_replica_mode, read_replica_e2e_fixture) { + const model::topic topic_name("tapioca"); + model::ntp ntp(model::kafka_namespace, topic_name, model::partition_id{0}); + + cluster::topic_properties props; + props.shadow_indexing = model::shadow_indexing_mode::full; + props.retention_local_target_bytes = tristate(0); + add_topic({model::kafka_namespace, topic_name}, 1, props).get(); + wait_for_leader(ntp).get(); + + auto partition = app.partition_manager.local().get(ntp); + auto log = partition->log(); + auto& archiver = partition->archiver().value().get(); + BOOST_REQUIRE(archiver.sync_for_tests().get()); + archiver.upload_topic_manifest().get(); + + const auto batches_per_segment = 1; + const auto num_segs = 5; + const auto batch_time_delta_ms = 10; + const auto base_timestamp = model::timestamp{0}; + tests::remote_segment_generator gen(make_kafka_client().get(), *partition); + auto total_records = gen.num_segments(num_segs) + .batches_per_segment(batches_per_segment) + .base_timestamp(base_timestamp) + .batch_time_delta_ms(batch_time_delta_ms) + .produce() + .get(); + BOOST_REQUIRE_EQUAL(total_records, 5); + + auto rr_rp = start_read_replica_fixture(); + + cluster::topic_properties read_replica_props; + read_replica_props.shadow_indexing = model::shadow_indexing_mode::disabled; + read_replica_props.read_replica = true; + read_replica_props.read_replica_bucket = "test-bucket"; + rr_rp + ->add_topic({model::kafka_namespace, topic_name}, 1, read_replica_props) + .get(); + rr_rp->wait_for_leader(ntp).get(); + auto rr_partition = rr_rp->app.partition_manager.local().get(ntp).get(); + auto rr_archiver_ref = rr_partition->archiver(); + BOOST_REQUIRE(rr_archiver_ref.has_value()); + auto& rr_archiver = rr_partition->archiver()->get(); + BOOST_REQUIRE(rr_archiver.sync_for_tests().get()); + rr_archiver.sync_manifest().get(); + BOOST_REQUIRE_EQUAL(rr_archiver.manifest().size(), 5); + + auto make_and_verify_timequery = + [rr_partition]( + model::timestamp t, + model::offset o, + bool expect_value = false, + std::optional expected_o = std::nullopt) { + auto timequery_conf = storage::timequery_config( + model::offset(0), t, o, ss::default_priority_class(), std::nullopt); + + auto result = rr_partition->timequery(timequery_conf).get(); + + if (expect_value) { + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE_EQUAL(result.value().offset, expected_o.value()); + } else { + BOOST_REQUIRE(!result.has_value()); + } + }; + + make_and_verify_timequery( + base_timestamp, model::offset{0}, true, model::offset{0}); + + for (int i = 1; i < total_records; ++i) { + const auto min_timestamp = base_timestamp() + + batch_time_delta_ms * (i - 1); + const auto max_timestamp = min_timestamp + batch_time_delta_ms; + const auto query_timestamp = random_generators::get_int( + min_timestamp + 1, max_timestamp); + make_and_verify_timequery( + model::timestamp{query_timestamp}, + model::offset{i}, + true, + model::offset{i}); + } + + // This won't have a valid result in cloud storage. + make_and_verify_timequery( + model::timestamp{ + base_timestamp() + (batch_time_delta_ms * total_records)}, + model::offset{total_records}, + false); +} + +FIXTURE_TEST(test_mixed_timequery, e2e_fixture) { + const model::topic topic_name("tapioca"); + model::ntp ntp(model::kafka_namespace, topic_name, model::partition_id{0}); + + // Enable full shadow indexing for now. + cluster::topic_properties props; + props.shadow_indexing = model::shadow_indexing_mode::full; + + add_topic({model::kafka_namespace, topic_name}, 1, props).get(); + + wait_for_leader(ntp).get(); + + auto partition = app.partition_manager.local().get(ntp); + auto log = partition->log(); + auto& archiver = partition->archiver().value().get(); + BOOST_REQUIRE(archiver.sync_for_tests().get()); + + // Generate batches [0, 10, 20, ..., 100] + const auto num_segs = 11; + const auto batches_per_segment = 1; + const auto batch_time_delta_ms = 10; + tests::remote_segment_generator gen(make_kafka_client().get(), *partition); + auto total_records = gen.num_segments(num_segs) + .batches_per_segment(batches_per_segment) + .base_timestamp(model::timestamp{0}) + .batch_time_delta_ms(batch_time_delta_ms) + .produce() + .get(); + BOOST_REQUIRE_EQUAL(total_records, 11); + + const auto base_timestamp = log->start_timestamp(); + BOOST_REQUIRE_EQUAL(base_timestamp, model::timestamp{0}); + + const auto num_segments_to_keep = 2; + const auto upper_timestamp = base_timestamp() + + (num_segs - num_segments_to_keep) + * batch_time_delta_ms; + const auto max_timestamp = base_timestamp() + + (num_segs - 1) * batch_time_delta_ms; + + // Sum the sizes of trailing segments + const auto& segments = log->segments(); + const size_t max_bytes = std::accumulate( + std::next(segments.begin(), segments.size() - num_segments_to_keep), + segments.end(), + size_t{0}, + [](size_t size, const auto& seg) { return size + seg->file_size(); }); + + // Force garbage collection of all local records [0, upper_timestamp). Full + // records [0, max_timestamp] still exist in the cloud. + storage::gc_config gc_conf(model::timestamp{upper_timestamp}, max_bytes); + log->gc(gc_conf).get(); + + RPTEST_REQUIRE_EVENTUALLY(10s, [log = partition->log()] { + return log->segments().size() == num_segments_to_keep; + }); + + // Disable remote fetch, forcing local data usage only. + auto disable_fetch_override = storage::ntp_config::default_overrides{ + .shadow_indexing_mode = model::shadow_indexing_mode::archival}; + log->update_configuration(disable_fetch_override).get(); + + auto make_and_verify_timequery = + [partition]( + model::timestamp t, + model::offset o, + bool expect_value = false, + std::optional expected_o = std::nullopt) { + auto timequery_conf = storage::timequery_config( + model::offset(0), t, o, ss::default_priority_class(), std::nullopt); + + auto result = partition->timequery(timequery_conf).get(); + + if (expect_value) { + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE_EQUAL(result.value().offset, expected_o.value()); + } else { + BOOST_REQUIRE(!result.has_value()); + } + }; + + // Queries for timestamps [0, upper_timestamp] should return + // [upper_timestamp], since we cannot read from cloud storage, and we have + // deleted local records [0, upper_timestamp) + for (int i = 0; i <= upper_timestamp; ++i) { + make_and_verify_timequery( + model::timestamp{i}, + model::offset::max(), + true, + model::offset{num_segs - num_segments_to_keep + 1}); + } + + // Queries for timestamps (upper_timestamp, max_timestamp] should return + // [max_timestamp]. + for (int i = upper_timestamp + 1; i < max_timestamp; ++i) { + make_and_verify_timequery( + model::timestamp{i}, + model::offset::max(), + true, + model::offset{num_segs - 1}); + } + + // Enable remote fetch. + auto allow_fetch_override = storage::ntp_config::default_overrides{ + .shadow_indexing_mode = model::shadow_indexing_mode::fetch}; + log->update_configuration(allow_fetch_override).get(); + + // Now, timequeries should be able to read over the whole domain [0, + // max_timestamp] + for (int i = 0; i < num_segs; ++i) { + auto timestamp = base_timestamp() + i * batch_time_delta_ms; + make_and_verify_timequery( + model::timestamp{timestamp}, + model::offset::max(), + true, + model::offset{i}); + } +} diff --git a/src/v/cloud_storage/tests/cloud_storage_fixture.h b/src/v/cloud_storage/tests/cloud_storage_fixture.h index 22778716b0fb8..bfc0397e7ce2a 100644 --- a/src/v/cloud_storage/tests/cloud_storage_fixture.h +++ b/src/v/cloud_storage/tests/cloud_storage_fixture.h @@ -49,7 +49,8 @@ struct cloud_storage_fixture : s3_imposter_fixture { config::mock_binding(0.0), config::mock_binding(1024 * 1024 * 1024), config::mock_binding>(std::nullopt), - config::mock_binding(100000)) + config::mock_binding(100000), + config::mock_binding(3)) .get(); cache.invoke_on_all([](cloud_storage::cache& c) { return c.start(); }) diff --git a/src/v/cloud_storage/tests/directory_walker_test.cc b/src/v/cloud_storage/tests/directory_walker_test.cc index bb54246db2dc4..03d0d5783ca42 100644 --- a/src/v/cloud_storage/tests/directory_walker_test.cc +++ b/src/v/cloud_storage/tests/directory_walker_test.cc @@ -62,7 +62,7 @@ SEASTAR_THREAD_TEST_CASE(one_level) { file2.close().get(); access_time_tracker tracker; - auto result = _walker.walk(target_dir.native(), tracker).get(); + auto result = _walker.walk(target_dir.native(), tracker, 3).get(); auto expect = std::set{ file_path1.native(), file_path2.native()}; @@ -81,6 +81,8 @@ SEASTAR_THREAD_TEST_CASE(three_levels) { ss::recursive_touch_directory((target_dir / "a").native()).get(); ss::recursive_touch_directory((target_dir / "b" / "c").native()).get(); + ss::recursive_touch_directory((target_dir / "b" / "c-empty").native()) + .get(); auto flags = ss::open_flags::wo | ss::open_flags::create | ss::open_flags::exclusive; @@ -94,10 +96,11 @@ SEASTAR_THREAD_TEST_CASE(three_levels) { file3.close().get(); access_time_tracker tracker; - auto result = _walker.walk(target_dir.native(), tracker).get(); + auto result = _walker.walk(target_dir.native(), tracker, 3).get(); BOOST_REQUIRE_EQUAL(result.cache_size, 0); BOOST_REQUIRE_EQUAL(result.regular_files.size(), 3); + BOOST_REQUIRE_EQUAL(result.empty_dirs.size(), 1); auto expect = std::set{ file_path1.native(), file_path2.native(), file_path3.native()}; @@ -115,7 +118,7 @@ SEASTAR_THREAD_TEST_CASE(no_files) { ss::recursive_touch_directory(dir2.native()).get(); access_time_tracker tracker; - auto result = _walker.walk(target_dir.native(), tracker).get(); + auto result = _walker.walk(target_dir.native(), tracker, 3).get(); BOOST_REQUIRE_EQUAL(result.cache_size, 0); BOOST_REQUIRE_EQUAL(result.regular_files.size(), 0); @@ -127,7 +130,7 @@ SEASTAR_THREAD_TEST_CASE(empty_dir) { const std::filesystem::path target_dir = tmpdir.get_path(); access_time_tracker tracker; - auto result = _walker.walk(target_dir.native(), tracker).get(); + auto result = _walker.walk(target_dir.native(), tracker, 3).get(); BOOST_REQUIRE_EQUAL(result.cache_size, 0); BOOST_REQUIRE_EQUAL(result.regular_files.size(), 0); @@ -181,6 +184,7 @@ SEASTAR_THREAD_TEST_CASE(total_size_correct) { .walk( target_dir.native(), tracker, + 3, [](std::string_view path) { return !( std::string_view(path).ends_with(".tx") diff --git a/src/v/cloud_storage/tests/read_replica_fixture.h b/src/v/cloud_storage/tests/read_replica_fixture.h new file mode 100644 index 0000000000000..2b1cb2e4386e3 --- /dev/null +++ b/src/v/cloud_storage/tests/read_replica_fixture.h @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "cloud_storage/tests/s3_imposter.h" +#include "cloud_storage/types.h" +#include "config/configuration.h" +#include "model/fundamental.h" +#include "redpanda/tests/fixture.h" +#include "test_utils/scoped_config.h" + +class read_replica_e2e_fixture + : public s3_imposter_fixture + , public redpanda_thread_fixture + , public enable_cloud_storage_fixture { +public: + read_replica_e2e_fixture() + : redpanda_thread_fixture( + redpanda_thread_fixture::init_cloud_storage_tag{}, + httpd_port_number()) { + // No expectations: tests will PUT and GET organically. + set_expectations_and_listen({}); + wait_for_controller_leadership().get(); + + // Disable metrics to speed things up. + test_local_cfg.get("enable_metrics_reporter").set_value(false); + test_local_cfg.get("disable_metrics").set_value(true); + test_local_cfg.get("disable_public_metrics").set_value(true); + + // Avoid background work since we'll control uploads ourselves. + test_local_cfg.get("cloud_storage_enable_segment_merging") + .set_value(false); + test_local_cfg.get("cloud_storage_disable_upload_loop_for_tests") + .set_value(true); + test_local_cfg.get("cloud_storage_disable_read_replica_loop_for_tests") + .set_value(true); + } + + std::unique_ptr start_read_replica_fixture() { + return std::make_unique( + model::node_id(2), + 9092 + 10, + 33145 + 10, + 8082 + 10, + 8081 + 10, + std::vector{}, + ssx::sformat("test.dir_read_replica{}", time(0)), + app.sched_groups, + true, + get_s3_config(httpd_port_number()), + get_archival_config(), + get_cloud_config(httpd_port_number())); + } + scoped_config test_local_cfg; +}; diff --git a/src/v/cloud_storage/tests/read_replica_test.cc b/src/v/cloud_storage/tests/read_replica_test.cc index 498f2808069c2..16a6c6ef92f2d 100644 --- a/src/v/cloud_storage/tests/read_replica_test.cc +++ b/src/v/cloud_storage/tests/read_replica_test.cc @@ -12,64 +12,18 @@ #include "archival/ntp_archiver_service.h" #include "cloud_storage/spillover_manifest.h" #include "cloud_storage/tests/produce_utils.h" -#include "cloud_storage/tests/s3_imposter.h" +#include "cloud_storage/tests/read_replica_fixture.h" #include "cloud_storage/types.h" #include "config/configuration.h" #include "kafka/server/tests/delete_records_utils.h" #include "kafka/server/tests/list_offsets_utils.h" #include "kafka/server/tests/produce_consume_utils.h" #include "model/fundamental.h" -#include "redpanda/tests/fixture.h" #include "storage/disk_log_impl.h" #include "test_utils/scoped_config.h" using tests::kafka_consume_transport; -class read_replica_e2e_fixture - : public s3_imposter_fixture - , public redpanda_thread_fixture - , public enable_cloud_storage_fixture { -public: - read_replica_e2e_fixture() - : redpanda_thread_fixture( - redpanda_thread_fixture::init_cloud_storage_tag{}, - httpd_port_number()) { - // No expectations: tests will PUT and GET organically. - set_expectations_and_listen({}); - wait_for_controller_leadership().get(); - - // Disable metrics to speed things up. - test_local_cfg.get("enable_metrics_reporter").set_value(false); - test_local_cfg.get("disable_metrics").set_value(true); - test_local_cfg.get("disable_public_metrics").set_value(true); - - // Avoid background work since we'll control uploads ourselves. - test_local_cfg.get("cloud_storage_enable_segment_merging") - .set_value(false); - test_local_cfg.get("cloud_storage_disable_upload_loop_for_tests") - .set_value(true); - test_local_cfg.get("cloud_storage_disable_read_replica_loop_for_tests") - .set_value(true); - } - - std::unique_ptr start_read_replica_fixture() { - return std::make_unique( - model::node_id(2), - 9092 + 10, - 33145 + 10, - 8082 + 10, - 8081 + 10, - std::vector{}, - ssx::sformat("test.dir_read_replica{}", time(0)), - app.sched_groups, - true, - get_s3_config(httpd_port_number()), - get_archival_config(), - get_cloud_config(httpd_port_number())); - } - scoped_config test_local_cfg; -}; - FIXTURE_TEST(test_read_replica_basic_sync, read_replica_e2e_fixture) { const model::topic topic_name("tapioca"); model::ntp ntp(model::kafka_namespace, topic_name, 0); diff --git a/src/v/cloud_storage/tests/remote_partition_fuzz_test.cc b/src/v/cloud_storage/tests/remote_partition_fuzz_test.cc index b4b879bf8ef53..605ccfdce181c 100644 --- a/src/v/cloud_storage/tests/remote_partition_fuzz_test.cc +++ b/src/v/cloud_storage/tests/remote_partition_fuzz_test.cc @@ -14,6 +14,7 @@ #include "cloud_storage/tests/cloud_storage_fixture.h" #include "cloud_storage/tests/s3_imposter.h" #include "cloud_storage/tests/util.h" +#include "model/record_batch_types.h" #include @@ -337,6 +338,49 @@ FIXTURE_TEST( } } +FIXTURE_TEST( + test_remote_partition_scan_incrementally_random_with_tx_fence_random_lso, + cloud_storage_fixture) { + vlog( + test_log.info, + "Seed used for read workload: {}", + random_generators::internal::seed); + + constexpr int num_segments = 1000; + const auto [segment_layout, num_data_batches] = generate_segment_layout( + num_segments, 42, false); + auto segments = setup_s3_imposter(*this, segment_layout); + auto base = segments[0].base_offset; + auto max = segments.back().max_offset; + vlog(test_log.debug, "offset range: {}-{}", base, max); + + try { + auto headers_read + = scan_remote_partition_incrementally_with_closest_lso( + *this, base, max, 5, 5); + vlog(test_log.debug, "{} record batches consumed", headers_read.size()); + model::offset expected_offset{0}; + size_t ix_header = 0; + for (const auto& ix_seg : segment_layout) { + for (const auto& batch : ix_seg) { + if (batch.type == model::record_batch_type::tx_fence) { + expected_offset++; + } else if (batch.type == model::record_batch_type::raft_data) { + auto header = headers_read[ix_header]; + BOOST_REQUIRE_EQUAL(expected_offset, header.base_offset); + expected_offset = header.last_offset() + model::offset(1); + ix_header++; + } else { + // raft_configuratoin or archival_metadata + // no need to update expected_offset or ix_header + } + } + } + } catch (const download_exception& ex) { + vlog(test_log.warn, "timeout connecting to s3 impostor: {}", ex.what()); + } +} + FIXTURE_TEST( test_remote_partition_scan_incrementally_random_with_reuploads, cloud_storage_fixture) { diff --git a/src/v/cloud_storage/tests/remote_partition_test.cc b/src/v/cloud_storage/tests/remote_partition_test.cc index abfff5417909a..9d90a8f0e3d63 100644 --- a/src/v/cloud_storage/tests/remote_partition_test.cc +++ b/src/v/cloud_storage/tests/remote_partition_test.cc @@ -18,6 +18,7 @@ #include "cloud_storage/remote.h" #include "cloud_storage/remote_partition.h" #include "cloud_storage/remote_segment.h" +#include "cloud_storage/spillover_manifest.h" #include "cloud_storage/tests/cloud_storage_fixture.h" #include "cloud_storage/tests/common_def.h" #include "cloud_storage/tests/s3_imposter.h" @@ -58,6 +59,7 @@ #include #include #include +#include #include using namespace std::chrono_literals; @@ -436,7 +438,14 @@ FIXTURE_TEST(test_scan_by_kafka_offset_truncated, cloud_storage_fixture) { *this, model::offset(6), model::offset_delta(3), batch_types); print_segments(segments); for (int i = 0; i <= 2; i++) { - BOOST_REQUIRE(check_fetch(*this, kafka::offset(i), false)); + BOOST_REQUIRE_EXCEPTION( + check_fetch(*this, kafka::offset(i), false), + std::runtime_error, + [](const std::runtime_error& e) { + return std::string(e.what()).find( + "Failed to query spillover manifests") + != std::string::npos; + }); } for (int i = 3; i <= 8; i++) { BOOST_REQUIRE(check_scan(*this, kafka::offset(i), 9 - i)); @@ -488,7 +497,14 @@ FIXTURE_TEST( auto segments = setup_s3_imposter( *this, model::offset(6), model::offset_delta(3), batch_types); print_segments(segments); - BOOST_REQUIRE(check_fetch(*this, kafka::offset(2), false)); + BOOST_REQUIRE_EXCEPTION( + check_fetch(*this, kafka::offset(2), false), + std::runtime_error, + [](const std::runtime_error& e) { + return std::string(e.what()).find( + "Failed to query spillover manifests") + != std::string::npos; + }); BOOST_REQUIRE(check_scan(*this, kafka::offset(3), 1)); BOOST_REQUIRE(check_fetch(*this, kafka::offset(3), true)); BOOST_REQUIRE(check_scan(*this, kafka::offset(4), 0)); @@ -536,7 +552,14 @@ FIXTURE_TEST( *this, model::offset(6), model::offset_delta(0), batch_types); print_segments(segments); for (int i = 0; i < 6; i++) { - BOOST_REQUIRE(check_fetch(*this, kafka::offset(i), false)); + BOOST_REQUIRE_EXCEPTION( + check_fetch(*this, kafka::offset(i), false), + std::runtime_error, + [](const std::runtime_error& e) { + return std::string(e.what()).find( + "Failed to query spillover manifests") + != std::string::npos; + }); } BOOST_REQUIRE(check_scan(*this, kafka::offset(6), 1)); BOOST_REQUIRE(check_fetch(*this, kafka::offset(6), true)); @@ -1241,12 +1264,14 @@ FIXTURE_TEST( vlog(test_log.debug, "Creating new reader {}", reader_config); // After truncation reading from the old end should be impossible - auto reader = partition->make_reader(reader_config).get().reader; - auto headers_read - = reader.consume(counting_batch_consumer(100), model::no_timeout) - .get(); - - BOOST_REQUIRE(headers_read.size() == 0); + BOOST_REQUIRE_EXCEPTION( + partition->make_reader(reader_config).get(), + std::runtime_error, + [](const std::runtime_error& e) { + return std::string(e.what()).find( + "Failed to query spillover manifests") + != std::string::npos; + }); } } @@ -1449,6 +1474,93 @@ FIXTURE_TEST( } } +namespace { +ss::future<> sleep_and_abort(ss::abort_source* as, ss::gate* gate) { + auto holder = gate->hold(); + auto rand_ms = random_generators::get_int(1, 50); + co_await ss::sleep(rand_ms * 1ms); + as->request_abort_ex( + std::system_error(std::make_error_code(std::errc::connection_aborted))); +} +ss::future<> +read(storage::log_reader_config reader_config, remote_partition* partition) { + auto next = reader_config.start_offset; + while (true) { + reader_config.start_offset = next; + auto translating_reader = co_await partition->make_reader( + reader_config); + auto reader = std::move(translating_reader.reader); + auto headers_read = co_await reader.consume( + test_consumer(), model::no_timeout); + if (headers_read.empty()) { + break; + } + next = headers_read.back().last_offset() + model::offset(1); + } +} +} // anonymous namespace + +// With some scheduling points/sleeps injected here and there, regression test +// for a crash seen when racing a client disconnect with the stopping of a +// reader. +FIXTURE_TEST(test_remote_partition_abort_eos_race, cloud_storage_fixture) { + batch_t data = { + .num_records = 1, .type = model::record_batch_type::raft_data}; + batch_t conf = { + .num_records = 1, .type = model::record_batch_type::raft_configuration}; + batch_t tx_fence = { + .num_records = 1, .type = model::record_batch_type::tx_fence}; + const std::vector> batch_types = { + {conf, data, data, data, data, data, data, tx_fence, data}, + {conf, data, data, data, data, data, data, tx_fence, data}, + {conf, data, data, data, data, data, data, tx_fence, data}, + }; + + auto segments = setup_s3_imposter( + *this, batch_types, manifest_inconsistency::none); + auto base = segments[0].base_offset; + auto max = segments[0].max_offset; + + vlog(test_log.debug, "offset range: {}-{}", base, max); + print_segments(segments); + + ss::lowres_clock::update(); + static auto bucket = cloud_storage_clients::bucket_name("bucket"); + auto manifest = hydrate_manifest(api.local(), bucket); + partition_probe probe(manifest.get_ntp()); + auto manifest_view = ss::make_shared( + api, cache, manifest, bucket); + auto partition = ss::make_shared( + manifest_view, api.local(), cache.local(), bucket, probe); + auto partition_stop = ss::defer([&partition] { partition->stop().get(); }); + partition->start().get(); + + // Intentionally use max - 1 so the reader stops early and is forced to + // handle it as an EOS. + ss::abort_source as; + storage::log_reader_config reader_config( + base, model::offset{max() - 1}, ss::default_priority_class()); + reader_config.abort_source = as; + + std::vector> futs; + ss::gate gate; + + // Explicitly abort, simulating the behavior when a client disconnects. + ssx::background = sleep_and_abort(&as, &gate); + for (int i = 0; i < 10; i++) { + futs.emplace_back(read(reader_config, partition.get())); + } + for (auto& f : futs) { + // Ignore exceptions from the reader -- we only care that we don't + // crash. + try { + std::move(f).get(); + } catch (...) { + } + } + gate.close().get(); +} + /// This test scans the partition with overlapping segments FIXTURE_TEST( test_remote_partition_scan_translate_overlap_1, cloud_storage_fixture) { @@ -2016,9 +2128,11 @@ FIXTURE_TEST(test_stale_reader, cloud_storage_fixture) { // Returns true if a kafka::offset scan returns the expected number of records. bool timequery( cloud_storage_fixture& fixture, + model::offset min, model::timestamp tm, int expected_num_records) { - auto scan_res = scan_remote_partition(fixture, tm); + auto scan_res = scan_remote_partition( + fixture, min, tm, model::offset::max()); int num_data_records = 0; size_t bytes_read_acc = 0; for (const auto& hdr : scan_res.headers) { @@ -2066,6 +2180,14 @@ bool timequery( return ret; } +// Returns true if a kafka::offset scan returns the expected number of records. +bool timequery( + cloud_storage_fixture& fixture, + model::timestamp tm, + int expected_num_records) { + return timequery(fixture, model::offset(0), tm, expected_num_records); +} + FIXTURE_TEST(test_scan_by_timestamp, cloud_storage_fixture) { // Build cloud partition with provided timestamps and query // it using the timequery. @@ -2128,3 +2250,253 @@ FIXTURE_TEST(test_scan_by_timestamp, cloud_storage_fixture) { test_log.debug("Timestamp undershoots the partition"); BOOST_REQUIRE(timequery(*this, model::timestamp(100), num_data_batches)); } + +FIXTURE_TEST(test_out_of_range_query, cloud_storage_fixture) { + auto data = [&](size_t t) { + return batch_t{ + .num_records = 1, + .type = model::record_batch_type::raft_data, + .timestamp = model::timestamp(t)}; + }; + + const std::vector> batches = { + {data(1000), data(1002), data(1004), data(1006), data(1008), data(1010)}, + {data(1012), data(1014), data(1016), data(1018), data(1020), data(1022)}, + }; + + auto segments = make_segments(batches, false, false); + cloud_storage::partition_manifest manifest(manifest_ntp, manifest_revision); + + auto expectations = make_imposter_expectations(manifest, segments); + set_expectations_and_listen(expectations); + + // Advance start offset as-if archiver did apply retention but didn't + // run GC yet (the clean offset is not updated). + BOOST_REQUIRE(manifest.advance_start_offset(segments[1].base_offset)); + auto serialize_manifest = [](const cloud_storage::partition_manifest& m) { + auto s_data = m.serialize().get(); + auto buf = s_data.stream.read_exactly(s_data.size_bytes).get(); + return ss::sstring(buf.begin(), buf.end()); + }; + std::ostringstream ostr; + manifest.serialize_json(ostr); + + vlog( + test_util_log.info, + "Rewriting manifest at {}:\n{}", + manifest.get_manifest_path(), + ostr.str()); + + auto manifest_url = "/" + manifest.get_manifest_path()().string(); + remove_expectations({manifest_url}); + add_expectations({ + cloud_storage_fixture::expectation{ + .url = manifest_url, .body = serialize_manifest(manifest)}, + }); + + auto base = segments[0].base_offset; + auto max = segments[segments.size() - 1].max_offset; + + vlog(test_log.debug, "offset range: {}-{}", base, max); + + BOOST_REQUIRE( + scan_remote_partition(*this, segments[1].base_offset, max).size() == 6); + + BOOST_REQUIRE_EXCEPTION( + scan_remote_partition(*this, base, max), + std::runtime_error, + [](const auto& ex) { + ss::sstring what{ex.what()}; + return what.find("Failed to query spillover manifests") != what.npos; + }); + + test_log.debug("Timestamp undershoots the partition"); + BOOST_TEST_REQUIRE(timequery(*this, model::timestamp(100), 6)); + + test_log.debug("Timestamp withing segment"); + BOOST_TEST_REQUIRE(timequery(*this, model::timestamp(1014), 5)); +} + +FIXTURE_TEST(test_out_of_range_spillover_query, cloud_storage_fixture) { + auto data = [&](size_t t) { + return batch_t{ + .num_records = 1, + .type = model::record_batch_type::raft_data, + .timestamp = model::timestamp(t)}; + }; + + const std::vector> batches = { + {data(1000), data(1002), data(1004), data(1006), data(1008), data(1010)}, + {data(1012), data(1014), data(1016), data(1018), data(1020), data(1022)}, + {data(1024), data(1026), data(1028), data(1030), data(1032), data(1034)}, + {data(1036), data(1038), data(1040), data(1042), data(1044), data(1046)}, + {data(1048), data(1050), data(1052), data(1054), data(1056), data(1058)}, + {data(1060), data(1062), data(1064), data(1066), data(1068), data(1070)}, + }; + + auto segments = make_segments(batches, false, false); + cloud_storage::partition_manifest manifest(manifest_ntp, manifest_revision); + + auto expectations = make_imposter_expectations(manifest, segments); + set_expectations_and_listen(expectations); + + for (int i = 0; i < 2; i++) { + spillover_manifest spm(manifest_ntp, manifest_revision); + + for (int j = 0; auto s : manifest) { + spm.add(s); + if (++j == 2) { + break; + } + } + manifest.spillover(spm.make_manifest_metadata()); + + std::ostringstream ostr; + spm.serialize_json(ostr); + + vlog( + test_util_log.info, + "Uploading spillover manifest at {}:\n{}", + spm.get_manifest_path(), + ostr.str()); + + auto s_data = spm.serialize().get(); + auto buf = s_data.stream.read_exactly(s_data.size_bytes).get(); + add_expectations({cloud_storage_fixture::expectation{ + .url = "/" + spm.get_manifest_path()().string(), + .body = ss::sstring(buf.begin(), buf.end()), + }}); + } + + // Advance start offset as-if archiver did apply retention but didn't + // run GC yet (the clean offset is not updated). + // + // We set it to the second segment of the second spillover manifest in an + // attempt to cover more potential edge cases. + auto archive_start = segments[3].base_offset; + manifest.set_archive_start_offset(archive_start, model::offset_delta(0)); + + // Upload latest manifest version. + auto serialize_manifest = [](const cloud_storage::partition_manifest& m) { + auto s_data = m.serialize().get(); + auto buf = s_data.stream.read_exactly(s_data.size_bytes).get(); + return ss::sstring(buf.begin(), buf.end()); + }; + std::ostringstream ostr; + manifest.serialize_json(ostr); + + vlog( + test_util_log.info, + "Rewriting manifest at {}:\n{}", + manifest.get_manifest_path(), + ostr.str()); + + auto manifest_url = "/" + manifest.get_manifest_path()().string(); + remove_expectations({manifest_url}); + add_expectations({ + cloud_storage_fixture::expectation{ + .url = manifest_url, .body = serialize_manifest(manifest)}, + }); + + auto base = segments[0].base_offset; + auto max = segments[segments.size() - 1].max_offset; + + vlog(test_log.debug, "offset range: {}-{}", base, max); + + // Can query from start of the archive. + BOOST_REQUIRE( + scan_remote_partition(*this, archive_start, max).size() == 3 * 6); + + // Can timequery from start of the archive. + BOOST_TEST_REQUIRE( + timequery(*this, archive_start, model::timestamp(100), 3 * 6)); + + // Can't query from the start of partition. + BOOST_REQUIRE_EXCEPTION( + scan_remote_partition(*this, base, max), + std::runtime_error, + [](const auto& ex) { + ss::sstring what{ex.what()}; + return what.find("Failed to query spillover manifests") != what.npos; + }); + + // Can't timequery from the base offset. + BOOST_REQUIRE_EXCEPTION( + timequery(*this, base, model::timestamp(100), 3 * 6), + std::runtime_error, + [](const auto& ex) { + ss::sstring what{ex.what()}; + return what.find("Failed to query spillover manifests") != what.npos; + }); + + // Can't query from start of the still valid spillover manifest. + // Since we don't rewrite spillover manifests we want to be sure that + // we don't allow querying stale segments (below the start offset). + BOOST_REQUIRE_EXCEPTION( + scan_remote_partition(*this, segments[2].base_offset, max), + std::runtime_error, + [](const auto& ex) { + ss::sstring what{ex.what()}; + return what.find("Failed to query spillover manifests") != what.npos; + }); + + // Can't query from start of the still valid spillover manifest. + // Since we don't rewrite spillover manifests we want to be sure that + // we don't allow querying stale segments (below the start offset). + // BUG: Currently it succeeds. This is a bug and should be fixed. + // BOOST_REQUIRE_EXCEPTION( + // timequery(*this, segments[2].base_offset, model::timestamp(100), 3 * + // 6), std::runtime_error, + // [](const auto& ex) { + // ss::sstring what{ex.what()}; + // return what.find("Failed to query spillover manifests") != + // what.npos; + // }); + BOOST_TEST_REQUIRE( + timequery(*this, segments[2].base_offset, model::timestamp(100), 3 * 6)); + + test_log.debug("Timestamp within valid spillover but below archive start"); + BOOST_TEST_REQUIRE( + timequery(*this, segments[2].base_timestamp.value(), 3 * 6)); + + test_log.debug("Valid timestamp start of retention"); + BOOST_TEST_REQUIRE( + timequery(*this, batches[3][0].timestamp.value(), 3 * 6)); + + test_log.debug("Valid timestamp within retention"); + BOOST_TEST_REQUIRE( + timequery(*this, batches[3][1].timestamp.value(), 3 * 6 - 1)); + + test_log.debug("Timestamp overshoots the partition"); + BOOST_TEST_REQUIRE(timequery(*this, model::timestamp::max(), 0)); + + // Rewrite the manifest with clean offset to match start offset. + manifest.set_archive_clean_offset( + archive_start, manifest.archive_size_bytes() / 2); + vlog( + test_util_log.info, + "Rewriting manifest at {}:\n{}", + manifest.get_manifest_path(), + ostr.str()); + + remove_expectations({manifest_url}); + add_expectations({ + cloud_storage_fixture::expectation{ + .url = manifest_url, .body = serialize_manifest(manifest)}, + }); + + // Still can't query from the base offset. + BOOST_REQUIRE_EXCEPTION( + scan_remote_partition(*this, base, max), + std::runtime_error, + [](const auto& ex) { + ss::sstring what{ex.what()}; + return what.find("Failed to query spillover manifests") != what.npos; + }); + + // Timequery from base offset must fail too as the regular query. + // BUG: Currently it succeeds. This is a bug and should be fixed. + BOOST_TEST_REQUIRE(timequery(*this, base, model::timestamp(100), 3 * 6)); + BOOST_TEST_REQUIRE( + timequery(*this, segments[2].base_offset, model::timestamp(100), 3 * 6)); +} diff --git a/src/v/cloud_storage/tests/remote_test.cc b/src/v/cloud_storage/tests/remote_test.cc index 174a5f0eda250..fbb9e51bb0add 100644 --- a/src/v/cloud_storage/tests/remote_test.cc +++ b/src/v/cloud_storage/tests/remote_test.cc @@ -641,6 +641,130 @@ FIXTURE_TEST(test_list_bucket, remote_fixture) { } } +FIXTURE_TEST(test_list_bucket_with_max_keys, remote_fixture) { + set_expectations_and_listen({}); + cloud_storage_clients::bucket_name bucket{"test"}; + retry_chain_node fib(never_abort, 10s, 20ms); + + const auto s3_imposter_max_keys = s3_imposter_fixture::default_max_keys; + const auto size = s3_imposter_max_keys + 50; + for (int i = 0; i < size; i++) { + cloud_storage_clients::object_key path{fmt::format("{}", i)}; + auto result + = remote.local().upload_object(bucket, path, iobuf{}, fib).get(); + BOOST_REQUIRE_EQUAL(cloud_storage::upload_result::success, result); + } + + { + // Passing max_keys indicates we, as a user, will handle truncation + // results. Here, we know that that size > s3_imposter_max_keys, and the + // result will end up truncated. + auto max_keys = s3_imposter_max_keys; + auto result + = remote.local() + .list_objects( + bucket, fib, std::nullopt, std::nullopt, std::nullopt, max_keys) + .get(); + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE(result.value().is_truncated); + // This continuation token is /54 because objects are sorted + // lexicographically. + BOOST_REQUIRE_EQUAL(result.value().next_continuation_token, "/54"); + BOOST_REQUIRE_EQUAL( + result.value().contents.size(), s3_imposter_max_keys); + BOOST_REQUIRE(result.value().common_prefixes.empty()); + + // Now, we can use the next_continuation_token from the previous, + // truncated result in order to query for the rest of the objects. We + // should expect to get the rest of the objects in "storage", and that + // this request is not truncated. + auto next_result = remote.local() + .list_objects( + bucket, + fib, + std::nullopt, + std::nullopt, + std::nullopt, + max_keys, + result.value().next_continuation_token) + .get(); + BOOST_REQUIRE(next_result.has_value()); + BOOST_REQUIRE(!next_result.value().is_truncated); + BOOST_REQUIRE_EQUAL( + next_result.value().contents.size(), size - s3_imposter_max_keys); + BOOST_REQUIRE(next_result.value().common_prefixes.empty()); + } + { + // On the other hand, passing max_keys as std::nullopt means + // truncation will be handled by the remote API, (all object keys will + // be read in a loop, we should expect no truncation in the return + // value), and the result contents should be full. + auto max_keys = std::nullopt; + auto result + = remote.local() + .list_objects( + bucket, fib, std::nullopt, std::nullopt, std::nullopt, max_keys) + .get(); + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE(!result.value().is_truncated); + BOOST_REQUIRE_EQUAL(result.value().contents.size(), size); + BOOST_REQUIRE(result.value().common_prefixes.empty()); + } + { + auto max_keys = 2; + auto result + = remote.local() + .list_objects( + bucket, fib, std::nullopt, std::nullopt, std::nullopt, max_keys) + .get(); + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE(result.value().is_truncated); + // This continuation token is /10 because objects are sorted + // lexicographically. + BOOST_REQUIRE_EQUAL(result.value().next_continuation_token, "/10"); + const auto& contents = result.value().contents; + BOOST_REQUIRE_EQUAL(contents.size(), max_keys); + BOOST_REQUIRE_EQUAL(contents[0].key, "0"); + BOOST_REQUIRE_EQUAL(contents[1].key, "1"); + BOOST_REQUIRE(result.value().common_prefixes.empty()); + } + { + // This will also be truncated, since size > s3_imposter_max_keys. + auto max_keys = size; + auto result + = remote.local() + .list_objects( + bucket, fib, std::nullopt, std::nullopt, std::nullopt, max_keys) + .get(); + BOOST_REQUIRE(result.has_value()); + BOOST_REQUIRE(result.value().is_truncated); + BOOST_REQUIRE_EQUAL( + result.value().contents.size(), s3_imposter_max_keys); + // This continuation token is /54 because objects are sorted + // lexicographically. + BOOST_REQUIRE_EQUAL(result.value().next_continuation_token, "/54"); + BOOST_REQUIRE(result.value().common_prefixes.empty()); + + // Reissue another request with continuation-token. This should capture + // the rest of the object keys, we expect a non-truncated result. + auto next_result = remote.local() + .list_objects( + bucket, + fib, + std::nullopt, + std::nullopt, + std::nullopt, + max_keys, + result.value().next_continuation_token) + .get(); + BOOST_REQUIRE(next_result.has_value()); + BOOST_REQUIRE(!next_result.value().is_truncated); + BOOST_REQUIRE_EQUAL( + next_result.value().contents.size(), size - s3_imposter_max_keys); + BOOST_REQUIRE(next_result.value().common_prefixes.empty()); + } +} + FIXTURE_TEST(test_list_bucket_with_prefix, remote_fixture) { set_expectations_and_listen({}); cloud_storage_clients::bucket_name bucket{"test"}; @@ -668,7 +792,6 @@ FIXTURE_TEST(test_list_bucket_with_prefix, remote_fixture) { BOOST_REQUIRE_EQUAL(request.method, "GET"); BOOST_REQUIRE_EQUAL(request.q_list_type, "2"); BOOST_REQUIRE_EQUAL(request.q_prefix, "x/"); - BOOST_REQUIRE_EQUAL(request.h_prefix, "x/"); } FIXTURE_TEST(test_list_bucket_with_filter, remote_fixture) { diff --git a/src/v/cloud_storage/tests/s3_imposter.cc b/src/v/cloud_storage/tests/s3_imposter.cc index afe1a10d08ade..78061a2acc42d 100644 --- a/src/v/cloud_storage/tests/s3_imposter.cc +++ b/src/v/cloud_storage/tests/s3_imposter.cc @@ -42,17 +42,39 @@ uint16_t unit_test_httpd_port_number() { return 4442; } namespace { +using expectation_map_t + = std::map; + // Takes the input map of keys to expectations and returns a stringified XML // corresponding to the appropriate S3 response. ss::sstring list_objects_resp( - const std::map& objects, + const expectation_map_t& objects, ss::sstring prefix, - ss::sstring delimiter) { + ss::sstring delimiter, + std::optional max_keys_opt, + std::optional continuation_token_opt) { std::map content_key_to_size; std::set common_prefixes; // Filter by prefix and group by the substring between the prefix and first // delimiter. - for (const auto& [_, expectation] : objects) { + auto max_keys = max_keys_opt.has_value() + ? std::min( + max_keys_opt.value(), + s3_imposter_fixture::default_max_keys) + : s3_imposter_fixture::default_max_keys; + auto it = (continuation_token_opt.has_value()) + ? objects.find(continuation_token_opt.value()) + : objects.begin(); + auto end_it = objects.end(); + ss::sstring next_continuation_token = ""; + for (; it != end_it; ++it) { + const auto& expectation = it->second; + + if (content_key_to_size.size() == max_keys) { + next_continuation_token = it->first; + break; + } + auto key = expectation.url; if (!key.empty() && key[0] == '/') { // Remove / character that S3 client adds @@ -89,6 +111,8 @@ ss::sstring list_objects_resp( prefix + key.substr(prefix.size(), delimiter_pos - prefix.size() + 1)); } + + const bool is_truncated = (it != end_it); // Populate the returned XML. ss::sstring ret; ret += fmt::format( @@ -97,14 +121,17 @@ ss::sstring list_objects_resp( test-bucket {} {} - 1000 + {} {} - false - next + {} + {} )xml", prefix, content_key_to_size.size(), - delimiter); + max_keys, + delimiter, + is_truncated, + next_continuation_token); for (const auto& [key, size] : content_key_to_size) { ret += fmt::format( R"xml( @@ -212,15 +239,32 @@ struct s3_imposter_fixture::content_handler { if ( fixture._search_on_get_list && request.get_query_param("list-type") == "2") { - auto prefix = request.get_header("prefix"); - auto delimiter = request.get_header("delimiter"); + auto prefix = request.get_query_param("prefix"); + auto delimiter = request.get_query_param("delimiter"); + auto max_keys_str = request.get_query_param("max-keys"); + auto continuation_token_str = request.get_query_param( + "continuation-token"); + std::optional max_keys = (max_keys_str.empty()) + ? std::optional{} + : std::stoi(max_keys_str); + std::optional continuation_token + = (continuation_token_str.empty()) + ? std::optional{} + : continuation_token_str; vlog( fixt_log.trace, - "S3 imposter list request {} - {} - {}", + "S3 imposter list request {} - {} - {} - {} - {}", prefix, delimiter, + max_keys, + continuation_token, request._method); - return list_objects_resp(expectations, prefix, delimiter); + return list_objects_resp( + expectations, + prefix, + delimiter, + max_keys, + continuation_token); } if ( expect_iter == expectations.end() @@ -319,7 +363,7 @@ struct s3_imposter_fixture::content_handler { RPTEST_FAIL("Unexpected request"); return ""; } - std::map expectations; + expectation_map_t expectations; s3_imposter_fixture& fixture; std::optional> headers = std::nullopt; }; diff --git a/src/v/cloud_storage/tests/s3_imposter.h b/src/v/cloud_storage/tests/s3_imposter.h index b654329751de5..cac3eb53b8a2f 100644 --- a/src/v/cloud_storage/tests/s3_imposter.h +++ b/src/v/cloud_storage/tests/s3_imposter.h @@ -38,6 +38,7 @@ /// be retrieved using the GET request or deleted using the DELETE request. class s3_imposter_fixture { public: + static constexpr size_t default_max_keys = 100; uint16_t httpd_port_number(); static constexpr const char* httpd_host_name = "127.0.0.1"; diff --git a/src/v/cloud_storage/tests/topic_manifest_test.cc b/src/v/cloud_storage/tests/topic_manifest_test.cc index e926c274a2533..f3d993346d76e 100644 --- a/src/v/cloud_storage/tests/topic_manifest_test.cc +++ b/src/v/cloud_storage/tests/topic_manifest_test.cc @@ -257,7 +257,17 @@ SEASTAR_THREAD_TEST_CASE(topic_manifest_min_serialization) { auto rstr = make_iobuf_input_stream(std::move(buf)); topic_manifest restored; restored.update(std::move(rstr)).get(); - BOOST_REQUIRE(m == restored); + BOOST_CHECK_EQUAL(m.get_revision(), restored.get_revision()); + auto restored_cfg = restored.get_topic_config().value(); + // as a safety net, negative values for retention_duration are converted to + // disabled tristate (infinite retention) + BOOST_CHECK(restored_cfg.properties.retention_duration.is_disabled()); + + BOOST_CHECK_EQUAL( + restored_cfg.properties.retention_bytes, + min_cfg.properties.retention_bytes); + BOOST_CHECK_EQUAL( + restored_cfg.properties.segment_size, min_cfg.properties.segment_size); } SEASTAR_THREAD_TEST_CASE(topic_manifest_max_serialization) { @@ -359,11 +369,9 @@ SEASTAR_THREAD_TEST_CASE(test_negative_property_manifest) { BOOST_REQUIRE_EQUAL(64, tp_cfg->partition_count); BOOST_REQUIRE_EQUAL(6, tp_cfg->replication_factor); auto tp_props = tp_cfg->properties; - BOOST_REQUIRE(tp_props.retention_duration.has_optional_value()); - BOOST_REQUIRE_EQUAL( - tp_props.retention_duration.value().count(), -36000000000); - // The usigned types that were passed in negative values shouldn't be set. + // The unsigned types that were passed in negative values shouldn't be set. + BOOST_REQUIRE(tp_props.retention_duration.is_disabled()); BOOST_REQUIRE(tp_props.retention_bytes.is_disabled()); BOOST_REQUIRE(!tp_props.segment_size.has_value()); } diff --git a/src/v/cloud_storage/tests/util.cc b/src/v/cloud_storage/tests/util.cc index 1dba9d585413b..2e22041cc819d 100644 --- a/src/v/cloud_storage/tests/util.cc +++ b/src/v/cloud_storage/tests/util.cc @@ -10,6 +10,7 @@ */ #include "cloud_storage/tests/util.h" +#include "model/record.h" #include "model/record_batch_types.h" #include @@ -741,6 +742,10 @@ std::vector scan_remote_partition( partition_probe probe(manifest.get_ntp()); auto manifest_view = ss::make_shared( imposter.api, imposter.cache, manifest, bucket); + auto manifest_view_stop = ss::defer( + [&manifest_view] { manifest_view->stop().get(); }); + manifest_view->start().get(); + auto partition = ss::make_shared( manifest_view, imposter.api.local(), @@ -763,6 +768,7 @@ std::vector scan_remote_partition( /// Similar to previous function but uses timequery to start the scan scan_result scan_remote_partition( cloud_storage_fixture& imposter, + model::offset min, model::timestamp timestamp, model::offset max, size_t maybe_max_segments, @@ -787,12 +793,16 @@ scan_result scan_remote_partition( } auto manifest = hydrate_manifest(imposter.api.local(), bucket); storage::log_reader_config reader_config( - model::offset(0), max, ss::default_priority_class()); + min, max, ss::default_priority_class()); reader_config.first_timestamp = timestamp; partition_probe probe(manifest.get_ntp()); auto manifest_view = ss::make_shared( imposter.api, imposter.cache, manifest, bucket); + auto manifest_view_stop = ss::defer( + [&manifest_view] { manifest_view->stop().get(); }); + + manifest_view->start().get(); auto partition = ss::make_shared( manifest_view, imposter.api.local(), @@ -823,6 +833,89 @@ scan_result scan_remote_partition( }; } +std::vector +scan_remote_partition_incrementally_with_closest_lso( + cloud_storage_fixture& imposter, + model::offset base, + model::offset max, + size_t maybe_max_segments, + size_t maybe_max_readers) { + ss::lowres_clock::update(); + auto conf = imposter.get_configuration(); + static auto bucket = cloud_storage_clients::bucket_name("bucket"); + if (maybe_max_segments) { + config::shard_local_cfg() + .cloud_storage_max_materialized_segments_per_shard.set_value( + maybe_max_segments); + } + if (maybe_max_readers) { + config::shard_local_cfg() + .cloud_storage_max_segment_readers_per_shard.set_value( + maybe_max_readers); + } + auto manifest = hydrate_manifest(imposter.api.local(), bucket); + partition_probe probe(manifest.get_ntp()); + + auto manifest_view = ss::make_shared( + imposter.api, imposter.cache, manifest, bucket); + + auto partition = ss::make_shared( + manifest_view, + imposter.api.local(), + imposter.cache.local(), + bucket, + probe); + + auto partition_stop = ss::defer([&partition] { partition->stop().get(); }); + + partition->start().get(); + + std::vector headers; + + storage::log_reader_config reader_config( + base, model::next_offset(base), ss::default_priority_class()); + + // starting max_bytes + reader_config.max_bytes = 1; + + auto next = base; + + int num_fetches = 0; + while (next < max) { + reader_config.start_offset = next; + reader_config.max_offset = model::next_offset(next); + vlog(test_util_log.info, "reader_config {}", reader_config); + auto reader = partition->make_reader(reader_config).get().reader; + auto headers_read + = reader.consume(test_consumer(), model::no_timeout).get(); + if (headers_read.empty()) { + // If the reader returned the empty result then the offset + // corresponds to tx-batch. Our own tx-batches looks like offset + // gaps to the client. We're always adding tx-batches with only one + // record so we can increment the 'next' offset and continue. + next = model::next_offset(next); + vlog( + test_util_log.info, + "Reader config: {} produced empty result, next offset set to {}", + reader_config, + next); + // test is prepared to see the gaps in place of tx-fence batches + continue; + } + BOOST_REQUIRE(headers_read.size() == 1); + vlog(test_util_log.info, "header {}", headers_read.front()); + next = headers_read.back().last_offset() + model::offset(1); + std::copy( + headers_read.begin(), + headers_read.end(), + std::back_inserter(headers)); + num_fetches++; + } + BOOST_REQUIRE(num_fetches > 0); + vlog(test_util_log.info, "{} fetch operations performed", num_fetches); + return headers; +} + void reupload_compacted_segments( cloud_storage_fixture& fixture, cloud_storage::partition_manifest& m, diff --git a/src/v/cloud_storage/tests/util.h b/src/v/cloud_storage/tests/util.h index f29f5a9b86518..8c85bc341c758 100644 --- a/src/v/cloud_storage/tests/util.h +++ b/src/v/cloud_storage/tests/util.h @@ -200,6 +200,7 @@ struct scan_result { /// Similar to prev function but uses timequery scan_result scan_remote_partition( cloud_storage_fixture& imposter, + model::offset min, model::timestamp timestamp, model::offset max = model::offset::max(), size_t maybe_max_segments = 0, @@ -218,4 +219,14 @@ std::vector replace_segments( model::offset_delta base_delta, const std::vector>& batches); +/// Read batches by one using max_bytes=1 and set max_offset to closes +/// value in the 'possible_lso_values' list. +std::vector +scan_remote_partition_incrementally_with_closest_lso( + cloud_storage_fixture& imposter, + model::offset base, + model::offset max, + size_t maybe_max_segments, + size_t maybe_max_readers); + } // namespace cloud_storage diff --git a/src/v/cloud_storage/topic_manifest.cc b/src/v/cloud_storage/topic_manifest.cc index 58e475531b1e8..9b8e64dc943cb 100644 --- a/src/v/cloud_storage/topic_manifest.cc +++ b/src/v/cloud_storage/topic_manifest.cc @@ -121,9 +121,12 @@ struct topic_manifest_handler _properties.retention_bytes = tristate{ disable_tristate}; } else if (_key == "retention_duration") { + // even though a negative number is valid for milliseconds, + // interpret any negative value as a request for infinite + // retention, that translates to a disabled tristate (like for + // retention_bytes) _properties.retention_duration - = tristate( - std::chrono::milliseconds(i)); + = tristate(disable_tristate); } else { return false; } diff --git a/src/v/cloud_storage_clients/abs_client.cc b/src/v/cloud_storage_clients/abs_client.cc index 06c8b593a33af..7227a8a73b51f 100644 --- a/src/v/cloud_storage_clients/abs_client.cc +++ b/src/v/cloud_storage_clients/abs_client.cc @@ -285,11 +285,11 @@ abs_request_creator::make_list_blobs_request( const bucket_name& name, bool files_only, std::optional prefix, - [[maybe_unused]] std::optional start_after, - std::optional max_keys, + std::optional max_results, + std::optional marker, std::optional delimiter) { // GET /{container-id}?restype=container&comp=list&prefix={prefix}... - // ...&max_results{max_keys} + // ...&maxresults{max_keys} // HTTP/1.1 Host: {storage-account-id}.blob.core.windows.net // x-ms-date:{req-datetime in RFC9110} # added by 'add_auth' // x-ms-version:"2023-01-23" # added by 'add_auth' @@ -299,14 +299,18 @@ abs_request_creator::make_list_blobs_request( target += fmt::format("&prefix={}", prefix.value()().string()); } - if (max_keys) { - target += fmt::format("&max_results={}", max_keys.value()); + if (max_results) { + target += fmt::format("&maxresults={}", max_results.value()); } if (delimiter) { target += fmt::format("&delimiter={}", delimiter.value()); } + if (marker.has_value()) { + target += fmt::format("&marker={}", marker.value()); + } + if (files_only) { target += fmt::format("&showonly=files"); } @@ -483,6 +487,14 @@ ss::future> abs_client::send_request( vlog(abs_log.debug, "BlobNotFound response received {}", key); outcome = error_outcome::key_not_found; _probe->register_failure(err.code()); + } else if ( + err.code() == abs_error_code::operation_not_supported_on_directory) { + vlog( + abs_log.debug, + "OperationNotSupportedOnDirectory response received {}", + key); + outcome = error_outcome::operation_not_supported; + _probe->register_failure(err.code()); } else { vlog( abs_log.error, @@ -673,20 +685,34 @@ abs_client::delete_object( }), key) .then([&name, &key](const ret_t& result) { - // ABS returns a 404 for attempts to delete a blob that doesn't - // exist. The remote doesn't expect this, so we map 404s to a - // successful response. - if (!result && result.error() == error_outcome::key_not_found) { - vlog( - abs_log.debug, - "Object to be deleted was not found in cloud storage: " - "object={}, bucket={}. Ignoring ...", - name, - key); - return ss::make_ready_future(no_response{}); - } else { - return ss::make_ready_future(result); + if (!result) { + if (result.error() == error_outcome::key_not_found) { + // ABS returns a 404 for attempts to delete a blob that + // doesn't exist. The remote doesn't expect this, so we + // map 404s to a successful response. + vlog( + abs_log.debug, + "Object to be deleted was not found in cloud storage: " + "object={}, bucket={}. Ignoring ...", + name, + key); + return ss::make_ready_future(no_response{}); + } else if ( + result.error() == error_outcome::operation_not_supported) { + // ABS does not allow for deletion of directories when HNS + // is disabled. The "folder" is "removed" when all blobs + // inside of it are deleted. Map this to a successful + // response. + vlog( + abs_log.warn, + "Cannot delete a directory in ABS cloud storage: " + "object={}, bucket={}. Ignoring ...", + name, + key); + return ss::make_ready_future(no_response{}); + } } + return ss::make_ready_future(result); }); } else { return delete_path(name, key, timeout); @@ -746,7 +772,7 @@ ss::future> abs_client::list_objects( const bucket_name& name, std::optional prefix, - std::optional start_after, + [[maybe_unused]] std::optional start_after, std::optional max_keys, std::optional continuation_token, ss::lowres_clock::duration timeout, @@ -756,7 +782,6 @@ abs_client::list_objects( do_list_objects( name, std::move(prefix), - std::move(start_after), max_keys, std::move(continuation_token), timeout, @@ -768,9 +793,8 @@ abs_client::list_objects( ss::future abs_client::do_list_objects( const bucket_name& name, std::optional prefix, - std::optional start_after, - std::optional max_keys, - [[maybe_unused]] std::optional continuation_token, + std::optional max_results, + std::optional marker, ss::lowres_clock::duration timeout, std::optional delimiter, std::optional gather_item_if) { @@ -778,8 +802,8 @@ ss::future abs_client::do_list_objects( name, _adls_client.has_value(), std::move(prefix), - std::move(start_after), - max_keys, + max_results, + std::move(marker), delimiter); if (!header) { vlog( @@ -923,7 +947,9 @@ ss::future<> abs_client::do_delete_path( try { co_await do_delete_file(name, *iter, timeout); } catch (const abs_rest_error_response& abs_error) { - if (abs_error.code() == abs_error_code::path_not_found) { + if ( + abs_error.code() == abs_error_code::path_not_found + || abs_error.code() == abs_error_code::blob_not_found) { vlog( abs_log.debug, "Object to be deleted was not found in cloud storage: " diff --git a/src/v/cloud_storage_clients/abs_client.h b/src/v/cloud_storage_clients/abs_client.h index cdfbe884349b4..34943016bbd4f 100644 --- a/src/v/cloud_storage_clients/abs_client.h +++ b/src/v/cloud_storage_clients/abs_client.h @@ -71,7 +71,8 @@ class abs_request_creator { /// \param name of the container /// \param files_only should always be set to true when HNS is enabled and false otherwise /// \param prefix prefix of returned blob's names - /// \param start_after is always ignored \param max_keys is the max number of returned objects + /// \param max_results is the max number of returned objects + /// \param marker is the "continuation-token" /// \param delimiter used to group common prefixes /// \return initialized and signed http header or error // clang-format on @@ -79,8 +80,8 @@ class abs_request_creator { const bucket_name& name, bool files_only, std::optional prefix, - std::optional start_after, - std::optional max_keys, + std::optional max_results, + std::optional marker, std::optional delimiter = std::nullopt); /// \brief Init http header for 'Get Account Information' request @@ -269,9 +270,8 @@ class abs_client : public client { ss::future do_list_objects( const bucket_name& name, std::optional prefix, - std::optional start_after, - std::optional max_keys, - std::optional continuation_token, + std::optional max_results, + std::optional marker, ss::lowres_clock::duration timeout, std::optional delimiter = std::nullopt, std::optional collect_item_if = std::nullopt); diff --git a/src/v/cloud_storage_clients/abs_error.cc b/src/v/cloud_storage_clients/abs_error.cc index 63b0c7b7711f8..236ac44f5fb71 100644 --- a/src/v/cloud_storage_clients/abs_error.cc +++ b/src/v/cloud_storage_clients/abs_error.cc @@ -32,7 +32,6 @@ std::istream& operator>>(std::istream& i, abs_error_code& code) { .match("InternalError", abs_error_code::internal_error) .match("OperationTimedOut", abs_error_code::operation_timed_out) .match("SystemInUse", abs_error_code::system_in_use) - .match("BlobNotFound", abs_error_code::blob_not_found) .match("AccountBeingCreated", abs_error_code::account_being_created) .match( "ResourceAlreadyExists", abs_error_code::resource_already_exists) @@ -47,6 +46,9 @@ std::istream& operator>>(std::istream& i, abs_error_code& code) { .match("ContainerNotFound", abs_error_code::container_not_found) .match("DirectoryNotEmpty", abs_error_code::directory_not_empty) .match("PathNotFound", abs_error_code::path_not_found) + .match( + "OperationNotSupportedOnDirectory", + abs_error_code::operation_not_supported_on_directory) .default_match(abs_error_code::_unknown); return i; diff --git a/src/v/cloud_storage_clients/abs_error.h b/src/v/cloud_storage_clients/abs_error.h index d69da37e158c3..79e20b08c93c8 100644 --- a/src/v/cloud_storage_clients/abs_error.h +++ b/src/v/cloud_storage_clients/abs_error.h @@ -40,7 +40,8 @@ enum class abs_error_code { container_being_deleted, container_not_found, directory_not_empty, - path_not_found + path_not_found, + operation_not_supported_on_directory }; /// Operators to use with lexical_cast diff --git a/src/v/cloud_storage_clients/client.h b/src/v/cloud_storage_clients/client.h index fef5e51228b2f..b6755bfbfb242 100644 --- a/src/v/cloud_storage_clients/client.h +++ b/src/v/cloud_storage_clients/client.h @@ -96,7 +96,7 @@ class client { ss::sstring etag; }; struct list_bucket_result { - bool is_truncated; + bool is_truncated = false; ss::sstring prefix; ss::sstring next_continuation_token; std::vector contents; diff --git a/src/v/cloud_storage_clients/s3_client.cc b/src/v/cloud_storage_clients/s3_client.cc index e8d32ac6963ab..fcdbfd56fd17f 100644 --- a/src/v/cloud_storage_clients/s3_client.cc +++ b/src/v/cloud_storage_clients/s3_client.cc @@ -182,6 +182,16 @@ request_creator::make_list_objects_v2_request( if (prefix.has_value()) { target = fmt::format("{}&prefix={}", target, (*prefix)().string()); } + if (start_after.has_value()) { + target = fmt::format("{}&start-after={}", target, *start_after); + } + if (max_keys.has_value()) { + target = fmt::format("{}&max-keys={}", target, *max_keys); + } + if (continuation_token.has_value()) { + target = fmt::format( + "{}&continuation-token={}", target, *continuation_token); + } if (delimiter.has_value()) { target = fmt::format("{}&delimiter={}", target, *delimiter); } @@ -192,26 +202,6 @@ request_creator::make_list_objects_v2_request( header.insert(boost::beast::http::field::host, host); header.insert(boost::beast::http::field::content_length, "0"); - if (prefix) { - header.insert(aws_header_names::prefix, (*prefix)().string()); - } - if (start_after) { - header.insert(aws_header_names::start_after, (*start_after)().string()); - } - if (max_keys) { - header.insert(aws_header_names::max_keys, std::to_string(*max_keys)); - } - if (continuation_token) { - header.insert( - aws_header_names::continuation_token, - {continuation_token->data(), continuation_token->size()}); - } - - if (delimiter) { - header.insert( - aws_header_names::delimiter, std::string(1, delimiter.value())); - } - auto ec = _apply_credentials->add_auth(header); vlog(s3_log.trace, "ListObjectsV2:\n {}", header); if (ec) { @@ -415,7 +405,11 @@ ss::future parse_head_error_response( code, msg, ss::sstring(rid.data(), rid.size()), key().native()); return ss::make_exception_future(err); } catch (...) { - vlog(s3_log.error, "!!error parse error {}", std::current_exception()); + vlog( + s3_log.error, + "!!error parse error {}, header: {}", + std::current_exception(), + hdr); throw; } } @@ -752,7 +746,7 @@ ss::future s3_client::do_list_objects_v2( name, std::move(prefix), std::move(start_after), - max_keys, + std::move(max_keys), std::move(continuation_token), delimiter); if (!header) { diff --git a/src/v/cloud_storage_clients/tests/s3_client_test.cc b/src/v/cloud_storage_clients/tests/s3_client_test.cc index 101c13653ae86..7ce01ffe972da 100644 --- a/src/v/cloud_storage_clients/tests/s3_client_test.cc +++ b/src/v/cloud_storage_clients/tests/s3_client_test.cc @@ -164,7 +164,7 @@ void set_routes(ss::httpd::routes& r) { [](const_req req, reply& reply) { BOOST_REQUIRE(!req.get_header("x-amz-content-sha256").empty()); BOOST_REQUIRE_EQUAL(req.get_query_param("list-type"), "2"); - auto prefix = req.get_header("prefix"); + auto prefix = req.get_query_param("prefix"); if (prefix == "test") { // normal response return list_objects_payload; @@ -173,7 +173,8 @@ void set_routes(ss::httpd::routes& r) { reply.set_status(reply::status_type::internal_server_error); return error_payload; } else if (prefix == "test-cont") { - BOOST_REQUIRE_EQUAL(req.get_header("continuation-token"), "ctok"); + BOOST_REQUIRE_EQUAL( + req.get_query_param("continuation-token"), "ctok"); return list_objects_payload; } return ""; diff --git a/src/v/cloud_storage_clients/tests/xml_sax_parser_test.cc b/src/v/cloud_storage_clients/tests/xml_sax_parser_test.cc index 1bdf69c33bb12..877b51355661f 100644 --- a/src/v/cloud_storage_clients/tests/xml_sax_parser_test.cc +++ b/src/v/cloud_storage_clients/tests/xml_sax_parser_test.cc @@ -44,21 +44,21 @@ static constexpr std::string_view payload = R"XML( )XML"; static constexpr std::string_view abs_payload = R"XML( - - prefix - string-value - int-value - string-value - - - blob-name - date-time-value + + prefix + string-value + int-value + string-value + + + blob-name + date-time-value date-time-vlue true true - + date-time-value - 2021-01-10T02:00:00.000Z + 2021-01-10T02:00:00.000Z etag owner user id owning group id @@ -66,25 +66,25 @@ static constexpr std::string_view abs_payload = R"XML( access control list file | directory true - 1112 - blob-content-type - - - - - sequence-number - BlockBlob|PageBlob|AppendBlob - tier - locked|unlocked - available | leased | expired | breaking | broken - infinite | fixed - id - pending | success | aborted | failed - source url - bytes copied/bytes total - datetime - error string - true + 1112 + blob-content-type + + + + + sequence-number + BlockBlob|PageBlob|AppendBlob + tier + locked|unlocked + available | leased | expired | breaking | broken + infinite | fixed + id + pending | success | aborted | failed + source url + bytes copied/bytes total + datetime + error string + true encryption-key-sha256 encryption-scope-name true @@ -95,10 +95,10 @@ static constexpr std::string_view abs_payload = R"XML( number of tags between 1 to 10 rehydrate priority date-time-value - - - value - + + + value + @@ -108,53 +108,81 @@ static constexpr std::string_view abs_payload = R"XML( - - - blob-prefix - - - + + + blob-prefix + + + )XML"; static constexpr std::string_view abs_payload_with_continuation = R"XML( - - prefix - string-value - int-value - string-value - - - blob-name - date-time-value + + prefix + string-value + int-value + string-value + + + blob-name + date-time-value date-time-vlue true true - + date-time-value - 2021-01-10T02:00:00.000Z + 2021-01-10T02:00:00.000Z etag access control list file | directory true - 1112 - blob-content-type - - - - - sequence-number - BlockBlob|PageBlob|AppendBlob + 1112 + blob-content-type + + + + + sequence-number + BlockBlob|PageBlob|AppendBlob no-of-days number of tags between 1 to 10 date-time-value - - - - blob-prefix - - - nnn + + + + blob-prefix + + + nnn + +)XML"; + +static constexpr std::string_view abs_payload_with_blob_prefix = R"XML( + + prefix + string-value + int-value + string-value + + + cluster_metadata/bb7527f1-3227-4d55-86da-c133ec955ea9/manifests/2/ + + Thu, 25 Jul 2024 14:07:26 GMT + Thu, 25 Jul 2024 14:07:26 GMT + 0x8DCACB31CE7DB5C + directory + 0 + BlockBlob + Hot + true + unlocked + available + true + + + + )XML"; @@ -247,3 +275,20 @@ BOOST_AUTO_TEST_CASE(test_parse_abs_with_continuation) { BOOST_REQUIRE_EQUAL(result.is_truncated, true); BOOST_REQUIRE_EQUAL(result.next_continuation_token, "nnn"); } + +BOOST_AUTO_TEST_CASE(test_parse_abs_with_blob_prefix) { + cloud_storage_clients::xml_sax_parser p{}; + ss::temporary_buffer buffer( + abs_payload_with_blob_prefix.data(), abs_payload_with_blob_prefix.size()); + + p.start_parse(std::make_unique()); + p.parse_chunk(std::move(buffer)); + p.end_parse(); + + auto result = p.result(); + BOOST_REQUIRE(result.contents.empty()); + BOOST_REQUIRE_EQUAL(result.common_prefixes.size(), 1); + BOOST_REQUIRE_EQUAL( + result.common_prefixes[0], + "cluster_metadata/bb7527f1-3227-4d55-86da-c133ec955ea9/manifests/2/"); +} diff --git a/src/v/cloud_storage_clients/types.h b/src/v/cloud_storage_clients/types.h index b25c4fc6e203d..cf0c95db34701 100644 --- a/src/v/cloud_storage_clients/types.h +++ b/src/v/cloud_storage_clients/types.h @@ -32,6 +32,9 @@ enum class error_outcome { fail, /// Missing key API error (only suitable for downloads and deletions) key_not_found, + /// Currently used for directory deletion errors in ABS, typically treated + /// as regular failure outcomes. + operation_not_supported }; struct error_outcome_category final : public std::error_category { @@ -47,6 +50,8 @@ struct error_outcome_category final : public std::error_category { return "Non retriable error"; case error_outcome::key_not_found: return "Key not found error"; + case error_outcome::operation_not_supported: + return "Operation not supported error"; default: return "Undefined error_outcome encountered"; } diff --git a/src/v/cloud_storage_clients/xml_sax_parser.cc b/src/v/cloud_storage_clients/xml_sax_parser.cc index 1fb6e96c0b9ed..7420c979de385 100644 --- a/src/v/cloud_storage_clients/xml_sax_parser.cc +++ b/src/v/cloud_storage_clients/xml_sax_parser.cc @@ -190,7 +190,9 @@ bool abs_parse_impl::is_in_blob_prefixes() const { } void abs_parse_impl::handle_start_element(std::string_view element_name) { - if (element_name == abs_tags::blob && _tags.size() == 2) { + if ( + (element_name == abs_tags::blob || element_name == abs_tags::blob_prefix) + && _tags.size() == 2) { // Reinitialize the item in preparation for next values _current_item.emplace(); } else if (element_name == abs_tags::name) { diff --git a/src/v/cluster/archival_metadata_stm.h b/src/v/cluster/archival_metadata_stm.h index ed0846d9cec5a..7b29c7d941e6c 100644 --- a/src/v/cluster/archival_metadata_stm.h +++ b/src/v/cluster/archival_metadata_stm.h @@ -104,6 +104,7 @@ class archival_metadata_stm final : public raft::persisted_stm<> { friend class details::archival_metadata_stm_accessor; public: + static constexpr const char* name = "archival_metadata_stm"; friend class command_batch_builder; explicit archival_metadata_stm( @@ -247,7 +248,6 @@ class archival_metadata_stm final : public raft::persisted_stm<> { model::offset max_collectible_offset() override; - std::string_view get_name() const final { return "archival_metadata_stm"; } ss::future take_snapshot(model::offset) final { co_return iobuf{}; } private: diff --git a/src/v/cluster/cloud_metadata/tests/cluster_recovery_backend_test.cc b/src/v/cluster/cloud_metadata/tests/cluster_recovery_backend_test.cc index 195b725110c2a..013615509af8a 100644 --- a/src/v/cluster/cloud_metadata/tests/cluster_recovery_backend_test.cc +++ b/src/v/cluster/cloud_metadata/tests/cluster_recovery_backend_test.cc @@ -164,7 +164,8 @@ TEST_P(ClusterRecoveryBackendLeadershipParamTest, TestRecoveryControllerState) { cluster::random_tx_generator{}.run_workload( spec, remote_p->raft()->term(), remote_p->rm_stm(), remote_p->log()); - for (const auto& [ntp, p] : app.partition_manager.local().partitions()) { + auto partitions = app.partition_manager.local().partitions(); + for (const auto& [ntp, p] : partitions) { if (ntp == model::controller_ntp) { continue; } @@ -178,6 +179,7 @@ TEST_P(ClusterRecoveryBackendLeadershipParamTest, TestRecoveryControllerState) { archiver.upload_topic_manifest().get(); archiver.upload_manifest("test").get(); } + partitions.clear(); // Write a controller snapshot and upload it. RPTEST_REQUIRE_EVENTUALLY( @@ -257,8 +259,8 @@ TEST_P(ClusterRecoveryBackendLeadershipParamTest, TestRecoveryControllerState) { auto topic_count = app.controller->get_topics_state().local().all_topics_count(); ASSERT_LE(2, topic_count); - for (const auto& [ntp, p] : - app.partition_manager.local().partitions()) { + auto partitions = app.partition_manager.local().partitions(); + for (const auto& [ntp, p] : partitions) { if (!model::is_user_topic(ntp)) { continue; } diff --git a/src/v/cluster/cluster_utils.cc b/src/v/cluster/cluster_utils.cc index 843e888b27caf..ec304df460594 100644 --- a/src/v/cluster/cluster_utils.cc +++ b/src/v/cluster/cluster_utils.cc @@ -174,6 +174,7 @@ cluster::errc map_update_interruption_error_code(std::error_code ec) { case rpc::errc::service_error: case rpc::errc::method_not_found: case rpc::errc::version_not_supported: + case rpc::errc::service_unavailable: case rpc::errc::unknown: return errc::replication_error; } diff --git a/src/v/cluster/controller.cc b/src/v/cluster/controller.cc index 4c6ae652838db..805fa8c43c6b1 100644 --- a/src/v/cluster/controller.cc +++ b/src/v/cluster/controller.cc @@ -31,6 +31,7 @@ #include "cluster/feature_manager.h" #include "cluster/fwd.h" #include "cluster/health_manager.h" +#include "cluster/health_monitor_backend.h" #include "cluster/health_monitor_frontend.h" #include "cluster/logger.h" #include "cluster/members_backend.h" @@ -413,6 +414,17 @@ ss::future<> controller::start( return config::shard_local_cfg() .initial_retention_local_target_ms_default.bind(); }), + ss::sharded_parameter([] { + return config::shard_local_cfg() + .retention_local_target_bytes_default.bind(); + }), + ss::sharded_parameter([] { + return config::shard_local_cfg() + .retention_local_target_ms_default.bind(); + }), + ss::sharded_parameter([] { + return config::shard_local_cfg().retention_local_strict.bind(); + }), std::ref(_as)); }) .then( @@ -577,7 +589,14 @@ ss::future<> controller::start( std::ref(_partition_leaders), std::ref(_tp_state)); }) - .then([this] { return _hm_frontend.start(std::ref(_hm_backend)); }) + .then([this] { + return _hm_frontend.start( + std::ref(_hm_backend), + std::ref(_node_status_table), + ss::sharded_parameter([]() { + return config::shard_local_cfg().alive_timeout_ms.bind(); + })); + }) .then([this] { return _hm_frontend.invoke_on_all(&health_monitor_frontend::start); }) diff --git a/src/v/cluster/controller_api.cc b/src/v/cluster/controller_api.cc index b0c618b82462a..bf1eb404d5dfa 100644 --- a/src/v/cluster/controller_api.cc +++ b/src/v/cluster/controller_api.cc @@ -360,7 +360,7 @@ controller_api::get_partitions_reconfiguration_state( auto& report = result.value(); for (auto& node_report : report.node_reports) { - for (auto& tp : node_report.topics) { + for (auto& tp : node_report->topics) { for (auto& p : tp.partitions) { model::ntp ntp(tp.tp_ns.ns, tp.tp_ns.tp, p.id); auto it = states.find(ntp); @@ -368,11 +368,11 @@ controller_api::get_partitions_reconfiguration_state( continue; } - if (p.leader_id == node_report.id) { + if (p.leader_id == node_report->id) { it->second.current_partition_size = p.size_bytes; } const auto moving_to = moving_to_node( - node_report.id, + node_report->id, it->second.previous_assignment, it->second.current_assignment); @@ -380,7 +380,7 @@ controller_api::get_partitions_reconfiguration_state( if (moving_to) { it->second.already_transferred_bytes.emplace_back( replica_bytes{ - .node = node_report.id, .bytes = p.size_bytes}); + .node = node_report->id, .bytes = p.size_bytes}); } co_await ss::maybe_yield(); diff --git a/src/v/cluster/controller_backend.cc b/src/v/cluster/controller_backend.cc index 55559ed9944cd..b249e3024d0db 100644 --- a/src/v/cluster/controller_backend.cc +++ b/src/v/cluster/controller_backend.cc @@ -51,6 +51,7 @@ #include #include +#include #include /// on every core, sharded @@ -216,6 +217,15 @@ std::error_code check_configuration_update( partition->ntp()); return errc::partition_configuration_in_joint_mode; } + + if (includes_self && partition->raft()->has_configuration_override()) { + vlog( + clusterlog.trace, + "[{}] contains current node and there is configuration override " + "active", + partition->ntp()); + return errc::partition_configuration_in_joint_mode; + } /* * if replica set is a leader it must have configuration committed i.e. it * was successfully replicated to majority of followers. @@ -263,6 +273,9 @@ controller_backend::controller_backend( config::binding> initial_retention_local_target_bytes, config::binding> initial_retention_local_target_ms, + config::binding> retention_local_target_bytes_default, + config::binding retention_local_target_ms_default, + config::binding retention_local_strict, ss::sharded& as) : _topics(tp_state) , _shard_table(st) @@ -280,6 +293,11 @@ controller_backend::controller_backend( std::move(initial_retention_local_target_bytes)) , _initial_retention_local_target_ms( std::move(initial_retention_local_target_ms)) + , _retention_local_target_bytes_default( + std::move(retention_local_target_bytes_default)) + , _retention_local_target_ms_default( + std::move(retention_local_target_ms_default)) + , _retention_local_strict(std::move(retention_local_strict)) , _as(as) {} bool controller_backend::command_based_membership_active() const { @@ -487,7 +505,7 @@ std::optional get_topic_property( std::optional controller_backend::calculate_learner_initial_offset( - const ss::lw_shared_ptr& p) const { + reconfiguration_policy policy, const ss::lw_shared_ptr& p) const { /** * Initial learner start offset only makes sense for partitions with cloud * storage data @@ -501,24 +519,68 @@ controller_backend::calculate_learner_initial_offset( /** * Calculate retention targets based on cluster and topic configuration */ - const auto initial_retention_bytes = get_topic_property( + auto initial_retention_bytes = get_topic_property( _initial_retention_local_target_bytes(), log->config().has_overrides() ? log->config().get_overrides().initial_retention_local_target_bytes : tristate{std::nullopt}); - const auto initial_retention_ms = get_topic_property( + auto initial_retention_ms = get_topic_property( _initial_retention_local_target_ms(), log->config().has_overrides() ? log->config().get_overrides().initial_retention_local_target_ms : tristate{std::nullopt}); + /** - * Initial target retention disabled + * There are two possibilities for learner start offset calculation: + * + * >>> fast partition movement <<< + * - the reconfiguration policy is set to use target_initial_retention and + * initial retention is configured, in this case the initial learner + * offset will be calculated based on the initial target retention + * settings + * + * >>> full local retention move <<< + * - with non strict local retention the storage manager may allow + * partitions to grow beyond their configured local retention target. In + * this case the controller backend will use the local retention target + * properties and will schedule move delivering only the data that would + * be retained if local retention was working in strict mode regardless of + * initial retention settings and configured move policy. */ - if ( - !initial_retention_bytes.has_value() - && !initial_retention_ms.has_value()) { - return std::nullopt; + const bool no_initial_retention_settings = !( + initial_retention_bytes.has_value() + || initial_retention_bytes.has_value()); + + bool full_move = policy == reconfiguration_policy::full_local_retention + || no_initial_retention_settings; + // full local retention move + if (full_move) { + // strict local retention, no need to override learner start + if (_retention_local_strict()) { + return std::nullopt; + } + + // use default target local retention settings + initial_retention_bytes = get_topic_property( + _retention_local_target_bytes_default(), + log->config().has_overrides() + ? log->config().get_overrides().retention_local_target_bytes + : tristate{std::nullopt}); + + initial_retention_ms = get_topic_property( + {_retention_local_target_ms_default()}, + log->config().has_overrides() + ? log->config().get_overrides().retention_local_target_ms + : tristate{std::nullopt}); + + vlog( + clusterlog.trace, + "[{}] full partition move requested. Using default target local " + "retention settings for the topic - target bytes: {}, target ms: {}", + p->ntp(), + initial_retention_bytes, + initial_retention_ms->count()); } model::timestamp retention_timestamp_threshold(0); @@ -542,7 +604,7 @@ controller_backend::calculate_learner_initial_offset( * uploaded to Cloud Storage. */ vlog( - clusterlog.trace, + clusterlog.info, "[{}] calculated retention offset: {}, last uploaded to cloud: {}, " "manifest clean offset: {}, max_collectible_offset: {}", p->ntp(), @@ -1685,6 +1747,14 @@ controller_backend::force_abort_replica_set_update( } co_return errc::waiting_for_recovery; } else { + auto leader_id = partition->get_leader_id(); + if (leader_id && leader_id != _self) { + // The leader is alive and we are a follower. Wait for the leader to + // replicate the aborting configuration, but don't append it + // ourselves to minimize the chance of log inconsistency. + co_return errc::not_leader; + } + vlog( clusterlog.debug, "[{}] force-aborting reconfiguration", @@ -1711,10 +1781,8 @@ controller_backend::update_partition_replica_set( * We want to keep full local retention on the learner, do not return * initial offset override */ - std::optional learner_initial_offset; - if (policy == reconfiguration_policy::target_initial_retention) { - learner_initial_offset = calculate_learner_initial_offset(p); - } + auto learner_initial_offset = calculate_learner_initial_offset( + policy, p); return do_update_replica_set( std::move(p), diff --git a/src/v/cluster/controller_backend.h b/src/v/cluster/controller_backend.h index 0a0dfa1510c0c..7add5a3e8e791 100644 --- a/src/v/cluster/controller_backend.h +++ b/src/v/cluster/controller_backend.h @@ -32,7 +32,7 @@ #include #include -#include +#include #include namespace cluster { @@ -207,6 +207,11 @@ class controller_backend initial_retention_local_target_bytes, config::binding> initial_retention_local_target_ms, + config::binding> + retention_local_target_bytes_default, + config::binding + retention_local_target_ms_default, + config::binding retention_local_strict, ss::sharded&); ss::future<> stop(); @@ -400,6 +405,7 @@ class controller_backend bool should_skip(const model::ntp&) const; std::optional calculate_learner_initial_offset( + reconfiguration_policy policy, const ss::lw_shared_ptr& partition) const; ss::sharded& _topics; @@ -417,6 +423,11 @@ class controller_backend _initial_retention_local_target_bytes; config::binding> _initial_retention_local_target_ms; + config::binding> + _retention_local_target_bytes_default; + config::binding + _retention_local_target_ms_default; + config::binding _retention_local_strict; ss::sharded& _as; absl::btree_map _states; diff --git a/src/v/cluster/controller_snapshot.h b/src/v/cluster/controller_snapshot.h index a4da75f77c13a..08fab598960c6 100644 --- a/src/v/cluster/controller_snapshot.h +++ b/src/v/cluster/controller_snapshot.h @@ -19,6 +19,7 @@ #include "security/types.h" #include "serde/envelope.h" #include "serde/serde.h" +#include "utils/chunked_hash_map.h" #include "utils/fragmented_vector.h" #include @@ -166,8 +167,8 @@ struct topics_t : public serde:: envelope, serde::compat_version<0>> { topic_metadata_fields metadata; - absl::node_hash_map partitions; - absl::node_hash_map updates; + chunked_hash_map partitions; + chunked_hash_map updates; std::optional disabled_set; friend bool operator==(const topic_t&, const topic_t&) = default; @@ -176,7 +177,7 @@ struct topics_t ss::future<> serde_async_read(iobuf_parser&, serde::header const); }; - absl::node_hash_map topics; + chunked_hash_map topics; raft::group_id highest_group_id; absl::node_hash_map< diff --git a/src/v/cluster/distributed_kv_stm.h b/src/v/cluster/distributed_kv_stm.h index 387e08dd258e8..66e947148a3c4 100644 --- a/src/v/cluster/distributed_kv_stm.h +++ b/src/v/cluster/distributed_kv_stm.h @@ -78,6 +78,7 @@ requires std::is_trivially_copyable_v && std::is_trivially_copyable_v class distributed_kv_stm final : public raft::persisted_stm<> { public: + static constexpr const char* name = "distributed_kv_stm"; explicit distributed_kv_stm( size_t max_partitions, ss::logger& logger, raft::consensus* raft) : persisted_stm<>("distributed_kv_stm.snapshot", logger, raft) @@ -119,7 +120,7 @@ class distributed_kv_stm final : public raft::persisted_stm<> { ss::future<> apply_local_snapshot( raft::stm_snapshot_header header, iobuf&& bytes) override { auto holder = _gate.hold(); - auto units = _snapshot_lock.hold_write_lock(); + auto units = co_await _snapshot_lock.hold_write_lock(); iobuf_parser parser(std::move(bytes)); auto snap = co_await serde::read_async(parser); @@ -136,7 +137,7 @@ class distributed_kv_stm final : public raft::persisted_stm<> { ss::future take_local_snapshot() override { auto holder = _gate.hold(); - auto units = _snapshot_lock.hold_write_lock(); + auto units = co_await _snapshot_lock.hold_write_lock(); auto last_applied = last_applied_offset(); snapshot result; if (_is_routing_partition) { @@ -156,7 +157,6 @@ class distributed_kv_stm final : public raft::persisted_stm<> { co_return; } - std::string_view get_name() const final { return "distributed_kv_stm"; } // TODO: implement delete retention with incremental raft snapshots. ss::future take_snapshot(model::offset) final { co_return iobuf{}; } diff --git a/src/v/cluster/errc.h b/src/v/cluster/errc.h index 03ede80645d62..30acfe01214e8 100644 --- a/src/v/cluster/errc.h +++ b/src/v/cluster/errc.h @@ -79,6 +79,11 @@ enum class errc : int16_t { invalid_partition_operation, concurrent_modification_error, transform_count_limit_exceeded, + topic_invalid_partitions_core_limit, + topic_invalid_partitions_memory_limit, + topic_invalid_partitions_fd_limit, + topic_invalid_partitions_decreased, + invalid_target_node_id, }; struct errc_category final : public std::error_category { const char* name() const noexcept final { return "cluster::errc"; } @@ -96,7 +101,7 @@ struct errc_category final : public std::error_category { case errc::topic_invalid_replication_factor: return "Unable to allocate topic with given replication factor"; case errc::topic_invalid_config: - return "Topic configuration is either bogus or not supported"; + return "Configuration is invalid"; case errc::not_leader_controller: return "This node is not raft-0 leader. i.e is not leader " "controller"; @@ -229,6 +234,16 @@ struct errc_category final : public std::error_category { return "Concurrent modification error"; case errc::transform_count_limit_exceeded: return "Too many transforms deployed"; + case errc::topic_invalid_partitions_core_limit: + return "Can not increase partition count due to core limit"; + case errc::topic_invalid_partitions_memory_limit: + return "Can not increase partition count due to memory limit"; + case errc::topic_invalid_partitions_fd_limit: + return "Can not increase partition count due to FD limit"; + case errc::topic_invalid_partitions_decreased: + return "Can not decrease the number of partitions"; + case errc::invalid_target_node_id: + return "Request was intended for the node with different node id"; } return "cluster::errc::unknown"; } diff --git a/src/v/cluster/feature_manager.cc b/src/v/cluster/feature_manager.cc index c001923c4624b..8b9108d177e81 100644 --- a/src/v/cluster/feature_manager.cc +++ b/src/v/cluster/feature_manager.cc @@ -14,7 +14,10 @@ #include "cluster/cluster_utils.h" #include "cluster/commands.h" #include "cluster/controller_service.h" +#include "cluster/health_monitor_backend.h" #include "cluster/health_monitor_frontend.h" +#include "cluster/health_monitor_types.h" +#include "cluster/logger.h" #include "cluster/members_table.h" #include "config/configuration.h" #include "config/validators.h" @@ -84,8 +87,8 @@ feature_manager::start(std::vector&& cluster_founder_nodes) { // Register for node health change notifications _health_notify_handle = _hm_backend.local().register_node_callback( [this]( - node_health_report const& report, - std::optional>) { + const node_health_report& report, + std::optional>) { // If we did not know the node's version or if the report is // higher, submit an update. auto i = _node_versions.find(report.id); @@ -405,25 +408,6 @@ ss::future<> feature_manager::do_maybe_update_active_version() { // B) All member nodes must be up // C) All versions must be >= the new active version - std::map node_status; - - // This call in principle can be a network fetch, but in practice - // we're only doing it immediately after cluster health has just - // been updated, so do not expect it to go remote. - auto node_status_v = co_await _hm_frontend.local().get_nodes_status( - model::timeout_clock::now() + 5s); - if (node_status_v.has_error()) { - // Raise exception to trigger backoff+retry - throw std::runtime_error(fmt::format( - "Can't update active cluster version, failed to get health " - "status: {}", - node_status_v.error())); - } else { - for (const auto& i : node_status_v.value()) { - node_status.emplace(i.id, i); - } - } - // Ensure that our _node_versions contains versions for all // nodes in members_table & that they are all sufficiently recent const auto& member_table = _members.local(); @@ -449,8 +433,8 @@ ss::future<> feature_manager::do_maybe_update_active_version() { co_return; } - auto state_iter = node_status.find(node_id); - if (state_iter == node_status.end()) { + auto is_alive_opt = _hm_frontend.local().is_alive(node_id); + if (!is_alive_opt.has_value()) { // Unexpected: the health monitor should be populating // state for all known members_table nodes, but this // could happen if we raced with a decom or node add. @@ -462,7 +446,7 @@ ss::future<> feature_manager::do_maybe_update_active_version() { max_version, node_id)); - } else if (!state_iter->second.is_alive) { + } else if (is_alive_opt == alive::no) { // Raise exception to trigger backoff+retry throw std::runtime_error(fmt_with_ctx( fmt::format, diff --git a/src/v/cluster/health_monitor_backend.cc b/src/v/cluster/health_monitor_backend.cc index 59950d35544cd..031a948ade926 100644 --- a/src/v/cluster/health_monitor_backend.cc +++ b/src/v/cluster/health_monitor_backend.cc @@ -21,21 +21,19 @@ #include "cluster/partition_manager.h" #include "cluster/partition_probe.h" #include "config/configuration.h" -#include "config/node_config.h" #include "config/property.h" #include "features/feature_table.h" #include "model/fundamental.h" #include "model/metadata.h" #include "raft/fwd.h" -#include "random/generators.h" #include "rpc/connection_cache.h" #include "storage/types.h" -#include "version.h" #include #include #include #include +#include #include #include #include @@ -74,16 +72,8 @@ health_monitor_backend::health_monitor_backend( , _feature_table(feature_table) , _partition_leaders_table(partition_leaders_table) , _topic_table(topic_table) - , _local_monitor(local_monitor) { - _leadership_notification_handle - = _raft_manager.local().register_leadership_notification( - [this]( - raft::group_id group, - model::term_id term, - std::optional leader_id) { - on_leadership_changed(group, term, leader_id); - }); -} + , _local_monitor(local_monitor) + , _self(_raft0->self().id()) {} cluster::notification_id_type health_monitor_backend::register_node_callback(health_node_cb_t cb) { @@ -92,7 +82,7 @@ health_monitor_backend::register_node_callback(health_node_cb_t cb) { auto id = _next_callback_id++; // call notification for all the groups for (const auto& report : _reports) { - cb(report.second, {}); + cb(*report.second, {}); } _node_callbacks.emplace_back(id, std::move(cb)); return id; @@ -115,8 +105,6 @@ void health_monitor_backend::unregister_node_callback( ss::future<> health_monitor_backend::stop() { vlog(clusterlog.info, "Stopping Health Monitor Backend..."); - _raft_manager.local().unregister_leadership_notification( - _leadership_notification_handle); auto f = _gate.close(); _refresh_mutex.broken(); @@ -130,19 +118,19 @@ ss::future<> health_monitor_backend::stop() { cluster_health_report health_monitor_backend::build_cluster_report( const cluster_report_filter& filter) { - std::vector reports; + std::vector reports; std::vector statuses; - // refreshing node status is not expensive on leader, we can refresh it - // every time - if (_raft0->is_elected_leader()) { - refresh_nodes_status(); - } auto nodes = filter.nodes.empty() ? _members.local().node_ids() : filter.nodes; reports.reserve(nodes.size()); statuses.reserve(nodes.size()); for (const auto& node_id : nodes) { + auto node_metadata = _members.local().get_node_metadata_ref(node_id); + if (!node_metadata) { + continue; + } + auto r = build_node_report(node_id, filter.node_report_filter); if (r) { reports.push_back(std::move(r.value())); @@ -150,7 +138,10 @@ cluster_health_report health_monitor_backend::build_cluster_report( auto it = _status.find(node_id); if (it != _status.end()) { - statuses.push_back(it->second); + statuses.emplace_back( + node_id, + node_metadata->get().state.get_membership_state(), + it->second.is_alive); } } @@ -161,28 +152,6 @@ cluster_health_report health_monitor_backend::build_cluster_report( .bytes_in_cloud_storage = _bytes_in_cloud_storage}; } -void health_monitor_backend::refresh_nodes_status() { - // remove all nodes not longer present in members collection - absl::erase_if( - _status, [this](auto& e) { return !_members.local().contains(e.first); }); - - for (auto& [id, nm] : _members.local().nodes()) { - node_state status; - status.id = id; - status.membership_state = nm.state.get_membership_state(); - - // current node is always alive - if (id == _raft0->self().id()) { - status.is_alive = alive::yes; - } - auto res = _raft0->get_follower_metrics(id); - if (res) { - status.is_alive = alive(res.value().is_live); - } - _status.insert_or_assign(id, status); - } -} - chunked_vector filter_topic_status( const chunked_vector& topics, const partitions_filter& filter) { // empty filter matches all @@ -210,28 +179,32 @@ chunked_vector filter_topic_status( return filtered; } -std::optional health_monitor_backend::build_node_report( +std::optional health_monitor_backend::build_node_report( model::node_id id, const node_report_filter& f) { auto it = _reports.find(id); if (it == _reports.end()) { return std::nullopt; } + if (f.include_partitions && f.ntp_filters.namespaces.empty()) { + return ss::make_foreign(it->second); + } node_health_report report; report.id = id; - report.local_state = it->second.local_state; + report.local_state = it->second->local_state; report.local_state.logical_version = features::feature_table::get_latest_logical_version(); if (f.include_partitions) { - report.topics = filter_topic_status(it->second.topics, f.ntp_filters); + report.topics = filter_topic_status(it->second->topics, f.ntp_filters); } - report.drain_status = it->second.drain_status; + report.drain_status = it->second->drain_status; report.include_drain_status = true; - return report; + return ss::make_foreign( + ss::make_lw_shared(std::move(report))); } void health_monitor_backend::abortable_refresh_request::abort() { @@ -243,9 +216,8 @@ void health_monitor_backend::abortable_refresh_request::abort() { } health_monitor_backend::abortable_refresh_request::abortable_refresh_request( - model::node_id leader_id, ss::gate::holder holder, ssx::semaphore_units u) - : leader_id(leader_id) - , holder(std::move(holder)) + ss::gate::holder holder, ssx::semaphore_units u) + : holder(std::move(holder)) , units(std::move(u)) {} ss::future @@ -275,29 +247,11 @@ health_monitor_backend::abortable_refresh_request::abortable_await( ss::future health_monitor_backend::refresh_cluster_health_cache(force_refresh force) { auto holder = _gate.hold(); - auto leader_id = _raft0->get_leader_id(); - - // leadership change, abort old refresh request - if (_refresh_request && leader_id != _refresh_request->leader_id) { - abort_current_refresh(); - } auto units = co_await _refresh_mutex.get_units(); - // refresh leader_id after acquiring mutex - leader_id = _raft0->get_leader_id(); - - // recheck if the leader exists, since this might have changed - // while we were waiting - if (!leader_id) { - vlog( - clusterlog.info, - "unable to refresh health metadata, no leader controller"); - co_return errc::no_leader_controller; - } - // check under semaphore if we need to force refresh, otherwise we will just - // skip refresh request since current state is 'fresh enough' i.e. not older - // than max metadata age + // just skip refresh request since current state is 'fresh enough' i.e. + // not older than max metadata age auto now = model::timeout_clock::now(); if (!force && now - _last_refresh < max_metadata_age()) { vlog( @@ -307,95 +261,18 @@ health_monitor_backend::refresh_cluster_health_cache(force_refresh force) { co_return errc::success; } - vlog( - clusterlog.debug, - "refreshing health cache, leader id: {} self: {}", - leader_id, - _raft0->self().id()); + vlog(clusterlog.debug, "refreshing health cache"); _refresh_request = ss::make_lw_shared( - *leader_id, std::move(holder), std ::move(units)); - - // we either collect the cluster health reports while on raft0 leader or ask - // current leader for cluster health - auto f = leader_id == _raft0->self().id() - ? collect_cluster_health() - : dispatch_refresh_cluster_health_request(*leader_id); + std::move(holder), std ::move(units)); - co_return co_await _refresh_request->abortable_await(std::move(f)); -} - -ss::future -health_monitor_backend::dispatch_refresh_cluster_health_request( - model::node_id node_id) { - const auto timeout = model::timeout_clock::now() + max_metadata_age(); - auto reply = co_await _connections.local() - .with_node_client( - _raft0->self().id(), - ss::this_shard_id(), - node_id, - max_metadata_age(), - [timeout](controller_client_protocol client) mutable { - get_cluster_health_request req{ - .filter = cluster_report_filter{}}; - return client.get_cluster_health_report( - std::move(req), rpc::client_opts(timeout)); - }) - .then(&rpc::get_ctx_data); - - if (!reply) { - vlog( - clusterlog.warn, - "unable to get cluster health metadata from {} - {}", - node_id, - reply.error().message()); - co_return reply.error(); - } - - if (!reply.value().report) { - vlog( - clusterlog.warn, - "unable to get cluster health metadata from {} - {}", - node_id, - reply.value().error); - co_return make_error_code(reply.value().error); - } - - _status.clear(); - for (auto& n_status : reply.value().report->node_states) { - _status.emplace(n_status.id, n_status); - } - vlog(clusterlog.trace, "Status cache updated from the leader: {}", _status); - - storage::disk_space_alert cluster_disk_health - = storage::disk_space_alert::ok; - _reports.clear(); - for (auto& n_report : reply.value().report->node_reports) { - const auto id = n_report.id; - - // Recompute alert state, in case it was deserialized from an old - // node that didn't include alert state in the serialized storage::disk. - node::local_monitor::update_alert(n_report.local_state.data_disk); - - // Update cached cluster-level disk health: non-raft0-leader nodes - cluster_disk_health = storage::max_severity( - cluster_disk_health, n_report.local_state.data_disk.alert); - - _reports.emplace(id, std::move(n_report)); - } - - _bytes_in_cloud_storage = reply.value().report->bytes_in_cloud_storage; - _reports_disk_health = cluster_disk_health; - _last_refresh = ss::lowres_clock::now(); - co_return make_error_code(errc::success); + co_return co_await _refresh_request->abortable_await( + collect_cluster_health()); } void health_monitor_backend::abort_current_refresh() { if (_refresh_request) { - vlog( - clusterlog.debug, - "aborting current refresh request to {}", - _refresh_request->leader_id); + vlog(clusterlog.debug, "aborting current refresh request"); _refresh_request->abort(); } } @@ -405,18 +282,6 @@ bool health_monitor_backend::contains_node_health_report( return _reports.contains(id); } -void health_monitor_backend::on_leadership_changed( - raft::group_id group, model::term_id, std::optional) { - // we are only interested in raft0 leadership notifications - if (_raft0->group() != group) { - return; - } - // controller leadership changed, abort refresh request to current leader, - // as it may be not available, and allow subsequent calls to be dispatched - // to new leader - abort_current_refresh(); -} - ss::future> health_monitor_backend::get_cluster_health( cluster_report_filter filter, @@ -497,14 +362,13 @@ health_monitor_backend::collect_remote_node_health(model::node_id id) { const auto timeout = model::timeout_clock::now() + max_metadata_age(); return _connections.local() .with_node_client( - _raft0->self().id(), + _self, ss::this_shard_id(), id, max_metadata_age(), - [timeout](controller_client_protocol client) mutable { + [timeout, id](controller_client_protocol client) mutable { return client.collect_node_health_report( - get_node_health_request{.filter = node_report_filter{}}, - rpc::client_opts(timeout)); + get_node_health_request(id), rpc::client_opts(timeout)); }) .then(&rpc::get_ctx_data) .then([this, id](result reply) { @@ -512,22 +376,25 @@ health_monitor_backend::collect_remote_node_health(model::node_id id) { }); } -result -map_reply_result(result reply) { +result map_reply_result( + model::node_id target_node_id, result reply) { if (!reply) { - return result(reply.error()); + return {reply.error()}; } if (!reply.value().report.has_value()) { - return result(reply.value().error); + return {reply.value().error}; } - return result(std::move(*reply.value().report)); + if (reply.value().report->id != target_node_id) { + return {errc::invalid_target_node_id}; + } + + return {std::move(*reply.value().report)}; } result health_monitor_backend::process_node_reply( model::node_id id, result reply) { - auto [it, _] = _last_replies.try_emplace(id); - - auto res = map_reply_result(reply); + auto res = map_reply_result(id, std::move(reply)); + auto [status_it, _] = _status.try_emplace(id); if (!res) { vlog( clusterlog.trace, @@ -537,29 +404,31 @@ result health_monitor_backend::process_node_reply( /** * log only once node state transition from alive to down */ - if (it->second.is_alive) { + if (status_it->second.is_alive) { vlog( clusterlog.warn, "unable to get node health report from {} - {}, marking node as " "down", id, res.error().message()); + status_it->second.is_alive = alive::no; } - return result(reply.error()); + return res.error(); } // TODO serialize storage_space_alert, instead of recomputing here. auto& s = res.value().local_state; node::local_monitor::update_alert(s.data_disk); - - it->second.last_reply_timestamp = ss::lowres_clock::now(); - if (!it->second.is_alive && clusterlog.is_enabled(ss::log_level::info)) { + if ( + !status_it->second.is_alive + && clusterlog.is_enabled(ss::log_level::info)) { vlog( clusterlog.info, "received node {} health report, marking node as up", id); } - it->second.is_alive = alive::yes; + status_it->second.last_reply_timestamp = ss::lowres_clock::now(); + status_it->second.is_alive = alive::yes; return res; } @@ -573,8 +442,9 @@ ss::future health_monitor_backend::collect_cluster_health() { auto ids = _members.local().node_ids(); auto reports = co_await ssx::async_transform( ids.begin(), ids.end(), [this](model::node_id id) { - if (id == _raft0->self().id()) { - return collect_current_node_health(node_report_filter{}); + if (id == _self) { + return _report_collection_mutex.with( + [this] { return collect_current_node_health(); }); } return collect_remote_node_health(id); }); @@ -593,8 +463,7 @@ ss::future health_monitor_backend::collect_cluster_health() { id, r.value()); - std::optional> - old_report; + std::optional old_report; if (auto old_i = old_reports.find(id); old_i != old_reports.end()) { vlog( clusterlog.debug, @@ -612,7 +481,8 @@ ss::future health_monitor_backend::collect_cluster_health() { cluster_disk_health = storage::max_severity( r.value().local_state.get_disk_alert(), cluster_disk_health); - _reports.emplace(id, std::move(r.value())); + _reports.emplace( + id, ss::make_lw_shared(std::move(r.value()))); } } _reports_disk_health = cluster_disk_health; @@ -639,15 +509,24 @@ ss::future health_monitor_backend::collect_cluster_health() { } } + auto not_in_members_table = [this](const auto& value) { + return !_members.local().contains(value.first); + }; + /** + * Remove reports from nodes that were removed + */ + absl::erase_if(_reports, not_in_members_table); + absl::erase_if(_status, not_in_members_table); + _last_refresh = ss::lowres_clock::now(); co_return errc::success; } ss::future> -health_monitor_backend::collect_current_node_health(node_report_filter filter) { - vlog(clusterlog.debug, "collecting health report with filter: {}", filter); +health_monitor_backend::collect_current_node_health() { + vlog(clusterlog.debug, "collecting health report"); node_health_report ret; - ret.id = _raft0->self().id(); + ret.id = _self; ret.local_state = _local_monitor.local().get_state_cached(); ret.local_state.logical_version @@ -655,81 +534,82 @@ health_monitor_backend::collect_current_node_health(node_report_filter filter) { ret.drain_status = co_await _drain_manager.local().status(); ret.include_drain_status = true; + ret.topics = co_await collect_topic_status(); - if (filter.include_partitions) { - ret.topics = co_await collect_topic_status( - std::move(filter.ntp_filters)); - } + auto [it, _] = _status.try_emplace(ret.id); + it->second.is_alive = alive::yes; + it->second.last_reply_timestamp = ss::lowres_clock::now(); co_return ret; } -namespace { +ss::future> +health_monitor_backend::get_current_node_health() { + vlog(clusterlog.debug, "getting current node health"); -struct ntp_leader { - model::term_id term; - std::optional leader_id; - model::revision_id revision_id; -}; + auto it = _reports.find(_self); + if (it != _reports.end()) { + co_return *it->second; + } -struct ntp_report { - model::ntp ntp; - ntp_leader leader; - size_t size_bytes; - std::optional under_replicated_replicas; - size_t reclaimable_size_bytes; -}; + auto u = _report_collection_mutex.try_get_units(); + /** + * If units are not available it indicates that the other fiber is + * collecting node health. We wait for the report to be available + */ + if (!u) { + vlog( + clusterlog.debug, + "report collection in progress, waiting for report to be available"); + u.emplace(co_await _report_collection_mutex.get_units()); + auto it = _reports.find(_self); + if (it != _reports.end()) { + co_return *it->second; + } + } + /** + * Current fiber will collect and cache the report + */ + auto r = co_await collect_current_node_health(); + if (r.has_value()) { + _reports.emplace( + _self, ss::make_lw_shared(r.value())); + } -partition_status to_partition_status(const ntp_report& ntpr) { - return partition_status{ - .id = ntpr.ntp.tp.partition, - .term = ntpr.leader.term, - .leader_id = ntpr.leader.leader_id, - .revision_id = ntpr.leader.revision_id, - .size_bytes = ntpr.size_bytes, - .under_replicated_replicas = ntpr.under_replicated_replicas, - .reclaimable_size_bytes = ntpr.reclaimable_size_bytes}; + co_return std::move(r); } -ss::chunked_fifo collect_shard_local_reports( - partition_manager& pm, const partitions_filter& filters) { - ss::chunked_fifo reports; - // empty filter, collect all - if (filters.namespaces.empty()) { - reports.reserve(pm.partitions().size()); - std::transform( - pm.partitions().begin(), - pm.partitions().end(), - std::back_inserter(reports), - [](auto& p) { - return ntp_report{ - .ntp = p.first, - .leader = ntp_leader{ - .term = p.second->term(), - .leader_id = p.second->get_leader_id(), - .revision_id = p.second->get_revision_id(), - }, - .size_bytes = p.second->size_bytes() + p.second->non_log_disk_size_bytes(), - .under_replicated_replicas = p.second->get_under_replicated(), - .reclaimable_size_bytes = p.second->reclaimable_size_bytes(), +namespace { + +struct ntp_report { + model::topic_namespace tp_ns; + partition_status status; +}; + +chunked_vector collect_shard_local_reports(partition_manager& pm) { + chunked_vector reports; + + reports.reserve(pm.partitions().size()); + std::transform( + pm.partitions().begin(), + pm.partitions().end(), + std::back_inserter(reports), + [](auto& p) { + return ntp_report { + .tp_ns = model::topic_namespace(p.first.ns, p.first.tp.topic), + .status = partition_status{ + .id = p.first.tp.partition, + .term = p.second->term(), + .leader_id = p.second->get_leader_id(), + .revision_id = p.second->get_revision_id(), + .size_bytes = p.second->size_bytes() + + p.second->non_log_disk_size_bytes(), + .under_replicated_replicas + = p.second->get_under_replicated(), + .reclaimable_size_bytes + = p.second->reclaimable_size_bytes(), + }, }; - }); - } else { - for (const auto& [ntp, partition] : pm.partitions()) { - if (filters.matches(ntp)) { - reports.push_back(ntp_report{ - .ntp = ntp, - .leader = ntp_leader{ - .term = partition->term(), - .leader_id = partition->get_leader_id(), - .revision_id = partition->get_revision_id(), - }, - .size_bytes = partition->size_bytes() + partition->non_log_disk_size_bytes(), - .under_replicated_replicas = partition->get_under_replicated(), - .reclaimable_size_bytes = partition->reclaimable_size_bytes(), - }); - } - } - } + }); return reports; } @@ -738,22 +618,17 @@ using reports_acc_t = absl::node_hash_map; reports_acc_t reduce_reports_map( - reports_acc_t acc, ss::chunked_fifo current_reports) { + reports_acc_t acc, chunked_vector current_reports) { for (auto& ntpr : current_reports) { - model::topic_namespace tp_ns( - std::move(ntpr.ntp.ns), std::move(ntpr.ntp.tp.topic)); - - acc[tp_ns].push_back(to_partition_status(ntpr)); + acc[ntpr.tp_ns].push_back(ntpr.status); } return acc; } } // namespace ss::future> -health_monitor_backend::collect_topic_status(partitions_filter filters) { +health_monitor_backend::collect_topic_status() { auto reports_map = co_await _partition_manager.map_reduce0( - [&filters](partition_manager& pm) { - return collect_shard_local_reports(pm, filters); - }, + [](partition_manager& pm) { return collect_shard_local_reports(pm); }, reports_acc_t{}, &reduce_reports_map); @@ -773,7 +648,7 @@ std::chrono::milliseconds health_monitor_backend::max_metadata_age() { ss::future>> health_monitor_backend::get_node_drain_status( model::node_id node_id, model::timeout_clock::time_point deadline) { - if (node_id == _raft0->self().id()) { + if (node_id == _self) { // Fast path: if we are asked for our own drain status, give fresh // data instead of spending time reloading health status which might // be outdated. @@ -791,7 +666,7 @@ health_monitor_backend::get_node_drain_status( co_return errc::node_does_not_exists; } - co_return it->second.drain_status; + co_return it->second->drain_status; } health_monitor_backend::aggregated_report @@ -828,7 +703,7 @@ health_monitor_backend::aggregate_reports(report_cache_t& reports) { collector leaderless, urp; for (const auto& [_, report] : reports) { - for (const auto& [tp_ns, partitions] : report.topics) { + for (const auto& [tp_ns, partitions] : report->topics) { auto& leaderless_this_topic = leaderless.t_to_p[tp_ns]; auto& urp_this_topic = urp.t_to_p[tp_ns]; @@ -862,7 +737,7 @@ health_monitor_backend::get_cluster_health_overview( for (auto& [id, _] : brokers) { ret.all_nodes.push_back(id); - if (id != _raft0->self().id()) { + if (id != _self) { auto it = _status.find(id); if (it == _status.end() || !it->second.is_alive) { ret.nodes_down.push_back(id); @@ -871,7 +746,7 @@ health_monitor_backend::get_cluster_health_overview( auto report_it = _reports.find(id); if ( report_it != _reports.end() - && report_it->second.local_state.recovery_mode_enabled) { + && report_it->second->local_state.recovery_mode_enabled) { ret.nodes_in_recovery_mode.push_back(id); } } diff --git a/src/v/cluster/health_monitor_backend.h b/src/v/cluster/health_monitor_backend.h index 06c51ea646028..c8ae59f88da52 100644 --- a/src/v/cluster/health_monitor_backend.h +++ b/src/v/cluster/health_monitor_backend.h @@ -31,8 +31,8 @@ namespace cluster { using health_node_cb_t = ss::noncopyable_function>)>; + const node_health_report&, + std::optional>)>; /** * Health monitor backend is responsible for collecting cluster health status @@ -68,8 +68,12 @@ class health_monitor_backend { ss::future get_cluster_disk_health( force_refresh refresh, model::timeout_clock::time_point deadline); - ss::future> - collect_current_node_health(node_report_filter); + ss::future> collect_current_node_health(); + /** + * Return cached version of current node health of collects it if it is not + * available in cache. + */ + ss::future> get_current_node_health(); cluster::notification_id_type register_node_callback(health_node_cb_t cb); void unregister_node_callback(cluster::notification_id_type id); @@ -90,15 +94,14 @@ class health_monitor_backend { */ struct abortable_refresh_request : ss::enable_lw_shared_from_this { - abortable_refresh_request( - model::node_id, ss::gate::holder, ssx::semaphore_units); + abortable_refresh_request(ss::gate::holder, ssx::semaphore_units); ss::future abortable_await(ss::future); void abort(); bool finished = false; - model::node_id leader_id; + ss::gate::holder holder; ssx::semaphore_units units; ss::promise done; @@ -110,12 +113,9 @@ class health_monitor_backend { alive is_alive = alive::no; }; - using status_cache_t = absl::node_hash_map; - using report_cache_t - = absl::node_hash_map; - - using last_reply_cache_t - = absl::node_hash_map; + using status_cache_t = absl::node_hash_map; + using nhr_ptr = ss::lw_shared_ptr; + using report_cache_t = absl::node_hash_map; void tick(); ss::future collect_cluster_health(); @@ -124,18 +124,13 @@ class health_monitor_backend { ss::future maybe_refresh_cluster_health( force_refresh, model::timeout_clock::time_point); ss::future refresh_cluster_health_cache(force_refresh); - ss::future - dispatch_refresh_cluster_health_request(model::node_id); cluster_health_report build_cluster_report(const cluster_report_filter&); - std::optional + std::optional build_node_report(model::node_id, const node_report_filter&); - ss::future> - collect_topic_status(partitions_filter); - - void refresh_nodes_status(); + ss::future> collect_topic_status(); result process_node_reply(model::node_id, result); @@ -143,9 +138,6 @@ class health_monitor_backend { std::chrono::milliseconds max_metadata_age(); void abort_current_refresh(); - void on_leadership_changed( - raft::group_id, model::term_id, std::optional); - /** * @brief Stucture holding the aggregated results of partition status. */ @@ -187,23 +179,24 @@ class health_monitor_backend { ss::lowres_clock::time_point _last_refresh; ss::lw_shared_ptr _refresh_request; - cluster::notification_id_type _leadership_notification_handle; status_cache_t _status; report_cache_t _reports; storage::disk_space_alert _reports_disk_health = storage::disk_space_alert::ok; - last_reply_cache_t _last_replies; std::optional _bytes_in_cloud_storage; ss::gate _gate; mutex _refresh_mutex; ss::sharded& _local_monitor; + model::node_id _self; std::vector> _node_callbacks; cluster::notification_id_type _next_callback_id{0}; + mutex _report_collection_mutex{"health_report_collection"}; + friend struct health_report_accessor; }; } // namespace cluster diff --git a/src/v/cluster/health_monitor_frontend.cc b/src/v/cluster/health_monitor_frontend.cc index 2b49caa705c4f..d1bf9b39b8627 100644 --- a/src/v/cluster/health_monitor_frontend.cc +++ b/src/v/cluster/health_monitor_frontend.cc @@ -10,16 +10,26 @@ */ #include "cluster/health_monitor_frontend.h" +#include "cluster/health_monitor_backend.h" #include "cluster/logger.h" +#include "config/property.h" #include "model/timeout_clock.h" #include +#include +#include +#include + namespace cluster { health_monitor_frontend::health_monitor_frontend( - ss::sharded& backend) - : _backend(backend) {} + ss::sharded& backend, + ss::sharded& node_status_table, + config::binding alive_timeout) + : _backend(backend) + , _node_status_table(node_status_table) + , _alive_timeout(std::move(alive_timeout)) {} ss::future<> health_monitor_frontend::start() { if (ss::this_shard_id() == refresher_shard) { @@ -54,38 +64,24 @@ storage::disk_space_alert health_monitor_frontend::get_cluster_disk_health() { return _cluster_disk_health; } -// Collcts and returns current node health report according to provided -// filters list +/** + * Gets cached or collects a node health report. + */ ss::future> -health_monitor_frontend::collect_node_health(node_report_filter f) { - return dispatch_to_backend( - [f = std::move(f)](health_monitor_backend& be) mutable { - return be.collect_current_node_health(std::move(f)); - }); -} - -// Return status of single node -ss::future>> -health_monitor_frontend::get_nodes_status( - model::timeout_clock::time_point deadline) { - return dispatch_to_backend([deadline](health_monitor_backend& be) { - // build filter - cluster_report_filter filter{ - .node_report_filter = node_report_filter{ - .include_partitions = include_partitions_info::no, - }}; - return be.get_cluster_health(filter, force_refresh::no, deadline) - .then([](result res) { - using ret_t = result>; - if (!res) { - return ret_t(res.error()); - } - - return ret_t(std::move(res.value().node_states)); - }); +health_monitor_frontend::get_current_node_health() { + return dispatch_to_backend([](health_monitor_backend& be) mutable { + return be.get_current_node_health(); }); } - +std::optional +health_monitor_frontend::is_alive(model::node_id id) const { + auto status = _node_status_table.local().get_node_status(id); + if (!status) { + return std::nullopt; + } + return alive( + status->last_seen + _alive_timeout() >= model::timeout_clock::now()); +} ss::future>> health_monitor_frontend::get_node_drain_status( model::node_id node_id, model::timeout_clock::time_point deadline) { diff --git a/src/v/cluster/health_monitor_frontend.h b/src/v/cluster/health_monitor_frontend.h index b51f0b01b9da0..ece4754ad6242 100644 --- a/src/v/cluster/health_monitor_frontend.h +++ b/src/v/cluster/health_monitor_frontend.h @@ -10,8 +10,9 @@ */ #pragma once #include "cluster/fwd.h" -#include "cluster/health_monitor_backend.h" #include "cluster/health_monitor_types.h" +#include "cluster/node_status_table.h" +#include "config/property.h" #include "controller_stm.h" #include "model/metadata.h" #include "model/timeout_clock.h" @@ -19,8 +20,6 @@ #include -#include - namespace cluster { /** @@ -31,6 +30,9 @@ namespace cluster { * health monitor backend which lives on single shard. * Most requests are forwarded to the backend shard, except cluster-level disk * health, which is kept cached on each core for fast access. + * + * Health monitor frontend is also an entry point for querying information about + * the node liveness status. */ class health_monitor_frontend : public seastar::peering_sharded_service { @@ -40,7 +42,10 @@ class health_monitor_frontend static constexpr ss::shard_id refresher_shard = cluster::controller_stm_shard; - explicit health_monitor_frontend(ss::sharded&); + explicit health_monitor_frontend( + ss::sharded&, + ss::sharded&, + config::binding); ss::future<> start(); ss::future<> stop(); @@ -54,14 +59,8 @@ class health_monitor_frontend storage::disk_space_alert get_cluster_disk_health(); - // Collcts and returns current node health report according to provided - // filters list - ss::future> - collect_node_health(node_report_filter); - - // Return status of all nodes - ss::future>> - get_nodes_status(model::timeout_clock::time_point); + // Collects or return cached version of current node health report. + ss::future> get_current_node_health(); /** * Return drain status for a given node. @@ -84,22 +83,28 @@ class health_monitor_frontend get_cluster_health_overview(model::timeout_clock::time_point); ss::future does_raft0_have_leader(); + /** + * Method validating if a node is known and alive. It will return an empty + * optional if the node is not present in node status table. + */ + std::optional is_alive(model::node_id) const; private: template auto dispatch_to_backend(Func&& f) { return _backend.invoke_on( - health_monitor_backend::shard, std::forward(f)); + health_monitor_backend_shard, std::forward(f)); } ss::sharded& _backend; + ss::sharded& _node_status_table; + config::binding _alive_timeout; // Currently the worst / max of all nodes' disk space state storage::disk_space_alert _cluster_disk_health{ storage::disk_space_alert::ok}; ss::timer _refresh_timer; ss::gate _refresh_gate; - void disk_health_tick(); ss::future<> update_other_shards(const storage::disk_space_alert); ss::future<> update_frontend_and_backend_cache(); diff --git a/src/v/cluster/health_monitor_types.cc b/src/v/cluster/health_monitor_types.cc index 94ab92c054b59..f1eaebe72ca0c 100644 --- a/src/v/cluster/health_monitor_types.cc +++ b/src/v/cluster/health_monitor_types.cc @@ -20,12 +20,15 @@ #include "utils/to_string.h" #include +#include +#include #include #include #include #include +#include namespace cluster { @@ -56,12 +59,18 @@ bool partitions_filter::matches( return false; } +node_state::node_state( + model::node_id id, model::membership_state membership_state, alive is_alive) + : _id(id) + , _membership_state(membership_state) + , _is_alive(is_alive) {} + std::ostream& operator<<(std::ostream& o, const node_state& s) { fmt::print( o, "{{membership_state: {}, is_alive: {}}}", - s.membership_state, - s.is_alive); + s._membership_state, + s._is_alive); return o; } @@ -188,8 +197,34 @@ bool operator==(const topic_status& a, const topic_status& b) { b.partitions.cend()); } +cluster_health_report cluster_health_report::copy() const { + cluster_health_report r; + r.raft0_leader = raft0_leader; + r.node_states = node_states; + r.bytes_in_cloud_storage = bytes_in_cloud_storage; + r.node_reports.reserve(node_reports.size()); + for (auto& nr : node_reports) { + node_health_report nr_copy; + nr_copy.id = nr->id; + nr_copy.drain_status = nr->drain_status; + nr_copy.topics = nr->topics.copy(); + nr_copy.local_state = nr->local_state; + + r.node_reports.emplace_back(ss::make_lw_shared(std::move(nr_copy))); + } + return r; +} + +get_cluster_health_reply get_cluster_health_reply::copy() const { + get_cluster_health_reply reply{.error = error}; + if (report.has_value()) { + reply.report = report->copy(); + } + return reply; +} + std::ostream& operator<<(std::ostream& o, const topic_status& tl) { - fmt::print(o, "{{topic: {}, leaders: {}}}", tl.tp_ns, tl.partitions); + fmt::print(o, "{{topic: {}, partitions: {}}}", tl.tp_ns, tl.partitions); return o; } @@ -232,8 +267,7 @@ std::ostream& operator<<(std::ostream& o, const partitions_filter& filter) { } std::ostream& operator<<(std::ostream& o, const get_node_health_request& r) { - fmt::print( - o, "{{filter: {}, current_version: {}}}", r.filter, r.current_version); + fmt::print(o, "{{target_node_id: {}}}", r.get_target_node_id()); return o; } @@ -257,4 +291,24 @@ std::ostream& operator<<(std::ostream& o, const get_cluster_health_reply& r) { return o; } +std::ostream& operator<<(std::ostream& o, const cluster_health_overview& ho) { + fmt::print( + o, + "{{controller_id: {}, nodes: {}, unhealthy_reasons: {}, nodes_down: {}, " + "nodes_in_recovery_mode: {}, bytes_in_cloud_storage: {}, " + "leaderless_count: {}, under_replicated_count: {}, " + "leaderless_partitions: {}, under_replicated_partitions: {}}}", + ho.controller_id, + ho.all_nodes, + ho.unhealthy_reasons, + ho.nodes_down, + ho.nodes_in_recovery_mode, + ho.bytes_in_cloud_storage, + ho.leaderless_count, + ho.under_replicated_count, + ho.leaderless_partitions, + ho.under_replicated_partitions); + return o; +} + } // namespace cluster diff --git a/src/v/cluster/health_monitor_types.h b/src/v/cluster/health_monitor_types.h index 48de6277ae90a..e41a1c3f05495 100644 --- a/src/v/cluster/health_monitor_types.h +++ b/src/v/cluster/health_monitor_types.h @@ -17,6 +17,7 @@ #include "model/fundamental.h" #include "model/metadata.h" #include "reflection/adl.h" +#include "serde/async.h" #include "utils/named_type.h" #include @@ -28,6 +29,7 @@ namespace cluster { +static constexpr ss::shard_id health_monitor_backend_shard = 0; /** * Health reports */ @@ -44,15 +46,40 @@ using application_version = named_type; struct node_state : serde::envelope, serde::compat_version<0>> { static constexpr int8_t current_version = 0; - - model::node_id id; - model::membership_state membership_state; - alive is_alive; + node_state( + model::node_id id, + model::membership_state membership_state, + alive is_alive); + + node_state() = default; + node_state(const node_state&) = default; + node_state(node_state&&) noexcept = default; + node_state& operator=(const node_state&) = default; + node_state& operator=(node_state&&) noexcept = default; + ~node_state() noexcept = default; + + model::node_id id() const { return _id; } + + model::membership_state membership_state() const { + return _membership_state; + } + // clang-format off + [[deprecated("please use health_monitor_frontend::is_alive() to query for " + "liveness")]] + alive is_alive() const { + return _is_alive; + } + // clang-format on friend std::ostream& operator<<(std::ostream&, const node_state&); friend bool operator==(const node_state&, const node_state&) = default; - auto serde_fields() { return std::tie(id, membership_state, is_alive); } + auto serde_fields() { return std::tie(_id, _membership_state, _is_alive); } + +private: + model::node_id _id; + model::membership_state _membership_state; + alive _is_alive; }; struct partition_status @@ -190,6 +217,8 @@ struct node_health_report operator==(const node_health_report& a, const node_health_report& b); }; +using node_health_report_ptr + = ss::foreign_ptr>; struct cluster_health_report : serde::envelope< cluster_health_report, @@ -202,7 +231,7 @@ struct cluster_health_report // node reports are node specific information collected directly on a // node - std::vector node_reports; + std::vector node_reports; // cluster-wide cached information about total cloud storage usage std::optional bytes_in_cloud_storage; @@ -213,9 +242,81 @@ struct cluster_health_report operator==(const cluster_health_report&, const cluster_health_report&) = default; - auto serde_fields() { - return std::tie( - raft0_leader, node_states, node_reports, bytes_in_cloud_storage); + cluster_health_report copy() const; + + ss::future<> serde_async_write(iobuf& out) { + using serde::write; + using serde::write_async; + // the current version decodes into the decoded version and is used in + // request handling--that is, it is used at layer above serialization so + // without further changes we'll need to preserve that behavior. + write(out, raft0_leader); + write(out, node_states); + write(out, static_cast(node_reports.size())); + for (auto& nr : node_reports) { + co_await write_async(out, *nr); + } + write(out, bytes_in_cloud_storage); + } + + ss::future<> serde_async_read(iobuf_parser& in, const serde::header& h) { + using serde::read_async_nested; + using serde::read_nested; + raft0_leader = read_nested>( + in, h._bytes_left_limit); + node_states = read_nested>( + in, h._bytes_left_limit); + const auto sz = read_nested( + in, h._bytes_left_limit); + node_reports.reserve(sz); + for (auto i = 0U; i < sz; ++i) { + auto r = co_await read_async_nested( + in, h._bytes_left_limit); + node_reports.emplace_back( + ss::make_lw_shared(std::move(r))); + } + bytes_in_cloud_storage = read_nested>( + in, h._bytes_left_limit); + + if (in.bytes_left() > h._bytes_left_limit) { + in.skip(in.bytes_left() - h._bytes_left_limit); + } + } + void serde_write(iobuf& out) { + using serde::write; + + // the current version decodes into the decoded version and is used in + // request handling--that is, it is used at layer above serialization so + // without further changes we'll need to preserve that behavior. + write(out, raft0_leader); + write(out, node_states); + write(out, static_cast(node_reports.size())); + for (auto& nr : node_reports) { + write(out, *nr); + } + write(out, bytes_in_cloud_storage); + } + + void serde_read(iobuf_parser& in, const serde::header& h) { + using serde::read_nested; + raft0_leader = read_nested>( + in, h._bytes_left_limit); + node_states = read_nested>( + in, h._bytes_left_limit); + const auto sz = read_nested( + in, h._bytes_left_limit); + node_reports.reserve(sz); + for (auto i = 0U; i < sz; ++i) { + auto r = read_nested(in, h._bytes_left_limit); + node_reports.emplace_back( + ss::make_lw_shared(std::move(r))); + } + bytes_in_cloud_storage = read_nested>( + in, h._bytes_left_limit); + + if (in.bytes_left() > h._bytes_left_limit) { + in.skip(in.bytes_left() - h._bytes_left_limit); + } } }; @@ -250,6 +351,9 @@ struct cluster_health_overview { std::vector under_replicated_partitions; size_t under_replicated_count{}; std::optional bytes_in_cloud_storage; + + friend std::ostream& + operator<<(std::ostream&, const cluster_health_overview&); }; using include_partitions_info = ss::bool_class; @@ -323,23 +427,16 @@ using force_refresh = ss::bool_class; * RPC requests */ -struct get_node_health_request - : serde::envelope< +class get_node_health_request + : public serde::envelope< get_node_health_request, - serde::version<0>, + serde::version<1>, serde::compat_version<0>> { +public: using rpc_adl_exempt = std::true_type; - static constexpr int8_t initial_version = 0; - // version -1: included revision id in partition status - static constexpr int8_t revision_id_version = -1; - // version -2: included size_bytes in partition status - static constexpr int8_t size_bytes_version = -2; - - static constexpr int8_t current_version = size_bytes_version; - - node_report_filter filter; - // this field is not serialized - int8_t decoded_version = current_version; + get_node_health_request() = default; + explicit get_node_health_request(model::node_id target_node_id) + : _target_node_id(target_node_id) {} friend bool operator==(const get_node_health_request&, const get_node_health_request&) @@ -348,7 +445,19 @@ struct get_node_health_request friend std::ostream& operator<<(std::ostream&, const get_node_health_request&); - auto serde_fields() { return std::tie(filter); } + auto serde_fields() { return std::tie(_filter, _target_node_id); } + static constexpr model::node_id node_id_not_set{-1}; + + model::node_id get_target_node_id() const { return _target_node_id; } + +private: + // default value for backward compatibility + model::node_id _target_node_id = node_id_not_set; + /** + * This field is no longer used, as it never was. It was made private on + * purpose + */ + node_report_filter _filter; }; struct get_node_health_reply @@ -357,7 +466,6 @@ struct get_node_health_reply serde::version<0>, serde::compat_version<0>> { using rpc_adl_exempt = std::true_type; - static constexpr int8_t current_version = 0; errc error = cluster::errc::success; std::optional report; @@ -435,6 +543,8 @@ struct get_cluster_health_reply friend std::ostream& operator<<(std::ostream&, const get_cluster_health_reply&); + get_cluster_health_reply copy() const; + auto serde_fields() { return std::tie(error, report); } }; diff --git a/src/v/cluster/id_allocator.cc b/src/v/cluster/id_allocator.cc index d7060fbaafb5b..d59235e06986a 100644 --- a/src/v/cluster/id_allocator.cc +++ b/src/v/cluster/id_allocator.cc @@ -31,15 +31,15 @@ id_allocator::allocate_id(allocate_id_request req, rpc::streaming_context&) { auto timeout = req.timeout; return _id_allocator_frontend.local() .allocator_router() - .allocate_router::process_or_dispatch( - std::move(req), model::id_allocator_ntp, timeout); + .find_shard_and_process(std::move(req), model::id_allocator_ntp, timeout); } ss::future id_allocator::reset_id_allocator( reset_id_allocator_request req, rpc::streaming_context&) { auto timeout = req.timeout; - return _id_allocator_frontend.local().id_reset_router().process_or_dispatch( - std::move(req), model::id_allocator_ntp, timeout); + return _id_allocator_frontend.local() + .id_reset_router() + .find_shard_and_process(std::move(req), model::id_allocator_ntp, timeout); } } // namespace cluster diff --git a/src/v/cluster/id_allocator_stm.h b/src/v/cluster/id_allocator_stm.h index 09c65a0041ce6..5d232fe0a2a5f 100644 --- a/src/v/cluster/id_allocator_stm.h +++ b/src/v/cluster/id_allocator_stm.h @@ -34,6 +34,8 @@ namespace cluster { class id_allocator_stm final : public raft::persisted_stm<> { public: + static constexpr const char* name = "id_allocator_stm"; + struct stm_allocation_result { int64_t id; raft::errc raft_status{raft::errc::success}; @@ -47,7 +49,6 @@ class id_allocator_stm final : public raft::persisted_stm<> { ss::future allocate_id(model::timeout_clock::duration timeout); - std::string_view get_name() const final { return "id_allocator_stm"; } ss::future take_snapshot(model::offset) final { co_return iobuf{}; } ss::future diff --git a/src/v/cluster/log_eviction_stm.h b/src/v/cluster/log_eviction_stm.h index 676fdd39b9dfa..206eba28331cd 100644 --- a/src/v/cluster/log_eviction_stm.h +++ b/src/v/cluster/log_eviction_stm.h @@ -46,6 +46,8 @@ class consensus; class log_eviction_stm : public raft::persisted_stm { public: + static constexpr const char* name = "log_eviction_stm"; + using offset_result = result; log_eviction_stm(raft::consensus*, ss::logger&, storage::kvstore&); @@ -103,7 +105,6 @@ class log_eviction_stm return model::next_offset(_delete_records_eviction_offset); } - std::string_view get_name() const final { return "log_eviction_stm"; } ss::future take_snapshot(model::offset) final { co_return iobuf{}; } protected: diff --git a/src/v/cluster/members_manager.h b/src/v/cluster/members_manager.h index 6e95bd3ca0e1f..fe51c5e224a4b 100644 --- a/src/v/cluster/members_manager.h +++ b/src/v/cluster/members_manager.h @@ -178,6 +178,11 @@ class members_manager { // when bootstrapping a cluster. ss::future<> set_initial_state(std::vector, uuid_map_t); + // Returns a reference to a map containing mapping between node ids and node + // uuids. Node UUID is node globally unique identifier which has an id + // assigned during join. + const uuid_map_t& get_id_by_uuid_map() const { return _id_by_uuid; } + private: using seed_iterator = std::vector::const_iterator; struct changed_nodes { diff --git a/src/v/cluster/metadata_cache.cc b/src/v/cluster/metadata_cache.cc index 304ddf5582e1e..00af346b28272 100644 --- a/src/v/cluster/metadata_cache.cc +++ b/src/v/cluster/metadata_cache.cc @@ -121,35 +121,17 @@ size_t metadata_cache::node_count() const { ss::future> metadata_cache::alive_nodes() const { std::vector brokers; - auto res = co_await _health_monitor.local().get_nodes_status( - config::shard_local_cfg().metadata_status_wait_timeout_ms() - + model::timeout_clock::now()); - if (!res) { - // if we were not able to refresh the cache, return all brokers - // (controller may be unreachable) - co_return _members_table.local().node_list(); - } - - std::set brokers_with_health; - for (auto& st : res.value()) { - brokers_with_health.insert(st.id); - if (st.is_alive) { - auto broker = _members_table.local().get_node_metadata(st.id); - if (broker) { - brokers.push_back(std::move(*broker)); - } - } - } - - // Corner case during node joins: - // If a node appears in the members table but not in the health report, - // presume it is newly added and assume it is alive. This avoids - // newly added nodes being inconsistently excluded from metadata - // responses until all nodes' health caches update. - for (const auto& [id, broker] : _members_table.local().nodes()) { - if (!brokers_with_health.contains(id)) { - brokers.push_back(broker); + for (auto& st : _members_table.local().node_list()) { + auto is_alive = _health_monitor.local().is_alive(st.broker.id()); + /** + * if node is not alive we skip adding it to the list of brokers. If + * there is no information or the node is healthy we include it into the + * list of alive brokers. + */ + if (is_alive == alive::no) { + continue; } + brokers.push_back(st); } co_return !brokers.empty() ? brokers : _members_table.local().node_list(); diff --git a/src/v/cluster/metrics_reporter.cc b/src/v/cluster/metrics_reporter.cc index ab2a5c8e3397b..b30661edacbb1 100644 --- a/src/v/cluster/metrics_reporter.cc +++ b/src/v/cluster/metrics_reporter.cc @@ -199,43 +199,33 @@ metrics_reporter::build_metrics_snapshot() { if (!report) { co_return result(report.error()); } - metrics_map.reserve(report.value().node_states.size()); + metrics_map.reserve(report.value().node_reports.size()); - for (auto& ns : report.value().node_states) { - auto [it, _] = metrics_map.emplace(ns.id, node_metrics{.id = ns.id}); + for (auto& report : report.value().node_reports) { + auto [it, _] = metrics_map.emplace( + report->id, node_metrics{.id = report->id}); auto& metrics = it->second; - metrics.is_alive = (bool)ns.is_alive; - auto nm = _members_table.local().get_node_metadata_ref(ns.id); + auto nm = _members_table.local().get_node_metadata_ref(report->id); if (!nm) { continue; } - metrics.cpu_count = nm->get().broker.properties().cores; - } - - for (auto& report : report.value().node_reports) { - auto it = metrics_map.find(report.id); - if (it == metrics_map.end()) { - auto [eit, _] = metrics_map.emplace( - report.id, node_metrics{.id = report.id}); - it = eit; - } - auto& metrics = it->second; - - metrics.version = report.local_state.redpanda_version; - metrics.logical_version = report.local_state.logical_version; - metrics.disks.reserve(report.local_state.shared_disk() ? 1 : 2); - auto transform_disk = [](storage::disk& d) -> node_disk_space { + metrics.is_alive = _health_monitor.local().is_alive(report->id) + == cluster::alive::yes; + metrics.version = report->local_state.redpanda_version; + metrics.logical_version = report->local_state.logical_version; + metrics.disks.reserve(report->local_state.shared_disk() ? 1 : 2); + auto transform_disk = [](const storage::disk& d) -> node_disk_space { return node_disk_space{.free = d.free, .total = d.total}; }; - metrics.disks.push_back(transform_disk(report.local_state.data_disk)); - if (!report.local_state.shared_disk()) { + metrics.disks.push_back(transform_disk(report->local_state.data_disk)); + if (!report->local_state.shared_disk()) { metrics.disks.push_back( - transform_disk(*(report.local_state.cache_disk))); + transform_disk(*(report->local_state.cache_disk))); } - metrics.uptime_ms = report.local_state.uptime / 1ms; + metrics.uptime_ms = report->local_state.uptime / 1ms; } auto& topics = _topics.local().topics_map(); snapshot.topic_count = 0; @@ -414,6 +404,23 @@ ss::future metrics_reporter::make_http_client() { co_return http::client(client_configuration, _as.local()); } +ss::future<> metrics_reporter::do_send_metrics( + http::client& client, + http::client::request_header header, + ss::input_stream& body) { + auto timeout = config::shard_local_cfg().metrics_reporter_tick_interval(); + auto res = co_await client.get_connected(timeout, _logger); + // skip sending metrics, unable to connect + if (res != http::reconnect_result_t::connected) { + vlog( + _logger.trace, "unable to send metrics report, connection timeout"); + co_return; + } + auto resp_stream = co_await client.request( + std::move(header), body, timeout); + co_await resp_stream->prefetch_headers(); +} + ss::future<> metrics_reporter::do_report_metrics() { // try initializing cluster info, if it is already present this operation // does nothing. @@ -471,22 +478,11 @@ ss::future<> metrics_reporter::do_report_metrics() { auto header = make_header(out); auto body = make_iobuf_input_stream(std::move(out)); try { - // prepare http client - auto client = co_await make_http_client(); - auto timeout - = config::shard_local_cfg().metrics_reporter_tick_interval(); - auto res = co_await client.get_connected(timeout, _logger); - // skip sending metrics, unable to connect - if (res != http::reconnect_result_t::connected) { - vlog( - _logger.trace, - "unable to send metrics report, connection timeout"); - co_return; - } - auto resp_stream = co_await client.request( - std::move(header), body, timeout); - co_await resp_stream->prefetch_headers(); - co_await resp_stream->shutdown(); + co_await http::with_client( + co_await make_http_client(), + [this, &header, &body](http::client& client) { + return do_send_metrics(client, std::move(header), body); + }); _last_success = ss::lowres_clock::now(); } catch (...) { vlog( diff --git a/src/v/cluster/metrics_reporter.h b/src/v/cluster/metrics_reporter.h index c6b394b9878a4..0dab11c8f7aa4 100644 --- a/src/v/cluster/metrics_reporter.h +++ b/src/v/cluster/metrics_reporter.h @@ -103,6 +103,10 @@ class metrics_reporter { ss::future> build_metrics_snapshot(); ss::future make_http_client(); + ss::future<> do_send_metrics( + http::client&, + http::client::request_header header, + ss::input_stream& body); ss::future<> try_initialize_cluster_info(); ss::future<> propagate_cluster_id(); diff --git a/src/v/cluster/node_isolation_watcher.cc b/src/v/cluster/node_isolation_watcher.cc index 05b77b620838e..efe05b4d18879 100644 --- a/src/v/cluster/node_isolation_watcher.cc +++ b/src/v/cluster/node_isolation_watcher.cc @@ -75,30 +75,6 @@ ss::future node_isolation_watcher::is_node_isolated() { co_return false; } - auto nodes_status_res = co_await _health_monitor.local().get_nodes_status( - config::shard_local_cfg().metadata_status_wait_timeout_ms() - + model::timeout_clock::now()); - - if (nodes_status_res.has_value()) { - auto& nodes_status = nodes_status_res.value(); - - // All nodes from health report should be not alive for isolation - bool is_one_of_node_not_alive = std::any_of( - nodes_status.begin(), - nodes_status.end(), - [](const node_state& node_state) { - // Current node should not influence on answer. - if (config::node().node_id() == node_state.id) { - return cluster::alive::no; - } - return node_state.is_alive; - }); - - if (is_one_of_node_not_alive) { - co_return false; - } - } - co_return true; } diff --git a/src/v/cluster/node_status_backend.cc b/src/v/cluster/node_status_backend.cc index a40c03389ae84..a8be17a29a2ed 100644 --- a/src/v/cluster/node_status_backend.cc +++ b/src/v/cluster/node_status_backend.cc @@ -16,6 +16,7 @@ #include "cluster/logger.h" #include "cluster/node_status_table.h" #include "config/node_config.h" +#include "model/metadata.h" #include "rpc/types.h" #include "ssx/future-util.h" @@ -182,6 +183,11 @@ ss::future<> node_status_backend::collect_and_store_updates() { }); } +void node_status_backend::reset_node_backoff(model::node_id id) { + vlog(clusterlog.debug, "Resetting reconnect backoff for node: {}", id); + _node_connection_set.reset_client_backoff(id); +} + ss::future> node_status_backend::collect_updates_from_peers() { node_status_request request = {.sender_metadata = {.node_id = _self}}; @@ -229,7 +235,7 @@ ss::future> node_status_backend::send_node_status_request( }) .then(&rpc::get_ctx_data); - co_return process_reply(reply); + co_return process_reply(target, reply); } ss::future<> node_status_backend::maybe_create_client( @@ -238,18 +244,11 @@ ss::future<> node_status_backend::maybe_create_client( target, address, _rpc_tls_config, create_backoff_policy()); } -result -node_status_backend::process_reply(result reply) { +result node_status_backend::process_reply( + model::node_id target_node_id, result reply) { vassert(ss::this_shard_id() == shard, "invoked on a wrong shard"); - - if (!reply.has_error()) { - _stats.rpcs_sent += 1; - auto& replier_metadata = reply.value().replier_metadata; - - return node_status{ - .node_id = replier_metadata.node_id, - .last_seen = rpc::clock_type::now()}; - } else { + static constexpr auto rate_limit = std::chrono::seconds(1); + if (reply.has_error()) { auto err = reply.error(); if ( err.category() == rpc::error_category() @@ -257,7 +256,6 @@ node_status_backend::process_reply(result reply) { == rpc::errc::client_request_timeout) { _stats.rpcs_timed_out += 1; } - static constexpr auto rate_limit = std::chrono::seconds(1); static ss::logger::rate_limit rate(rate_limit); clusterlog.log( ss::log_level::debug, @@ -266,6 +264,23 @@ node_status_backend::process_reply(result reply) { err.message()); return err; } + + _stats.rpcs_sent += 1; + auto& replier_metadata = reply.value().replier_metadata; + if (replier_metadata.node_id != target_node_id) { + static ss::logger::rate_limit rate(rate_limit); + clusterlog.log( + ss::log_level::debug, + rate, + "Received reply from node with different node id. Expected: {}, " + "current: {}", + target_node_id, + replier_metadata.node_id); + return errc::invalid_target_node_id; + } + + return node_status{ + .node_id = replier_metadata.node_id, .last_seen = rpc::clock_type::now()}; } ss::future diff --git a/src/v/cluster/node_status_backend.h b/src/v/cluster/node_status_backend.h index 0959e781d7d96..0edab178e214d 100644 --- a/src/v/cluster/node_status_backend.h +++ b/src/v/cluster/node_status_backend.h @@ -60,6 +60,11 @@ class node_status_backend { ss::future<> start(); ss::future<> stop(); + /** + * Resets node connection backoff. This method is called when current node + * receives hello request. + */ + void reset_node_backoff(model::node_id id); private: ss::future<> drain_notifications_queue(); @@ -71,7 +76,8 @@ class node_status_backend { ss::future<> collect_and_store_updates(); ss::future> collect_updates_from_peers(); - result process_reply(result); + result process_reply( + model::node_id target_node_id, result reply); ss::future process_request(node_status_request); ss::future> diff --git a/src/v/cluster/partition.cc b/src/v/cluster/partition.cc index fe40bb2f0daab..017c413739738 100644 --- a/src/v/cluster/partition.cc +++ b/src/v/cluster/partition.cc @@ -443,6 +443,11 @@ ss::future<> partition::start(std::optional topic_cfg) { co_return co_await _raft->start(std::move(builder)); } + if (!storage::deletion_exempt(_raft->ntp())) { + _log_eviction_stm = builder.create_stm( + _raft.get(), clusterlog, _kvstore); + _raft->log()->stm_manager()->add_stm(_log_eviction_stm); + } if (is_tx_manager_topic(_raft->ntp()) && _is_tx_enabled) { _tm_stm = builder.create_stm( clusterlog, @@ -464,11 +469,6 @@ ss::future<> partition::start(std::optional topic_cfg) { /** * Data partitions */ - if (!storage::deletion_exempt(_raft->ntp())) { - _log_eviction_stm = builder.create_stm( - _raft.get(), clusterlog, _kvstore); - _raft->log()->stm_manager()->add_stm(_log_eviction_stm); - } const model::topic_namespace_view tp_ns(_raft->ntp()); const bool is_group_ntp = tp_ns == model::kafka_consumer_offsets_nt; const bool has_rm_stm = (_is_tx_enabled || _is_idempotence_enabled) @@ -614,125 +614,152 @@ partition::timequery(storage::timequery_config cfg) { co_return co_await cloud_storage_timequery(cfg); } + const bool may_answer_from_cloud + = may_read_from_cloud() + && _cloud_storage_partition->bounds_timestamp(cfg.time) + && cfg.min_offset < kafka::offset_cast( + _cloud_storage_partition->next_kafka_offset()); + if (_raft->log()->start_timestamp() <= cfg.time) { // The query is ahead of the local data's start_timestamp: this // means it _might_ hit on local data: start_timestamp is not // precise, so once we query we might still fall back to cloud // storage - auto result = co_await local_timequery(cfg); - if (!result.has_value()) { + // + // We also need to adjust the lower bound for the local query as the + // min_offset corresponds to the full log (including tiered storage). + auto local_query_cfg = cfg; + local_query_cfg.min_offset = std::max( + _raft->get_offset_translator_state()->from_log_offset( + _raft->start_offset()), + local_query_cfg.min_offset); + + // If the min_offset is ahead of max_offset, the local log is empty + // or was truncated since the timequery_config was created. + if (local_query_cfg.min_offset > local_query_cfg.max_offset) { + co_return std::nullopt; + } + + auto result = co_await local_timequery( + local_query_cfg, may_answer_from_cloud); + if (result.has_value()) { + co_return result; + } else { // The local storage hit a case where it needs to fall back // to querying cloud storage. co_return co_await cloud_storage_timequery(cfg); - } else { - co_return result; } } else { - if ( - may_read_from_cloud() - && _cloud_storage_partition->bounds_timestamp(cfg.time)) { + if (may_answer_from_cloud) { // Timestamp is before local storage but within cloud storage co_return co_await cloud_storage_timequery(cfg); } else { - // No cloud data: queries earlier than the start of the log - // will hit on the start of the log. - co_return co_await local_timequery(cfg); + // No cloud data OR not allowed to read from cloud: queries earlier + // than the start of the log will hit on the start of the log. + // + // Adjust the lower bound for the local query as the min_offset + // corresponds to the full log (including tiered storage). + auto local_query_cfg = cfg; + local_query_cfg.min_offset = std::max( + _raft->get_offset_translator_state()->from_log_offset( + _raft->start_offset()), + local_query_cfg.min_offset); + + // If the min_offset is ahead of max_offset, the local log is empty + // or was truncated since the timequery_config was created. + if (local_query_cfg.min_offset > local_query_cfg.max_offset) { + co_return std::nullopt; + } + + co_return co_await local_timequery(local_query_cfg, false); } } } bool partition::may_read_from_cloud() const { - return _cloud_storage_partition - && _cloud_storage_partition->is_data_available(); + return (is_remote_fetch_enabled() || is_read_replica_mode_enabled()) + && (_cloud_storage_partition && _cloud_storage_partition->is_data_available()); } ss::future> partition::cloud_storage_timequery(storage::timequery_config cfg) { - if (may_read_from_cloud()) { - // We have data in the remote partition, and all the data in the - // raft log is ahead of the query timestamp or the topic is a read - // replica, so proceed to query the remote partition to try and - // find the earliest data that has timestamp >= the query time. + if (!may_read_from_cloud()) { + co_return std::nullopt; + } + + // We have data in the remote partition, and all the data in the + // raft log is ahead of the query timestamp or the topic is a read + // replica, so proceed to query the remote partition to try and + // find the earliest data that has timestamp >= the query time. + vlog(clusterlog.debug, "timequery (cloud) {} cfg(k)={}", _raft->ntp(), cfg); + + // remote_partition pre-translates offsets for us, so no call into + // the offset translator here + auto result = co_await _cloud_storage_partition->timequery(cfg); + if (result.has_value()) { vlog( clusterlog.debug, - "timequery (cloud) {} t={} max_offset(k)={}", + "timequery (cloud) {} cfg(k)={} result(k)={}", _raft->ntp(), - cfg.time, - cfg.max_offset); - - // remote_partition pre-translates offsets for us, so no call into - // the offset translator here - auto result = co_await _cloud_storage_partition->timequery(cfg); - if (result) { - vlog( - clusterlog.debug, - "timequery (cloud) {} t={} max_offset(r)={} result(r)={}", - _raft->ntp(), - cfg.time, - cfg.max_offset, - result->offset); - } - - co_return result; + cfg, + result->offset); } - co_return std::nullopt; + co_return result; } -ss::future> -partition::local_timequery(storage::timequery_config cfg) { - vlog( - clusterlog.debug, - "timequery (raft) {} t={} max_offset(k)={}", - _raft->ntp(), - cfg.time, - cfg.max_offset); +ss::future> partition::local_timequery( + storage::timequery_config cfg, bool allow_cloud_fallback) { + vlog(clusterlog.debug, "timequery (raft) {} cfg(k)={}", _raft->ntp(), cfg); + cfg.min_offset = _raft->get_offset_translator_state()->to_log_offset( + cfg.min_offset); cfg.max_offset = _raft->get_offset_translator_state()->to_log_offset( cfg.max_offset); - auto result = co_await _raft->timequery(cfg); + vlog(clusterlog.debug, "timequery (raft) {} cfg(r)={}", _raft->ntp(), cfg); - bool may_answer_from_cloud = may_read_from_cloud() - && _cloud_storage_partition->bounds_timestamp( - cfg.time); + auto result = co_await _raft->timequery(cfg); - if (result) { - if ( - _raft->log()->start_timestamp() > cfg.time && may_answer_from_cloud) { - // Query raced with prefix truncation - vlog( - clusterlog.debug, - "timequery (raft) {} ts={} raced with truncation " - "(start_timestamp {}, result {})", - _raft->ntp(), - cfg.time, - _raft->log()->start_timestamp(), - result->time); - co_return std::nullopt; - } + if (result.has_value()) { + if (allow_cloud_fallback) { + // We need to test for cases in which we will fall back to querying + // cloud storage. + if (_raft->log()->start_timestamp() > cfg.time) { + // Query raced with prefix truncation + vlog( + clusterlog.debug, + "timequery (raft) {} cfg(r)={} raced with truncation " + "(start_timestamp {}, result {})", + _raft->ntp(), + cfg, + _raft->log()->start_timestamp(), + result->time); + co_return std::nullopt; + } - if ( - _raft->log()->start_timestamp() <= cfg.time && result->time > cfg.time - && may_answer_from_cloud) { - // start_timestamp() points to the beginning of the oldest - // segment, but start_offset points to somewhere within a - // segment. If our timequery hits the range between the start - // of segment and the start_offset, consensus::timequery may - // answer with the start offset rather than the - // pre-start-offset location where the timestamp is actually - // found. Ref - // https://github.com/redpanda-data/redpanda/issues/9669 - vlog( - clusterlog.debug, - "Timequery (raft) {} ts={} miss on local log " - "(start_timestamp " - "{}, result {})", - _raft->ntp(), - cfg.time, - _raft->log()->start_timestamp(), - result->time); - co_return std::nullopt; + if ( + _raft->log()->start_timestamp() <= cfg.time + && result->time > cfg.time) { + // start_timestamp() points to the beginning of the oldest + // segment, but start_offset points to somewhere within a + // segment. If our timequery hits the range between the start + // of segment and the start_offset, consensus::timequery may + // answer with the start offset rather than the + // pre-start-offset location where the timestamp is actually + // found. Ref + // https://github.com/redpanda-data/redpanda/issues/9669 + vlog( + clusterlog.debug, + "Timequery (raft) {} cfg(r)={} miss on local log " + "(start_timestamp " + "{}, result {})", + _raft->ntp(), + cfg, + _raft->log()->start_timestamp(), + result->time); + co_return std::nullopt; + } } if (result->offset == _raft->log()->offsets().start_offset) { @@ -741,17 +768,15 @@ partition::local_timequery(storage::timequery_config cfg) { // have the same timestamp and are present in cloud storage. vlog( clusterlog.debug, - "Timequery (raft) {} ts={} hit start_offset in local log " + "Timequery (raft) {} cfg(r)={} hit start_offset in local log " "(start_offset {} start_timestamp {}, result {})", _raft->ntp(), + cfg, _raft->log()->offsets().start_offset, - cfg.time, _raft->log()->start_timestamp(), cfg.time); - if ( - _cloud_storage_partition - && _cloud_storage_partition->is_data_available() - && may_answer_from_cloud) { + + if (allow_cloud_fallback) { // Even though we hit data with the desired timestamp, we // cannot be certain that this is the _first_ batch with // the desired timestamp: return null so that the caller @@ -762,10 +787,9 @@ partition::local_timequery(storage::timequery_config cfg) { vlog( clusterlog.debug, - "timequery (raft) {} t={} max_offset(r)={} result(r)={}", + "timequery (raft) {} cfg(r)={} result(r)={}", _raft->ntp(), - cfg.time, - cfg.max_offset, + cfg, result->offset); result->offset = _raft->get_offset_translator_state()->from_log_offset( result->offset); diff --git a/src/v/cluster/partition.h b/src/v/cluster/partition.h index aca23a33887f5..8c0b6e9b08621 100644 --- a/src/v/cluster/partition.h +++ b/src/v/cluster/partition.h @@ -504,7 +504,7 @@ class partition { bool may_read_from_cloud() const; ss::future> - local_timequery(storage::timequery_config); + local_timequery(storage::timequery_config, bool allow_cloud_fallback); consensus_ptr _raft; ss::shared_ptr _partition_mem_tracker; diff --git a/src/v/cluster/partition_balancer_backend.cc b/src/v/cluster/partition_balancer_backend.cc index 0cfb8c19c69b3..aa9bbdcc79062 100644 --- a/src/v/cluster/partition_balancer_backend.cc +++ b/src/v/cluster/partition_balancer_backend.cc @@ -10,6 +10,7 @@ #include "cluster/partition_balancer_backend.h" +#include "cluster/health_monitor_backend.h" #include "cluster/health_monitor_frontend.h" #include "cluster/health_monitor_types.h" #include "cluster/logger.h" @@ -26,6 +27,7 @@ #include "utils/stable_iterator_adaptor.h" #include +#include #include #include @@ -235,7 +237,7 @@ void partition_balancer_backend::on_topic_table_update() { void partition_balancer_backend::on_health_monitor_update( node_health_report const& report, - std::optional> old_report) { + std::optional> old_report) { if (!old_report) { vlog( clusterlog.debug, diff --git a/src/v/cluster/partition_balancer_backend.h b/src/v/cluster/partition_balancer_backend.h index 3085d8f956301..291a43b486a34 100644 --- a/src/v/cluster/partition_balancer_backend.h +++ b/src/v/cluster/partition_balancer_backend.h @@ -82,7 +82,7 @@ class partition_balancer_backend { void on_topic_table_update(); void on_health_monitor_update( node_health_report const&, - std::optional>); + std::optional>); size_t get_min_partition_size_threshold() const; private: diff --git a/src/v/cluster/partition_balancer_planner.cc b/src/v/cluster/partition_balancer_planner.cc index 6ea3771a48c45..d4bfe703ac4ad 100644 --- a/src/v/cluster/partition_balancer_planner.cc +++ b/src/v/cluster/partition_balancer_planner.cc @@ -341,9 +341,11 @@ void partition_balancer_planner::init_per_node_state( } for (const auto& node_report : health_report.node_reports) { - const auto [total, free] = get_node_bytes_info(node_report.local_state); + const auto [total, free] = get_node_bytes_info( + node_report->local_state); ctx.node_disk_reports.emplace( - node_report.id, node_disk_space(node_report.id, total, total - free)); + node_report->id, + node_disk_space(node_report->id, total, total - free)); } for (model::node_id id : ctx.all_nodes) { @@ -375,7 +377,7 @@ void partition_balancer_planner::init_per_node_state( ss::future<> partition_balancer_planner::init_ntp_sizes_from_health_report( const cluster_health_report& health_report, request_context& ctx) { for (const auto& node_report : health_report.node_reports) { - for (const auto& tp_ns : node_report.topics) { + for (const auto& tp_ns : node_report->topics) { for (const auto& partition : tp_ns.partitions) { model::ntp ntp{tp_ns.tp_ns.ns, tp_ns.tp_ns.tp, partition.id}; size_t reclaimable = partition.reclaimable_size_bytes.value_or( @@ -384,12 +386,12 @@ ss::future<> partition_balancer_planner::init_ntp_sizes_from_health_report( clusterlog.trace, "ntp {} on node {}: size {}, reclaimable: {}", ntp, - node_report.id, + node_report->id, human::bytes(partition.size_bytes), human::bytes(reclaimable)); auto& sizes = ctx._ntp2sizes[ntp]; - sizes.current[node_report.id] = partition.size_bytes; + sizes.current[node_report->id] = partition.size_bytes; size_t non_reclaimable = 0; if (reclaimable < partition.size_bytes) { @@ -883,13 +885,13 @@ auto partition_balancer_planner::request_context::do_with_partition( } // check if the ntp is to be force reconfigured. - auto topic_md = _parent._state.topics().get_topic_metadata( + auto topic_md = _parent._state.topics().get_topic_metadata_ref( model::topic_namespace_view{ntp}); const auto& force_reconfigurable_partitions = _parent._state.topics().partitions_to_force_recover(); auto force_it = force_reconfigurable_partitions.find(ntp); if (topic_md && force_it != force_reconfigurable_partitions.end()) { - auto topic_revision = topic_md.value().get_revision(); + auto topic_revision = topic_md.value().get().get_revision(); const auto& entries = force_it->second; auto it = std::find_if( entries.begin(), entries.end(), [&](const auto& entry) { @@ -1172,11 +1174,17 @@ void partition_balancer_planner::reassignable_partition::revert( _reallocated->partition.is_original(move.previous()->node_id), "ntp {}: move {}->{} should have been from original node", _ntp, - move.current(), - move.previous()); + move.previous(), + move.current()); auto err = _reallocated->partition.try_revert(move); vassert(err == errc::success, "ntp {}: revert error: {}", _ntp, err); + vlog( + clusterlog.info, + "ntp {}: reverted previously scheduled move {} -> {}", + _ntp, + move.previous()->node_id, + move.current().node_id); auto from_it = _ctx.node_disk_reports.find(move.previous()->node_id); if (from_it != _ctx.node_disk_reports.end()) { @@ -1744,7 +1752,7 @@ ss::future<> partition_balancer_planner::get_counts_rebalancing_actions( // haven't been able to improve the objective, this means that we've reached // (local) optimum and rebalance can be finished. - bool actions_added = false; + bool should_stop = true; co_await ctx.for_each_partition_random_order([&](partition& part) { part.match_variant( [&](reassignable_partition& part) { @@ -1777,11 +1785,15 @@ ss::future<> partition_balancer_planner::get_counts_rebalancing_actions( // number of partitions) part.revert(res.value()); } else { - actions_added = true; + should_stop = false; } } } }, + [&](immutable_partition& p) { + p.report_failure(change_reason::partition_count_rebalancing); + should_stop = false; + }, [](auto&) {}); return ss::stop_iteration::no; @@ -1791,7 +1803,7 @@ ss::future<> partition_balancer_planner::get_counts_rebalancing_actions( double cur_objective = calc_objective(domain); vlog( clusterlog.info, - "counts rebalancing objective in domain {}: {:6} -> {:6}", + "counts rebalancing objective in domain {}: {:.6} -> {:.6}", domain, orig_objective, cur_objective); @@ -1812,7 +1824,7 @@ ss::future<> partition_balancer_planner::get_counts_rebalancing_actions( return true; }; - if (!actions_added && all_nodes_healthy()) { + if (should_stop && all_nodes_healthy()) { ctx._counts_rebalancing_finished = true; } } diff --git a/src/v/cluster/partition_balancer_types.h b/src/v/cluster/partition_balancer_types.h index 47035171a75bd..06c89e5b81d7e 100644 --- a/src/v/cluster/partition_balancer_types.h +++ b/src/v/cluster/partition_balancer_types.h @@ -45,6 +45,13 @@ struct node_disk_space { double peak_used_ratio() const { return double(used + assigned) / total; } double final_used_ratio() const { + // it sometimes may happen that the partition replica size on one node + // is out of date with the total used size reported by a node space + // manager. This may lead to an overflow of final used ratio. + if (released >= used + assigned) { + return 0.0; + } + return double(used + assigned - released) / total; } diff --git a/src/v/cluster/partition_leaders_table.cc b/src/v/cluster/partition_leaders_table.cc index 2d1d262ee5bbf..3a6c870913b68 100644 --- a/src/v/cluster/partition_leaders_table.cc +++ b/src/v/cluster/partition_leaders_table.cc @@ -88,9 +88,9 @@ void partition_leaders_table::update_partition_leader( } ss::future<> partition_leaders_table::update_with_node_report( - const node_health_report& node_report) { + const node_health_report_ptr& node_report) { ssx::async_counter counter; - for (const auto& topic : node_report.topics) { + for (const auto& topic : node_report->topics) { /** * Here we minimize the number of topic table and topic map lookups by * doing it only once for each topic. diff --git a/src/v/cluster/partition_leaders_table.h b/src/v/cluster/partition_leaders_table.h index 01e33c7855aa7..208608c99473f 100644 --- a/src/v/cluster/partition_leaders_table.h +++ b/src/v/cluster/partition_leaders_table.h @@ -18,6 +18,7 @@ #include "model/fundamental.h" #include "model/metadata.h" #include "ssx/async_algorithm.h" +#include "utils/chunked_hash_map.h" #include "utils/contiguous_range_map.h" #include "utils/named_type.h" @@ -25,12 +26,6 @@ #include #include -#include -#include - -#include -#include - namespace cluster { /// Partition leaders contains information about currently elected partition @@ -138,7 +133,8 @@ class partition_leaders_table { * IMPORTANT: node_report must be kept alive during the execution of this * method */ - ss::future<> update_with_node_report(const node_health_report& node_report); + ss::future<> + update_with_node_report(const node_health_report_ptr& node_report); struct leader_info_t { model::topic_namespace tp_ns; @@ -196,7 +192,7 @@ class partition_leaders_table { using partition_leaders = contiguous_range_map; - using topics_t = absl::node_hash_map< + using topics_t = chunked_hash_map< model::topic_namespace, partition_leaders, model::topic_namespace_hash, diff --git a/src/v/cluster/producer_state.cc b/src/v/cluster/producer_state.cc index 9441fe8a0e928..db0bd2e698877 100644 --- a/src/v/cluster/producer_state.cc +++ b/src/v/cluster/producer_state.cc @@ -17,10 +17,44 @@ namespace cluster { +std::ostream& operator<<(std::ostream& os, request_state state) { + switch (state) { + case request_state::initialized: + return os << "initialized"; + case request_state::in_progress: + return os << "in_progress"; + case request_state::completed: + return os << "completed"; + } +} + result_promise_t::future_type request::result() const { return _result.get_shared_future(); } +void request::set_value(request_result_t::value_type value) { + vassert( + _state <= request_state::in_progress && !_result.available(), + "unexpected request state during result set: {}", + *this); + _result.set_value(value); + _state = request_state::completed; +} + +void request::set_error(request_result_t::error_type error) { + // This is idempotent as different fibers can mark the result error + // at different times in some edge cases. + if (_state != request_state::completed) { + _result.set_value(error); + _state = request_state::completed; + return; + } + vassert( + _result.available() && result().get0().has_error(), + "Invalid result state, expected to be available and errored out: {}", + *this); +} + bool request::operator==(const request& other) const { bool compare = _first_sequence == other._first_sequence && _last_sequence == other._last_sequence @@ -109,7 +143,7 @@ result requests::try_emplace( // checks for sequence tracking. while (!_inflight_requests.empty()) { if (!_inflight_requests.front()->has_completed()) { - _inflight_requests.front()->set_value(errc::timeout); + _inflight_requests.front()->set_error(errc::timeout); } _inflight_requests.pop_front(); } @@ -123,7 +157,7 @@ result requests::try_emplace( if (!_inflight_requests.front()->has_completed()) { // Here we know for sure the term change, these in flight // requests are going to fail anyway, mark them so. - _inflight_requests.front()->set_value(errc::timeout); + _inflight_requests.front()->set_error(errc::timeout); } _inflight_requests.pop_front(); } @@ -204,7 +238,7 @@ bool requests::stm_apply( void requests::shutdown() { for (auto& request : _inflight_requests) { if (!request->has_completed()) { - request->_result.set_value(errc::shutting_down); + request->set_error(errc::shutting_down); } } _inflight_requests.clear(); @@ -237,6 +271,18 @@ bool producer_state::operator==(const producer_state& other) const { && _evicted == other._evicted && _requests == other._requests; } +std::ostream& operator<<(std::ostream& o, const request& request) { + fmt::print( + o, + "{{ first: {}, last: {}, term: {}, result_available: {}, state: {} }}", + request._first_sequence, + request._last_sequence, + request._term, + request._result.available(), + request._state); + return o; +} + std::ostream& operator<<(std::ostream& o, const requests& requests) { fmt::print( o, @@ -259,26 +305,30 @@ std::ostream& operator<<(std::ostream& o, const producer_state& state) { return o; } -ss::future<> producer_state::shutdown_input() { +void producer_state::do_shutdown_input() { + // linked hook ensures no task in progress. + vassert( + _hook.is_linked(), "unexpected state, producer {} unlinked.", *this); + deregister_self(); + _op_lock.broken(); + _requests.shutdown(); +} + +void producer_state::shutdown_input() { if (_evicted) { - return ss::now(); + return; } - _op_lock.broken(); - return _gate.close().then([this] { _requests.shutdown(); }); + do_shutdown_input(); } -ss::future<> producer_state::evict() { +void producer_state::evict() { if (_evicted) { - return ss::now(); + return; } vlog(clusterlog.debug, "evicting producer: {}", *this); _evicted = true; - vassert(_hook.is_linked(), "unexpected state, producer unlinked."); - unlink_self(); - return shutdown_input().then_wrapped([this](auto result) { - _post_eviction_hook(); - return result; - }); + do_shutdown_input(); + _post_eviction_hook(); } void producer_state::register_self() { @@ -320,13 +370,26 @@ result producer_state::try_emplace_request( current_term, reset, _requests); - return _requests.try_emplace( + + auto result = _requests.try_emplace( bid.first_seq, bid.last_seq, current_term, reset); + + if (unlikely(result.has_error())) { + vlog( + clusterlog.warn, + "[{}] error {} processing request {}, term: {}, reset: {}", + *this, + result.error(), + bid, + current_term, + reset); + } + return result; } void producer_state::update( const model::batch_identity& bid, kafka::offset offset) { - if (_gate.is_closed()) { + if (_evicted) { return; } bool relink_producer = _requests.stm_apply(bid, offset); diff --git a/src/v/cluster/producer_state.h b/src/v/cluster/producer_state.h index 82fe4d8bb83a6..aa4b3a429d25f 100644 --- a/src/v/cluster/producer_state.h +++ b/src/v/cluster/producer_state.h @@ -42,7 +42,8 @@ using producer_ptr = ss::lw_shared_ptr; // right after set_value(), this is an implementation quirk, be // mindful of that behavior when using it. We have a test for // it in expiring_promise_test -using result_promise_t = ss::shared_promise>; +using request_result_t = result; +using result_promise_t = ss::shared_promise; using request_ptr = ss::lw_shared_ptr; using seq_t = int32_t; @@ -52,6 +53,8 @@ enum class request_state : uint8_t { completed = 2 }; +std::ostream& operator<<(std::ostream&, request_state); + /// A request for a given sequence range, both inclusive. /// The sequence numbers are stamped by the client and are a part /// of batch header. A request can either be in progress or completed @@ -69,23 +72,16 @@ class request { } } - template - void set_value(ValueType&& value) { - vassert( - _state <= request_state::in_progress && !_result.available(), - "unexpected request state during set: state: {}, result available: " - "{}", - static_cast>(_state), - _result.available()); - _result.set_value(std::forward(value)); - _state = request_state::completed; - } + void set_value(request_result_t::value_type); + void set_error(request_result_t::error_type); void mark_request_in_progress() { _state = request_state::in_progress; } request_state state() const { return _state; } result_promise_t::future_type result() const; bool operator==(const request&) const; + friend std::ostream& operator<<(std::ostream&, const request&); + private: request_state _state{request_state::initialized}; seq_t _first_sequence; @@ -180,24 +176,21 @@ class producer_state { /// considering candidates for eviction. template auto run_with_lock(AsyncFunc&& func) { - return ss::with_gate( - _gate, [this, func = std::forward(func)]() mutable { - return _op_lock.get_units().then( - [this, f = std::forward(func)](auto units) { - unlink_self(); - _ops_in_progress++; - return ss::futurize_invoke(f, std::move(units)) - .then_wrapped([this](auto result) { - _ops_in_progress--; - link_self(); - return result; - }); + return _op_lock.get_units().then( + [this, f = std::forward(func)](auto units) { + unlink_self(); + _ops_in_progress++; + return ss::futurize_invoke(f, std::move(units)) + .then_wrapped([this](auto result) { + _ops_in_progress--; + link_self(); + return result; }); }); } - ss::future<> shutdown_input(); - ss::future<> evict(); + void shutdown_input(); + void evict(); bool is_evicted() const { return _evicted; } /* reset sequences resets the tracking state and skips the sequence @@ -233,6 +226,8 @@ class producer_state { void link_self(); void unlink_self(); + void do_shutdown_input(); + std::chrono::milliseconds ms_since_last_update() const { return std::chrono::duration_cast( ss::lowres_system_clock::now() - _last_updated_ts); @@ -251,7 +246,6 @@ class producer_state { // Used to evict stale producers. ss::lowres_system_clock::time_point _last_updated_ts; intrusive_list_hook _hook; - ss::gate _gate; // function hook called on eviction bool _evicted = false; size_t _ops_in_progress = 0; diff --git a/src/v/cluster/producer_state_manager.cc b/src/v/cluster/producer_state_manager.cc index 596613e14ba4f..ef962501c2024 100644 --- a/src/v/cluster/producer_state_manager.cc +++ b/src/v/cluster/producer_state_manager.cc @@ -29,7 +29,7 @@ producer_state_manager::producer_state_manager( ss::future<> producer_state_manager::start() { _reaper.set_callback([this] { evict_excess_producers(); }); - _reaper.arm(period); + _reaper.arm(_reaper_period); vlog(clusterlog.info, "Started producer state manager"); return ss::now(); } @@ -57,6 +57,12 @@ void producer_state_manager::setup_metrics() { sm::description("Number of evicted producers so far."))}); } +void producer_state_manager::rearm_timer_for_testing( + std::chrono::milliseconds new_period) { + _reaper_period = new_period; + _reaper.rearm(ss::lowres_clock::now() + _reaper_period); +} + void producer_state_manager::register_producer(producer_state& state) { link(state); ++_num_producers; @@ -90,7 +96,7 @@ void producer_state_manager::evict_excess_producers() { do_evict_excess_producers(); }).finally([this] { if (!_gate.is_closed()) { - _reaper.arm(period); + _reaper.arm(_reaper_period); } }); } @@ -101,7 +107,11 @@ void producer_state_manager::do_evict_excess_producers() { } vlog(clusterlog.debug, "producer eviction tick"); auto it = _lru_producers.begin(); - while (it != _lru_producers.end() && can_evict_producer(*it)) { + // to avoid reactor stalls. + static constexpr auto max_evictions_per_tick = 10000; + int evicted_so_far = 0; + while (evicted_so_far++ < max_evictions_per_tick + && it != _lru_producers.end() && can_evict_producer(*it)) { auto it_copy = it; ++it; auto& state = *it_copy; @@ -111,8 +121,7 @@ void producer_state_manager::do_evict_excess_producers() { // temporarily and relinks back after the operation is finished // essentially resulting in the fact that only currently inactive // producers are in the list. This makes the whole logic lock free. - ssx::spawn_with_gate(_gate, [&state] { return state.evict(); }); - --_num_producers; + state.evict(); ++_eviction_counter; } } diff --git a/src/v/cluster/producer_state_manager.h b/src/v/cluster/producer_state_manager.h index 63f449750dd11..68a6444f5808f 100644 --- a/src/v/cluster/producer_state_manager.h +++ b/src/v/cluster/producer_state_manager.h @@ -35,9 +35,10 @@ class producer_state_manager void deregister_producer(producer_state&); // temporary relink already accounted producer void link(producer_state&); + void rearm_timer_for_testing(std::chrono::milliseconds); private: - static constexpr std::chrono::seconds period{5}; + std::chrono::milliseconds _reaper_period{5000}; void setup_metrics(); void evict_excess_producers(); void do_evict_excess_producers(); @@ -57,7 +58,7 @@ class producer_state_manager // is the responsibility of the producers themselves. // Check producer_state::run_func() intrusive_list _lru_producers; - ss::timer _reaper; + ss::timer _reaper; ss::gate _gate; metrics::internal_metric_groups _metrics; diff --git a/src/v/cluster/rm_stm.cc b/src/v/cluster/rm_stm.cc index fce211f38286f..6988cdfc6946a 100644 --- a/src/v/cluster/rm_stm.cc +++ b/src/v/cluster/rm_stm.cc @@ -11,6 +11,7 @@ #include "bytes/iostream.h" #include "cluster/logger.h" +#include "cluster/producer_state_manager.h" #include "cluster/tx_gateway_frontend.h" #include "cluster/tx_snapshot_utils.h" #include "kafka/protocol/wire.h" @@ -247,6 +248,29 @@ rm_stm::parse_tx_control_batch(const model::record_batch& b) { return parse_control_batch(b); } +void rm_stm::log_state::forget(const model::producer_identity& pid) { + auto it = fence_pid_epoch.find(pid.get_id()); + if (it != fence_pid_epoch.end() && it->second == pid.get_epoch()) { + fence_pid_epoch.erase(pid.get_id()); + } + ongoing_map.erase(pid); + prepared.erase(pid); + current_txes.erase(pid); + expiration.erase(pid); +} + +void rm_stm::log_state::reset() { + fence_pid_epoch.clear(); + ongoing_map.clear(); + ongoing_set.clear(); + prepared.clear(); + current_txes.clear(); + expiration.clear(); + aborted.clear(); + abort_indexes.clear(); + last_abort_snapshot = {model::offset(-1)}; +} + rm_stm::rm_stm( ss::logger& logger, raft::consensus* c, @@ -363,12 +387,11 @@ void rm_stm::cleanup_producer_state(model::producer_identity pid) { }; ss::future<> rm_stm::reset_producers() { - co_await ss::max_concurrent_for_each( - _producers.begin(), _producers.end(), 32, [](auto& it) { - auto& producer = it.second; - return producer->shutdown_input().discard_result(); - }); + for (auto& [_, producer] : _producers) { + producer->shutdown_input(); + } _producers.clear(); + co_return; } ss::future> rm_stm::begin_tx( @@ -1108,6 +1131,8 @@ ss::future> rm_stm::do_sync_and_transactional_replicate( bid.first_seq, bid.last_seq); if (result.error() == errc::sequence_out_of_order) { + // no need to hold while the barrier is in progress. + units.return_all(); auto barrier = co_await _raft->linearizable_barrier(); if (!barrier) { co_return errc::not_leader; @@ -1174,7 +1199,7 @@ ss::future> rm_stm::do_transactional_replicate( auto expiration_it = _log_state.expiration.find(bid.pid); if (expiration_it == _log_state.expiration.end()) { vlog(_ctx_log.warn, "Can not find expiration info for pid:{}", bid.pid); - req_ptr->set_value(errc::generic_tx_error); + req_ptr->set_error(errc::generic_tx_error); co_return errc::generic_tx_error; } expiration_it->second.last_update = clock_type::now(); @@ -1194,7 +1219,7 @@ ss::future> rm_stm::do_transactional_replicate( // an error during replication, preventin tx from progress _mem_state.expected.erase(bid.pid); } - req_ptr->set_value(r.error()); + req_ptr->set_error(r.error()); co_return r.error(); } if (!co_await wait_no_throw( @@ -1204,7 +1229,7 @@ ss::future> rm_stm::do_transactional_replicate( _ctx_log.warn, "application of the replicated tx batch has timed out pid:{}", bid.pid); - req_ptr->set_value(errc::timeout); + req_ptr->set_error(errc::timeout); co_return tx_errc::timeout; } _mem_state.estimated.erase(bid.pid); @@ -1257,6 +1282,10 @@ ss::future> rm_stm::do_sync_and_idempotent_replicate( bid.first_seq, bid.last_seq); if (result.error() == errc::sequence_out_of_order) { + // release the lock so it is not held for the duration of the + // barrier, other requests can make progress if they are + // in the right sequence. + units.return_all(); // Ensure we are actually the leader and request didn't // ooosn on a stale state. If we are not the leader return // a retryable error code to the client. @@ -1286,7 +1315,6 @@ ss::future> rm_stm::do_idempotent_replicate( raft::replicate_options opts, ss::lw_shared_ptr> enqueued, ssx::semaphore_units& units) { - using ret_t = result; auto request = producer->try_emplace_request(bid, synced_term); if (!request) { co_return request.error(); @@ -1306,7 +1334,7 @@ ss::future> rm_stm::do_idempotent_replicate( _ctx_log.warn, "replication failed, request enqueue returned error: {}", req_enqueued.get_exception()); - req_ptr->set_value(errc::replication_error); + req_ptr->set_error(errc::replication_error); co_return errc::replication_error; } units.return_all(); @@ -1316,13 +1344,13 @@ ss::future> rm_stm::do_idempotent_replicate( if (replicated.failed()) { vlog( _ctx_log.warn, "replication failed: {}", replicated.get_exception()); - req_ptr->set_value(errc::replication_error); + req_ptr->set_error(errc::replication_error); co_return errc::replication_error; } auto result = replicated.get0(); if (result.has_error()) { vlog(_ctx_log.warn, "replication failed: {}", result.error()); - req_ptr->set_value(result.error()); + req_ptr->set_error(result.error()); co_return result.error(); } // translate to kafka offset. @@ -1540,32 +1568,41 @@ void rm_stm::abort_old_txes() { }); } -ss::future<> rm_stm::do_abort_old_txes() { - if (!co_await sync(_sync_timeout)) { - co_return; +absl::btree_set +rm_stm::get_expired_producers() const { + absl::btree_set expired; + auto maybe_add_to_expired = [&](const auto& pid) { + if (expired.contains(pid)) { + return; + } + auto it = _log_state.expiration.find(pid); + if ( + it != _log_state.expiration.end() + && it->second.is_expired(clock_type::now())) { + expired.insert(pid); + } + }; + for (auto& [pid, _] : _mem_state.estimated) { + maybe_add_to_expired(pid); } - - fragmented_vector pids; - for (auto& [k, _] : _mem_state.estimated) { - pids.push_back(k); + for (auto& [pid, _] : _mem_state.tx_start) { + maybe_add_to_expired(pid); } - for (auto& [k, _] : _mem_state.tx_start) { - pids.push_back(k); + for (auto& [id, epoch] : _log_state.fence_pid_epoch) { + maybe_add_to_expired(model::producer_identity{id, epoch}); } - for (auto& [k, _] : _log_state.ongoing_map) { - pids.push_back(k); + for (auto& [pid, _] : _log_state.ongoing_map) { + maybe_add_to_expired(pid); } - absl::btree_set expired; - for (auto pid : pids) { - auto expiration_it = _log_state.expiration.find(pid); - if (expiration_it != _log_state.expiration.end()) { - if (!expiration_it->second.is_expired(clock_type::now())) { - continue; - } - } - expired.insert(pid); + return expired; +} + +ss::future<> rm_stm::do_abort_old_txes() { + if (!co_await sync(_sync_timeout)) { + co_return; } + auto expired = get_expired_producers(); for (auto pid : expired) { co_await try_abort_old_tx(pid); } @@ -2029,13 +2066,16 @@ rm_stm::apply_local_snapshot(raft::stm_snapshot_header hdr, iobuf&& tx_ss_buf) { for (auto& entry : data.fenced) { _log_state.fence_pid_epoch.emplace(entry.get_id(), entry.get_epoch()); } + data.fenced.clear(); for (auto& entry : data.ongoing) { _log_state.ongoing_map.emplace(entry.pid, entry); _log_state.ongoing_set.insert(entry.first); } + data.ongoing.clear(); for (auto& entry : data.prepared) { _log_state.prepared.emplace(entry.pid, entry); } + data.prepared.clear(); for (auto it = std::make_move_iterator(data.aborted.begin()); it != std::make_move_iterator(data.aborted.end()); it++) { @@ -2071,6 +2111,7 @@ rm_stm::apply_local_snapshot(raft::stm_snapshot_header hdr, iobuf&& tx_ss_buf) { [this, pid] { cleanup_producer_state(pid); }, std::move(entry))); } + data.producers.clear(); abort_index last{.last = model::offset(-1)}; for (auto& entry : _log_state.abort_indexes) { @@ -2089,6 +2130,7 @@ rm_stm::apply_local_snapshot(raft::stm_snapshot_header hdr, iobuf&& tx_ss_buf) { _log_state.current_txes.emplace( entry.pid, tx_data{entry.tx_seq, entry.tm}); } + data.tx_data.clear(); for (auto& entry : data.expiration) { _log_state.expiration.emplace( diff --git a/src/v/cluster/rm_stm.h b/src/v/cluster/rm_stm.h index fe84adb3ac517..72293f702a37b 100644 --- a/src/v/cluster/rm_stm.h +++ b/src/v/cluster/rm_stm.h @@ -64,6 +64,7 @@ namespace cluster { */ class rm_stm final : public raft::persisted_stm<> { public: + static constexpr const char* name = "rm_stm"; using clock_type = ss::lowres_clock; using time_point_type = clock_type::time_point; using duration_type = clock_type::duration; @@ -261,7 +262,6 @@ class rm_stm final : public raft::persisted_stm<> { uint64_t get_local_snapshot_size() const override; - std::string_view get_name() const final { return "rm_stm"; } ss::future take_snapshot(model::offset) final { co_return iobuf{}; } const producers_t& get_producers() const { return _producers; } @@ -361,6 +361,8 @@ class rm_stm final : public raft::persisted_stm<> { ss::future do_mark_expired(model::producer_identity pid); + absl::btree_set get_expired_producers() const; + bool is_known_session(model::producer_identity pid) const { auto is_known = false; is_known |= _mem_state.estimated.contains(pid); @@ -481,25 +483,8 @@ class rm_stm final : public raft::persisted_stm<> { expiration_info> expiration; - void forget(const model::producer_identity& pid) { - fence_pid_epoch.erase(pid.get_id()); - ongoing_map.erase(pid); - prepared.erase(pid); - current_txes.erase(pid); - expiration.erase(pid); - } - - void reset() { - fence_pid_epoch.clear(); - ongoing_map.clear(); - ongoing_set.clear(); - prepared.clear(); - current_txes.clear(); - expiration.clear(); - aborted.clear(); - abort_indexes.clear(); - last_abort_snapshot = {model::offset(-1)}; - } + void forget(const model::producer_identity& pid); + void reset(); }; struct mem_state { diff --git a/src/v/cluster/scheduling/leader_balancer.cc b/src/v/cluster/scheduling/leader_balancer.cc index e8df344762473..a0b9199a5fb69 100644 --- a/src/v/cluster/scheduling/leader_balancer.cc +++ b/src/v/cluster/scheduling/leader_balancer.cc @@ -129,6 +129,10 @@ void leader_balancer::on_leadership_change( // Update in flight state if (auto it = _in_flight_changes.find(group); it != _in_flight_changes.end()) { + vlog( + clusterlog.trace, + "transfer of group {} finished, removing from in-flight set", + group); _in_flight_changes.erase(it); check_unregister_leadership_change_notification(); @@ -490,6 +494,7 @@ ss::future leader_balancer::balance() { co_return ss::stop_iteration::yes; } + size_t num_dispatched = 0; for (size_t i = 0; i < allowed_change_cnt; i++) { if (should_stop_balance()) { co_return ss::stop_iteration::yes; @@ -498,11 +503,14 @@ ss::future leader_balancer::balance() { auto transfer = strategy->find_movement(muted_groups()); if (!transfer) { vlog( - clusterlog.debug, - "No leadership balance improvements found with total delta {}, " - "number of muted groups {}", + clusterlog.info, + "Leadership balancer tick: no further improvements found, " + "total error: {:.4}, number of muted groups: {}, " + "number in flight: {}, dispatched in this tick: {}", strategy->error(), - _muted.size()); + _muted.size(), + _in_flight_changes.size(), + num_dispatched); if (!_timer.armed()) { _timer.arm(_idle_timeout()); } @@ -510,6 +518,16 @@ ss::future leader_balancer::balance() { co_return ss::stop_iteration::yes; } + vlog( + clusterlog.trace, + "dispatching transfer of group {}: {} -> {}, " + "current num_dispatched: {}, in_flight: {}", + transfer->group, + transfer->from, + transfer->to, + num_dispatched, + _in_flight_changes.size()); + _in_flight_changes[transfer->group] = { *transfer, clock_type::now() + _mute_timeout()}; check_register_leadership_change_notification(); @@ -518,10 +536,12 @@ ss::future leader_balancer::balance() { if (!success) { vlog( clusterlog.info, - "Error transferring leadership group {} from {} to {}", + "Error transferring leadership group {} from {} to {} " + "(already dispatched in this tick: {})", transfer->group, transfer->from, - transfer->to); + transfer->to, + num_dispatched); _in_flight_changes.erase(transfer->group); check_unregister_leadership_change_notification(); @@ -541,6 +561,7 @@ ss::future leader_balancer::balance() { } else { _probe.leader_transfer_succeeded(); + num_dispatched += 1; strategy->apply_movement(*transfer); } @@ -787,13 +808,6 @@ leader_balancer::index_type leader_balancer::build_index() { } ss::future leader_balancer::do_transfer(reassignment transfer) { - vlog( - clusterlog.debug, - "Transferring leadership for group {} from {} to {}", - transfer.group, - transfer.from, - transfer.to); - if (transfer.from.node_id == _raft0->self().id()) { co_return co_await do_transfer_local(transfer); } else { @@ -920,10 +934,6 @@ ss::future leader_balancer::do_transfer_remote(reassignment transfer) { res.error().message()); co_return false; } else if (res.value().data.success) { - vlog( - clusterlog.trace, - "Leadership transfer of group {} succeeded", - transfer.group); co_return true; } else { vlog( diff --git a/src/v/cluster/scheduling/leader_balancer_random.h b/src/v/cluster/scheduling/leader_balancer_random.h index cdddb714d620b..4951a80d7aa1f 100644 --- a/src/v/cluster/scheduling/leader_balancer_random.h +++ b/src/v/cluster/scheduling/leader_balancer_random.h @@ -16,13 +16,10 @@ #include "model/metadata.h" #include "raft/types.h" #include "random/generators.h" +#include "utils/chunked_hash_map.h" #include "utils/fragmented_vector.h" #include "vassert.h" -#include -#include -#include - #include #include #include @@ -34,7 +31,8 @@ namespace cluster::leader_balancer_types { /* - * Given a `shard_index` this class will generate every possible reassignment. + * Given a `shard_index` this class will generate every possible + * reassignment. */ class random_reassignments { public: @@ -98,7 +96,7 @@ class random_reassignments { raft::group_id group_id; model::broker_shard broker_shard; }; - absl::node_hash_map _current_leaders; + chunked_hash_map _current_leaders; using replicas_t = fragmented_vector; replicas_t _replicas; diff --git a/src/v/cluster/scheduling/partition_allocator.cc b/src/v/cluster/scheduling/partition_allocator.cc index 79aeceaa8810d..d8b32ba4947ca 100644 --- a/src/v/cluster/scheduling/partition_allocator.cc +++ b/src/v/cluster/scheduling/partition_allocator.cc @@ -211,7 +211,7 @@ std::error_code partition_allocator::check_cluster_limits( create_count, proposed_total_partitions, effective_cpu_count * _partitions_per_shard()); - return errc::topic_invalid_partitions; + return errc::topic_invalid_partitions_core_limit; } // Refuse to create partitions that would violate the configured @@ -232,7 +232,7 @@ std::error_code partition_allocator::check_cluster_limits( create_count, proposed_total_partitions, memory_limit); - return errc::topic_invalid_partitions; + return errc::topic_invalid_partitions_memory_limit; } } @@ -254,7 +254,7 @@ std::error_code partition_allocator::check_cluster_limits( create_count, proposed_total_partitions, fds_limit); - return errc::topic_invalid_partitions; + return errc::topic_invalid_partitions_fd_limit; } } } else { diff --git a/src/v/cluster/scheduling/types.cc b/src/v/cluster/scheduling/types.cc index 1becc229fea4d..a20ecdd03827c 100644 --- a/src/v/cluster/scheduling/types.cc +++ b/src/v/cluster/scheduling/types.cc @@ -13,6 +13,7 @@ #include "cluster/logger.h" #include "cluster/scheduling/allocation_state.h" +#include "utils/exceptions.h" #include "utils/to_string.h" #include @@ -76,6 +77,9 @@ allocation_units::allocation_units( allocation_units::~allocation_units() { oncore_debug_verify(_oncore); + if (unlikely(!_state)) { + return; + } for (auto& pas : _assignments) { for (auto& replica : pas.replicas) { _state->remove_allocation(replica, _domain); @@ -96,6 +100,11 @@ allocated_partition::allocated_partition( std::optional allocated_partition::prepare_move(model::node_id prev_node) { + if (unlikely(!_state)) { + throw concurrent_modification_error( + "allocation_state was concurrently replaced"); + } + previous_replica prev; auto it = std::find_if( _replicas.begin(), _replicas.end(), [prev_node](const auto& bs) { @@ -149,6 +158,11 @@ allocated_partition::prepare_move(model::node_id prev_node) { model::broker_shard allocated_partition::add_replica( model::node_id node, const std::optional& prev) { + if (unlikely(!_state)) { + throw concurrent_modification_error( + "allocation_state was concurrently replaced"); + } + if (!_original_node2shard) { _original_node2shard.emplace(); for (const auto& bs : _replicas) { @@ -225,7 +239,12 @@ bool allocated_partition::is_original(model::node_id node) const { } errc allocated_partition::try_revert(const reallocation_step& step) { - if (!_original_node2shard || !_state) { + if (unlikely(!_state)) { + throw concurrent_modification_error( + "allocation_state was concurrently replaced"); + } + + if (!_original_node2shard) { return errc::no_update_in_progress; } diff --git a/src/v/cluster/service.cc b/src/v/cluster/service.cc index c48ff3e4e76ab..f5ae301b60365 100644 --- a/src/v/cluster/service.cc +++ b/src/v/cluster/service.cc @@ -21,6 +21,7 @@ #include "cluster/members_frontend.h" #include "cluster/members_manager.h" #include "cluster/metadata_cache.h" +#include "cluster/node_status_backend.h" #include "cluster/partition_manager.h" #include "cluster/plugin_frontend.h" #include "cluster/security_frontend.h" @@ -56,7 +57,8 @@ service::service( ss::sharded& feature_table, ss::sharded& hm_frontend, ss::sharded& conn_cache, - ss::sharded& partition_manager) + ss::sharded& partition_manager, + ss::sharded& node_status_backend) : controller_service(sg, ssg) , _controller(controller) , _topics_frontend(tf) @@ -72,7 +74,8 @@ service::service( , _hm_frontend(hm_frontend) , _conn_cache(conn_cache) , _partition_manager(partition_manager) - , _plugin_frontend(pf) {} + , _plugin_frontend(pf) + , _node_status_backend(node_status_backend) {} ss::future service::join_node(join_node_request req, rpc::streaming_context&) { @@ -211,7 +214,9 @@ service::do_finish_partition_update(finish_partition_update_request req) { req.ntp, req.new_replica_set, config::shard_local_cfg().replicate_append_timeout_ms() - + model::timeout_clock::now()); + + model::timeout_clock::now(), + topics_frontend::dispatch_to_leader::no); + finish_partition_update_reply reply{.result = errc::success}; if (ec) { if (ec.category() == cluster::error_category()) { @@ -381,6 +386,10 @@ service::hello(hello_request req, rpc::streaming_context&) { cache.get(peer)->reset_backoff(); } }); + co_await _node_status_backend.invoke_on( + 0, [peer = req.peer](node_status_backend& backend) { + backend.reset_node_backoff(peer); + }); co_return hello_reply{.error = errc::success}; } @@ -500,46 +509,30 @@ ss::future service::get_cluster_health_report( }); } -namespace { -void clear_partition_revisions(node_health_report& report) { - for (auto& t : report.topics) { - for (auto& p : t.partitions) { - p.revision_id = model::revision_id{}; - } - } -} - -void clear_partition_sizes(node_health_report& report) { - for (auto& t : report.topics) { - for (auto& p : t.partitions) { - p.size_bytes = partition_status::invalid_size_bytes; - } - } -} -} // namespace - ss::future service::do_collect_node_health_report(get_node_health_request req) { - auto res = co_await _hm_frontend.local().collect_node_health( - std::move(req.filter)); + // validate if the receiving node is the one that that the request is + // addressed to + if ( + req.get_target_node_id() != get_node_health_request::node_id_not_set + && req.get_target_node_id() != _controller->self()) { + vlog( + clusterlog.debug, + "Received a get_node_health request addressed to different node. " + "Requested node id: {}, current node id: {}", + req.get_target_node_id(), + _controller->self()); + co_return get_node_health_reply{.error = errc::invalid_target_node_id}; + } + + auto res = co_await _hm_frontend.local().get_current_node_health(); if (res.has_error()) { co_return get_node_health_reply{ .error = map_health_monitor_error_code(res.error())}; } - auto report = std::move(res.value()); - // clear all revision ids to prevent sending them to old versioned redpanda - // nodes - if (req.decoded_version > get_node_health_request::revision_id_version) { - clear_partition_revisions(report); - } - // clear all partition sizes to prevent sending them to old versioned - // redpanda nodes - if (req.decoded_version > get_node_health_request::size_bytes_version) { - clear_partition_sizes(report); - } co_return get_node_health_reply{ .error = errc::success, - .report = std::move(report), + .report = std::move(res.value()), }; } @@ -555,21 +548,7 @@ service::do_get_cluster_health_report(get_cluster_health_request req) { .error = map_health_monitor_error_code(res.error())}; } auto report = std::move(res.value()); - // clear all revision ids to prevent sending them to old versioned redpanda - // nodes - if (req.decoded_version > get_cluster_health_request::revision_id_version) { - for (auto& r : report.node_reports) { - clear_partition_revisions(r); - } - } - // clear all partition sizes to prevent sending them to old versioned - // redpanda nodes - if (req.decoded_version > get_cluster_health_request::size_bytes_version) { - for (auto& r : report.node_reports) { - clear_partition_sizes(r); - } - } co_return get_cluster_health_reply{ .error = errc::success, .report = std::move(report), diff --git a/src/v/cluster/service.h b/src/v/cluster/service.h index 8f5efd6cc2125..d94f02d5bce5d 100644 --- a/src/v/cluster/service.h +++ b/src/v/cluster/service.h @@ -41,7 +41,8 @@ class service : public controller_service { ss::sharded&, ss::sharded&, ss::sharded&, - ss::sharded&); + ss::sharded&, + ss::sharded&); virtual ss::future join_node(join_node_request, rpc::streaming_context&) override; @@ -189,5 +190,6 @@ class service : public controller_service { ss::sharded& _conn_cache; ss::sharded& _partition_manager; ss::sharded& _plugin_frontend; + ss::sharded& _node_status_backend; }; } // namespace cluster diff --git a/src/v/cluster/shard_table.h b/src/v/cluster/shard_table.h index ffafd785eb516..9fcc6812956c6 100644 --- a/src/v/cluster/shard_table.h +++ b/src/v/cluster/shard_table.h @@ -16,6 +16,7 @@ #include "model/ktp.h" #include "raft/types.h" #include "seastarx.h" +#include "utils/chunked_hash_map.h" #include // shard_id @@ -153,13 +154,13 @@ class shard_table final { */ // kafka index - absl::node_hash_map< + chunked_hash_map< model::ntp, shard_revision, model::ktp_hash_eq, model::ktp_hash_eq> _ntp_idx; // raft index - absl::node_hash_map _group_idx; + chunked_hash_map _group_idx; }; } // namespace cluster diff --git a/src/v/cluster/tests/CMakeLists.txt b/src/v/cluster/tests/CMakeLists.txt index fcda2cc2cedb8..6bf7b9320dd5d 100644 --- a/src/v/cluster/tests/CMakeLists.txt +++ b/src/v/cluster/tests/CMakeLists.txt @@ -65,8 +65,7 @@ set(srcs ephemeral_credential_test.cc health_monitor_test.cc metadata_dissemination_test.cc - replicas_rebalancing_tests.cc - tx_topic_test.cc) + replicas_rebalancing_tests.cc) foreach(cluster_test_src ${srcs}) get_filename_component(test_name ${cluster_test_src} NAME_WE) @@ -130,6 +129,19 @@ rp_test( LABELS cluster ) +rp_test( + FIXTURE_TEST + BINARY_NAME tx_coordinator + SOURCES + tx_topic_test.cc + DEFINITIONS BOOST_TEST_DYN_LINK + LIBRARIES + v::seastar_testing_main + v::application + ARGS "-- -c 1" + LABELS cluster +) + rp_test( UNIT_TEST GTEST @@ -169,3 +181,11 @@ rp_test( ARGS "-- -c 1" LABELS cluster ) + +rp_test( + BENCHMARK_TEST + BINARY_NAME health_monitor + SOURCES health_monitor_bench.cc + LIBRARIES Seastar::seastar_perf_testing v::cluster + LABELS cluster +) diff --git a/src/v/cluster/tests/create_partitions_test.cc b/src/v/cluster/tests/create_partitions_test.cc index d5be226491b86..7c61df4657fcb 100644 --- a/src/v/cluster/tests/create_partitions_test.cc +++ b/src/v/cluster/tests/create_partitions_test.cc @@ -80,5 +80,6 @@ FIXTURE_TEST(test_error_handling, rebalancing_tests_fixture) { BOOST_REQUIRE_EQUAL(res.size(), 1); BOOST_REQUIRE_EQUAL( res[0].ec, - cluster::make_error_code(cluster::errc::topic_invalid_partitions)); + cluster::make_error_code( + cluster::errc::topic_invalid_partitions_decreased)); } diff --git a/src/v/cluster/tests/health_bench.cc b/src/v/cluster/tests/health_bench.cc index 10802834c9700..508c9ad7d46ae 100644 --- a/src/v/cluster/tests/health_bench.cc +++ b/src/v/cluster/tests/health_bench.cc @@ -14,6 +14,7 @@ #include "random/generators.h" #include "vassert.h" +#include #include #include @@ -36,7 +37,7 @@ struct health_bench : health_report_accessor { leaderless, urp; for (const auto& [_, report] : reports) { - for (const auto& [tp_ns, partitions] : report.topics) { + for (const auto& [tp_ns, partitions] : report->topics) { for (const auto& partition : partitions) { if ( !partition.leader_id.has_value() @@ -66,7 +67,9 @@ struct health_bench : health_report_accessor { constexpr int nodes = 32; // genreate a random health report - absl::node_hash_map + absl::node_hash_map< + model::node_id, + ss::lw_shared_ptr> reports; for (int topic = 0; topic < topic_count; topic++) { @@ -92,7 +95,10 @@ struct health_bench : health_report_accessor { } for (model::node_id node{0}; node < nodes; node++) { - reports[node].topics.emplace_back(statuses.at(node)); + node_health_report report; + report.topics.emplace_back(statuses.at(node)); + reports[node] = ss::make_lw_shared( + std::move(report)); } } diff --git a/src/v/cluster/tests/health_monitor_bench.cc b/src/v/cluster/tests/health_monitor_bench.cc new file mode 100644 index 0000000000000..2c5dde029c6de --- /dev/null +++ b/src/v/cluster/tests/health_monitor_bench.cc @@ -0,0 +1,111 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "cluster/health_monitor_types.h" + +#include + +cluster::topic_status make_topic_status(size_t id, size_t num_partitions) { + cluster::topic_status ts; + ts.tp_ns = model::topic_namespace( + model::ns("foo"), model::topic("bar" + std::to_string(id))); + + for (size_t i = 0; i < num_partitions; ++i) { + cluster::partition_status part_status; + part_status.id = model::partition_id(i); + part_status.leader_id = model::node_id(1); + part_status.reclaimable_size_bytes = 100; + part_status.revision_id = model::revision_id(1); + part_status.term = model::term_id(1); + + ts.partitions.push_back(part_status); + } + + return ts; +} + +cluster::node_health_report +make_node_health_report(size_t num_topics, size_t partitions_per_topic) { + cluster::node_health_report hr; + hr.id = model::node_id(1); + + hr.local_state.redpanda_version = cluster::node::application_version( + "v21.1.1"); + hr.local_state.logical_version = cluster::cluster_version(1); + hr.local_state.uptime = std::chrono::milliseconds(100); + + hr.local_state.data_disk.path = "/bar/baz/foo/foo/foo/foo/foo/foo/foo/bar"; + hr.local_state.data_disk.total = 1000; + hr.local_state.data_disk.free = 500; + + for (size_t i = 0; i < num_topics; ++i) { + hr.topics.push_back(make_topic_status(i, partitions_per_topic)); + } + + return hr; +} + +template +[[gnu::noinline]] void +do_bench_serialize_node_health_report(iobuf& buf, T& hr) { + return serde::write(buf, std::move(hr)); +} + +template +void bench_serialize_node_health_report( + size_t num_topics, size_t partitions_per_topic, GenFunc& f) { + auto hr = f(num_topics, partitions_per_topic); + auto buf = iobuf(); + + perf_tests::start_measuring_time(); + do_bench_serialize_node_health_report(buf, hr); + perf_tests::do_not_optimize(buf); + perf_tests::stop_measuring_time(); +} + +PERF_TEST(node_health_report, serialize_many_partitions) { + bench_serialize_node_health_report(10, 5000, make_node_health_report); +} + +PERF_TEST(node_health_report, serialize_many_topics) { + bench_serialize_node_health_report(50000, 1, make_node_health_report); +} + +PERF_TEST(node_health_report, serialize_many_topics_replicated_partitions) { + bench_serialize_node_health_report(50000, 3, make_node_health_report); +} +template +[[gnu::noinline]] T do_bench_deserialize_node_health_report(iobuf buf) { + return serde::from_iobuf(std::move(buf)); +} +template +void bench_deserialize_node_health_report( + size_t num_topics, size_t partitions_per_topic, GenFunc& f) { + auto hr = make_node_health_report(num_topics, partitions_per_topic); + auto buf = iobuf(); + serde::write(buf, std::move(hr)); + + perf_tests::start_measuring_time(); + auto result = do_bench_deserialize_node_health_report( + std::move(buf)); + perf_tests::do_not_optimize(result); + perf_tests::stop_measuring_time(); +} + +PERF_TEST(node_health_report, deserialize_many_partitions) { + bench_deserialize_node_health_report(10, 5000, make_node_health_report); +} + +PERF_TEST(node_health_report, deserialize_many_topics) { + bench_deserialize_node_health_report(50000, 1, make_node_health_report); +} + +PERF_TEST(node_health_report, deserialize_many_topics_replicated_partitions) { + bench_deserialize_node_health_report(50000, 3, make_node_health_report); +} diff --git a/src/v/cluster/tests/health_monitor_test.cc b/src/v/cluster/tests/health_monitor_test.cc index e9e7f545cc4a7..57531e68b11b5 100644 --- a/src/v/cluster/tests/health_monitor_test.cc +++ b/src/v/cluster/tests/health_monitor_test.cc @@ -9,24 +9,19 @@ #include "cluster/health_monitor_frontend.h" #include "cluster/health_monitor_types.h" -#include "cluster/metadata_cache.h" #include "cluster/node/types.h" -#include "cluster/shard_table.h" -#include "cluster/simple_batch_builder.h" #include "cluster/tests/cluster_test_fixture.h" #include "cluster/tests/health_monitor_test_utils.h" #include "cluster/types.h" -#include "config/configuration.h" #include "model/fundamental.h" #include "model/metadata.h" #include "model/namespace.h" #include "model/timeout_clock.h" -#include "net/unresolved_address.h" -#include "outcome.h" -#include "test_utils/async.h" #include "test_utils/fixture.h" +#include #include +#include #include #include @@ -41,13 +36,13 @@ static cluster::cluster_report_filter get_all{}; void check_reports_the_same( - std::vector& lhs, - std::vector& rhs) { + std::vector& lhs, + std::vector& rhs) { BOOST_TEST_REQUIRE(lhs.size() == rhs.size()); auto by_id = []( - const cluster::node_health_report& lr, - const cluster::node_health_report& rr) { - return lr.id < rr.id; + const cluster::node_health_report_ptr& lr, + const cluster::node_health_report_ptr& rr) { + return lr->id < rr->id; }; std::sort(lhs.begin(), lhs.end(), by_id); std::sort(rhs.begin(), rhs.end(), by_id); @@ -56,30 +51,38 @@ void check_reports_the_same( auto& lr = lhs[i]; auto& rr = rhs[i]; BOOST_TEST_REQUIRE( - lr.local_state.redpanda_version == rr.local_state.redpanda_version); - BOOST_TEST_REQUIRE(std::equal( - lr.topics.cbegin(), - lr.topics.cend(), - rr.topics.cbegin(), - rr.topics.cend())); + lr->local_state.redpanda_version == rr->local_state.redpanda_version); + BOOST_REQUIRE_EQUAL(lr->topics.size(), rr->topics.size()); + for (auto i = 0; i < lr->topics.size(); ++i) { + BOOST_REQUIRE_EQUAL(lr->topics[i].tp_ns, rr->topics[i].tp_ns); + auto& l_partitions = lr->topics[i].partitions; + auto& r_partitions = rr->topics[i].partitions; + BOOST_REQUIRE_EQUAL(l_partitions.size(), r_partitions.size()); + for (auto p = 0; p < l_partitions.size(); ++p) { + auto& l_p = l_partitions[p]; + auto& r_p = r_partitions[p]; + BOOST_REQUIRE_EQUAL(l_p.id, r_p.id); + BOOST_REQUIRE_EQUAL(l_p.leader_id, r_p.leader_id); + BOOST_REQUIRE_EQUAL(l_p.term, r_p.term); + BOOST_REQUIRE_EQUAL(l_p.revision_id, r_p.revision_id); + } + } + BOOST_TEST_REQUIRE( - lr.local_state.disks().size() == rr.local_state.disks().size()); - for (auto i = 0; i < lr.local_state.disks().size(); ++i) { + lr->local_state.disks().size() == rr->local_state.disks().size()); + for (auto i = 0; i < lr->local_state.disks().size(); ++i) { BOOST_REQUIRE_EQUAL( - lr.local_state.disks().at(i).alert, - rr.local_state.disks().at(i).alert); + lr->local_state.disks().at(i).alert, + rr->local_state.disks().at(i).alert); BOOST_REQUIRE_EQUAL( - lr.local_state.disks().at(i).free, - rr.local_state.disks().at(i).free); + lr->local_state.disks().at(i).path, + rr->local_state.disks().at(i).path); BOOST_REQUIRE_EQUAL( - lr.local_state.disks().at(i).path, - rr.local_state.disks().at(i).path); - BOOST_REQUIRE_EQUAL( - lr.local_state.disks().at(i).total, - rr.local_state.disks().at(i).total); + lr->local_state.disks().at(i).total, + rr->local_state.disks().at(i).total); } BOOST_TEST_REQUIRE( - lr.local_state.get_disk_alert() == rr.local_state.get_disk_alert()); + lr->local_state.get_disk_alert() == rr->local_state.get_disk_alert()); } } @@ -90,15 +93,14 @@ void check_states_the_same( auto by_id = []( const cluster::node_state& lr, - const cluster::node_state& rr) { return lr.id < rr.id; }; + const cluster::node_state& rr) { return lr.id() < rr.id(); }; std::sort(lhs.begin(), lhs.end(), by_id); std::sort(rhs.begin(), rhs.end(), by_id); for (auto i = 0; i < lhs.size(); ++i) { auto& lr = lhs[i]; auto& rr = rhs[i]; - BOOST_TEST_REQUIRE(lr.is_alive == rr.is_alive); - BOOST_TEST_REQUIRE(lr.membership_state == rr.membership_state); + BOOST_TEST_REQUIRE(lr.membership_state() == rr.membership_state()); } } @@ -146,9 +148,9 @@ FIXTURE_TEST(data_are_consistent_across_nodes, cluster_test_fixture) { BOOST_TEST_REQUIRE(r_2.has_value()); BOOST_TEST_REQUIRE(r_3.has_value()); - auto report_1 = r_1.value(); - auto report_2 = r_2.value(); - auto report_3 = r_3.value(); + auto report_1 = std::move(r_1.value()); + auto report_2 = std::move(r_2.value()); + auto report_3 = std::move(r_3.value()); BOOST_TEST_REQUIRE(report_1.raft0_leader == report_2.raft0_leader); BOOST_TEST_REQUIRE(report_2.raft0_leader == report_3.raft0_leader); @@ -288,28 +290,7 @@ FIXTURE_TEST(test_ntp_filter, cluster_test_fixture) { ntp(model::kafka_internal_namespace, "internal-1", 1), ntp(model::redpanda_ns, "controller", 0), }, - report.value().node_reports.begin()->topics); - }); - }).get(); - - // check filtering in node report - tests::cooperative_spin_wait_with_timeout(10s, [&] { - return n1->controller->get_health_monitor() - .local() - .collect_node_health(f_1.node_report_filter) - .then([](result report) { - return report.has_value() - && contains_exactly_ntp_leaders( - g_seastar_test_log, - { - ntp(model::kafka_namespace, "tp-1", 0), - ntp(model::kafka_namespace, "tp-1", 2), - ntp(model::kafka_namespace, "tp-2", 0), - ntp(model::kafka_internal_namespace, "internal-1", 0), - ntp(model::kafka_internal_namespace, "internal-1", 1), - ntp(model::redpanda_ns, "controller", 0), - }, - report.value().topics); + (*report.value().node_reports.begin())->topics); }); }).get(); } @@ -342,25 +323,9 @@ FIXTURE_TEST(test_alive_status, cluster_test_fixture) { // wait until the node will be reported as not alive tests::cooperative_spin_wait_with_timeout(10s, [&n1] { - return n1->controller->get_health_monitor() - .local() - .get_cluster_health( - get_all, cluster::force_refresh::yes, model::no_timeout) - .then([](result res) { - if (!res) { - return false; - } - if (res.value().node_reports.empty()) { - return false; - } - auto it = std::find_if( - res.value().node_states.begin(), - res.value().node_states.end(), - [](cluster::node_state& s) { - return s.id == model::node_id(1); - }); - return it->is_alive == cluster::alive::no; - }); + return n1->controller->get_health_monitor().local().is_alive( + model::node_id(1)) + == cluster::alive::no; }).get(); } @@ -419,7 +384,8 @@ struct node_and_status { auto make_reports(const std::vector& statuses) { health_report_accessor::report_cache_t ret; for (auto& s : statuses) { - ret[node_id{s.nid}] = make_nhr(s.nid, s.statuses); + ret[node_id{s.nid}] = ss::make_lw_shared( + make_nhr(s.nid, s.statuses)); } return ret; }; @@ -468,7 +434,8 @@ FIXTURE_TEST(test_aggregate, health_report_unit) { model::ntp ntp1_b{model::kafka_namespace, topic_b, 1}; model::ntp ntp2_b{model::kafka_namespace, topic_b, 2}; - report_cache_t empty_reports{{model::node_id(0), {}}}; + report_cache_t empty_reports{ + {model::node_id(0), ss::make_lw_shared()}}; { // empty input, empty report @@ -555,7 +522,8 @@ FIXTURE_TEST(test_report_truncation, health_report_unit) { } health_report_accessor::report_cache_t reports; - reports[model::node_id(0)] = make_nhr(0, statuses); + reports[model::node_id(0)] = ss::make_lw_shared( + make_nhr(0, statuses)); auto result = aggregate(reports); @@ -586,3 +554,19 @@ FIXTURE_TEST(test_report_truncation, health_report_unit) { test_unhealthy(max_count + 1, LEADERLESS); test_unhealthy(max_count + 1, URP); } + +FIXTURE_TEST( + test_requesting_collection_at_the_same_time, cluster_test_fixture) { + auto n1 = create_node_application(model::node_id{0}); + /** + * Request reports + */ + auto f_h_1 + = n1->controller->get_health_monitor().local().get_current_node_health(); + auto f_h_2 + = n1->controller->get_health_monitor().local().get_current_node_health(); + + auto results = ss::when_all(std::move(f_h_1), std::move(f_h_2)).get(); + BOOST_REQUIRE( + std::get<0>(results).get().value() == std::get<1>(results).get().value()); +} diff --git a/src/v/cluster/tests/partition_allocator_fixture.h b/src/v/cluster/tests/partition_allocator_fixture.h index 725095ba97655..185cf5dfb1a43 100644 --- a/src/v/cluster/tests/partition_allocator_fixture.h +++ b/src/v/cluster/tests/partition_allocator_fixture.h @@ -27,26 +27,14 @@ #include +#include + struct partition_allocator_fixture { static constexpr uint32_t partitions_per_shard = 1000; static constexpr uint32_t partitions_reserve_shard0 = 2; partition_allocator_fixture() - : allocator( - std::ref(members), - config::mock_binding>(std::nullopt), - config::mock_binding>(std::nullopt), - config::mock_binding(uint32_t{partitions_per_shard}), - config::mock_binding(uint32_t{partitions_reserve_shard0}), - kafka_internal_topics.bind(), - config::mock_binding(true)) { - members.start().get0(); - ss::smp::invoke_on_all([] { - config::shard_local_cfg() - .get("partition_autobalancing_mode") - .set_value(model::partition_autobalancing_mode::node_add); - }).get0(); - } + : partition_allocator_fixture(std::nullopt, std::nullopt) {} ~partition_allocator_fixture() { members.stop().get0(); } @@ -139,4 +127,43 @@ struct partition_allocator_fixture { cluster::partition_allocator allocator; fast_prng prng; + +protected: + explicit partition_allocator_fixture( + std::optional memory_per_partition, + std::optional fds_per_partition) + : allocator( + std::ref(members), + config::mock_binding>( + std::optional{memory_per_partition}), + config::mock_binding>( + std::optional{fds_per_partition}), + config::mock_binding(uint32_t{partitions_per_shard}), + config::mock_binding(uint32_t{partitions_reserve_shard0}), + kafka_internal_topics.bind(), + config::mock_binding(true)) { + members.start().get0(); + ss::smp::invoke_on_all([] { + config::shard_local_cfg() + .get("partition_autobalancing_mode") + .set_value(model::partition_autobalancing_mode::node_add); + }).get0(); + } +}; + +struct partition_allocator_memory_limited_fixture + : public partition_allocator_fixture { + static constexpr size_t memory_per_partition + = std::numeric_limits::max(); + partition_allocator_memory_limited_fixture() + : partition_allocator_fixture(memory_per_partition, std::nullopt) {} +}; + +struct partition_allocator_fd_limited_fixture + : public partition_allocator_fixture { + static constexpr int32_t fds_per_partition + = std::numeric_limits::max(); + + partition_allocator_fd_limited_fixture() + : partition_allocator_fixture(std::nullopt, fds_per_partition) {} }; diff --git a/src/v/cluster/tests/partition_allocator_tests.cc b/src/v/cluster/tests/partition_allocator_tests.cc index 5d62d173bb4e9..e120e9bb8ec85 100644 --- a/src/v/cluster/tests/partition_allocator_tests.cc +++ b/src/v/cluster/tests/partition_allocator_tests.cc @@ -85,6 +85,41 @@ FIXTURE_TEST(unregister_node, partition_allocator_fixture) { BOOST_REQUIRE_EQUAL(allocator.state().available_nodes(), 2); } +FIXTURE_TEST(allocation_over_core_capacity, partition_allocator_fixture) { + const auto partition_count + = partition_allocator_fixture::partitions_per_shard + 1; + register_node(0, 1); + auto result + = allocator.allocate(make_allocation_request(partition_count, 1)).get(); + BOOST_REQUIRE(result.has_error()); + BOOST_REQUIRE_EQUAL( + result.assume_error(), + cluster::make_error_code( + cluster::errc::topic_invalid_partitions_core_limit)); +} + +FIXTURE_TEST( + allocation_over_memory_capacity, partition_allocator_memory_limited_fixture) { + register_node(0, 1); + auto result = allocator.allocate(make_allocation_request(1, 1)).get(); + BOOST_REQUIRE(result.has_error()); + BOOST_REQUIRE_EQUAL( + result.assume_error(), + cluster::make_error_code( + cluster::errc::topic_invalid_partitions_memory_limit)); +} + +FIXTURE_TEST( + allocation_over_fds_capacity, partition_allocator_fd_limited_fixture) { + register_node(0, 1); + auto result = allocator.allocate(make_allocation_request(1, 1)).get(); + BOOST_REQUIRE(result.has_error()); + BOOST_REQUIRE_EQUAL( + result.assume_error(), + cluster::make_error_code( + cluster::errc::topic_invalid_partitions_fd_limit)); +} + FIXTURE_TEST(allocation_over_capacity, partition_allocator_fixture) { register_node(0, 6); register_node(1, 6); diff --git a/src/v/cluster/tests/partition_balancer_planner_fixture.h b/src/v/cluster/tests/partition_balancer_planner_fixture.h index 48a257140f1f4..beec2ef842b87 100644 --- a/src/v/cluster/tests/partition_balancer_planner_fixture.h +++ b/src/v/cluster/tests/partition_balancer_planner_fixture.h @@ -12,6 +12,7 @@ #pragma once #include "cluster/commands.h" +#include "cluster/health_monitor_types.h" #include "cluster/members_table.h" #include "cluster/node_status_table.h" #include "cluster/partition_balancer_planner.h" @@ -27,6 +28,7 @@ #include "utils/fragmented_vector.h" #include +#include #include #include @@ -381,9 +383,14 @@ struct partition_balancer_planner_fixture { .data_current_size = node_disk.total - node_disk.free, .data_reclaimable_size = 0}; node_report.local_state.set_disk(node_disk); - health_report.node_reports.push_back(node_report); + if (i == 0) { + node_report.topics = topics.copy(); + } + health_report.node_reports.emplace_back( + ss::make_lw_shared( + std::move(node_report))); } - health_report.node_reports[0].topics = std::move(topics); + return health_report; } diff --git a/src/v/cluster/tests/partition_balancer_planner_test.cc b/src/v/cluster/tests/partition_balancer_planner_test.cc index 6a87b8817de49..148bda5b1119c 100644 --- a/src/v/cluster/tests/partition_balancer_planner_test.cc +++ b/src/v/cluster/tests/partition_balancer_planner_test.cc @@ -465,12 +465,17 @@ FIXTURE_TEST(test_move_part_of_replicas, partition_balancer_planner_fixture) { auto hr = create_health_report(full_nodes); populate_node_status_table().get(); - + auto nr_1 = *hr.node_reports[1]; + auto nr_2 = *hr.node_reports[2]; // Set order of full nodes - hr.node_reports[1].local_state.log_data_size.value().data_current_size - += 1_MiB; - hr.node_reports[2].local_state.log_data_size.value().data_current_size - += 2_MiB; + + nr_1.local_state.log_data_size.value().data_current_size += 1_MiB; + nr_2.local_state.log_data_size.value().data_current_size += 2_MiB; + + hr.node_reports[1] = ss::make_foreign( + ss::make_lw_shared(std::move(nr_1))); + hr.node_reports[2] = ss::make_foreign( + ss::make_lw_shared(std::move(nr_2))); auto planner = make_planner(); auto plan_data = planner.plan_actions(hr, as).get(); @@ -512,13 +517,13 @@ FIXTURE_TEST( std::set full_nodes = {0, 1}; auto hr = create_health_report(full_nodes); populate_node_status_table().get(); + auto nr_0 = *hr.node_reports[0]; // Set order of full nodes - hr.node_reports[0].local_state.log_data_size.value().data_current_size - += 1_MiB; + nr_0.local_state.log_data_size.value().data_current_size += 1_MiB; // Set partition sizes - for (auto& topic : hr.node_reports[0].topics) { + for (auto& topic : nr_0.topics) { if (topic.tp_ns.tp == "topic-1") { for (auto& partition : topic.partitions) { if (partition.id == 1) { @@ -531,6 +536,8 @@ FIXTURE_TEST( } } + hr.node_reports[0] = ss::make_foreign( + ss::make_lw_shared(std::move(nr_0))); auto planner = make_planner(); auto plan_data = planner.plan_actions(hr, as).get(); @@ -697,10 +704,14 @@ FIXTURE_TEST(test_rack_awareness, partition_balancer_planner_fixture) { auto hr = create_health_report(); // Make node_4 disk free size less to make partition allocator disk usage // constraint prefer node_3 rather than node_4 - hr.node_reports[4].local_state.log_data_size.value().data_current_size - = hr.node_reports[3].local_state.log_data_size.value().data_current_size + auto nr_4 = *hr.node_reports[4]; + nr_4.local_state.log_data_size.value().data_current_size + = hr.node_reports[3]->local_state.log_data_size.value().data_current_size - 10_MiB; + hr.node_reports[4] = ss::make_foreign( + ss::make_lw_shared(std::move(nr_4))); + std::set unavailable_nodes = {0}; populate_node_status_table(unavailable_nodes).get(); diff --git a/src/v/cluster/tests/partition_balancer_simulator_test.cc b/src/v/cluster/tests/partition_balancer_simulator_test.cc index 232ac84be091e..4c552fa2a770f 100644 --- a/src/v/cluster/tests/partition_balancer_simulator_test.cc +++ b/src/v/cluster/tests/partition_balancer_simulator_test.cc @@ -598,7 +598,7 @@ class partition_balancer_sim_fixture { size_t bandwidth_left = recovery_throttle_burst; size_t ticks_since_refill = 0; - cluster::node_health_report get_health_report() const { + cluster::node_health_report_ptr get_health_report() const { cluster::node_health_report report; storage::disk node_disk{.free = total - used, .total = total}; report.id = id; @@ -623,7 +623,8 @@ class partition_balancer_sim_fixture { cluster::topic_status(topic, std::move(partitions))); } - return report; + return ss::make_foreign( + ss::make_lw_shared(report)); } }; diff --git a/src/v/cluster/tests/producer_state_tests.cc b/src/v/cluster/tests/producer_state_tests.cc index 2969b52ec29b4..19c992c09683e 100644 --- a/src/v/cluster/tests/producer_state_tests.cc +++ b/src/v/cluster/tests/producer_state_tests.cc @@ -77,11 +77,13 @@ struct test_fixture { void clean(std::vector& producers) { for (auto& producer : producers) { - producer->shutdown_input().get0(); + producer->shutdown_input(); } producers.clear(); } + void local_eviction_tick() { manager().do_evict_excess_producers(); } + long _counter = 0; ss::sharded> _max_producers; ss::sharded _psm; @@ -197,3 +199,34 @@ FIXTURE_TEST(test_eviction_expired_pids, test_fixture) { 10s, [&] { return evicted_so_far == total_producers; }); clean(producers); } + +FIXTURE_TEST(test_evict_many_producers_at_once, test_fixture) { + // disable eviction timer. + // we manually invoke it below. + _psm + .invoke_on_all([](cluster::producer_state_manager& local) { + local.rearm_timer_for_testing(std::chrono::milliseconds{1h}); + }) + .get(); + + auto total_producers = 100000; + int evicted_so_far = 0; + std::vector producers; + producers.reserve(total_producers); + for (int i = 0; i < total_producers; i++) { + producers.push_back(ss::make_lw_shared( + _psm.local(), + model::random_producer_identity(), + raft::group_id{i}, + [&] { evicted_so_far++; })); + } + check_producers(total_producers, total_producers); + BOOST_REQUIRE_EQUAL(evicted_so_far, 0); + + // allow producer eviction + _max_producers.invoke_on_all([](auto& local) { local.update(0UL); }).get(); + local_eviction_tick(); + BOOST_REQUIRE_EQUAL(evicted_so_far, 10000); + + clean(producers); +} diff --git a/src/v/cluster/tests/randoms.h b/src/v/cluster/tests/randoms.h index e5ebe7cfebb92..16b4666ecd5d7 100644 --- a/src/v/cluster/tests/randoms.h +++ b/src/v/cluster/tests/randoms.h @@ -43,8 +43,7 @@ inline errc random_failed_errc() { } inline node_state random_node_state() { - return node_state{ - {}, + return { tests::random_named_int(), model::random_membership_state(), cluster::alive(tests::random_bool())}; @@ -101,7 +100,11 @@ inline cluster_health_report random_cluster_health_report() { tests::random_optional( [] { return tests::random_named_int(); }), tests::random_vector(random_node_state), - tests::random_vector(random_node_health_report)}; + tests::random_vector([] { + return ss::make_foreign( + ss::make_lw_shared( + random_node_health_report())); + })}; } inline partitions_filter random_partitions_filter() { diff --git a/src/v/cluster/tests/rm_stm_test_fixture.h b/src/v/cluster/tests/rm_stm_test_fixture.h index cbc1f8aa8e522..ce1ed50f3b7e6 100644 --- a/src/v/cluster/tests/rm_stm_test_fixture.h +++ b/src/v/cluster/tests/rm_stm_test_fixture.h @@ -30,6 +30,9 @@ struct rm_stm_test_fixture : simple_raft_fixture { _raft->start(std::move(stm_m_builder)).get(); _started = true; } + auto apply_raft_snapshot(const iobuf& buf) { + return _stm->apply_raft_snapshot(buf); + } const cluster::rm_stm::producers_t& producers() const { return _stm->_producers; @@ -48,6 +51,27 @@ struct rm_stm_test_fixture : simple_raft_fixture { return _stm->wait(raft_offset, model::timeout_clock::now() + 10ms); } + auto get_expired_producers() const { return _stm->get_expired_producers(); } + + auto maybe_create_producer(model::producer_identity pid) { + return _stm->maybe_create_producer(pid); + } + + auto reset_producers() { + return _stm->_state_lock.hold_write_lock().then([this](auto units) { + return _stm->reset_producers().then([units = std::move(units)] {}); + }); + } + + auto rearm_eviction_timer(std::chrono::milliseconds period) { + return _producer_state_manager + .invoke_on_all( + [period](auto& mgr) { return mgr.rearm_timer_for_testing(period); }) + .get(); + } + + auto stm_read_lock() { return _stm->_state_lock.hold_read_lock(); } + ss::sharded tx_gateway_frontend; ss::shared_ptr _stm; }; diff --git a/src/v/cluster/tests/rm_stm_tests.cc b/src/v/cluster/tests/rm_stm_tests.cc index fe05dd33bace6..e9f8f91769452 100644 --- a/src/v/cluster/tests/rm_stm_tests.cc +++ b/src/v/cluster/tests/rm_stm_tests.cc @@ -26,6 +26,7 @@ #include "storage/tests/utils/disk_log_builder.h" #include "test_utils/async.h" #include "test_utils/randoms.h" +#include "test_utils/scoped_config.h" #include "utils/directory_walker.h" #include @@ -927,3 +928,95 @@ FIXTURE_TEST(test_snapshot_v3_v4_v5_equivalence, rm_stm_test_fixture) { highest_pid_from_snapshot, _stm->highest_producer_id()); } } + +FIXTURE_TEST(test_tx_expiration_without_data_batches, rm_stm_test_fixture) { + create_stm_and_start_raft(); + auto& stm = *_stm; + stm.start().get0(); + stm.testing_only_disable_auto_abort(); + + wait_for_confirmed_leader(); + wait_for_meta_initialized(); + // Add a fence batch + auto pid = model::producer_identity{0, 0}; + auto term_op = stm + .begin_tx( + pid, + model::tx_seq{0}, + std::chrono::milliseconds(10), + model::partition_id(0)) + .get0(); + BOOST_REQUIRE(term_op.has_value()); + BOOST_REQUIRE_EQUAL(term_op.value(), _raft->confirmed_term()); + tests::cooperative_spin_wait_with_timeout(5s, [this, pid]() { + auto expired = get_expired_producers(); + return std::find(expired.begin(), expired.end(), pid) != expired.end(); + }).get0(); +} + +/* + * This test ensures concurrent evictions can happen in the presence of + * replication operations and operations that reset the state (snapshots, + * partition stop). + */ +FIXTURE_TEST(test_concurrent_producer_evictions, rm_stm_test_fixture) { + create_stm_and_start_raft(); + auto& stm = *_stm; + stm.start().get0(); + stm.testing_only_disable_auto_abort(); + + wait_for_confirmed_leader(); + wait_for_meta_initialized(); + + // Ensure eviction runs with higher frequency + // and evicts everything. + scoped_config config; + config.get("max_concurrent_producer_ids").set_value(0UL); + rearm_eviction_timer(1ms); + + int64_t counter = 0; + bool stop = false; + ss::gate gate; + size_t max_replication_fibers = 1000; + + auto replicate_f = ss::do_until( + [&stop] { return stop; }, + [&, this] { + for (int i = 0; i < 5; i++) { + auto producer = maybe_create_producer( + model::producer_identity{counter++, 0}); + if ( + gate.get_count() < max_replication_fibers + && tests::random_bool()) { + // simulates replication. + ssx::spawn_with_gate(gate, [this, producer] { + return stm_read_lock().then([producer](auto stm_units) { + return producer + ->run_with_lock([](auto units) { + auto sleep_ms = std::chrono::milliseconds{ + random_generators::get_int(3)}; + return ss::sleep(sleep_ms).finally( + [units = std::move(units)] {}); + }) + .finally( + [producer, stm_units = std::move(stm_units)] {}); + }); + }); + } + } + return ss::sleep(1ms); + }); + + // simulates raft snapshot application / partition shutdown + auto reset_f = ss::do_until( + [&stop] { return stop; }, + [&, this] { + return reset_producers().then([] { return ss::sleep(3ms); }); + }); + + ss::sleep(20s).get(); + stop = true; + std::move(replicate_f).get(); + std::move(reset_f).get(); + gate.close().get(); +} diff --git a/src/v/cluster/tests/serialization_rt_test.cc b/src/v/cluster/tests/serialization_rt_test.cc index fd588546703c1..991fc8b0901fc 100644 --- a/src/v/cluster/tests/serialization_rt_test.cc +++ b/src/v/cluster/tests/serialization_rt_test.cc @@ -728,13 +728,9 @@ cluster::node::local_state random_local_state() { cluster::cluster_health_report random_cluster_health_report() { std::vector node_states; for (auto i = 0, mi = random_generators::get_int(20); i < mi; ++i) { - node_states.push_back(cluster::node_state{ - .id = tests::random_named_int(), - .membership_state = model::membership_state::draining, - .is_alive = cluster::alive(tests::random_bool()), - }); + node_states.push_back(cluster::random_node_state()); } - std::vector node_reports; + std::vector node_reports; for (auto i = 0, mi = random_generators::get_int(20); i < mi; ++i) { chunked_vector topics; for (auto i = 0, mi = random_generators::get_int(20); i < mi; ++i) { @@ -750,12 +746,13 @@ cluster::cluster_health_report random_cluster_health_report() { // Reduce to an ADL-encodable state report.local_state.cache_disk = std::nullopt; - node_reports.push_back(report); + node_reports.emplace_back( + ss::make_lw_shared(std::move(report))); } cluster::cluster_health_report data{ .raft0_leader = std::nullopt, .node_states = node_states, - .node_reports = node_reports, + .node_reports = std::move(node_reports), }; if (tests::random_bool()) { data.raft0_leader = tests::random_named_int(); @@ -1553,14 +1550,6 @@ SEASTAR_THREAD_TEST_CASE(serde_reflection_roundtrip) { }; roundtrip_test(data); } - { - cluster::get_node_health_request data{ - .filter = { - .ntp_filters = random_partitions_filter(), - }, - }; - roundtrip_test(data); - } { storage::disk data{ .path = random_generators::gen_alphanum_string( @@ -1675,18 +1664,8 @@ SEASTAR_THREAD_TEST_CASE(serde_reflection_roundtrip) { }; roundtrip_test(data); } - { - cluster::node_state data{ - .id = tests::random_named_int(), - .membership_state = model::membership_state::draining, - .is_alive = cluster::alive(tests::random_bool()), - }; - roundtrip_test(data); - } - { - auto data = random_cluster_health_report(); - roundtrip_test(data); - } + { roundtrip_test(cluster::random_node_state()); } + { roundtrip_test(random_cluster_health_report()); } { cluster::get_cluster_health_reply data{ .error = cluster::errc::join_request_dispatch_error, @@ -1694,7 +1673,7 @@ SEASTAR_THREAD_TEST_CASE(serde_reflection_roundtrip) { if (tests::random_bool()) { data.report = random_cluster_health_report(); } - roundtrip_test(data); + roundtrip_test(std::move(data)); } { cluster::topic_configuration_vector topics; diff --git a/src/v/cluster/tests/tm_stm_tests.cc b/src/v/cluster/tests/tm_stm_tests.cc index 0b60f42bdbb03..f7fe2149efdb5 100644 --- a/src/v/cluster/tests/tm_stm_tests.cc +++ b/src/v/cluster/tests/tm_stm_tests.cc @@ -189,7 +189,8 @@ FIXTURE_TEST(test_tm_stm_re_tx, tm_stm_test_fixture) { tx_id, std::chrono::milliseconds(0), pid2, - expected_pid) + expected_pid, + pid1) .get0(); BOOST_REQUIRE_EQUAL(op_code, op_status::success); auto tx7 = expect_tx(stm.get_tx(tx_id).get0()); diff --git a/src/v/cluster/tests/topic_table_test.cc b/src/v/cluster/tests/topic_table_test.cc index 99de157277070..2e8a2be8c0190 100644 --- a/src/v/cluster/tests/topic_table_test.cc +++ b/src/v/cluster/tests/topic_table_test.cc @@ -517,6 +517,34 @@ FIXTURE_TEST(test_topic_with_schema_id_validation_ops, topic_table_fixture) { cfg = topics.get_topic_cfg(tp_ns); BOOST_REQUIRE(cfg.has_value()); BOOST_REQUIRE(!cfg->properties.record_key_schema_id_validation.has_value()); + + // Ensure that an invalid update cmd does not get persisted in the topic + // table. + // Sanity check before starting. + BOOST_REQUIRE(!cfg->properties.record_key_schema_id_validation.has_value()); + BOOST_REQUIRE( + !cfg->properties.record_key_schema_id_validation_compat.has_value()); + + update.record_key_schema_id_validation.op + = cluster::incremental_update_operation::set; + update.record_key_schema_id_validation.value.emplace(true); + + update.record_key_schema_id_validation_compat.op + = cluster::incremental_update_operation::set; + update.record_key_schema_id_validation_compat.value.emplace(false); + ec = topics + .apply( + cluster::update_topic_properties_cmd{tp_ns, update}, + model::offset{11}) + .get(); + BOOST_REQUIRE_EQUAL(ec, cluster::errc::topic_invalid_config); + cfg = topics.get_topic_cfg(tp_ns); + BOOST_REQUIRE(cfg.has_value()); + + // Properties from invalid configuration should not have been persisted. + BOOST_REQUIRE(!cfg->properties.record_key_schema_id_validation.has_value()); + BOOST_REQUIRE( + !cfg->properties.record_key_schema_id_validation_compat.has_value()); } FIXTURE_TEST(test_topic_table_iterator_basic, topic_table_fixture) { diff --git a/src/v/cluster/tests/tx_topic_test.cc b/src/v/cluster/tests/tx_topic_test.cc index 7856021d3bc34..9eeede9e860bb 100644 --- a/src/v/cluster/tests/tx_topic_test.cc +++ b/src/v/cluster/tests/tx_topic_test.cc @@ -10,9 +10,13 @@ #include "cluster/tx_gateway_frontend.h" #include "config/configuration.h" #include "kafka/protocol/types.h" +#include "model/fundamental.h" #include "model/namespace.h" #include "redpanda/application.h" #include "redpanda/tests/fixture.h" +#include "test_utils/async.h" + +#include #include @@ -84,3 +88,58 @@ FIXTURE_TEST(test_tm_stm_new_tx, redpanda_thread_fixture) { && cfg->properties.segment_size.value() == newer_segment_size; }); } + +FIXTURE_TEST(test_tm_stm_eviction, redpanda_thread_fixture) { + // call find coordinator to initialize the topic + auto coordinator = app.tx_gateway_frontend.local() + .find_coordinator( + kafka::transactional_id{"test-tx-id"}) + .get(); + auto tx_mgr_prts = app.partition_manager.local().get_topic_partition_table( + model::tx_manager_nt); + BOOST_REQUIRE(!tx_mgr_prts.empty()); + auto tx_mgr_prt = tx_mgr_prts[model::ntp{ + model::tx_manager_nt.ns, + model::tx_manager_topic, + model::partition_id{0}}]; + BOOST_REQUIRE(tx_mgr_prt != nullptr); + + // Start a tx and roll to the next segment. + auto tx_stm = tx_mgr_prt->raft()->stm_manager()->get(); + auto pid = model::producer_identity{1, 0}; + auto op_code = tx_stm + ->register_new_producer( + tx_mgr_prt->raft()->term(), + kafka::transactional_id{"tx-1"}, + std::chrono::milliseconds(0), + pid) + .get0(); + BOOST_REQUIRE_EQUAL(op_code, cluster::tm_stm::op_status::success); + auto tx_mgr_log = tx_mgr_prt->log(); + tx_mgr_log->flush().get(); + tx_mgr_log->force_roll(ss::default_priority_class()).get(); + + // Start another tx and roll to the next segment. + op_code = tx_stm + ->register_new_producer( + tx_mgr_prt->raft()->term(), + kafka::transactional_id{"tx-2"}, + std::chrono::milliseconds(0), + pid) + .get0(); + BOOST_REQUIRE_EQUAL(op_code, cluster::tm_stm::op_status::success); + BOOST_REQUIRE_EQUAL(2, tx_mgr_log->segment_count()); + + tx_mgr_log->flush().get(); + tx_mgr_log->force_roll(ss::default_priority_class()).get(); + BOOST_REQUIRE_EQUAL(op_code, cluster::tm_stm::op_status::success); + BOOST_REQUIRE_EQUAL(3, tx_mgr_log->segment_count()); + + // Run GC such that we remove the first segment. + auto size_to_keep = tx_mgr_log->size_bytes() + - tx_mgr_log->segments()[0]->size_bytes(); + tx_mgr_log->gc(storage::gc_config{model::timestamp::max(), size_to_keep}) + .get(); + RPTEST_REQUIRE_EVENTUALLY( + 5s, [&] { return tx_mgr_log->segment_count() == 2; }); +} diff --git a/src/v/cluster/tm_stm.cc b/src/v/cluster/tm_stm.cc index bbcd326fa651e..2e468501d5679 100644 --- a/src/v/cluster/tm_stm.cc +++ b/src/v/cluster/tm_stm.cc @@ -105,7 +105,7 @@ tm_stm::tm_stm( : raft::persisted_stm<>(tm_stm_snapshot, logger, c) , _sync_timeout(config::shard_local_cfg().tm_sync_timeout_ms.value()) , _transactional_id_expiration( - config::shard_local_cfg().transactional_id_expiration_ms.value()) + config::shard_local_cfg().transactional_id_expiration_ms.bind()) , _feature_table(feature_table) , _cache(tm_stm_cache) , _ctx_log(logger, ssx::sformat("[{}]", _raft->ntp())) {} @@ -518,14 +518,16 @@ ss::future tm_stm::re_register_producer( kafka::transactional_id tx_id, std::chrono::milliseconds transaction_timeout_ms, model::producer_identity pid, - model::producer_identity last_pid) { + model::producer_identity last_pid, + model::producer_identity rolled_pid) { vlog( _ctx_log.trace, "[tx_id={}] Registering existing transaction with new pid: {}, previous " - "pid: {}", + "pid: {}, rolled_pid: {}", tx_id, pid, - last_pid); + last_pid, + rolled_pid); auto tx_opt = co_await get_tx(tx_id); if (!tx_opt.has_value()) { @@ -549,6 +551,7 @@ ss::future tm_stm::re_register_producer( if (!r.has_value()) { co_return tm_stm::op_status::unknown; } + _pid_tx_id.erase(rolled_pid); co_return tm_stm::op_status::success; } @@ -914,6 +917,7 @@ tm_stm::apply_tm_update(model::record_batch_header hdr, model::record_batch b) { } _cache->set_log(tx); + _pid_tx_id.erase(tx.last_pid); _pid_tx_id[tx.pid] = tx.id; return ss::now(); @@ -931,7 +935,7 @@ ss::future<> tm_stm::apply(const model::record_batch& b) { bool tm_stm::is_expired(const tm_transaction& tx) { auto now_ts = clock_type::now(); - return _transactional_id_expiration < now_ts - tx.last_update_ts; + return _transactional_id_expiration() < now_ts - tx.last_update_ts; } ss::lw_shared_ptr tm_stm::get_tx_lock(kafka::transactional_id tid) { @@ -968,7 +972,7 @@ tm_stm::try_lock_tx(kafka::transactional_id tx_id, std::string_view lock_name) { absl::btree_set tm_stm::get_expired_txs() { auto now_ts = clock_type::now(); auto ids = _cache->filter_all_txid_by_tx([this, now_ts](auto tx) { - return _transactional_id_expiration < now_ts - tx.last_update_ts; + return _transactional_id_expiration() < now_ts - tx.last_update_ts; }); return ids; } diff --git a/src/v/cluster/tm_stm.h b/src/v/cluster/tm_stm.h index 639d1dbb14556..4561280020e5a 100644 --- a/src/v/cluster/tm_stm.h +++ b/src/v/cluster/tm_stm.h @@ -115,6 +115,7 @@ class txlock_unit { */ class tm_stm final : public raft::persisted_stm<> { public: + static constexpr const char* name = "tm_stm"; using clock_type = ss::lowres_system_clock; enum op_status { @@ -305,12 +306,17 @@ class tm_stm final : public raft::persisted_stm<> { mark_tx_prepared(model::term_id, kafka::transactional_id); ss::future> mark_tx_killed(model::term_id, kafka::transactional_id); + // todo: cleanup last_pid and rolled_pid. It seems like they are doing + // the same thing but in practice they are not. last_pid is not updated + // in all cases whereas rolled_pid is need to cleanup all the state + // from previous epochs. ss::future re_register_producer( model::term_id, kafka::transactional_id, std::chrono::milliseconds, - model::producer_identity, - model::producer_identity); + model::producer_identity pid_to_register, + model::producer_identity last_pid, + model::producer_identity rolled_pid); ss::future register_new_producer( model::term_id, kafka::transactional_id, @@ -354,7 +360,6 @@ class tm_stm final : public raft::persisted_stm<> { size_t tx_cache_size() const; std::optional oldest_tx() const; - std::string_view get_name() const final { return "tm_stm"; } ss::future take_snapshot(model::offset) final { co_return iobuf{}; } protected: @@ -367,7 +372,7 @@ class tm_stm final : public raft::persisted_stm<> { ss::future take_local_snapshot() override; std::chrono::milliseconds _sync_timeout; - std::chrono::milliseconds _transactional_id_expiration; + config::binding _transactional_id_expiration; absl::flat_hash_map _pid_tx_id; absl::flat_hash_map> diff --git a/src/v/cluster/topic_table.cc b/src/v/cluster/topic_table.cc index d5d6d3bacdfb6..d3a52a3554028 100644 --- a/src/v/cluster/topic_table.cc +++ b/src/v/cluster/topic_table.cc @@ -20,6 +20,7 @@ #include "model/fundamental.h" #include "model/metadata.h" #include "storage/ntp_config.h" +#include "utils/chunked_hash_map.h" #include #include @@ -159,9 +160,8 @@ topic_table::apply(topic_lifecycle_transition soft_del, model::offset offset) { _lifecycle_markers.erase(soft_del.topic); return ss::make_ready_future(errc::success); } else { - // This is harmless but should not happen and indicates a bug. vlog( - clusterlog.error, + clusterlog.info, "Unexpected record at offset {} to drop non-existent lifecycle " "marker {} {}", offset, @@ -344,6 +344,8 @@ topic_table::apply(finish_moving_partition_replicas_cmd cmd, model::offset o) { _updates_in_progress.erase(it); + _topics_map_revision++; + on_partition_move_finish(cmd.key, cmd.value); // notify backend about finished update @@ -417,6 +419,8 @@ topic_table::apply(cancel_moving_partition_replicas_cmd cmd, model::offset o) { current_assignment_it->replicas = in_progress_it->second.get_previous_replicas(); + _topics_map_revision++; + _pending_deltas.emplace_back( std::move(cmd.key), model::revision_id(o), @@ -459,6 +463,11 @@ topic_table::apply(revert_cancel_partition_move_cmd cmd, model::offset o) { co_return errc::no_update_in_progress; } + auto p_meta_it = tp->second.partitions.find(ntp.tp.partition); + if (p_meta_it == tp->second.partitions.end()) { + co_return errc::partition_not_exists; + } + // revert replica set update current_assignment_it->replicas = in_progress_it->second.get_target_replicas(); @@ -469,11 +478,7 @@ topic_table::apply(revert_cancel_partition_move_cmd cmd, model::offset o) { current_assignment_it->replicas, }; - // update partition_meta object - auto p_meta_it = tp->second.partitions.find(ntp.tp.partition); - if (p_meta_it == tp->second.partitions.end()) { - co_return errc::partition_not_exists; - } + // update partition_meta object: // the cancellation was reverted and update went through, we must // update replicas_revisions. p_meta_it->second.replicas_revisions = update_replicas_revisions( @@ -485,6 +490,8 @@ topic_table::apply(revert_cancel_partition_move_cmd cmd, model::offset o) { /// Since the update is already finished we drop in_progress state _updates_in_progress.erase(in_progress_it); + _topics_map_revision++; + // notify backend about finished update _pending_deltas.emplace_back( ntp, model::revision_id(o), topic_table_delta_type::replicas_updated); @@ -664,6 +671,7 @@ topic_table::apply(set_topic_partitions_disabled_cmd cmd, model::offset o) { } } + _topics_map_revision++; notify_waiters(); co_return errc::success; @@ -825,76 +833,84 @@ topic_table::apply(update_topic_properties_cmd cmd, model::offset o) { if (tp == _topics.end()) { co_return make_error_code(errc::topic_not_exists); } - auto& properties = tp->second.get_configuration().properties; - auto properties_snapshot = properties; + auto updated_properties = tp->second.get_configuration().properties; auto& overrides = cmd.value; /** * Update topic properties */ incremental_update( - properties.cleanup_policy_bitflags, overrides.cleanup_policy_bitflags); + updated_properties.cleanup_policy_bitflags, + overrides.cleanup_policy_bitflags); incremental_update( - properties.compaction_strategy, overrides.compaction_strategy); - incremental_update(properties.compression, overrides.compression); - incremental_update(properties.retention_bytes, overrides.retention_bytes); + updated_properties.compaction_strategy, overrides.compaction_strategy); + incremental_update(updated_properties.compression, overrides.compression); incremental_update( - properties.retention_duration, overrides.retention_duration); - incremental_update(properties.segment_size, overrides.segment_size); - incremental_update(properties.timestamp_type, overrides.timestamp_type); - - incremental_update(properties.shadow_indexing, overrides.shadow_indexing); - incremental_update(properties.batch_max_bytes, overrides.batch_max_bytes); - + updated_properties.retention_bytes, overrides.retention_bytes); + incremental_update( + updated_properties.retention_duration, overrides.retention_duration); + incremental_update(updated_properties.segment_size, overrides.segment_size); + incremental_update( + updated_properties.timestamp_type, overrides.timestamp_type); + incremental_update( + updated_properties.shadow_indexing, overrides.shadow_indexing); + incremental_update( + updated_properties.batch_max_bytes, overrides.batch_max_bytes); incremental_update( - properties.retention_local_target_bytes, + updated_properties.retention_local_target_bytes, overrides.retention_local_target_bytes); incremental_update( - properties.retention_local_target_ms, + updated_properties.retention_local_target_ms, overrides.retention_local_target_ms); incremental_update( - properties.remote_delete, + updated_properties.remote_delete, overrides.remote_delete, storage::ntp_config::default_remote_delete); - incremental_update(properties.segment_ms, overrides.segment_ms); + incremental_update(updated_properties.segment_ms, overrides.segment_ms); incremental_update( - properties.record_key_schema_id_validation, + updated_properties.record_key_schema_id_validation, overrides.record_key_schema_id_validation); incremental_update( - properties.record_key_schema_id_validation_compat, + updated_properties.record_key_schema_id_validation_compat, overrides.record_key_schema_id_validation_compat); incremental_update( - properties.record_key_subject_name_strategy, + updated_properties.record_key_subject_name_strategy, overrides.record_key_subject_name_strategy); incremental_update( - properties.record_key_subject_name_strategy_compat, + updated_properties.record_key_subject_name_strategy_compat, overrides.record_key_subject_name_strategy_compat); incremental_update( - properties.record_value_schema_id_validation, + updated_properties.record_value_schema_id_validation, overrides.record_value_schema_id_validation); incremental_update( - properties.record_value_schema_id_validation_compat, + updated_properties.record_value_schema_id_validation_compat, overrides.record_value_schema_id_validation_compat); incremental_update( - properties.record_value_subject_name_strategy, + updated_properties.record_value_subject_name_strategy, overrides.record_value_subject_name_strategy); incremental_update( - properties.record_value_subject_name_strategy_compat, + updated_properties.record_value_subject_name_strategy_compat, overrides.record_value_subject_name_strategy_compat); incremental_update( - properties.initial_retention_local_target_bytes, + updated_properties.initial_retention_local_target_bytes, overrides.initial_retention_local_target_bytes); incremental_update( - properties.initial_retention_local_target_ms, + updated_properties.initial_retention_local_target_ms, overrides.initial_retention_local_target_ms); + + auto& properties = tp->second.get_configuration().properties; + // no configuration change, no need to generate delta - if (properties == properties_snapshot) { + if (updated_properties == properties) { co_return errc::success; } - if (!schema_id_validation_validator::is_valid(properties)) { + if (!schema_id_validation_validator::is_valid(updated_properties)) { co_return schema_id_validation_validator::ec; } + // Apply the changes + properties = std::move(updated_properties); + // generate deltas for controller backend const auto& assignments = tp->second.get_assignments(); for (auto& p_as : assignments) { @@ -912,11 +928,11 @@ ss::future<> topic_table::fill_snapshot(controller_snapshot& controller_snap) const { auto& snap = controller_snap.topics; for (const auto& [ns_tp, md_item] : _topics) { - absl::node_hash_map< + chunked_hash_map< model::partition_id, controller_snapshot_parts::topics_t::partition_t> partitions; - absl::node_hash_map< + chunked_hash_map< model::partition_id, controller_snapshot_parts::topics_t::update_t> updates; @@ -988,6 +1004,7 @@ class topic_table::snapshot_applier { disabled_partitions_t& _disabled_partitions; fragmented_vector& _pending_deltas; topic_table_probe& _probe; + model::revision_id& _topics_map_revision; model::revision_id _snap_revision; public: @@ -996,6 +1013,7 @@ class topic_table::snapshot_applier { , _disabled_partitions(parent._disabled_partitions) , _pending_deltas(parent._pending_deltas) , _probe(parent._probe) + , _topics_map_revision(parent._topics_map_revision) , _snap_revision(snap_revision) {} void delete_ntp( @@ -1003,7 +1021,9 @@ class topic_table::snapshot_applier { auto ntp = model::ntp(ns_tp.ns, ns_tp.tp, p_as.id); vlog( clusterlog.trace, "deleting ntp {} not in controller snapshot", ntp); - _updates_in_progress.erase(ntp); + if (_updates_in_progress.erase(ntp)) { + _topics_map_revision++; + }; _pending_deltas.emplace_back( std::move(ntp), _snap_revision, topic_table_delta_type::removed); @@ -1022,7 +1042,9 @@ class topic_table::snapshot_applier { delete_ntp(ns_tp, p_as); co_await ss::coroutine::maybe_yield(); } - _disabled_partitions.erase(ns_tp); + if (_disabled_partitions.erase(ns_tp)) { + _topics_map_revision++; + }; _probe.handle_topic_deletion(ns_tp); // topic_metadata_item object is supposed to be removed from _topics by // the caller @@ -1037,6 +1059,9 @@ class topic_table::snapshot_applier { vlog(clusterlog.trace, "adding ntp {} from controller snapshot", ntp); size_t pending_deltas_start_idx = _pending_deltas.size(); + // we are going to modify md_item so increment the revision right away. + _topics_map_revision++; + const model::partition_id p_id = ntp.tp.partition; // 1. reconcile the _topics state (the md_item object) and generate @@ -1169,7 +1194,9 @@ class topic_table::snapshot_applier { topic_metadata_item ret{topic_metadata{topic.metadata, {}}}; if (topic.disabled_set) { _disabled_partitions[ns_tp] = *topic.disabled_set; + _topics_map_revision++; } + for (const auto& [p_id, partition] : topic.partitions) { auto ntp = model::ntp(ns_tp.ns, ns_tp.tp, p_id); add_ntp(ntp, topic, partition, ret, false); @@ -1208,6 +1235,7 @@ ss::future<> topic_table::apply_snapshot( // The topic was re-created, delete and add it anew. co_await applier.delete_topic(ns_tp, md_item); md_item = co_await applier.create_topic(ns_tp, topic_snapshot); + _topics_map_revision++; } else { // The topic was present in the previous set, now we need to // reconcile individual partitions. @@ -1225,10 +1253,12 @@ ss::future<> topic_table::apply_snapshot( old_disabled_set = std::exchange( _disabled_partitions[ns_tp], *topic_snapshot.disabled_set); + _topics_map_revision++; } else if (auto it = _disabled_partitions.find(ns_tp); it != _disabled_partitions.end()) { old_disabled_set = std::move(it->second); _disabled_partitions.erase(it); + _topics_map_revision++; } // 2. For each partition in the new set, reconcile assignments @@ -1265,6 +1295,7 @@ ss::future<> topic_table::apply_snapshot( if (!topic_snapshot.partitions.contains(as_it_copy->id)) { applier.delete_ntp(ns_tp, *as_it_copy); md_item.get_assignments().erase(as_it_copy); + _topics_map_revision++; } co_await ss::coroutine::maybe_yield(); } @@ -1275,8 +1306,7 @@ ss::future<> topic_table::apply_snapshot( // For topics that are not present in the new set, simply // remove them and generate del deltas. co_await applier.delete_topic(ns_tp, md_item); - auto to_delete = old_it++; - _topics.erase(to_delete); + old_it = _topics.erase(old_it); _topics_map_revision++; } } @@ -1643,6 +1673,7 @@ void topic_table::change_partition_replicas( auto previous_assignment = current_assignment.replicas; // replace partition replica set current_assignment.replicas = new_assignment; + _topics_map_revision++; // calculate delta for backend diff --git a/src/v/cluster/topic_table.h b/src/v/cluster/topic_table.h index 68b0321956197..bcef38f7e26d0 100644 --- a/src/v/cluster/topic_table.h +++ b/src/v/cluster/topic_table.h @@ -17,6 +17,7 @@ #include "model/fundamental.h" #include "model/limits.h" #include "model/metadata.h" +#include "utils/chunked_hash_map.h" #include "utils/contiguous_range_map.h" #include "utils/expiring_promise.h" #include "utils/stable_iterator_adaptor.h" @@ -97,21 +98,17 @@ class topic_table { // * partition::get_revision_id() // * raft::group_configuration::revision_id() - class concurrent_modification_error final : public std::exception { + class concurrent_modification_error final + : public ::concurrent_modification_error { public: concurrent_modification_error( model::revision_id initial_revision, model::revision_id current_revision) - : _msg(ssx::sformat( - "Topic table was modified by concurrent fiber. (initial_revision: " - "{}, current_revision: {}) ", + : ::concurrent_modification_error(ssx::sformat( + "Topic table was modified by concurrent fiber. " + "(initial_revision: {}, current_revision: {}) ", initial_revision, current_revision)) {} - - const char* what() const noexcept final { return _msg.c_str(); } - - private: - ss::sstring _msg; }; class in_progress_update { @@ -241,7 +238,7 @@ class topic_table { using delta = topic_table_delta; - using underlying_t = absl::node_hash_map< + using underlying_t = chunked_hash_map< model::topic_namespace, topic_metadata_item, model::topic_namespace_hash, @@ -253,7 +250,7 @@ class topic_table { nt_revision_hash, nt_revision_eq>; - using disabled_partitions_t = absl::node_hash_map< + using disabled_partitions_t = chunked_hash_map< model::topic_namespace, topic_disabled_partitions_set, model::topic_namespace_hash, @@ -626,8 +623,13 @@ class topic_table { updates_t _updates_in_progress; model::revision_id _last_applied_revision_id; - // Monotonic counter that is bumped for every addition/deletion to topics - // map. Unlike other revisions this does not correspond to the command + + // Monotonic counter that is bumped each time _topics, _disabled_partitions, + // or _updates_in_progress are modified in a way that makes iteration over + // them unsafe (i.e. invalidates iterators or references, including + // for nested collections like partition sets and replica sets). + // + // Unlike other revisions this does not correspond to the command // revision that updated the map. model::revision_id _topics_map_revision{0}; diff --git a/src/v/cluster/topic_table_partition_generator.cc b/src/v/cluster/topic_table_partition_generator.cc index 5e2f5095541d2..db9de13b22d48 100644 --- a/src/v/cluster/topic_table_partition_generator.cc +++ b/src/v/cluster/topic_table_partition_generator.cc @@ -19,7 +19,7 @@ topic_table_partition_generator_exception:: topic_table_partition_generator::topic_table_partition_generator( ss::sharded& topic_table, size_t batch_size) : _topic_table(topic_table) - , _stable_revision_id(_topic_table.local().last_applied_revision()) + , _stable_revision_id(_topic_table.local().topics_map_revision()) , _batch_size(batch_size) { if (_topic_table.local()._topics.empty()) { _topic_iterator = _topic_table.local()._topics.end(); @@ -32,8 +32,7 @@ topic_table_partition_generator::topic_table_partition_generator( ss::future> topic_table_partition_generator::next_batch() { - const auto current_revision_id - = _topic_table.local().last_applied_revision(); + const auto current_revision_id = _topic_table.local().topics_map_revision(); if (current_revision_id != _stable_revision_id) { throw topic_table_partition_generator_exception(fmt::format( "Last applied revision id moved from {} to {} whilst " diff --git a/src/v/cluster/topics_frontend.cc b/src/v/cluster/topics_frontend.cc index 57bd35af83a22..58b7262aa66e3 100644 --- a/src/v/cluster/topics_frontend.cc +++ b/src/v/cluster/topics_frontend.cc @@ -976,7 +976,7 @@ topics_frontend::partitions_with_lost_majority( co_return errc::concurrent_modification_error; } co_return result; - } catch (const topic_table::concurrent_modification_error& e) { + } catch (const concurrent_modification_error& e) { // state changed while generating the plan, force caller to retry; vlog( clusterlog.info, @@ -1089,7 +1089,8 @@ ss::future topics_frontend::abort_moving_partition_replicas( ss::future topics_frontend::finish_moving_partition_replicas( model::ntp ntp, std::vector new_replica_set, - model::timeout_clock::time_point tout) { + model::timeout_clock::time_point tout, + dispatch_to_leader dispatch) { auto leader = _leaders.local().get_leader(model::controller_ntp); // no leader available @@ -1097,19 +1098,26 @@ ss::future topics_frontend::finish_moving_partition_replicas( return ss::make_ready_future( errc::no_leader_controller); } - // optimization: if update is not in progress return early - if (!_topics.local().is_update_in_progress(ntp)) { - return ss::make_ready_future( - errc::no_update_in_progress); - } + // current node is a leader, just replicate if (leader == _self) { + // optimization: if update is not in progress return early + if (!_topics.local().is_update_in_progress(ntp)) { + return ss::make_ready_future( + errc::no_update_in_progress); + } + finish_moving_partition_replicas_cmd cmd( std::move(ntp), std::move(new_replica_set)); return replicate_and_wait(_stm, _as, std::move(cmd), tout); } + if (!dispatch) { + return ss::make_ready_future( + errc::not_leader_controller); + } + return _connections.local() .with_node_client( _self, @@ -1294,7 +1302,7 @@ ss::future topics_frontend::do_create_partition( // we only support increasing number of partitions if (p_cfg.new_total_partition_count <= tp_cfg->partition_count) { co_return make_error_result( - p_cfg.tp_ns, errc::topic_invalid_partitions); + p_cfg.tp_ns, errc::topic_invalid_partitions_decreased); } if (_topics.local().is_fully_disabled(p_cfg.tp_ns)) { co_return make_error_result(p_cfg.tp_ns, errc::topic_disabled); @@ -1496,16 +1504,17 @@ ss::future topics_frontend::get_health_info( // This health report is just on the data disk. If the cache has // a separate disk, it is not reflected in the node health. - total += node_report.local_state.data_disk.total; - free += node_report.local_state.data_disk.free; + total += node_report->local_state.data_disk.total; + free += node_report->local_state.data_disk.free; info.node_disk_reports.emplace( - node_report.id, node_disk_space(node_report.id, total, total - free)); + node_report->id, + node_disk_space(node_report->id, total, total - free)); } for (auto& node_report : health_report.value().node_reports) { co_await ss::max_concurrent_for_each( - std::move(node_report.topics), + std::move(node_report->topics), 32, [&info](const topic_status& status) { for (const auto& partition : status.partitions) { diff --git a/src/v/cluster/topics_frontend.h b/src/v/cluster/topics_frontend.h index f1c48b641409a..d55a3a2c5534c 100644 --- a/src/v/cluster/topics_frontend.h +++ b/src/v/cluster/topics_frontend.h @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -38,6 +39,13 @@ namespace cluster { // on every core class topics_frontend { public: + /** + * A boolean that may be used by the caller to request redirecting a request + * to the leader. This is useful as topic operations must be executed on + * `redpanda/controller` partition leader. + */ + using dispatch_to_leader = ss::bool_class; + struct capacity_info { absl::flat_hash_map node_disk_reports; absl::flat_hash_map ntp_sizes; @@ -126,7 +134,8 @@ class topics_frontend { ss::future finish_moving_partition_replicas( model::ntp, std::vector, - model::timeout_clock::time_point); + model::timeout_clock::time_point, + dispatch_to_leader = dispatch_to_leader::yes); ss::future revert_cancel_partition_move( model::ntp, model::timeout_clock::time_point); diff --git a/src/v/cluster/tx_gateway_frontend.cc b/src/v/cluster/tx_gateway_frontend.cc index 7c32009df01d7..e1a85cd01b2ed 100644 --- a/src/v/cluster/tx_gateway_frontend.cc +++ b/src/v/cluster/tx_gateway_frontend.cc @@ -317,7 +317,7 @@ tx_gateway_frontend::tx_gateway_frontend( , _metadata_dissemination_retry_delay_ms( config::shard_local_cfg().metadata_dissemination_retry_delay_ms.value()) , _transactional_id_expiration( - config::shard_local_cfg().transactional_id_expiration_ms.value()) + config::shard_local_cfg().transactional_id_expiration_ms.bind()) , _transactions_enabled(config::shard_local_cfg().enable_transactions.value()) , _max_transactions_per_coordinator(max_transactions_per_coordinator) { /** @@ -326,6 +326,8 @@ tx_gateway_frontend::tx_gateway_frontend( if (_transactions_enabled) { start_expire_timer(); } + _transactional_id_expiration.watch( + [this]() { rearm_expire_timer(/*force=*/true); }); } void tx_gateway_frontend::start_expire_timer() { @@ -343,6 +345,23 @@ void tx_gateway_frontend::start_expire_timer() { rearm_expire_timer(); } +void tx_gateway_frontend::rearm_expire_timer(bool force) { + if (ss::this_shard_id() != 0 || _gate.is_closed()) { + return; + } + if (force) { + _expire_timer.cancel(); + } + if (!_expire_timer.armed()) { + // we need to expire transactional ids which were inactive more than + // transactional_id_expiration period. if we check for the expired + // transactions twice during the period then in the worst case an + // expired id lives at most 1.5 x transactional_id_expiration + auto delay = _transactional_id_expiration() / 2; + _expire_timer.arm(model::timeout_clock::now() + delay); + } +} + ss::future<> tx_gateway_frontend::stop() { vlog(txlog.debug, "Asked to stop tx coordinator"); _expire_timer.cancel(); @@ -515,8 +534,8 @@ ss::future> tx_gateway_frontend::fetch_tx( txlog.trace, "[tx_id={}] fetching transactions from partition: {} in term: {}", tx_id, - term, - tm); + tm, + term); auto outcome = ss::make_lw_shared< available_promise>>(); @@ -1296,6 +1315,13 @@ ss::future tx_gateway_frontend::do_init_tm_tx( tx = r.value(); init_tm_tx_reply reply; + // note: while rolled_pid and last_pid look very similar in intent which is + // to track previous incarnation of this transaction_id, it doesn't seem to + // work like that in practice. last_pid is not set in all cases (refer to + // kip-360 for details) whereas we want to cleanup older epochs state in all + // cases, hence a separate rolled_pid was added. This is definitely not + // ideal, probably needs a closer look. + model::producer_identity rolled_pid = tx.pid; model::producer_identity last_pid = model::unknown_pid; if (expected_pid == model::unknown_pid) { @@ -1333,7 +1359,7 @@ ss::future tx_gateway_frontend::do_init_tm_tx( reply.pid, last_pid); auto op_status = co_await stm->re_register_producer( - term, tx.id, transaction_timeout_ms, reply.pid, last_pid); + term, tx.id, transaction_timeout_ms, reply.pid, last_pid, rolled_pid); if (op_status == tm_stm::op_status::success) { reply.ec = tx_errc::none; } else if (op_status == tm_stm::op_status::conflict) { diff --git a/src/v/cluster/tx_gateway_frontend.h b/src/v/cluster/tx_gateway_frontend.h index 3ee680bdca812..0c9171d5e17b8 100644 --- a/src/v/cluster/tx_gateway_frontend.h +++ b/src/v/cluster/tx_gateway_frontend.h @@ -98,7 +98,7 @@ class tx_gateway_frontend final int16_t _metadata_dissemination_retries; std::chrono::milliseconds _metadata_dissemination_retry_delay_ms; ss::timer _expire_timer; - std::chrono::milliseconds _transactional_id_expiration; + config::binding _transactional_id_expiration; bool _transactions_enabled; config::binding _max_transactions_per_coordinator; @@ -117,16 +117,7 @@ class tx_gateway_frontend final void start_expire_timer(); - void rearm_expire_timer() { - if (!_expire_timer.armed() && !_gate.is_closed()) { - // we need to expire transactional ids which were inactive more than - // transactional_id_expiration period. if we check for the expired - // transactions twice during the period then in the worst case an - // expired id lives at most 1.5 x transactional_id_expiration - auto delay = _transactional_id_expiration / 2; - _expire_timer.arm(model::timeout_clock::now() + delay); - } - } + void rearm_expire_timer(bool force = false); ss::future> wait_for_leader(const model::ntp&); diff --git a/src/v/compat/check.h b/src/v/compat/check.h index c96ec9beadc3a..6aee67e240828 100644 --- a/src/v/compat/check.h +++ b/src/v/compat/check.h @@ -298,11 +298,11 @@ void verify_serde_round_trip(T, compat_binary test) { } \ \ static std::vector to_binary(Type obj) { \ - return {compat_binary::serde(obj)}; \ + return {compat_binary::serde(std::move(obj))}; \ } \ \ static void check(Type obj, compat_binary test) { \ - verify_serde_only(obj, std::move(test)); \ + verify_serde_only(std::move(obj), std::move(test)); \ } \ }; diff --git a/src/v/compat/cluster_generator.h b/src/v/compat/cluster_generator.h index f24501bf3cb98..ece787460967f 100644 --- a/src/v/compat/cluster_generator.h +++ b/src/v/compat/cluster_generator.h @@ -69,7 +69,11 @@ struct instance_generator { cluster::errc::feature_disabled, cluster::errc::invalid_request, cluster::errc::no_update_in_progress, - cluster::errc::unknown_update_interruption_error}); + cluster::errc::unknown_update_interruption_error, + cluster::errc::topic_invalid_partitions_core_limit, + cluster::errc::topic_invalid_partitions_memory_limit, + cluster::errc::topic_invalid_partitions_fd_limit, + cluster::errc::topic_invalid_partitions_decreased}); } static std::vector limits() { return {}; } diff --git a/src/v/compat/cluster_json.h b/src/v/compat/cluster_json.h index 6b9c1fb30c425..dab1d28ae5cfe 100644 --- a/src/v/compat/cluster_json.h +++ b/src/v/compat/cluster_json.h @@ -313,11 +313,14 @@ inline void rjson_serialize( json::Writer& w, const cluster::node_state& f) { w.StartObject(); w.Key("id"); - rjson_serialize(w, f.id); + rjson_serialize(w, f.id()); w.Key("membership_state"); - rjson_serialize(w, f.membership_state); + rjson_serialize(w, f.membership_state()); w.Key("is_alive"); - rjson_serialize(w, f.is_alive); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + rjson_serialize(w, f.is_alive()); +#pragma clang diagnostic pop w.EndObject(); } @@ -330,7 +333,11 @@ inline void rjson_serialize( w.Key("node_states"); rjson_serialize(w, f.node_states); w.Key("node_reports"); - rjson_serialize(w, f.node_reports); + w.StartArray(); + for (auto& r : f.node_reports) { + rjson_serialize(w, *r); + } + w.EndArray(); w.EndObject(); } @@ -449,20 +456,29 @@ inline void read_value(json::Value const& rd, cluster::node_state& obj) { read_member(rd, "membership_state", membership_state); read_member(rd, "is_alive", is_alive); - obj = cluster::node_state{{}, id, membership_state, is_alive}; + obj = cluster::node_state(id, membership_state, is_alive); } inline void read_value(json::Value const& rd, cluster::cluster_health_report& obj) { std::optional raft0_leader; std::vector node_states; - std::vector node_reports; + std::vector node_reports; read_member(rd, "raft0_leader", raft0_leader); read_member(rd, "node_states", node_states); - read_member(rd, "node_reports", node_reports); + auto reports_v = rd.FindMember("node_reports"); + if (reports_v != rd.MemberEnd()) { + for (auto const& e : reports_v->value.GetArray()) { + cluster::node_health_report report; + read_value(e, report); + node_reports.emplace_back( + ss::make_lw_shared( + std::move(report))); + } + } obj = cluster::cluster_health_report{ - {}, raft0_leader, node_states, node_reports}; + {}, raft0_leader, node_states, std::move(node_reports)}; } inline void diff --git a/src/v/compat/get_node_health_compat.h b/src/v/compat/get_node_health_compat.h index 5b37e6ba66fe7..4be06c29a1b7a 100644 --- a/src/v/compat/get_node_health_compat.h +++ b/src/v/compat/get_node_health_compat.h @@ -18,10 +18,7 @@ namespace compat { -GEN_COMPAT_CHECK_SERDE_ONLY( - cluster::get_node_health_request, - { json_write(filter); }, - { json_read(filter); }); +EMPTY_COMPAT_CHECK_SERDE_ONLY(cluster::get_node_health_request); template<> struct compat_check { diff --git a/src/v/compat/get_node_health_generator.h b/src/v/compat/get_node_health_generator.h index a43a63e52006a..636489a70c213 100644 --- a/src/v/compat/get_node_health_generator.h +++ b/src/v/compat/get_node_health_generator.h @@ -21,10 +21,7 @@ namespace compat { template<> struct instance_generator { static cluster::get_node_health_request random() { - return cluster::get_node_health_request{ - {}, - cluster::random_node_report_filter(), - random_generators::get_int()}; + return cluster::get_node_health_request{}; } static std::vector limits() { return {}; } }; diff --git a/src/v/compat/json.h b/src/v/compat/json.h index 5e3b844837b63..174530c648ced 100644 --- a/src/v/compat/json.h +++ b/src/v/compat/json.h @@ -19,6 +19,7 @@ #include "net/unresolved_address.h" #include "security/acl.h" #include "utils/base64.h" +#include "utils/json.h" #include @@ -516,7 +517,7 @@ inline void rjson_serialize( ss.rdstate())); } w.Key("address"); - rjson_serialize(w, ss.str()); + rjson_serialize(w, std::string_view{ss.str()}); w.EndObject(); } diff --git a/src/v/compression/CMakeLists.txt b/src/v/compression/CMakeLists.txt index e621f2a363353..acafd26fb4cd5 100644 --- a/src/v/compression/CMakeLists.txt +++ b/src/v/compression/CMakeLists.txt @@ -15,11 +15,13 @@ v_cc_library( "stream_zstd.cc" "async_stream_zstd.cc" "snappy_standard_compressor.cc" + "lz4_decompression_buffers.cc" "internal/snappy_java_compressor.cc" "internal/lz4_frame_compressor.cc" "internal/gzip_compressor.cc" DEPS v::bytes + v::ssx Zstd::zstd LZ4::LZ4 Snappy::snappy diff --git a/src/v/compression/internal/lz4_frame_compressor.cc b/src/v/compression/internal/lz4_frame_compressor.cc index 4028ee2581237..236244692dae8 100644 --- a/src/v/compression/internal/lz4_frame_compressor.cc +++ b/src/v/compression/internal/lz4_frame_compressor.cc @@ -10,9 +10,8 @@ #include "compression/internal/lz4_frame_compressor.h" #include "bytes/bytes.h" +#include "compression/lz4_decompression_buffers.h" #include "static_deleter_fn.h" -#include "units.h" -#include "vassert.h" #include @@ -59,13 +58,21 @@ using lz4_decompression_ctx = std::unique_ptr< &LZ4F_freeDecompressionContext>>; static lz4_decompression_ctx make_decompression_context() { - LZ4F_dctx* c = nullptr; - LZ4F_errorCode_t code = LZ4F_createDecompressionContext(&c, LZ4F_VERSION); - check_lz4_error("LZ4F_createDecompressionContext error: {}", code); + LZ4F_dctx* c = LZ4F_createDecompressionContext_advanced( + lz4_decompression_buffers_instance().custom_mem_alloc(), LZ4F_VERSION); + if (c == nullptr) { + throw std::runtime_error("Failed to initialize decompression context"); + } + return lz4_decompression_ctx(c); } iobuf lz4_frame_compressor::compress(const iobuf& b) { + return compress_with_block_size(b, std::nullopt); +} + +iobuf lz4_frame_compressor::compress_with_block_size( + const iobuf& b, std::optional block_size_id) { auto ctx_ptr = make_compression_context(); LZ4F_compressionContext_t ctx = ctx_ptr.get(); /* Required by Kafka */ @@ -73,7 +80,13 @@ iobuf lz4_frame_compressor::compress(const iobuf& b) { std::memset(&prefs, 0, sizeof(prefs)); prefs.compressionLevel = 1; // default prefs.frameInfo = { - .blockMode = LZ4F_blockIndependent, .contentSize = b.size_bytes()}; + .blockMode = LZ4F_blockIndependent, + .contentSize = b.size_bytes(), + }; + + if (block_size_id.has_value()) { + prefs.frameInfo.blockSizeID = block_size_id.value(); + } const size_t max_chunk_size = details::io_allocation_size::max_chunk_size; @@ -101,8 +114,8 @@ iobuf lz4_frame_compressor::compress(const iobuf& b) { // We do not consume entire input chunks at once, to avoid // max_chunk_size input chunks resulting in >max_chunk_size output - // chunks. A half-sized input chunk never results in a LZ4F_compressBound - // that exceeds a the max output chunk. + // chunks. A half-sized input chunk never results in a + // LZ4F_compressBound that exceeds a the max output chunk. const size_t max_input_chunk_size = max_chunk_size / 2; iobuf ret; diff --git a/src/v/compression/internal/lz4_frame_compressor.h b/src/v/compression/internal/lz4_frame_compressor.h index 080a2913a2477..f5bbf743ddea5 100644 --- a/src/v/compression/internal/lz4_frame_compressor.h +++ b/src/v/compression/internal/lz4_frame_compressor.h @@ -11,10 +11,15 @@ #pragma once #include "bytes/iobuf.h" + +#include + namespace compression::internal { struct lz4_frame_compressor { static iobuf compress(const iobuf&); + static iobuf + compress_with_block_size(const iobuf&, std::optional); static iobuf uncompress(const iobuf&); }; diff --git a/src/v/compression/lz4_decompression_buffers.cc b/src/v/compression/lz4_decompression_buffers.cc new file mode 100644 index 0000000000000..cb54dcba2bbf3 --- /dev/null +++ b/src/v/compression/lz4_decompression_buffers.cc @@ -0,0 +1,224 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "compression/lz4_decompression_buffers.h" + +#include "vassert.h" + +#include + +namespace compression { + +std::ostream& operator<<( + std::ostream& os, lz4_decompression_buffers::alloc_ctx::allocation_state st) { + switch (st) { + using enum compression::lz4_decompression_buffers::alloc_ctx:: + allocation_state; + case no_buffers_allocated: + return os << "no buffers allocated"; + case input_buffer_allocated: + return os << "input buffer allocated"; + case output_buffer_allocated: + return os << "output buffer allocated"; + case both_buffers_allocated: + return os << "both buffers allocated"; + } +} + +lz4_decompression_buffers::lz4_decompression_buffers( + size_t buffer_size, size_t min_alloc_threshold, bool disabled) + : _buffer_size{buffer_size} + , _min_alloc_threshold{min_alloc_threshold} + , _disabled{disabled} { + if (!_disabled) { + _buffers = { + .input_buffer = ss::allocate_aligned_buffer(buffer_size, 8), + .output_buffer = ss::allocate_aligned_buffer(buffer_size, 8), + .state = alloc_ctx::allocation_state::no_buffers_allocated, + }; + } +} + +bool lz4_decompression_buffers::alloc_ctx::is_managed_address( + const void* const address) const { + return address == input_buffer.get() || address == output_buffer.get(); +} + +lz4_decompression_buffers::alloc_ctx& lz4_decompression_buffers::buffers() { + return _buffers; +} + +size_t lz4_decompression_buffers::min_alloc_threshold() const { + return _min_alloc_threshold; +} + +LZ4F_CustomMem lz4_decompression_buffers::custom_mem_alloc() { + // If custom allocation is disabled, setting all alloc functions to null + // makes lz4 fall back to malloc, calloc and free. + if (_disabled) { + return { + .customAlloc = nullptr, + .customCalloc = nullptr, + .customFree = nullptr, + .opaqueState = nullptr}; + } + + return { + .customAlloc = alloc_lz4_obj, + .customCalloc = nullptr, + .customFree = free_lz4_obj, + .opaqueState = this}; +} + +static thread_local std::unique_ptr + _buffers_instance; + +void init_lz4_decompression_buffers( + size_t buffer_size, size_t min_alloc_threshold, bool prealloc_disabled) { + if (!_buffers_instance) { + _buffers_instance = std::make_unique( + buffer_size, min_alloc_threshold, prealloc_disabled); + } +} + +void reset_lz4_decompression_buffers() { + if (_buffers_instance) { + _buffers_instance.reset(); + } +} + +lz4_decompression_buffers& lz4_decompression_buffers_instance() { + if (unlikely(!_buffers_instance)) { + init_lz4_decompression_buffers( + lz4_decompression_buffers::bufsize, + lz4_decompression_buffers::min_threshold); + } + + return *_buffers_instance; +} + +} // namespace compression + +namespace { + +using alloc_st + = compression::lz4_decompression_buffers::alloc_ctx::allocation_state; +using t = std::underlying_type_t; + +alloc_st operator|(alloc_st a, alloc_st b) { return alloc_st(t(a) | t(b)); } + +void operator|=(alloc_st& a, alloc_st b) { a = (a | b); } + +alloc_st operator&(alloc_st a, alloc_st b) { return alloc_st(t(a) & t(b)); } + +void operator&=(alloc_st& a, alloc_st b) { a = (a & b); } + +alloc_st operator~(alloc_st a) { return alloc_st(~t(a)); } + +} // namespace + +// During a typical lz4 decompression operation the following LZ4F_malloc calls +// will be processed via this alloc function: +// 1. Allocation for the tmp input buffer: this can be a maximum of 4MiB + 4 +// bytes +// 2. Allocation for the tmp output buffer: this can be a maximum of 4MiB + +// 128KiB +// These two calls will typically happen once per decompression context, and are +// preceded by calls to LZ4F_free to first free up the two buffers. +void* alloc_lz4_obj(void* state, size_t size) { + auto* st = static_cast(state); + vassert( + size <= st->buffer_size(), + "Request to allocate {} bytes which is more than max buffer size " + "available: {} bytes", + size, + st->buffer_size()); + + if (size < st->min_alloc_threshold()) { + st->pass_through_allocated(); + return malloc(size); + } + + auto& bufs = st->buffers(); + + switch (bufs.state) { + using enum compression::lz4_decompression_buffers::alloc_ctx:: + allocation_state; + case no_buffers_allocated: + bufs.state |= input_buffer_allocated; + st->allocated(); + return bufs.input_buffer.get(); + case input_buffer_allocated: + bufs.state |= output_buffer_allocated; + st->allocated(); + return bufs.output_buffer.get(); + case both_buffers_allocated: + case output_buffer_allocated: + vassert( + false, "invalid allocation request when both buffers allocated"); + } +} + +// During a decompression operation this function is called via the LZ4F_free +// wrapper. The function is typically called in the following sequence: +// 1. When freeing the decompression context: +// a. free the tmp out buffer +// b. free the tmp in buffer +// c. free the decompression context +// 2. When initializing the decompression context, this function will be called +// on the two buffer addresses. +// In all cases we either pass the address straight through to `free()` or if +// the address is managed, we update the state. The state update ensures that +// the next decompression operation starts with the correct state (no buffers +// allocated) +void free_lz4_obj(void* state, void* address) { + auto* st = static_cast(state); + + auto& bufs = st->buffers(); + + // If the address being freed does not match one of the static addresses we + // manage, fall back to free. This can happen because: + // + // 1. LZ4 frees memory before performing each allocation, resulting in + // interspersed calls to free/malloc where the freed address was not + // allocated from our pool. + // + // 2. The allocation was not done via this allocator, eg for blocks + // small enough that they should not be managed by custom allocator. + // + // In both cases these memory addresses will not match our managed buffers. + if (!bufs.is_managed_address(address)) { + st->pass_through_deallocated(); + free(address); + return; + } + + // Buffers are released by lz4 in the order: input buffer, output buffer, + // decompression ctx. The first two calls update the state here. The third + // call is passed through to free because we do not allocate memory for the + // decompression ctx. + switch (bufs.state) { + using enum compression::lz4_decompression_buffers::alloc_ctx:: + allocation_state; + case no_buffers_allocated: + case input_buffer_allocated: + vassert( + false, "unexpected buffer state {} during deallocation", bufs.state); + case output_buffer_allocated: + st->deallocated(); + bufs.state &= (~output_buffer_allocated); + return; + case both_buffers_allocated: + st->deallocated(); + bufs.state &= (~input_buffer_allocated); + return; + } +} diff --git a/src/v/compression/lz4_decompression_buffers.h b/src/v/compression/lz4_decompression_buffers.h new file mode 100644 index 0000000000000..d8b96805c3fbd --- /dev/null +++ b/src/v/compression/lz4_decompression_buffers.h @@ -0,0 +1,148 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "seastarx.h" +#include "units.h" + +#include +#include + +#define LZ4F_STATIC_LINKING_ONLY + +#include + +namespace compression { + +class lz4_decompression_buffers { +public: + static constexpr auto bufsize{4_MiB + 128_KiB}; + static constexpr auto min_threshold{128_KiB + 1}; + + explicit lz4_decompression_buffers( + size_t buffer_size, size_t min_alloc_threshold, bool disabled = false); + + // LZ4 decompression requires two buffers during a single decompression + // operation. This struct carries the buffers and associated book-keeping + // state of allocation. + struct alloc_ctx { + // A typical transition cycle for this set of buffers is: + // no_buffers_allocated -> input_buffer_allocated -> + // output_buffer_allocated -> both_buffers_allocated + // During deallocation/free the states are: both_buffers_allocated -> + // output_buffer_allocated -> no_buffers_allocated + enum class allocation_state : uint8_t { + // No buffers have been allocated to the LZ4 decompression routine. + // The buffers are effectively not in use. + no_buffers_allocated, + // The input buffer has been allocated out to LZ4 decompression + // routine. + input_buffer_allocated, + // The output buffer has also been allocated. Note that output + // buffer will never be allocated alone. + output_buffer_allocated, + // Both buffers are allocated to decompression routine. + both_buffers_allocated, + }; + + std::unique_ptr input_buffer; + std::unique_ptr output_buffer; + allocation_state state; + + // Checks if the address belongs to one of the two managed buffers. This + // address check is used when freeing an address. If the address is + // not managed by this context, then we fall back to `free()`. + [[nodiscard]] bool is_managed_address(const void* const address) const; + }; + + // Returns a reference to allocated buffer pair. The buffers must have been + // reserved before this call. + [[nodiscard]] alloc_ctx& buffers(); + + // Returns the minimum allocation threshold, allocation requests below this + // size are passed through to `malloc()`. + [[nodiscard]] size_t min_alloc_threshold() const; + + // Returns a struct usable by LZ4 memory allocation API. The struct holds a + // pointer to this object as its state field. + [[nodiscard]] LZ4F_CustomMem custom_mem_alloc(); + + struct stats { + size_t allocs{0}; + size_t deallocs{0}; + size_t pass_through_allocs{0}; + size_t pass_through_deallocs{0}; + bool operator==(const stats&) const = default; + }; + + void allocated() { _allocation_stats.allocs += 1; } + + void deallocated() { _allocation_stats.deallocs += 1; } + + void pass_through_allocated() { + _allocation_stats.pass_through_allocs += 1; + } + + void pass_through_deallocated() { + _allocation_stats.pass_through_deallocs += 1; + } + + stats allocation_stats() const { return _allocation_stats; } + + void reset_stats() { _allocation_stats = {}; } + + [[nodiscard]] size_t buffer_size() const { return _buffer_size; } + +private: + size_t _buffer_size; + size_t _min_alloc_threshold; + bool _disabled{false}; + + alloc_ctx _buffers; + stats _allocation_stats; +}; + +std::ostream& operator<<( + std::ostream&, lz4_decompression_buffers::alloc_ctx::allocation_state); + +// Initializes the buffer instance. If preallocation is disabled the instance +// will pass through all calls to malloc and free. Two buffers of size +// buffer_size are allocated. Calls below the min_alloc_threshold are passed +// through to malloc. +void init_lz4_decompression_buffers( + size_t buffer_size, + size_t min_alloc_threshold, + bool prealloc_disabled = false); + +// Resets the buffer instance, for use in tests. +void reset_lz4_decompression_buffers(); + +// Returns the static shard specific preallocated buffer instance. If the +// instance is not created yet it will be initialized first. +lz4_decompression_buffers& lz4_decompression_buffers_instance(); + +} // namespace compression + +extern "C" { +// Allocates buffers for decompression out of static pool. Accepts +// `lz4_decompression_buffers` as the state pointer. The buffers must first have +// been reserved for use via `lz4_decompression_buffers::reserve_buffers`. May +// also be called for objects which will not be allocated out of the static +// pool, in which case it falls back to `malloc()`. +void* alloc_lz4_obj(void* state, size_t size); + +// Manages updating state for the buffers used for decompression. This function +// may also be called for objects not allocated out of the static pool, in which +// case it falls back to using `free()`. For managed buffers only the state +// flags are updated. +void free_lz4_obj(void* state, void* address); +} diff --git a/src/v/compression/tests/CMakeLists.txt b/src/v/compression/tests/CMakeLists.txt index 15c2da741e353..18c8f7d77fd01 100644 --- a/src/v/compression/tests/CMakeLists.txt +++ b/src/v/compression/tests/CMakeLists.txt @@ -13,3 +13,13 @@ rp_test( LABELS compression ARGS "-- -c 1" ) + +rp_test( + UNIT_TEST + GTEST + BINARY_NAME lz4_buf_tests + SOURCES lz4_buf_tests.cc + LIBRARIES v::compression v::gtest_main v::rprandom + LABELS compression + ARGS "-- -c 1" +) diff --git a/src/v/compression/tests/lz4_buf_tests.cc b/src/v/compression/tests/lz4_buf_tests.cc new file mode 100644 index 0000000000000..7cfbc4af69924 --- /dev/null +++ b/src/v/compression/tests/lz4_buf_tests.cc @@ -0,0 +1,151 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "compression/internal/lz4_frame_compressor.h" +#include "compression/lz4_decompression_buffers.h" +#include "random/generators.h" +#include "units.h" + +#include + +#include + +using enum compression::lz4_decompression_buffers::alloc_ctx::allocation_state; + +TEST(AllocateBuffers, StateTransitions) { + auto b = compression::lz4_decompression_buffers{4_MiB, 128_KiB + 1}; + const auto& buffers = b.buffers(); + EXPECT_EQ(buffers.state, no_buffers_allocated); + auto allocator = b.custom_mem_alloc(); + + auto* input = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold()); + EXPECT_EQ(buffers.state, input_buffer_allocated); + EXPECT_EQ(input, buffers.input_buffer.get()); + + auto* output = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold()); + EXPECT_EQ(buffers.state, both_buffers_allocated); + EXPECT_EQ(output, buffers.output_buffer.get()); + + allocator.customFree(allocator.opaqueState, input); + EXPECT_EQ(buffers.state, output_buffer_allocated); + + allocator.customFree(allocator.opaqueState, output); + EXPECT_EQ(buffers.state, no_buffers_allocated); +} + +TEST(FallbackForSmallAllocs, CustomAllocator) { + auto b = compression::lz4_decompression_buffers{4_MiB, 128_KiB + 1}; + auto allocator = b.custom_mem_alloc(); + auto* allocated = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold() - 1); + EXPECT_NE(allocated, nullptr); + allocator.customFree(allocator.opaqueState, allocated); +} + +TEST(MixedAllocations, CustomAllocator) { + auto b = compression::lz4_decompression_buffers{4_MiB, 128_KiB + 1}; + const auto& buffers = b.buffers(); + auto allocator = b.custom_mem_alloc(); + + auto* input = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold()); + EXPECT_EQ(input, buffers.input_buffer.get()); + EXPECT_EQ(buffers.state, input_buffer_allocated); + + auto* random_alloc = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold() - 1); + EXPECT_FALSE(buffers.is_managed_address(random_alloc)); + EXPECT_EQ(buffers.state, input_buffer_allocated); + + auto* output = allocator.customAlloc( + allocator.opaqueState, b.min_alloc_threshold()); + EXPECT_EQ(output, buffers.output_buffer.get()); + EXPECT_EQ(buffers.state, both_buffers_allocated); + + allocator.customFree(allocator.opaqueState, input); + EXPECT_EQ(buffers.state, output_buffer_allocated); + + allocator.customFree(allocator.opaqueState, random_alloc); + EXPECT_EQ(buffers.state, output_buffer_allocated); + + allocator.customFree(allocator.opaqueState, output); + EXPECT_EQ(buffers.state, no_buffers_allocated); +} + +TEST(MaxBufSizeDeathTest, CustomAllocator) { + auto b = compression::lz4_decompression_buffers{4_MiB, 128_KiB + 1}; + auto allocator = b.custom_mem_alloc(); + ASSERT_DEATH( + { allocator.customAlloc(allocator.opaqueState, b.buffer_size() + 1); }, + "Request to allocate 4194305 bytes which is more than max buffer size " + "available: 4194304 bytes"); +} + +class StaticInstanceTest : public ::testing::Test { +public: + void SetUp() override { compression::reset_lz4_decompression_buffers(); } + void TearDown() override { + compression::lz4_decompression_buffers_instance().reset_stats(); + } +}; + +void test_decompression_calls( + compression::lz4_decompression_buffers::stats expected, + bool disable_prealloc = false, + std::optional blocksize = std::nullopt) { + if (disable_prealloc) { + compression::init_lz4_decompression_buffers( + 4_MiB, 128_KiB + 1, disable_prealloc); + } + + const auto data = random_generators::gen_alphanum_string(512); + + iobuf input; + input.append(data.data(), data.size()); + + using compression::internal::lz4_frame_compressor; + auto& instance = compression::lz4_decompression_buffers_instance(); + auto compressed = blocksize.has_value() + ? lz4_frame_compressor::compress_with_block_size( + input, blocksize.value()) + : lz4_frame_compressor::compress(input); + auto uncompressed = lz4_frame_compressor::uncompress(compressed); + EXPECT_EQ(instance.allocation_stats(), expected); +} + +TEST_F(StaticInstanceTest, DecompressLargeBlocks) { + test_decompression_calls( + {.allocs = 2, + .deallocs = 2, + .pass_through_allocs = 1, + .pass_through_deallocs = 3}, + false, + LZ4F_max4MB); +} + +TEST_F(StaticInstanceTest, DecompressPassThroughBlocks) { + test_decompression_calls( + {.allocs = 0, + .deallocs = 0, + .pass_through_allocs = 3, + .pass_through_deallocs = 5}); +} + +TEST_F(StaticInstanceTest, CustomAllocDisabled) { + test_decompression_calls( + {.allocs = 0, + .deallocs = 0, + .pass_through_allocs = 0, + .pass_through_deallocs = 0}, + true); +} diff --git a/src/v/config/configuration.cc b/src/v/config/configuration.cc index ed7eaf47778fc..7e1f480fcc52a 100644 --- a/src/v/config/configuration.cc +++ b/src/v/config/configuration.cc @@ -744,7 +744,7 @@ configuration::configuration() "transactional_id_expiration_ms", "Producer ids are expired once this time has elapsed after the last " "write with the given producer id.", - {.visibility = visibility::user}, + {.needs_restart = needs_restart::no, .visibility = visibility::user}, 10080min) , max_concurrent_producer_ids( *this, @@ -1553,7 +1553,7 @@ configuration::configuration() *this, "cloud_storage_enabled", "Enable archival storage", - {.visibility = visibility::user}, + {.needs_restart = needs_restart::yes, .visibility = visibility::user}, false) , cloud_storage_enable_remote_read( *this, @@ -2272,6 +2272,18 @@ configuration::configuration() // Enough for a >1TiB cache of 16MiB objects. Decrease this in case // of issues with trim performance. 100000) + , cloud_storage_cache_trim_carryover_bytes( + *this, + "cloud_storage_cache_trim_carryover_bytes", + "The cache performs a recursive directory inspection during the cache " + "trim. The information obtained during the inspection can be carried " + "over to the next trim operation. This parameter sets a limit on the " + "memory occupied by objects that can be carried over from one trim to " + "next, and allows cache to quickly unblock readers before starting the " + "directory inspection.", + {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, + // This roughly translates to around 1000 carryover file names + 256_KiB) , cloud_storage_cache_check_interval_ms( *this, "cloud_storage_cache_check_interval", @@ -2281,6 +2293,16 @@ configuration::configuration() "elapsed", {.visibility = visibility::tunable}, 5s) + , cloud_storage_cache_trim_walk_concurrency( + *this, + "cloud_storage_cache_trim_walk_concurrency", + "The maximum number of concurrent tasks launched for directory walk " + "during cache trimming. A higher number allows cache trimming to run " + "faster but can cause latency spikes due to increased pressure on I/O " + "subsystem and syscall threads.", + {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, + 1, + {.min = 1, .max = 1000}) , cloud_storage_max_segment_readers_per_shard( *this, "cloud_storage_max_segment_readers_per_shard", @@ -2374,6 +2396,24 @@ configuration::configuration() "Number of chunks to prefetch ahead of every downloaded chunk", {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, 0) + , cloud_storage_cache_trim_threshold_percent_size( + *this, + "cloud_storage_cache_trim_threshold_percent_size", + "Trim is triggered when the cache reaches this percent of the maximum " + "cache size. If this is unset, the default behavior" + "is to start trim when the cache is about 100\% full.", + {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, + std::nullopt, + {.min = 1.0, .max = 100.0}) + , cloud_storage_cache_trim_threshold_percent_objects( + *this, + "cloud_storage_cache_trim_threshold_percent_objects", + "Trim is triggered when the cache reaches this percent of the maximum " + "object count. If this is unset, the default behavior" + "is to start trim when the cache is about 100\% full.", + {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, + std::nullopt, + {.min = 1.0, .max = 100.0}) , superusers( *this, "superusers", @@ -2446,6 +2486,12 @@ configuration::configuration() "Size of the zstd decompression workspace", {.visibility = visibility::tunable}, 8_MiB) + , lz4_decompress_reusable_buffers_disabled( + *this, + "lz4_decompress_reusable_buffers_disabled", + "Disable reusable preallocated buffers for LZ4 decompression", + {.needs_restart = needs_restart::yes, .visibility = visibility::tunable}, + false) , full_raft_configuration_recovery_pattern( *this, "full_raft_configuration_recovery_pattern", @@ -2625,6 +2671,13 @@ configuration::configuration() "the data directory. Redpanda will refuse to start if it is not found.", {.needs_restart = needs_restart::no, .visibility = visibility::user}, false) + , alive_timeout_ms( + *this, + "alive_timeout_ms", + "Time from the last node status heartbeat after which a node will be " + "considered offline and not alive", + {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, + 5s) , memory_abort_on_alloc_failure( *this, "memory_abort_on_alloc_failure", @@ -2816,7 +2869,7 @@ configuration::configuration() "milliseconds. Value of 0 disables the balancer and makes all the " "throughput quotas immutable.", {.needs_restart = needs_restart::no, .visibility = visibility::user}, - 750ms, + 0ms, {.min = 0ms}) , kafka_quota_balancer_min_shard_throughput_ratio( *this, @@ -2904,6 +2957,12 @@ configuration::configuration() "Per-shard capacity of the cache for validating schema IDs.", {.needs_restart = needs_restart::no, .visibility = visibility::tunable}, 128) + , schema_registry_normalize_on_startup( + *this, + "schema_registry_normalize_on_startup", + "Normalize schemas as they are read from the topic on startup.", + {.needs_restart = needs_restart::yes, .visibility = visibility::user}, + false) , kafka_memory_share_for_fetch( *this, "kafka_memory_share_for_fetch", @@ -2982,7 +3041,15 @@ configuration::configuration() "are allowed.", {.needs_restart = needs_restart::no, .visibility = visibility::user}, {"BASIC"}, - validate_http_authn_mechanisms) {} + validate_http_authn_mechanisms) + , unsafe_enable_consumer_offsets_delete_retention( + *this, + "unsafe_enable_consumer_offsets_delete_retention", + "Enables delete retention of consumer offsets topic. This is an " + "internal-only configuration and should be enabled only after consulting " + "with Redpanda Support or engineers.", + {.needs_restart = needs_restart::yes, .visibility = visibility::user}, + false) {} configuration::error_map_t configuration::load(const YAML::Node& root_node) { if (!root_node["redpanda"]) { diff --git a/src/v/config/configuration.h b/src/v/config/configuration.h index 0cc0f2a2b6ca5..5db245c8cbb42 100644 --- a/src/v/config/configuration.h +++ b/src/v/config/configuration.h @@ -415,7 +415,9 @@ struct configuration final : public config_store { bounded_property, numeric_bounds> cloud_storage_cache_size_percent; property cloud_storage_cache_max_objects; + property cloud_storage_cache_trim_carryover_bytes; property cloud_storage_cache_check_interval_ms; + bounded_property cloud_storage_cache_trim_walk_concurrency; property> cloud_storage_max_segment_readers_per_shard; property> @@ -431,6 +433,10 @@ struct configuration final : public config_store { enum_property cloud_storage_chunk_eviction_strategy; property cloud_storage_chunk_prefetch; + bounded_property, numeric_bounds> + cloud_storage_cache_trim_threshold_percent_size; + bounded_property, numeric_bounds> + cloud_storage_cache_trim_threshold_percent_objects; one_or_many_property superusers; @@ -448,6 +454,7 @@ struct configuration final : public config_store { property kafka_qdc_max_depth; property kafka_qdc_depth_update_ms; property zstd_decompress_workspace_bytes; + property lz4_decompress_reusable_buffers_disabled; one_or_many_property full_raft_configuration_recovery_pattern; property enable_auto_rebalance_on_node_add; @@ -479,6 +486,7 @@ struct configuration final : public config_store { bounded_property storage_space_alert_free_threshold_bytes; bounded_property storage_min_free_bytes; property storage_strict_data_init; + property alive_timeout_ms; // memory related settings property memory_abort_on_alloc_failure; @@ -542,6 +550,8 @@ struct configuration final : public config_store { enable_schema_id_validation; config::property kafka_schema_id_validation_cache_capacity; + property schema_registry_normalize_on_startup; + bounded_property kafka_memory_share_for_fetch; property kafka_memory_batch_size_estimate_for_fetch; // debug controls @@ -558,6 +568,9 @@ struct configuration final : public config_store { // HTTP Authentication property> http_authentication; + // temporary - to be deprecated + property unsafe_enable_consumer_offsets_delete_retention; + configuration(); error_map_t load(const YAML::Node& root_node); diff --git a/src/v/config/property.h b/src/v/config/property.h index 356acd4de1d9f..9a17edceacdc1 100644 --- a/src/v/config/property.h +++ b/src/v/config/property.h @@ -458,6 +458,8 @@ class binding : public binding_base { friend class mock_property; template friend inline binding mock_binding(U&&); + template + friend inline binding mock_binding(U const&); }; /** @@ -474,6 +476,11 @@ inline binding mock_binding(T&& value) { return binding(std::forward(value)); } +template +inline binding mock_binding(T const& value) { + return binding(T(value)); +} + /** * A conversion property binding contains the result of application of * a conversion function to property value. The result is update in-place diff --git a/src/v/features/CMakeLists.txt b/src/v/features/CMakeLists.txt index 449e78492abf9..b1653972f3015 100644 --- a/src/v/features/CMakeLists.txt +++ b/src/v/features/CMakeLists.txt @@ -10,6 +10,8 @@ v_cc_library( v::model v::config v::version + v::security + v::metrics ) add_dependencies(v_features kafka_codegen_headers) diff --git a/src/v/features/feature_table.cc b/src/v/features/feature_table.cc index 97599d66adc75..3d61eb974cc5c 100644 --- a/src/v/features/feature_table.cc +++ b/src/v/features/feature_table.cc @@ -14,12 +14,18 @@ #include "cluster/types.h" #include "config/node_config.h" #include "features/logger.h" +#include "metrics/metrics.h" +#include "prometheus/prometheus_sanitize.h" #include "version.h" #include +#include +#include + // The feature table is closely related to cluster and uses many types from it using namespace cluster; +using namespace std::chrono_literals; namespace features { @@ -189,6 +195,60 @@ static std::array test_extra_schema{ feature_spec::prepare_policy::always}, }; +class feature_table::probe { +public: + explicit probe(const feature_table& parent) + : _parent(parent) {} + + probe(const probe&) = delete; + probe& operator=(const probe&) = delete; + probe(probe&&) = delete; + probe& operator=(probe&&) = delete; + ~probe() noexcept = default; + + void setup_metrics() { + if (ss::this_shard_id() != 0) { + return; + } + + if (!config::shard_local_cfg().disable_metrics()) { + setup_metrics_for(_metrics); + } + + if (!config::shard_local_cfg().disable_public_metrics()) { + setup_metrics_for(_public_metrics); + } + } + + void setup_metrics_for(metrics::metric_groups_base& metrics) { + namespace sm = ss::metrics; + + static_assert( + !std::is_move_constructible_v + && !std::is_move_assignable_v + && !std::is_copy_constructible_v + && !std::is_copy_assignable_v, + "The probe captures a reference to this"); + + metrics.add_group( + prometheus_sanitize::metrics_name("cluster:features"), + { + sm::make_gauge( + "enterprise_license_expiry_sec", + [&ft = _parent]() { + return calculate_expiry_metric(ft.get_license()); + }, + sm::description("Number of seconds remaining until the " + "Enterprise license expires")) + .aggregate({sm::shard_label}), + }); + } + + const feature_table& _parent; + metrics::internal_metric_groups _metrics; + metrics::public_metric_groups _public_metrics; +}; + feature_table::feature_table() { // Intentionally undocumented environment variable, only for use // in integration tests. @@ -221,9 +281,15 @@ feature_table::feature_table() { } } } + + _probe = std::make_unique(*this); + _probe->setup_metrics(); } +feature_table::~feature_table() noexcept = default; + ss::future<> feature_table::stop() { + _probe.reset(); _as.request_abort(); // Don't trust callers to have fired their abort source in the right @@ -686,6 +752,18 @@ void feature_table::assert_compatible_version(bool override) { } } +long long feature_table::calculate_expiry_metric( + const std::optional& license, + security::license::clock::time_point now) { + if (!license) { + return -1; + } + + auto rem = license->expiration() - now; + auto rem_capped = std::max(rem.zero(), rem); + return rem_capped / 1s; +} + } // namespace features namespace cluster { diff --git a/src/v/features/feature_table.h b/src/v/features/feature_table.h index 6ebc791bd001b..e0818bd8c849a 100644 --- a/src/v/features/feature_table.h +++ b/src/v/features/feature_table.h @@ -18,6 +18,7 @@ #include "utils/waiter_queue.h" #include +#include #include #include @@ -444,6 +445,11 @@ class feature_table { static cluster::cluster_version get_earliest_logical_version(); feature_table(); + feature_table(const feature_table&) = delete; + feature_table& operator=(const feature_table&) = delete; + feature_table(feature_table&&) = delete; + feature_table& operator=(feature_table&&) = delete; + ~feature_table() noexcept; feature_state& get_state(feature f_id); const feature_state& get_state(feature f_id) const { @@ -545,7 +551,15 @@ class feature_table { // Assert out on startup if we appear to have upgraded too far void assert_compatible_version(bool); + // Visible for testing + static long long calculate_expiry_metric( + const std::optional& license, + security::license::clock::time_point now + = security::license::clock::now()); + private: + class probe; + // Only for use by our friends feature backend & manager void set_active_version( cluster::cluster_version, @@ -601,6 +615,7 @@ class feature_table { ss::gate _gate; ss::abort_source _as; + std::unique_ptr _probe; }; } // namespace features diff --git a/src/v/features/tests/feature_table_test.cc b/src/v/features/tests/feature_table_test.cc index 225f0e1dc596a..d90e97314226f 100644 --- a/src/v/features/tests/feature_table_test.cc +++ b/src/v/features/tests/feature_table_test.cc @@ -11,6 +11,7 @@ #include "features/feature_table.h" #include "features/feature_table_snapshot.h" +#include "security/license.h" #include "test_utils/fixture.h" #include "vlog.h" @@ -334,3 +335,25 @@ FIXTURE_TEST(feature_table_old_snapshot, feature_table_fixture) { ft.get_state(feature::test_alpha).get_state() == feature_state::state::active); } + +SEASTAR_THREAD_TEST_CASE(feature_table_probe_expiry_metric_test) { + using ft = features::feature_table; + const char* sample_valid_license = std::getenv("REDPANDA_SAMPLE_LICENSE"); + if (sample_valid_license == nullptr) { + const char* is_on_ci = std::getenv("CI"); + BOOST_TEST_REQUIRE( + !is_on_ci, + "Expecting the REDPANDA_SAMPLE_LICENSE env var in the CI " + "enviornment"); + return; + } + const ss::sstring license_str{sample_valid_license}; + const auto license = security::make_license(license_str); + + auto expiry = security::license::clock::time_point{4813252273s}; + + BOOST_CHECK_EQUAL(ft::calculate_expiry_metric(license, expiry - 1s), 1); + BOOST_CHECK_EQUAL(ft::calculate_expiry_metric(license, expiry), 0); + BOOST_CHECK_EQUAL(ft::calculate_expiry_metric(license, expiry + 1s), 0); + BOOST_CHECK_EQUAL(ft::calculate_expiry_metric(std::nullopt), -1); +} diff --git a/src/v/http/client.cc b/src/v/http/client.cc index b8b442167c844..eeb4ea5716207 100644 --- a/src/v/http/client.cc +++ b/src/v/http/client.cc @@ -14,6 +14,7 @@ #include "bytes/scattered_message.h" #include "config/base_property.h" #include "http/logger.h" +#include "likely.h" #include "ssx/sformat.h" #include "vlog.h" @@ -82,6 +83,11 @@ void client::check() const { ss::future client::make_request( client::request_header&& header, ss::lowres_clock::duration timeout) { + if (unlikely(_stopped)) { + std::runtime_error err("client is stopped"); + return ss::make_exception_future(err); + } + auto verb = header.method(); auto target = header.target(); ss::sstring target_str(target.data(), target.size()); @@ -122,19 +128,24 @@ ss::future client::make_request( return ss::make_ready_future( std::make_tuple(req, res)); }) - .handle_exception_type([this](ss::tls::verification_error err) { - return stop().then([err = std::move(err)] { - return ss::make_exception_future(err); - }); + .handle_exception_type([this, ctxlog](ss::tls::verification_error err) { + vlog(ctxlog.warn, "make_request tls verification error {}", err); + shutdown(); + return ss::make_exception_future(err); }); } ss::future client::get_connected( ss::lowres_clock::duration timeout, prefix_logger ctxlog) { + if (unlikely(_stopped)) { + throw std::runtime_error("client is stopped"); + } vlog( ctxlog.debug, - "about to start connecting, {}, is-closed {}", + "about to start connecting, is_valid: {}, connect gate closed: {}, " + "dispatch gate closed: {}", is_valid(), + _connect_gate.is_closed(), _dispatch_gate.is_closed()); auto current = ss::lowres_clock::now(); const auto deadline = current + timeout; @@ -172,6 +183,13 @@ ss::future client::get_connected( } ss::future<> client::stop() { + if (_stopped) { + // Prevent double call to stop() as constructs such as with_client() + // will unconditionally call stop(), while exception handlers in this + // file may also call stop() + co_return; + } + _stopped = true; co_await _connect_gate.close(); // Can safely stop base_transport co_return co_await base_transport::stop(); @@ -252,8 +270,6 @@ iobuf_to_constbufseq(const iobuf& iobuf) { return seq; } -ss::future<> client::response_stream::shutdown() { return _client->stop(); } - /// Return failed future if ec is set, otherwise return future in ready state static ss::future fail_on_error(prefix_logger& ctxlog, const boost::beast::error_code& ec) { @@ -362,8 +378,9 @@ ss::future client::response_stream::recv_some() { }) .handle_exception_type([this](const ss::tls::verification_error& err) { _client->_probe->register_transport_error(); - return _client->stop().then( - [err] { return ss::make_exception_future(err); }); + vlog(_ctxlog.warn, "receive tls verification error {}", err); + _client->shutdown(); + return ss::make_exception_future(err); }) .handle_exception_type([this](const boost::system::system_error& ec) { vlog(_ctxlog.warn, "receive error {}", ec); @@ -459,8 +476,9 @@ ss::future<> client::request_stream::send_some(iobuf&& seq) { }) .handle_exception_type( [this](const ss::tls::verification_error& err) { - return _client->stop().then( - [err] { return ss::make_exception_future<>(err); }); + vlog(_ctxlog.warn, "send tls verification error {}", err); + _client->shutdown(); + return ss::make_exception_future<>(err); }) .handle_exception_type([this](const std::system_error& ec) { // Things like EPIPE, ERESET. This happens routinely diff --git a/src/v/http/client.h b/src/v/http/client.h index d5595f5e5334a..f74f095595f2f 100644 --- a/src/v/http/client.h +++ b/src/v/http/client.h @@ -79,6 +79,7 @@ class client : protected net::base_transport { ss::shared_ptr probe, ss::lowres_clock::duration max_idle_time = {}); + /// Stop must be called before destroying the client object. ss::future<> stop(); using net::base_transport::shutdown; using net::base_transport::wait_input_shutdown; @@ -103,9 +104,6 @@ class client : protected net::base_transport { response_stream& operator=(response_stream const&) = delete; response_stream operator=(response_stream&&) = delete; - /// \brief Shutdown connection gracefully - ss::future<> shutdown(); - /// Return true if the whole http payload is received and parsed bool is_done() const; @@ -227,6 +225,7 @@ class client : protected net::base_transport { /// Throw exception if _as is aborted void check() const; + bool _stopped{false}; ss::gate _connect_gate; const ss::abort_source* _as; ss::shared_ptr _probe; diff --git a/src/v/http/tests/registered_urls.h b/src/v/http/tests/registered_urls.h index 77eaee3b8c04e..06b3ba9b37c56 100644 --- a/src/v/http/tests/registered_urls.h +++ b/src/v/http/tests/registered_urls.h @@ -46,7 +46,6 @@ struct request_info { */ ss::sstring q_list_type; ss::sstring q_prefix; - ss::sstring h_prefix; bool has_q_delete; explicit request_info(const ss::http::request& req) @@ -56,7 +55,6 @@ struct request_info { , content_length(req.content_length) { q_list_type = req.get_query_param("list-type"); q_prefix = req.get_query_param("prefix"); - h_prefix = req.get_header("prefix"); has_q_delete = req.query_parameters.contains("delete"); } diff --git a/src/v/json/chunked_buffer.h b/src/v/json/chunked_buffer.h new file mode 100644 index 0000000000000..3c9154594f0d9 --- /dev/null +++ b/src/v/json/chunked_buffer.h @@ -0,0 +1,83 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#pragma once + +#include "bytes/iobuf.h" +#include "json/encodings.h" + +namespace json { + +template< + typename OutputStream, + typename SourceEncoding, + typename TargetEncoding, + unsigned writeFlags> +class generic_iobuf_writer; + +namespace impl { + +/** + * \brief An in-memory output stream with non-contiguous memory allocation. + */ +template +struct generic_chunked_buffer { + using Ch = Encoding::Ch; + + /** + * \defgroup Implement rapidjson::Stream + */ + /**@{*/ + + void Put(Ch c) { _impl.append(&c, sizeof(Ch)); } + void Flush() {} + + //! Get the size of string in bytes in the string buffer. + size_t GetSize() const { return _impl.size_bytes(); } + + //! Get the length of string in Ch in the string buffer. + size_t GetLength() const { return _impl.size_bytes() / sizeof(Ch); } + + void Reserve(size_t s) { _impl.reserve_memory(s); } + + void Clear() { _impl.clear(); } + + /**@}*/ + + /** + * Append a fragment to this chunked_buffer. This takes ownership of the + * fragment and is a zero-copy operation. + */ + void append(std::unique_ptr frag) { + _impl.append(std::move(frag)); + } + + /** + * Return the underlying iobuf, this is destructive and zero-copy. + */ + iobuf as_iobuf() && { return std::move(_impl); } + +private: + template< + typename OutputStream, + typename SourceEncoding, + typename TargetEncoding, + unsigned writeFlags> + friend class json::generic_iobuf_writer; + iobuf _impl; +}; + +} // namespace impl + +template +using generic_chunked_buffer = impl::generic_chunked_buffer; + +using chunked_buffer = generic_chunked_buffer>; + +} // namespace json diff --git a/src/v/json/chunked_input_stream.h b/src/v/json/chunked_input_stream.h new file mode 100644 index 0000000000000..d405b2ba438fd --- /dev/null +++ b/src/v/json/chunked_input_stream.h @@ -0,0 +1,64 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#pragma once + +#include "bytes/streambuf.h" +#include "json/encodings.h" +#include "json/istreamwrapper.h" + +namespace json { + +namespace impl { + +/** + * \brief An in-memory input stream with non-contiguous memory allocation. + */ +template> +class chunked_input_stream { +public: + using Ch = Encoding::Ch; + + explicit chunked_input_stream(iobuf&& buf) + : _buf(std::move(buf)) + , _is(_buf) + , _sis{&_is} + , _isw(_sis) {} + + /** + * \defgroup Implement rapidjson::Stream + */ + /**@{*/ + + Ch Peek() const { return _isw.Peek(); } + Ch Peek4() const { return _isw.Peek4(); } + Ch Take() { return _isw.Take(); } + size_t Tell() const { return _isw.Tell(); } + void Put(Ch ch) { return _isw.Put(ch); } + Ch* PutBegin() { return _isw.PutBegin(); } + size_t PutEnd(Ch* ch) { return _isw.PutEnd(ch); } + void Flush() { return _isw.Flush(); } + + /**@}*/ + +private: + iobuf _buf; + iobuf_istreambuf _is; + std::istream _sis; + ::json::IStreamWrapper _isw; +}; + +} // namespace impl + +template +using generic_chunked_input_stream = impl::chunked_input_stream; + +using chunked_input_stream = generic_chunked_input_stream>; + +} // namespace json diff --git a/src/v/json/iobuf_writer.h b/src/v/json/iobuf_writer.h new file mode 100644 index 0000000000000..fbcbc503ddef3 --- /dev/null +++ b/src/v/json/iobuf_writer.h @@ -0,0 +1,117 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#pragma once + +#include "bytes/iobuf.h" +#include "bytes/iobuf_parser.h" +#include "json/chunked_buffer.h" +#include "json/writer.h" + +#include + +namespace json { + +///\brief a json::Writer that can accept an iobuf as a String payload. +template< + typename OutputStream, + typename SourceEncoding = json::UTF8<>, + typename TargetEncoding = json::UTF8<>, + unsigned writeFlags = rapidjson::kWriteDefaultFlags> +class generic_iobuf_writer + : public Writer { + using Base + = Writer; + +public: + explicit generic_iobuf_writer(OutputStream& os) + : Base{os} {} + + using Base::String; + bool String(const iobuf& buf) { + constexpr bool buffer_is_chunked + = std::same_as; + if constexpr (buffer_is_chunked) { + return write_chunked_string(buf); + } else { + iobuf_const_parser p{buf}; + auto str = p.read_string(p.bytes_left()); + return this->String(str.data(), str.size(), true); + } + } + +private: + bool write_chunked_string(const iobuf& buf) { + const auto last_frag = [this]() { + return std::prev(this->os_->_impl.end()); + }; + using Ch = Base::Ch; + this->Prefix(rapidjson::kStringType); + const auto beg = buf.begin(); + const auto end = buf.end(); + const auto last = std::prev(end); + Ch stashed{}; + Ch* stash_pos{}; + // Base::WriteString is used to JSON encode the string, and requires a + // contiguous range (pointer, len), so we pass it each fragment. + // + // Unfortunately it also encloses the encoded fragment with double + // quotes: + // R"("A string made of ""fragments will need ""fixing")" + // + // This algorithm efficiently removes the extra quotes without + // additional copying: + // For each encoded fragment that is written (except the last one): + // 1. Trim the suffix quote + // 2. Stash the final character, and where it is to be written + // 3. Drop the final character + // For each encoded fragment that is written (except the first one): + // 4. Restore the stashed character over the prefix-quote + for (auto i = beg; i != end; ++i) { + if (!Base::WriteString(i->get(), i->size())) { + return false; + } + if (i != beg) { + // 4. Restore the stashed character over the prefix-quote + *stash_pos = stashed; + } + if (i != last) { + // 1. Trim the suffix quote + this->os_->_impl.trim_back(1); + + // 2. Stash the final character, ... + auto last = last_frag(); + stashed = *std::prev(last->get_current()); + // 3. Drop the final character + this->os_->_impl.trim_back(1); + + // Ensure a stable address to restore the stashed character + if (last != last_frag()) { + this->os_->_impl.reserve_memory(1); + } + // 2. ...and where it is to be written. + stash_pos = last_frag()->get_current(); + } + } + return this->EndValue(true); + } +}; + +template< + typename OutputStream, + typename SourceEncoding = json::UTF8<>, + typename TargetEncoding = json::UTF8<>, + unsigned writeFlags = rapidjson::kWriteDefaultFlags> +using iobuf_writer = generic_iobuf_writer< + OutputStream, + SourceEncoding, + TargetEncoding, + writeFlags>; + +} // namespace json diff --git a/src/v/json/json.cc b/src/v/json/json.cc index 2af499cd844b1..69ee33de7920a 100644 --- a/src/v/json/json.cc +++ b/src/v/json/json.cc @@ -9,40 +9,59 @@ #include "json/json.h" +#include "json/chunked_buffer.h" +#include "json/chunked_input_stream.h" +#include "json/stringbuffer.h" + namespace json { -void rjson_serialize(json::Writer& w, short v) { w.Int(v); } +template +void rjson_serialize(json::Writer& w, short v) { + w.Int(v); +} -void rjson_serialize(json::Writer& w, bool v) { w.Bool(v); } +template +void rjson_serialize(json::Writer& w, bool v) { + w.Bool(v); +} -void rjson_serialize(json::Writer& w, long long v) { +template +void rjson_serialize(json::Writer& w, long long v) { w.Int64(v); } -void rjson_serialize(json::Writer& w, int v) { w.Int(v); } +template +void rjson_serialize(json::Writer& w, int v) { + w.Int(v); +} -void rjson_serialize(json::Writer& w, unsigned int v) { +template +void rjson_serialize(json::Writer& w, unsigned int v) { w.Uint(v); } -void rjson_serialize(json::Writer& w, long v) { +template +void rjson_serialize(json::Writer& w, long v) { w.Int64(v); } -void rjson_serialize(json::Writer& w, unsigned long v) { +template +void rjson_serialize(json::Writer& w, unsigned long v) { w.Uint64(v); } -void rjson_serialize(json::Writer& w, double v) { +template +void rjson_serialize(json::Writer& w, double v) { w.Double(v); } -void rjson_serialize(json::Writer& w, std::string_view v) { +template +void rjson_serialize(json::Writer& w, std::string_view v) { w.String(v.data(), v.size()); } -void rjson_serialize( - json::Writer& w, const ss::socket_address& v) { +template +void rjson_serialize(json::Writer& w, const ss::socket_address& v) { w.StartObject(); std::ostringstream a; @@ -68,8 +87,9 @@ void rjson_serialize( w.EndObject(); } +template void rjson_serialize( - json::Writer& w, const net::unresolved_address& v) { + json::Writer& w, const net::unresolved_address& v) { w.StartObject(); w.Key("address"); @@ -81,21 +101,128 @@ void rjson_serialize( w.EndObject(); } +template void rjson_serialize( - json::Writer& w, const std::chrono::milliseconds& v) { + json::Writer& w, const std::chrono::milliseconds& v) { uint64_t _tmp = v.count(); rjson_serialize(w, _tmp); } -void rjson_serialize( - json::Writer& w, const std::chrono::seconds& v) { +template +void rjson_serialize(json::Writer& w, const std::chrono::seconds& v) { uint64_t _tmp = v.count(); rjson_serialize(w, _tmp); } +template void rjson_serialize( - json::Writer& w, const std::filesystem::path& path) { + json::Writer& w, const std::filesystem::path& path) { rjson_serialize(w, std::string_view{path.native()}); } +ss::sstring minify(std::string_view json) { + json::Reader r; + json::StringStream in(json.data()); + json::StringBuffer out; + json::Writer w{out}; + r.Parse(in, w); + return ss::sstring(out.GetString(), out.GetSize()); +} + +iobuf minify(iobuf json) { + json::Reader r; + json::chunked_input_stream in(std::move(json)); + json::chunked_buffer out; + json::Writer w{out}; + r.Parse(in, w); + return std::move(out).as_iobuf(); +} + +ss::sstring prettify(std::string_view json) { + json::Reader r; + json::StringStream in(json.data()); + json::StringBuffer out; + json::PrettyWriter w{out}; + r.Parse(in, w); + return ss::sstring(out.GetString(), out.GetSize()); +} + +template void rjson_serialize( + json::Writer& w, short v); + +template void rjson_serialize( + json::Writer& w, bool v); + +template void rjson_serialize( + json::Writer& w, long long v); + +template void +rjson_serialize(json::Writer& w, int v); + +template void rjson_serialize( + json::Writer& w, unsigned int v); + +template void rjson_serialize( + json::Writer& w, long v); + +template void rjson_serialize( + json::Writer& w, unsigned long v); + +template void rjson_serialize( + json::Writer& w, double v); + +template void rjson_serialize( + json::Writer& w, std::string_view s); + +template void rjson_serialize( + json::Writer& w, const net::unresolved_address& v); + +template void rjson_serialize( + json::Writer& w, const std::chrono::milliseconds& v); + +template void rjson_serialize( + json::Writer& w, const std::chrono::seconds& v); + +template void rjson_serialize( + json::Writer& w, const std::filesystem::path& path); + +template void +rjson_serialize(json::Writer& w, short v); + +template void +rjson_serialize(json::Writer& w, bool v); + +template void +rjson_serialize(json::Writer& w, long long v); + +template void +rjson_serialize(json::Writer& w, int v); + +template void rjson_serialize( + json::Writer& w, unsigned int v); + +template void +rjson_serialize(json::Writer& w, long v); + +template void rjson_serialize( + json::Writer& w, unsigned long v); + +template void +rjson_serialize(json::Writer& w, double v); + +template void rjson_serialize( + json::Writer& w, std::string_view s); + +template void rjson_serialize( + json::Writer& w, const net::unresolved_address& v); + +template void rjson_serialize( + json::Writer& w, const std::chrono::milliseconds& v); + +template void rjson_serialize( + json::Writer& w, const std::chrono::seconds& v); + +template void rjson_serialize( + json::Writer& w, const std::filesystem::path& path); + } // namespace json diff --git a/src/v/json/json.h b/src/v/json/json.h index 0e73106870832..7b15d12d7dc34 100644 --- a/src/v/json/json.h +++ b/src/v/json/json.h @@ -11,6 +11,7 @@ #pragma once +#include "bytes/iobuf.h" #include "json/_include_first.h" #include "json/prettywriter.h" #include "json/reader.h" @@ -21,9 +22,7 @@ #include "utils/fragmented_vector.h" #include "utils/named_type.h" -#include -#include -#include +#include #include #include @@ -31,53 +30,62 @@ namespace json { -void rjson_serialize(json::Writer& w, short v); +template +void rjson_serialize(json::Writer& w, short v); -void rjson_serialize(json::Writer& w, bool v); +template +void rjson_serialize(json::Writer& w, bool v); -void rjson_serialize(json::Writer& w, long long v); +template +void rjson_serialize(json::Writer& w, long long v); -void rjson_serialize(json::Writer& w, int v); +template +void rjson_serialize(json::Writer& w, int v); -void rjson_serialize(json::Writer& w, unsigned int v); +template +void rjson_serialize(json::Writer& w, unsigned int v); -void rjson_serialize(json::Writer& w, long v); +template +void rjson_serialize(json::Writer& w, long v); -void rjson_serialize(json::Writer& w, unsigned long v); +template +void rjson_serialize(json::Writer& w, unsigned long v); -void rjson_serialize(json::Writer& w, double v); +template +void rjson_serialize(json::Writer& w, double v); -void rjson_serialize(json::Writer& w, std::string_view s); +template +void rjson_serialize(json::Writer& w, std::string_view s); -void rjson_serialize( - json::Writer& w, const ss::socket_address& v); +template +void rjson_serialize(json::Writer& w, const net::unresolved_address& v); +template void rjson_serialize( - json::Writer& w, const net::unresolved_address& v); + json::Writer& w, const std::chrono::milliseconds& v); -void rjson_serialize( - json::Writer& w, const std::chrono::milliseconds& v); +template +void rjson_serialize(json::Writer& w, const std::chrono::seconds& v); +template void rjson_serialize( - json::Writer& w, const std::chrono::seconds& v); + json::Writer& w, const std::filesystem::path& path); -void rjson_serialize( - json::Writer& w, const std::filesystem::path& path); - -template>> -void rjson_serialize(json::Writer& w, T v) { +template< + typename Buffer, + typename T, + typename = std::enable_if_t>> +void rjson_serialize(json::Writer& w, T v) { rjson_serialize(w, static_cast>(v)); } -template -void rjson_serialize( - json::Writer& w, const named_type& v) { +template +void rjson_serialize(json::Writer& w, const named_type& v) { rjson_serialize(w, v()); } -template -void rjson_serialize( - json::Writer& w, const std::optional& v) { +template +void rjson_serialize(json::Writer& w, const std::optional& v) { if (v) { rjson_serialize(w, *v); return; @@ -85,9 +93,8 @@ void rjson_serialize( w.Null(); } -template -void rjson_serialize( - json::Writer& w, const std::vector& v) { +template +void rjson_serialize(json::Writer& w, const std::vector& v) { w.StartArray(); for (const auto& e : v) { rjson_serialize(w, e); @@ -95,10 +102,9 @@ void rjson_serialize( w.EndArray(); } -template +template void rjson_serialize( - json::Writer& w, - const fragmented_vector& v) { + json::Writer& w, const ss::chunked_fifo& v) { w.StartArray(); for (const auto& e : v) { rjson_serialize(w, e); @@ -106,20 +112,9 @@ void rjson_serialize( w.EndArray(); } -template +template void rjson_serialize( - json::Writer& w, - const ss::chunked_fifo& v) { - w.StartArray(); - for (const auto& e : v) { - rjson_serialize(w, e); - } - w.EndArray(); -} - -template -void rjson_serialize( - json::Writer& w, + json::Writer& w, const std::unordered_map& v) { w.StartArray(); for (const auto& e : v) { @@ -128,9 +123,9 @@ void rjson_serialize( w.EndArray(); } -template +template void rjson_serialize( - json::Writer& w, const ss::circular_buffer& v) { + json::Writer& w, const ss::circular_buffer& v) { w.StartArray(); for (const auto& e : v) { rjson_serialize(w, e); @@ -138,22 +133,9 @@ void rjson_serialize( w.EndArray(); } -inline ss::sstring minify(std::string_view json) { - json::Reader r; - json::StringStream in(json.data()); - json::StringBuffer out; - json::Writer w{out}; - r.Parse(in, w); - return ss::sstring(out.GetString(), out.GetSize()); -} +ss::sstring minify(std::string_view json); +iobuf minify(iobuf json); -inline ss::sstring prettify(std::string_view json) { - json::Reader r; - json::StringStream in(json.data()); - json::StringBuffer out; - json::PrettyWriter w{out}; - r.Parse(in, w); - return ss::sstring(out.GetString(), out.GetSize()); -} +ss::sstring prettify(std::string_view json); } // namespace json diff --git a/src/v/json/tests/json_serialization_test.cc b/src/v/json/tests/json_serialization_test.cc index 92aabc82e399d..b47eb1919e327 100644 --- a/src/v/json/tests/json_serialization_test.cc +++ b/src/v/json/tests/json_serialization_test.cc @@ -7,7 +7,11 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0 +#include "bytes/iobuf_parser.h" +#include "json/chunked_buffer.h" +#include "json/chunked_input_stream.h" #include "json/document.h" +#include "json/iobuf_writer.h" #include "json/json.h" #include "json/stringbuffer.h" #include "json/writer.h" @@ -41,8 +45,8 @@ struct personne_t { } // namespace -void rjson_serialize( - json::Writer& w, const personne_t::nested& obj) { +template +void rjson_serialize(json::Writer& w, const personne_t::nested& obj) { w.StartObject(); w.Key("x"); @@ -57,11 +61,12 @@ void rjson_serialize( w.EndObject(); } -void rjson_serialize(json::Writer& w, const personne_t& p) { +template +void rjson_serialize(json::Writer& w, const personne_t& p) { w.StartObject(); w.Key("full_name"); - json::rjson_serialize(w, p.full_name); + json::rjson_serialize(w, std::string_view{p.full_name}); w.Key("nic"); json::rjson_serialize(w, p.nic); @@ -133,3 +138,56 @@ SEASTAR_THREAD_TEST_CASE(json_serialization_test) { BOOST_TEST(res_doc["obj"].IsObject()); } + +static constexpr std::string_view input_string{ + R"(The quick brown fox jumps over the lazy dog)"}; + +static constexpr auto make_chunked_str = []() { + constexpr auto half = input_string.size() / 2; + iobuf in; + in.append_fragments(iobuf::from(input_string.substr(0, half))); + in.append_fragments(iobuf::from(input_string.substr(half))); + BOOST_REQUIRE_EQUAL(std::distance(in.begin(), in.end()), 2); + return in; +}; + +static constexpr auto make_chunked_json = []() { + iobuf in; + in.append_fragments(iobuf::from("\"")); + in.append_fragments(make_chunked_str()); + in.append_fragments(iobuf::from("\"")); + BOOST_REQUIRE_EQUAL(std::distance(in.begin(), in.end()), 4); + return in; +}; + +SEASTAR_THREAD_TEST_CASE(json_chunked_input_stream_test) { + { + json::chunked_input_stream is{make_chunked_json()}; + json::Document doc; + doc.ParseStream(is); + BOOST_REQUIRE(!doc.HasParseError()); + + BOOST_REQUIRE(doc.IsString()); + auto out_str = std::string_view{doc.GetString(), doc.GetStringLength()}; + BOOST_REQUIRE_EQUAL(out_str, input_string); + } +} + +SEASTAR_THREAD_TEST_CASE(json_iobuf_writer_test) { + constexpr auto to_string = [](const iobuf& buf) { + iobuf_const_parser p{std::move(buf)}; + auto b = p.read_bytes(p.bytes_left()); + return std::string{b.begin(), b.end()}; + }; + + { + json::chunked_buffer out; + json::iobuf_writer os{out}; + auto buf = make_chunked_str(); + os.String(buf); + auto out_buf = std::move(out).as_iobuf(); + auto expected = make_chunked_json(); + BOOST_CHECK_EQUAL(out_buf, expected); + BOOST_CHECK_EQUAL(to_string(out_buf), to_string(expected)); + } +} diff --git a/src/v/kafka/CMakeLists.txt b/src/v/kafka/CMakeLists.txt index fbb6965b83dc5..2df506abdbafb 100644 --- a/src/v/kafka/CMakeLists.txt +++ b/src/v/kafka/CMakeLists.txt @@ -20,6 +20,7 @@ set(handlers_srcs server/handlers/alter_partition_reassignments.cc server/handlers/list_partition_reassignments.cc server/handlers/handler_interface.cc + server/handlers/configs/config_response_utils.cc server/handlers/topics/types.cc server/handlers/topics/topic_utils.cc server/handlers/delete_records.cc diff --git a/src/v/kafka/client/assignment_plans.cc b/src/v/kafka/client/assignment_plans.cc index 4bf1d90d7d8bb..0a87be6033ff7 100644 --- a/src/v/kafka/client/assignment_plans.cc +++ b/src/v/kafka/client/assignment_plans.cc @@ -30,9 +30,9 @@ assignment_plan::encode(const assignments::value_type& m) const { .member_id = m.first, .assignment = iobuf_to_bytes(assignments_buf)}; }; -std::vector +chunked_vector assignment_plan::encode(const assignments& assignments) const { - std::vector result; + chunked_vector result; result.reserve(assignments.size()); for (const auto& m : assignments) { result.push_back(encode(m)); @@ -58,8 +58,8 @@ assignment assignment_plan::decode(const bytes& b) const { } assignments assignment_range::plan( - const std::vector& members, - const std::vector& topics) { + const chunked_vector& members, + const chunked_vector& topics) { assignments assignments; for (auto const& t : topics) { auto [len, rem] = std::ldiv(t.partitions.size(), members.size()); @@ -96,7 +96,7 @@ make_assignment_plan(const protocol_name& protocol_name) { } join_group_request_protocol make_join_group_request_protocol_range( - const std::vector& topics) { + const chunked_vector& topics) { iobuf metadata; protocol::encoder writer(metadata); writer.write_array( @@ -109,8 +109,8 @@ join_group_request_protocol make_join_group_request_protocol_range( .name{protocol_name{"range"}}, .metadata{iobuf_to_bytes(metadata)}}; } -std::vector -make_join_group_request_protocols(const std::vector& topics) { +chunked_vector +make_join_group_request_protocols(const chunked_vector& topics) { // When this is extended, create them in order of preference return {make_join_group_request_protocol_range(topics)}; } diff --git a/src/v/kafka/client/assignment_plans.h b/src/v/kafka/client/assignment_plans.h index 0aadfc44401e7..7b93c595c5ecd 100644 --- a/src/v/kafka/client/assignment_plans.h +++ b/src/v/kafka/client/assignment_plans.h @@ -16,6 +16,7 @@ #include "kafka/protocol/sync_group.h" #include "kafka/types.h" #include "model/fundamental.h" +#include "utils/fragmented_vector.h" #include @@ -36,14 +37,14 @@ struct assignment_plan { virtual ~assignment_plan() = default; virtual assignments plan( - const std::vector& members, - const std::vector& topics) + const chunked_vector& members, + const chunked_vector& topics) = 0; sync_group_request_assignment encode(const assignments::value_type& m) const; - std::vector + chunked_vector encode(const assignments& assignments) const; assignment decode(const bytes& b) const; @@ -56,14 +57,14 @@ make_assignment_plan(const protocol_name& protocol_name); struct assignment_range final : public assignment_plan { static inline const protocol_name name{"range"}; assignments plan( - const std::vector& members, - const std::vector& topics) final; + const chunked_vector& members, + const chunked_vector& topics) final; }; join_group_request_protocol make_join_group_request_protocol_range(const std::vector& topics); -std::vector -make_join_group_request_protocols(const std::vector& topics); +chunked_vector +make_join_group_request_protocols(const chunked_vector& topics); } // namespace kafka::client diff --git a/src/v/kafka/client/broker.cc b/src/v/kafka/client/broker.cc index 9f8542d0b25de..d896adfa195aa 100644 --- a/src/v/kafka/client/broker.cc +++ b/src/v/kafka/client/broker.cc @@ -17,6 +17,27 @@ #include "rpc/rpc_utils.h" #include +#include + +#include + +namespace { +bool is_dns_failure_error(const std::system_error& e) { + if (e.code().category() == ss::net::dns::error_category()) { + switch (e.code().value()) { + case ARES_ENOTFOUND: + case ARES_ENODATA: + case ARES_ETIMEOUT: + case ARES_ECONNREFUSED: + return true; + default: + return false; + } + } + + return false; +} +} // namespace namespace kafka::client { @@ -50,7 +71,7 @@ ss::future make_broker( }); }) .handle_exception_type([node_id](const std::system_error& ex) { - if (net::is_reconnect_error(ex)) { + if (net::is_reconnect_error(ex) || is_dns_failure_error(ex)) { return ss::make_exception_future( broker_error(node_id, error_code::network_exception)); } diff --git a/src/v/kafka/client/brokers.cc b/src/v/kafka/client/brokers.cc index 5e66f150df6e8..60bd61eff31dd 100644 --- a/src/v/kafka/client/brokers.cc +++ b/src/v/kafka/client/brokers.cc @@ -48,8 +48,8 @@ ss::future<> brokers::erase(model::node_id node_id) { return ss::now(); } -ss::future<> brokers::apply(std::vector&& res) { - using new_brokers_t = std::vector; +ss::future<> brokers::apply(chunked_vector&& res) { + using new_brokers_t = chunked_vector; return ss::do_with(std::move(res), [this](new_brokers_t& new_brokers) { auto new_brokers_begin = std::partition( new_brokers.begin(), diff --git a/src/v/kafka/client/brokers.h b/src/v/kafka/client/brokers.h index ac75742ae8df9..de369616f67ee 100644 --- a/src/v/kafka/client/brokers.h +++ b/src/v/kafka/client/brokers.h @@ -16,6 +16,7 @@ #include "kafka/protocol/metadata.h" #include "model/fundamental.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -55,7 +56,7 @@ class brokers { ss::future<> erase(model::node_id id); /// \brief Apply the given metadata response. - ss::future<> apply(std::vector&& brokers); + ss::future<> apply(chunked_vector&& brokers); /// \brief Returns true if there are no connected brokers ss::future empty() const; diff --git a/src/v/kafka/client/client.cc b/src/v/kafka/client/client.cc index 0d7b9d02ab76f..a09b495d1b9d9 100644 --- a/src/v/kafka/client/client.cc +++ b/src/v/kafka/client/client.cc @@ -289,10 +289,13 @@ ss::future client::produce_records( std::move(*p.records->adapter.batch)); }); + chunked_vector responses_cv; + responses_cv.emplace_back(topic_produce_response{ + .name{std::move(topic)}, .partitions{std::move(responses)}}); + co_return produce_response{ .data = produce_response_data{ - .responses{ - {.name{std::move(topic)}, .partitions{std::move(responses)}}}, + .responses = std::move(responses_cv), .throttle_time_ms{{std::chrono::milliseconds{0}}}}}; } @@ -302,8 +305,12 @@ client::create_topic(kafka::creatable_topic req) { auto controller = _controller; return _brokers.find(controller) .then([req](auto broker) mutable { - return broker->dispatch( - kafka::create_topics_request{.data{.topics{std::move(req)}}}); + chunked_vector cv; + cv.push_back(std::move(req)); + return broker->dispatch(kafka::create_topics_request{ + .data = { + .topics = std::move(cv), + }}); }) .then([controller](auto res) { auto ec = res.data.topics[0].error_code; @@ -337,11 +344,19 @@ ss::future client::do_list_offsets(model::topic_partition tp) { auto node_id = co_await _topic_cache.leader(tp); auto broker = co_await _brokers.find(node_id); + chunked_vector cv; + cv.push_back(kafka::list_offset_topic{ + .name{tp.topic}, + .partitions{ + { + {.partition_index{tp.partition}, .max_num_offsets = 1}, + }, + }, + }); auto res = co_await broker->dispatch(kafka::list_offsets_request{ - .data = {.topics{ - {{.name{tp.topic}, - .partitions{ - {{.partition_index{tp.partition}, .max_num_offsets = 1}}}}}}}}); + .data = { + .topics = std::move(cv), + }}); const auto& topics = res.data.topics; auto ec = error_code::none; @@ -474,17 +489,18 @@ ss::future<> client::remove_consumer(group_id g_id, const member_id& name) { ss::future<> client::subscribe_consumer( const group_id& g_id, const member_id& name, - std::vector topics) { + chunked_vector topics) { return get_consumer(g_id, name) .then([topics{std::move(topics)}](shared_consumer_t c) mutable { return c->subscribe(std::move(topics)); }); } -ss::future> +ss::future> client::consumer_topics(const group_id& g_id, const member_id& name) { return get_consumer(g_id, name).then([](shared_consumer_t c) { - return ss::make_ready_future>(c->topics()); + return ss::make_ready_future>( + c->topics().copy()); }); } diff --git a/src/v/kafka/client/client.h b/src/v/kafka/client/client.h index c9ffa7a1b2202..580ac538eb9fa 100644 --- a/src/v/kafka/client/client.h +++ b/src/v/kafka/client/client.h @@ -28,6 +28,7 @@ #include "kafka/types.h" #include "net/unresolved_address.h" #include "ssx/semaphore.h" +#include "utils/fragmented_vector.h" #include "utils/retry.h" #include @@ -138,9 +139,9 @@ class client { ss::future<> subscribe_consumer( const group_id& group_id, const member_id& member_id, - std::vector topics); + chunked_vector topics); - ss::future> + ss::future> consumer_topics(const group_id& g_id, const member_id& m_id); ss::future diff --git a/src/v/kafka/client/consumer.cc b/src/v/kafka/client/consumer.cc index 9a7f4a2b285d9..54dbb32bde6ee 100644 --- a/src/v/kafka/client/consumer.cc +++ b/src/v/kafka/client/consumer.cc @@ -76,11 +76,10 @@ struct partition_comp { fetch_response reduce_fetch_response(fetch_response result, fetch_response val) { result.data.throttle_time_ms += val.data.throttle_time_ms; - result.data.topics.insert( - result.data.topics.end(), - std::make_move_iterator(val.data.topics.begin()), - std::make_move_iterator(val.data.topics.end())); - + std::move( + val.data.topics.begin(), + val.data.topics.end(), + std::back_inserter(result.data.topics)); return result; }; @@ -207,7 +206,7 @@ ss::future<> consumer::join() { }); } -ss::future<> consumer::subscribe(std::vector topics) { +ss::future<> consumer::subscribe(chunked_vector topics) { refresh_inactivity_timer(); _topics = std::move(topics); return join(); @@ -220,8 +219,7 @@ void consumer::on_leader_join(const join_group_response& res) { _members.push_back(m.member_id); } std::sort(_members.begin(), _members.end()); - _members.erase( - std::unique(_members.begin(), _members.end()), _members.end()); + _members.erase_to_end(std::unique(_members.begin(), _members.end())); _subscribed_topics.clear(); for (auto const& m : res.data.members) { @@ -233,9 +231,8 @@ void consumer::on_leader_join(const join_group_response& res) { topics.begin(), topics.end(), std::back_inserter(_subscribed_topics)); } std::sort(_subscribed_topics.begin(), _subscribed_topics.end()); - _subscribed_topics.erase( - std::unique(_subscribed_topics.begin(), _subscribed_topics.end()), - _subscribed_topics.end()); + _subscribed_topics.erase_to_end( + std::unique(_subscribed_topics.begin(), _subscribed_topics.end())); vlog( kclog.info, @@ -255,17 +252,15 @@ ss::future consumer::leave() { }); } -ss::future> +ss::future> consumer::get_subscribed_topic_metadata() { return req_res([]() { return metadata_request{.list_all_topics = true}; }) .then([this](metadata_response res) { - std::vector assignments; - std::sort( res.data.topics.begin(), res.data.topics.end(), detail::topic_comp{}); - std::vector topics; + chunked_vector topics; topics.reserve(_subscribed_topics.size()); std::set_intersection( std::make_move_iterator(res.data.topics.begin()), @@ -285,22 +280,22 @@ consumer::get_subscribed_topic_metadata() { } ss::future<> consumer::sync() { - return (is_leader() - ? get_subscribed_topic_metadata() - : ss::make_ready_future>()) - .then([this](std::vector topics) { + return (is_leader() ? get_subscribed_topic_metadata() + : ss::make_ready_future< + chunked_vector>()) + .then([this](chunked_vector topics) { auto req_builder = [me{shared_from_this()}, - topics{std::move(topics)}]() mutable { + topics{std::move(topics)}]() { auto assignments = me->is_leader() ? me->_plan->encode(me->_plan->plan(me->_members, topics)) - : std::vector{}; + : chunked_vector{}; return sync_group_request{.data{ .group_id = me->_group_id, .generation_id = me->_generation_id, .member_id = me->_member_id, .group_instance_id = std::nullopt, - .assignments = assignments}}; + .assignments = std::move(assignments)}}; }; return req_res(std::move(req_builder)) @@ -365,7 +360,7 @@ ss::future consumer::describe_group() { ss::future consumer::offset_fetch(std::vector topics) { refresh_inactivity_timer(); - auto req_builder = [topics{std::move(topics)}, group_id{_group_id}] { + auto req_builder = [topics{std::move(topics)}, group_id{_group_id}]() { return offset_fetch_request{ .data{.group_id = group_id, .topics = topics}}; }; @@ -386,10 +381,7 @@ consumer::offset_commit(std::vector topics) { if (topics.empty()) { // commit all offsets for (const auto& s : _fetch_sessions) { auto res = s.second.make_offset_commit_request(); - topics.insert( - topics.end(), - std::make_move_iterator(res.begin()), - std::make_move_iterator(res.end())); + std::move(res.begin(), res.end(), std::back_inserter(topics)); } } else { // set epoch for requests tps for (auto& t : topics) { diff --git a/src/v/kafka/client/consumer.h b/src/v/kafka/client/consumer.h index 833b620e3a0e4..63cb20da4ef3e 100644 --- a/src/v/kafka/client/consumer.h +++ b/src/v/kafka/client/consumer.h @@ -22,6 +22,7 @@ #include "kafka/protocol/offset_commit.h" #include "kafka/protocol/offset_fetch.h" #include "kafka/types.h" +#include "utils/fragmented_vector.h" #include @@ -65,12 +66,12 @@ class consumer final : public ss::enable_lw_shared_from_this { const kafka::member_id& name() const { return _name != kafka::no_member ? _name : _member_id; } - const std::vector& topics() const { return _topics; } + const chunked_vector& topics() const { return _topics; } const assignment_t& assignment() const { return _assignment; } ss::future<> initialize(); ss::future leave(); - ss::future<> subscribe(std::vector topics); + ss::future<> subscribe(chunked_vector topics); ss::future offset_fetch(std::vector topics); ss::future @@ -91,7 +92,7 @@ class consumer final : public ss::enable_lw_shared_from_this { ss::future<> join(); ss::future<> sync(); - ss::future> + ss::future> get_subscribed_topic_metadata(); ss::future<> heartbeat(); @@ -102,6 +103,7 @@ class consumer final : public ss::enable_lw_shared_from_this { ss::future dispatch_fetch(broker_reqs_t::value_type br); template + requires requires(const RequestFactory v) { v.operator()(); } ss::future< typename std::invoke_result_t::api_type::response_type> req_res(RequestFactory req) { @@ -165,9 +167,9 @@ class consumer final : public ss::enable_lw_shared_from_this { kafka::member_id _member_id{no_member}; kafka::member_id _name{no_member}; kafka::member_id _leader_id{no_leader}; - std::vector _topics{}; - std::vector _members{}; - std::vector _subscribed_topics{}; + chunked_vector _topics{}; + chunked_vector _members{}; + chunked_vector _subscribed_topics{}; std::unique_ptr _plan{}; assignment_t _assignment{}; absl::node_hash_map _fetch_sessions; diff --git a/src/v/kafka/client/fetcher.cc b/src/v/kafka/client/fetcher.cc index 70bd7ab007f03..28444fa841be9 100644 --- a/src/v/kafka/client/fetcher.cc +++ b/src/v/kafka/client/fetcher.cc @@ -18,6 +18,7 @@ #include "kafka/types.h" #include "model/fundamental.h" #include "seastar/core/gate.hh" +#include "utils/fragmented_vector.h" namespace kafka::client { @@ -27,14 +28,14 @@ fetch_request make_fetch_request( int32_t min_bytes, int32_t max_bytes, std::chrono::milliseconds timeout) { - std::vector partitions; + chunked_vector partitions; partitions.push_back(fetch_request::partition{ .partition_index{tp.partition}, .current_leader_epoch = kafka::invalid_leader_epoch, .fetch_offset{offset}, .log_start_offset{model::offset{-1}}, .max_bytes = max_bytes}); - std::vector topics; + chunked_vector topics; topics.push_back(fetch_request::topic{ .name{tp.topic}, .fetch_partitions{std::move(partitions)}}); @@ -79,7 +80,7 @@ make_fetch_response(const model::topic_partition& tp, std::exception_ptr ex) { responses.push_back(std::move(pr)); auto response = fetch_response::partition{.name = tp.topic}; response.partitions = std::move(responses); - std::vector parts; + chunked_vector parts; parts.push_back(std::move(response)); return fetch_response{ .data = { diff --git a/src/v/kafka/client/producer.cc b/src/v/kafka/client/producer.cc index 32cba3ede756b..15cb459970638 100644 --- a/src/v/kafka/client/producer.cc +++ b/src/v/kafka/client/producer.cc @@ -17,6 +17,7 @@ #include "kafka/protocol/errors.h" #include "kafka/protocol/produce.h" #include "model/fundamental.h" +#include "utils/fragmented_vector.h" #include @@ -26,12 +27,12 @@ namespace kafka::client { produce_request make_produce_request( model::topic_partition tp, model::record_batch&& batch, int16_t acks) { - std::vector partitions; + chunked_vector partitions; partitions.emplace_back(produce_request::partition{ .partition_index{tp.partition}, .records = produce_request_record_data(std::move(batch))}); - std::vector topics; + chunked_vector topics; topics.emplace_back(produce_request::topic{ .name{std::move(tp.topic)}, .partitions{std::move(partitions)}}); std::optional t_id; diff --git a/src/v/kafka/client/test/produce_batcher.cc b/src/v/kafka/client/test/produce_batcher.cc index 5644df640814d..c47e70961ecd2 100644 --- a/src/v/kafka/client/test/produce_batcher.cc +++ b/src/v/kafka/client/test/produce_batcher.cc @@ -73,8 +73,8 @@ struct produce_batcher_context { std::vector offsets; offsets.reserve(results.size()); std::transform( - results.begin(), - results.end(), + std::make_move_iterator(results.begin()), + std::make_move_iterator(results.end()), std::back_inserter(offsets), [](kafka::produce_response::partition p) { return p.base_offset; diff --git a/src/v/kafka/protocol/fetch.h b/src/v/kafka/protocol/fetch.h index ad68e06c7f465..bf3c081a3ef08 100644 --- a/src/v/kafka/protocol/fetch.h +++ b/src/v/kafka/protocol/fetch.h @@ -20,6 +20,7 @@ #include "model/metadata.h" #include "model/timeout_clock.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -92,8 +93,9 @@ struct fetch_request final { */ class const_iterator { public: - using const_topic_iterator = std::vector::const_iterator; - using const_partition_iterator = std::vector::const_iterator; + using const_topic_iterator = chunked_vector::const_iterator; + using const_partition_iterator + = chunked_vector::const_iterator; struct value_type { bool new_topic; @@ -211,7 +213,7 @@ struct fetch_response final { */ class iterator { public: - using partition_iterator = std::vector::iterator; + using partition_iterator = chunked_vector::iterator; using partition_response_iterator = small_fragment_vector::iterator; diff --git a/src/v/kafka/protocol/join_group.h b/src/v/kafka/protocol/join_group.h index ff4771ab11029..f83655338fd91 100644 --- a/src/v/kafka/protocol/join_group.h +++ b/src/v/kafka/protocol/join_group.h @@ -16,6 +16,7 @@ #include "kafka/types.h" #include "model/fundamental.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -48,8 +49,9 @@ struct join_group_request final { * type is also the type stored on disk and we do not want it to be tied to * the type produced by code generation. */ - std::vector native_member_protocols() const { - std::vector res; + chunked_vector native_member_protocols() const { + chunked_vector res; + res.reserve(data.protocols.size()); std::transform( data.protocols.cbegin(), data.protocols.cend(), @@ -102,7 +104,7 @@ struct join_group_response final { kafka::protocol_name protocol_name, kafka::member_id leader_id, kafka::member_id member_id, - std::vector members = {}) { + chunked_vector members = {}) { data.throttle_time_ms = std::chrono::milliseconds(0); data.error_code = error; data.generation_id = generation_id; @@ -134,6 +136,19 @@ make_join_error(kafka::member_id member_id, error_code error) { // group membership helper to compare a protocol set from the wire with our // internal type without doing a full type conversion. +inline bool operator==( + const chunked_vector& a, + const chunked_vector& b) { + return std::equal( + a.cbegin(), + a.cend(), + b.cbegin(), + b.cend(), + [](const join_group_request_protocol& a, const member_protocol& b) { + return a.name == b.name && a.metadata == b.metadata; + }); +} + inline bool operator==( const std::vector& a, const std::vector& b) { diff --git a/src/v/kafka/protocol/logger.cc b/src/v/kafka/protocol/logger.cc index 5f27588c99d8c..f901b4d1bc495 100644 --- a/src/v/kafka/protocol/logger.cc +++ b/src/v/kafka/protocol/logger.cc @@ -9,7 +9,10 @@ #include "kafka/server/logger.h" +#include "units.h" + namespace kafka { +static constexpr size_t max_log_line_bytes = 128_KiB; ss::logger klog("kafka"); -truncating_logger kwire(klog, 1048576); +truncating_logger kwire(klog, max_log_line_bytes); } // namespace kafka diff --git a/src/v/kafka/protocol/offset_for_leader_epoch.h b/src/v/kafka/protocol/offset_for_leader_epoch.h index 417fc08e4761c..44deaf6ca181a 100644 --- a/src/v/kafka/protocol/offset_for_leader_epoch.h +++ b/src/v/kafka/protocol/offset_for_leader_epoch.h @@ -57,7 +57,7 @@ struct offset_for_leader_epoch_response final { current_request.data.topics.end(), std::back_inserter(data.topics), [ec](offset_for_leader_topic& o) { - std::vector offsets; + chunked_vector offsets; offsets.reserve(o.partitions.size()); std::transform( o.partitions.begin(), diff --git a/src/v/kafka/protocol/produce.h b/src/v/kafka/protocol/produce.h index 66af9512831fa..bfb0537f61b05 100644 --- a/src/v/kafka/protocol/produce.h +++ b/src/v/kafka/protocol/produce.h @@ -18,6 +18,7 @@ #include "kafka/types.h" #include "model/timestamp.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -40,7 +41,7 @@ struct produce_request final { produce_request( std::optional t_id, int16_t acks, - std::vector topics) { + chunked_vector topics) { if (t_id) { data.transactional_id = transactional_id(std::move(*t_id)); } diff --git a/src/v/kafka/protocol/sasl_handshake.h b/src/v/kafka/protocol/sasl_handshake.h index f0c3748fb44b4..8f89b0bc170b1 100644 --- a/src/v/kafka/protocol/sasl_handshake.h +++ b/src/v/kafka/protocol/sasl_handshake.h @@ -14,6 +14,7 @@ #include "kafka/protocol/schemata/sasl_handshake_response.h" #include "kafka/types.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -48,7 +49,7 @@ struct sasl_handshake_response final { sasl_handshake_response() = default; sasl_handshake_response( - error_code error, std::vector mechanisms) { + error_code error, chunked_vector mechanisms) { data.error_code = error; data.mechanisms = std::move(mechanisms); } diff --git a/src/v/kafka/protocol/schemata/generator.py b/src/v/kafka/protocol/schemata/generator.py index a5f4cfd0dba0e..392da4f125a39 100755 --- a/src/v/kafka/protocol/schemata/generator.py +++ b/src/v/kafka/protocol/schemata/generator.py @@ -468,7 +468,28 @@ 'metadata_response_topic': 'small_fragment_vector', 'fetchable_partition_response': 'small_fragment_vector', 'offset_fetch_response_partition': 'small_fragment_vector', - 'creatable_topic': 'chunked_vector' + 'int32_t': 'std::vector', + 'model::node_id': 'std::vector', + 'model::partition_id': 'std::vector', + 'reassignable_partition_response': 'std::vector', + 'reassignable_partition': 'std::vector', + 'describe_configs_synonym': 'std::vector', + 'createable_topic_config': 'std::vector', + 'creatable_topic_configs': 'std::vector', + 'creatable_replica_assignment': 'std::vector', + 'offset_commit_request_partition': 'std::vector', + 'offset_commit_response_partition': 'std::vector', + 'offset_commit_request_topic': 'std::vector', + 'offset_fetch_request_topic': 'std::vector', + 'partition_produce_response': 'std::vector', + 'creatable_acl_result': 'std::vector', + 'listed_group': 'std::vector', + 'offset_delete_request_partition': 'std::vector', + 'deletable_group_result': 'std::vector', + 'delete_acls_matching_acl': 'std::vector', + 'txn_offset_commit_request_partition': 'std::vector', + 'txn_offset_commit_request_topic': 'std::vector', + 'txn_offset_commit_response_partition': 'std::vector', } @@ -1061,7 +1082,7 @@ def type_name_parts(self): if name in override_member_container: yield override_member_container[name] else: - yield "std::vector" + yield "chunked_vector" if self.nullable(): assert default_value is None # not supported yield "std::optional" diff --git a/src/v/kafka/protocol/tests/protocol_test.cc b/src/v/kafka/protocol/tests/protocol_test.cc index 85a9c1ef2d8a0..121b541742432 100644 --- a/src/v/kafka/protocol/tests/protocol_test.cc +++ b/src/v/kafka/protocol/tests/protocol_test.cc @@ -216,21 +216,24 @@ template<> long create_default_and_non_default_data( decltype(create_topics_response::data)& non_default_data, decltype(create_topics_response::data)& default_data) { - non_default_data.throttle_time_ms = std::chrono::milliseconds(1000); - - non_default_data.topics.emplace_back(creatable_topic_result{ - model::topic{"topic1"}, - {}, - kafka::error_code{1}, - "test_error_message", - 3, - 16, - std::nullopt, - kafka::error_code{2}}); + auto make_topic_result = [](kafka::error_code ec) { + return creatable_topic_result{ + model::topic{"topic1"}, + {}, + kafka::error_code{1}, + "test_error_message", + 3, + 16, + std::nullopt, + ec}; + }; - default_data = non_default_data; + non_default_data.throttle_time_ms = std::chrono::milliseconds(1000); + non_default_data.topics.emplace_back( + make_topic_result(kafka::error_code{2})); - default_data.topics.at(0).topic_config_error_code = kafka::error_code{0}; + default_data.throttle_time_ms = std::chrono::milliseconds(1000); + default_data.topics.emplace_back(make_topic_result(kafka::error_code{0})); // int16 (2 bytes) + tag (2 bytes) return 4; @@ -241,7 +244,15 @@ long create_default_and_non_default_data( decltype(api_versions_response::data)& non_default_data, decltype(api_versions_response::data)& default_data) { non_default_data.finalized_features_epoch = 0; - default_data = non_default_data; + default_data = { + .error_code = non_default_data.error_code, + .api_keys = non_default_data.api_keys.copy(), + .throttle_time_ms = non_default_data.throttle_time_ms, + .supported_features = non_default_data.supported_features.copy(), + .finalized_features_epoch = non_default_data.finalized_features_epoch, + .finalized_features = non_default_data.finalized_features.copy(), + .unknown_tags = non_default_data.unknown_tags, + }; default_data.finalized_features_epoch = -1; // int64 (8 bytes) + tag (2 bytes) diff --git a/src/v/kafka/protocol/wire.h b/src/v/kafka/protocol/wire.h index 6458fd100e315..93aaebb2fee82 100644 --- a/src/v/kafka/protocol/wire.h +++ b/src/v/kafka/protocol/wire.h @@ -562,12 +562,12 @@ class encoder { return _out->size_bytes() - start_size; } - template - requires requires(ElementWriter writer, encoder& rw, T& elem) { + template + requires requires( + ElementWriter writer, encoder& rw, typename C::value_type& elem) { { writer(elem, rw) } -> std::same_as; } - uint32_t write_nullable_array( - std::optional>& v, ElementWriter&& writer) { + uint32_t write_nullable_array(std::optional& v, ElementWriter&& writer) { if (!v) { return write(int32_t(-1)); } @@ -589,12 +589,13 @@ class encoder { return _out->size_bytes() - start_size; } - template - requires requires(ElementWriter writer, encoder& rw, T& elem) { + template + requires requires( + ElementWriter writer, encoder& rw, typename C::value_type& elem) { { writer(elem, rw) } -> std::same_as; } - uint32_t write_nullable_flex_array( - std::optional>& v, ElementWriter&& writer) { + uint32_t + write_nullable_flex_array(std::optional& v, ElementWriter&& writer) { if (!v) { return write_unsigned_varint(0); } diff --git a/src/v/kafka/server/connection_context.cc b/src/v/kafka/server/connection_context.cc index 5c3dab54904cb..558c8ab8e348e 100644 --- a/src/v/kafka/server/connection_context.cc +++ b/src/v/kafka/server/connection_context.cc @@ -96,7 +96,7 @@ ss::future<> connection_context::start() { } ss::future<> connection_context::stop() { - if (_hook) { + if (_hook && is_linked()) { _hook.value().get().erase(_hook.value().get().iterator_to(*this)); } if (conn) { @@ -413,33 +413,49 @@ connection_context::record_tp_and_calculate_throttle( const auto now = clock::now(); // Throttle on client based quotas - quota_manager::throttle_delay client_quota_delay{}; + connection_context::delay_t client_quota_delay{}; if (hdr.key == fetch_api::key) { - client_quota_delay = _server.quota_mgr().throttle_fetch_tp( + auto fetch_delay = _server.quota_mgr().throttle_fetch_tp( hdr.client_id, now); + auto fetch_enforced = _throttling_state.update_fetch_delay( + fetch_delay.duration, now); + client_quota_delay = delay_t{ + .request = fetch_delay.duration, + .enforce = fetch_enforced, + }; } else if (hdr.key == produce_api::key) { - client_quota_delay = _server.quota_mgr().record_produce_tp_and_throttle( + auto produce_delay = _server.quota_mgr().record_produce_tp_and_throttle( hdr.client_id, request_size, now); + auto produce_enforced = _throttling_state.update_produce_delay( + produce_delay.duration, now); + client_quota_delay = delay_t{ + .request = produce_delay.duration, + .enforce = produce_enforced, + }; } // Throttle on shard wide quotas - snc_quota_manager::delays_t shard_delays; + connection_context::delay_t snc_delay; if (_kafka_throughput_controlled_api_keys().at(hdr.key)) { _server.snc_quota_mgr().get_or_create_quota_context( _snc_quota_context, hdr.client_id); _server.snc_quota_mgr().record_request_receive( *_snc_quota_context, request_size, now); - shard_delays = _server.snc_quota_mgr().get_shard_delays( - *_snc_quota_context, now); + auto shard_delays = _server.snc_quota_mgr().get_shard_delays( + *_snc_quota_context); + auto snc_enforced = _throttling_state.update_snc_delay( + shard_delays.request, now); + snc_delay = delay_t{ + .request = shard_delays.request, + .enforce = snc_enforced, + }; } // Sum up const clock::duration delay_enforce = std::max( - shard_delays.enforce, client_quota_delay.enforce_duration()); + {snc_delay.enforce, client_quota_delay.enforce, clock::duration::zero()}); const clock::duration delay_request = std::max( - {shard_delays.request, - client_quota_delay.duration, - clock::duration::zero()}); + {snc_delay.request, client_quota_delay.request, clock::duration::zero()}); if ( delay_enforce != clock::duration::zero() || delay_request != clock::duration::zero()) { @@ -449,10 +465,10 @@ connection_context::record_tp_and_calculate_throttle( "enforce:{{snc:{}, client:{}}}, key:{}, request_size:{}", _client_addr, client_port(), - shard_delays.request, - client_quota_delay.duration, - shard_delays.enforce, - client_quota_delay.enforce_duration(), + snc_delay.request, + client_quota_delay.request, + snc_delay.enforce, + client_quota_delay.enforce, hdr.key, request_size); } @@ -477,6 +493,12 @@ ss::future connection_context::throttle_request( auto tracker = std::make_unique(_server.probe(), h_probe); auto fut = ss::now(); if (delay.enforce > delay_t::clock::duration::zero()) { + vlog( + klog.trace, + "[{}:{}] enforcing throttling delay of {}", + _client_addr, + client_port(), + delay.enforce); fut = ss::sleep_abortable(delay.enforce, abort_source().local()); } auto track = track_latency(hdr.key); diff --git a/src/v/kafka/server/connection_context.h b/src/v/kafka/server/connection_context.h index 7b8db3fa3fbde..fb2ccf68074f2 100644 --- a/src/v/kafka/server/connection_context.h +++ b/src/v/kafka/server/connection_context.h @@ -320,6 +320,38 @@ class connection_context final uint16_t _client_port; }; + class throttling_state { + public: + ss::lowres_clock::duration update_fetch_delay( + ss::lowres_clock::duration new_delay, + ss::lowres_clock::time_point now) { + auto result_enforced = fetch_throttled_until - now; + fetch_throttled_until = now + new_delay; + return result_enforced; + } + + ss::lowres_clock::duration update_produce_delay( + ss::lowres_clock::duration new_delay, + ss::lowres_clock::time_point now) { + auto result_enforced = produce_throttled_until - now; + produce_throttled_until = now + new_delay; + return result_enforced; + } + + ss::lowres_clock::duration update_snc_delay( + ss::lowres_clock::duration new_delay, + ss::lowres_clock::time_point now) { + auto result_enforced = snc_throttled_until - now; + snc_throttled_until = now + new_delay; + return result_enforced; + } + + private: + ss::lowres_clock::time_point snc_throttled_until; + ss::lowres_clock::time_point produce_throttled_until; + ss::lowres_clock::time_point fetch_throttled_until; + }; + std::optional< std::reference_wrapper>> _hook; @@ -339,6 +371,11 @@ class connection_context final _kafka_throughput_controlled_api_keys; std::unique_ptr _snc_quota_context; ss::promise<> _wait_input_shutdown; + + /// What time the client on this conection should be throttled until + /// Used to enforce client quotas and ingress/egress quotas broker-side + /// if the client does not obey the ThrottleTimeMs in the response + throttling_state _throttling_state; }; } // namespace kafka diff --git a/src/v/kafka/server/errors.h b/src/v/kafka/server/errors.h index 6ce9b0bba852f..e1f784b5c4406 100644 --- a/src/v/kafka/server/errors.h +++ b/src/v/kafka/server/errors.h @@ -21,6 +21,10 @@ constexpr error_code map_topic_error_code(cluster::errc code) { case cluster::errc::topic_invalid_config: return error_code::invalid_config; case cluster::errc::topic_invalid_partitions: + case cluster::errc::topic_invalid_partitions_core_limit: + case cluster::errc::topic_invalid_partitions_memory_limit: + case cluster::errc::topic_invalid_partitions_fd_limit: + case cluster::errc::topic_invalid_partitions_decreased: return error_code::invalid_partitions; case cluster::errc::topic_invalid_replication_factor: return error_code::invalid_replication_factor; @@ -97,6 +101,7 @@ constexpr error_code map_topic_error_code(cluster::errc code) { case cluster::errc::invalid_partition_operation: case cluster::errc::concurrent_modification_error: case cluster::errc::transform_count_limit_exceeded: + case cluster::errc::invalid_target_node_id: break; } return error_code::unknown_server_error; diff --git a/src/v/kafka/server/fetch_session.h b/src/v/kafka/server/fetch_session.h index c2388cef6a978..cc24470b3150a 100644 --- a/src/v/kafka/server/fetch_session.h +++ b/src/v/kafka/server/fetch_session.h @@ -10,6 +10,7 @@ */ #pragma once #include "kafka/protocol/errors.h" +#include "kafka/protocol/fetch.h" #include "kafka/types.h" #include "model/fundamental.h" #include "model/ktp.h" @@ -31,6 +32,15 @@ struct fetch_session_partition { model::offset high_watermark; model::offset last_stable_offset; kafka::leader_epoch current_leader_epoch = invalid_leader_epoch; + + fetch_session_partition( + const model::topic& tp, const fetch_request::partition& p) + : topic_partition(tp, p.partition_index) + , max_bytes(p.max_bytes) + , fetch_offset(p.fetch_offset) + , high_watermark(model::offset(-1)) + , last_stable_offset(model::offset(-1)) + , current_leader_epoch(p.current_leader_epoch) {} }; /** * Map of partitions that is kept by fetch session. This map is using intrusive diff --git a/src/v/kafka/server/fetch_session_cache.cc b/src/v/kafka/server/fetch_session_cache.cc index 65e13fbc3c956..2b7a724c63725 100644 --- a/src/v/kafka/server/fetch_session_cache.cc +++ b/src/v/kafka/server/fetch_session_cache.cc @@ -2,6 +2,7 @@ #include "config/configuration.h" #include "kafka/protocol/fetch.h" +#include "kafka/server/fetch_session.h" #include "kafka/server/logger.h" #include "kafka/types.h" #include "model/fundamental.h" @@ -14,18 +15,6 @@ namespace kafka { -static fetch_session_partition make_fetch_partition( - const model::topic& tp, const fetch_request::partition& p) { - return fetch_session_partition{ - .topic_partition = {tp, p.partition_index}, - .max_bytes = p.max_bytes, - .fetch_offset = p.fetch_offset, - .high_watermark = model::offset(-1), - .last_stable_offset = model::offset(-1), - .current_leader_epoch = p.current_leader_epoch, - }; -} - void update_fetch_session(fetch_session& session, const fetch_request& req) { for (auto it = req.cbegin(); it != req.cend(); ++it) { auto& topic = *it->topic; @@ -36,9 +25,11 @@ void update_fetch_session(fetch_session& session, const fetch_request& req) { s_it != session.partitions().end()) { s_it->second->partition.max_bytes = partition.max_bytes; s_it->second->partition.fetch_offset = partition.fetch_offset; + s_it->second->partition.current_leader_epoch + = partition.current_leader_epoch; } else { session.partitions().emplace( - make_fetch_partition(topic.name, partition)); + fetch_session_partition(topic.name, partition)); } } diff --git a/src/v/kafka/server/group.cc b/src/v/kafka/server/group.cc index 5f8fc9695856c..b183aac03e65f 100644 --- a/src/v/kafka/server/group.cc +++ b/src/v/kafka/server/group.cc @@ -124,7 +124,7 @@ group::group( std::move(m), id, _protocol_type.value(), - std::vector{member_protocol{ + chunked_vector{member_protocol{ .name = _protocol.value_or(protocol_name("")), .metadata = iobuf_to_bytes(m.subscription), }}); @@ -287,13 +287,20 @@ ss::future group::add_member(member_ptr member) { } void group::update_member_no_join( - member_ptr member, std::vector&& new_protocols) { + member_ptr member, + chunked_vector&& new_protocols, + const std::optional& new_client_id, + const kafka::client_host& new_client_host, + std::chrono::milliseconds new_session_timeout, + std::chrono::milliseconds new_rebalance_timeout) { vlog( _ctxlog.trace, - "Updating {}joining member {} with protocols {}", + "Updating {}joining member {} with protocols {} and timeouts {}/{}", member->is_joining() ? "" : "non-", member, - new_protocols); + new_protocols, + new_session_timeout, + new_rebalance_timeout); /* * before updating the member, subtract its existing protocols from @@ -313,11 +320,29 @@ void group::update_member_no_join( for (auto& p : member->protocols()) { _supported_protocols[p.name]++; } + + if (new_client_id) { + member->replace_client_id(*new_client_id); + } + member->replace_client_host(new_client_host); + member->replace_session_timeout(new_session_timeout); + member->replace_rebalance_timeout(new_rebalance_timeout); } ss::future group::update_member( - member_ptr member, std::vector&& new_protocols) { - update_member_no_join(member, std::move(new_protocols)); + member_ptr member, + chunked_vector&& new_protocols, + const std::optional& new_client_id, + const kafka::client_host& new_client_host, + std::chrono::milliseconds new_session_timeout, + std::chrono::milliseconds new_rebalance_timeout) { + update_member_no_join( + member, + std::move(new_protocols), + new_client_id, + new_client_host, + new_session_timeout, + new_rebalance_timeout); if (!member->is_joining()) { _num_members_joining++; @@ -343,7 +368,7 @@ group::duration_type group::rebalance_timeout() const { } } -std::vector group::member_metadata() const { +chunked_vector group::member_metadata() const { if ( in_state(group_state::dead) || in_state(group_state::preparing_rebalance)) { @@ -355,7 +380,7 @@ std::vector group::member_metadata() const { fmt::format("invalid group state: {}", _state)); } - std::vector out; + chunked_vector out; std::transform( std::cbegin(_members), std::cend(_members), @@ -623,8 +648,24 @@ group::join_group_stages group::update_static_member_and_rebalance( * with new member id. */ schedule_next_heartbeat_expiration(member); - auto f = update_member(member, r.native_member_protocols()); - auto old_protocols = _members.at(new_member_id)->protocols(); + + kafka::client_id old_client_id = member->client_id(); + kafka::client_host old_client_host = member->client_host(); + auto old_session_timeout + = std::chrono::duration_cast( + member->session_timeout()); + auto old_rebalance_timeout + = std::chrono::duration_cast( + member->rebalance_timeout()); + + auto f = update_member( + member, + r.native_member_protocols(), + r.client_id, + r.client_host, + r.data.session_timeout_ms, + r.data.rebalance_timeout_ms); + auto old_protocols = _members.at(new_member_id)->protocols().copy(); switch (state()) { case group_state::stable: { auto next_gen_protocol = select_protocol(); @@ -642,7 +683,11 @@ group::join_group_stages group::update_static_member_and_rebalance( instance_id = *r.data.group_instance_id, new_member_id = std::move(new_member_id), old_member_id = std::move(old_member_id), - old_protocols = std::move(old_protocols)]( + old_protocols = std::move(old_protocols), + old_client_id = std::move(old_client_id), + old_client_host = std::move(old_client_host), + old_session_timeout = old_session_timeout, + old_rebalance_timeout = old_rebalance_timeout]( result result) mutable { if (!result) { vlog( @@ -655,7 +700,12 @@ group::join_group_stages group::update_static_member_and_rebalance( auto member = replace_static_member( instance_id, new_member_id, old_member_id); update_member_no_join( - member, std::move(old_protocols)); + member, + std::move(old_protocols), + old_client_id, + old_client_host, + old_session_timeout, + old_rebalance_timeout); schedule_next_heartbeat_expiration(member); try_finish_joining_member( member, @@ -666,7 +716,7 @@ group::join_group_stages group::update_static_member_and_rebalance( } // leader -> member metadata // followers -> [] - std::vector md; + chunked_vector md; if (is_leader(new_member_id)) { md = member_metadata(); } @@ -832,7 +882,7 @@ group::join_group_known_member(join_group_request&& r) { // generation. // the leader receives group member metadata - std::vector members; + chunked_vector members; if (is_leader(r.data.member_id)) { members = member_metadata(); } @@ -963,7 +1013,12 @@ group::join_group_stages group::add_member_and_rebalance( group::join_group_stages group::update_member_and_rebalance(member_ptr member, join_group_request&& r) { auto response = update_member( - std::move(member), r.native_member_protocols()); + std::move(member), + r.native_member_protocols(), + r.client_id, + r.client_host, + r.data.session_timeout_ms, + r.data.rebalance_timeout_ms); try_prepare_rebalance(); return join_group_stages(std::move(response)); } @@ -1133,7 +1188,7 @@ void group::complete_join() { // leader -> member metadata // followers -> [] - std::vector md; + chunked_vector md; if (is_leader(member->id())) { md = member_metadata(); } @@ -2684,9 +2739,9 @@ ss::future group::remove() { co_return error_code::none; } -ss::future<> -group::remove_topic_partitions(const std::vector& tps) { - std::vector> removed; +ss::future<> group::remove_topic_partitions( + const chunked_vector& tps) { + chunked_vector> removed; for (const auto& tp : tps) { _pending_offset_commits.erase(tp); if (auto offset = _offsets.extract(tp); offset) { diff --git a/src/v/kafka/server/group.h b/src/v/kafka/server/group.h index b03919cebea89..de23e63d1e538 100644 --- a/src/v/kafka/server/group.h +++ b/src/v/kafka/server/group.h @@ -28,6 +28,7 @@ #include "model/record.h" #include "model/timestamp.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include "utils/mutex.h" #include "utils/rwlock.h" @@ -350,13 +351,24 @@ class group final : public ss::enable_lw_shared_from_this { * \returns join response promise set at the end of the join phase. */ ss::future update_member( - member_ptr member, std::vector&& new_protocols); + member_ptr member, + chunked_vector&& new_protocols, + const std::optional& new_client_id, + const kafka::client_host& new_client_host, + std::chrono::milliseconds new_session_timeout, + std::chrono::milliseconds new_rebalance_timeout); + /** * Same as update_member but without returning the join promise. Used when * reverting member state after failed group checkpoint */ void update_member_no_join( - member_ptr member, std::vector&& new_protocols); + member_ptr member, + chunked_vector&& new_protocols, + const std::optional& new_client_id, + const kafka::client_host& new_client_host, + std::chrono::milliseconds new_session_timeout, + std::chrono::milliseconds new_rebalance_timeout); /** * \brief Get the timeout duration for rebalancing. @@ -376,7 +388,7 @@ class group final : public ss::enable_lw_shared_from_this { * * Caller must ensure that the group's protocol is set. */ - std::vector member_metadata() const; + chunked_vector member_metadata() const; /** * \brief Add empty assignments for missing group members. @@ -644,7 +656,7 @@ class group final : public ss::enable_lw_shared_from_this { // remove offsets associated with topic partitions ss::future<> - remove_topic_partitions(const std::vector& tps); + remove_topic_partitions(const chunked_vector& tps); const ss::lw_shared_ptr partition() const { return _partition; diff --git a/src/v/kafka/server/group_manager.cc b/src/v/kafka/server/group_manager.cc index 17462775aa1aa..5e464d3135634 100644 --- a/src/v/kafka/server/group_manager.cc +++ b/src/v/kafka/server/group_manager.cc @@ -34,6 +34,7 @@ #include "raft/types.h" #include "resource_mgmt/io_priority.h" #include "ssx/future-util.h" +#include "utils/fragmented_vector.h" #include #include @@ -454,17 +455,17 @@ void group_manager::attach_partition(ss::lw_shared_ptr p) { } ss::future<> group_manager::cleanup_removed_topic_partitions( - const std::vector& tps) { + const chunked_vector& tps) { // operate on a light-weight copy of group pointers to avoid iterating over // the main index which is subject to concurrent modification. - std::vector groups; + chunked_vector groups; groups.reserve(_groups.size()); for (auto& group : _groups) { groups.push_back(group.second); } return ss::do_with( - std::move(groups), [this, &tps](std::vector& groups) { + std::move(groups), [this, &tps](chunked_vector& groups) { return ss::do_for_each(groups, [this, &tps](group_ptr& group) { return group->remove_topic_partitions(tps).then( [this, g = group] { @@ -492,7 +493,7 @@ void group_manager::handle_topic_delta( cluster::topic_table::delta_range_t deltas) { // topic-partition deletions in the kafka namespace are the only deltas that // are relevant to the group manager - std::vector tps; + chunked_vector tps; for (const auto& delta : deltas) { if ( delta.type == cluster::topic_table_delta_type::removed @@ -511,7 +512,7 @@ void group_manager::handle_topic_delta( [this, tps = std::move(tps)]() mutable { return ss::do_with( std::move(tps), - [this](const std::vector& tps) { + [this](const chunked_vector& tps) { return cleanup_removed_topic_partitions(tps); }); }) @@ -838,16 +839,31 @@ ss::future<> group_manager::handle_partition_leader_change( std::nullopt, std::nullopt, std::nullopt); - + auto expected_to_read = model::prev_offset( + p->partition->high_watermark()); return p->partition->make_reader(reader_config) - .then([this, term, p, timeout]( + .then([this, term, p, timeout, expected_to_read]( model::record_batch_reader reader) { return std::move(reader) .consume( group_recovery_consumer(_serializer_factory(), p->as), timeout) - .then([this, term, p]( + .then([this, term, p, expected_to_read]( group_recovery_consumer_state state) { + if (state.last_read_offset < expected_to_read) { + vlog( + klog.error, + "error recovering group state from {}. " + "Expected to read up to {} but last offset " + "consumed is equal to {}", + p->partition->ntp(), + expected_to_read, + state.last_read_offset); + // force step down to allow other node to + // recover group + return p->partition->raft()->step_down( + "unable to recover group, short read"); + } // avoid trying to recover if we stopped the // reader because an abort was requested if (p->as.abort_requested()) { @@ -940,6 +956,7 @@ ss::future<> group_manager::do_recover_group( .log_offset = meta.log_offset, .offset = meta.metadata.offset, .metadata = meta.metadata.metadata, + .committed_leader_epoch = meta.metadata.leader_epoch, .commit_timestamp = meta.metadata.commit_timestamp, .expiry_timestamp = expiry_timestamp, .non_reclaimable = meta.metadata.non_reclaimable, @@ -1236,7 +1253,11 @@ group_manager::leave_group(leave_group_request&& r) { ss::future group_manager::txn_offset_commit(txn_offset_commit_request&& r) { auto p = get_attached_partition(r.ntp); - if (!p || !p->catchup_lock->try_read_lock()) { + if (!p || !p->partition->is_leader()) { + return ss::make_ready_future( + txn_offset_commit_response(r, error_code::not_coordinator)); + } + if (!p->catchup_lock->try_read_lock()) { // transaction operations can't run in parallel with loading // state from the log (happens once per term change) vlog( @@ -1287,7 +1308,11 @@ group_manager::txn_offset_commit(txn_offset_commit_request&& r) { ss::future group_manager::commit_tx(cluster::commit_group_tx_request&& r) { auto p = get_attached_partition(r.ntp); - if (!p || !p->catchup_lock->try_read_lock()) { + if (!p || !p->partition->is_leader()) { + return ss::make_ready_future( + make_commit_tx_reply(cluster::tx_errc::not_coordinator)); + } + if (!p->catchup_lock->try_read_lock()) { // transaction operations can't run in parallel with loading // state from the log (happens once per term change) vlog( @@ -1326,7 +1351,11 @@ group_manager::commit_tx(cluster::commit_group_tx_request&& r) { ss::future group_manager::begin_tx(cluster::begin_group_tx_request&& r) { auto p = get_attached_partition(r.ntp); - if (!p || !p->catchup_lock->try_read_lock()) { + if (!p || !p->partition->is_leader()) { + return ss::make_ready_future( + make_begin_tx_reply(cluster::tx_errc::not_coordinator)); + } + if (!p->catchup_lock->try_read_lock()) { // transaction operations can't run in parallel with loading // state from the log (happens once per term change) vlog( @@ -1374,7 +1403,11 @@ group_manager::begin_tx(cluster::begin_group_tx_request&& r) { ss::future group_manager::abort_tx(cluster::abort_group_tx_request&& r) { auto p = get_attached_partition(r.ntp); - if (!p || !p->catchup_lock->try_read_lock()) { + if (!p || !p->partition->is_leader()) { + return ss::make_ready_future( + make_abort_tx_reply(cluster::tx_errc::not_coordinator)); + } + if (!p->catchup_lock->try_read_lock()) { // transaction operations can't run in parallel with loading // state from the log (happens once per term change) vlog( @@ -1499,9 +1532,10 @@ group_manager::offset_delete(offset_delete_request&& r) { deleted_offsets_set.insert(std::move(tp)); } - absl:: - flat_hash_map> - response_data; + absl::flat_hash_map< + model::topic, + chunked_vector> + response_data; for (const auto& tp : requested_deletions) { auto error = kafka::error_code::none; if (!deleted_offsets_set.contains(tp)) { diff --git a/src/v/kafka/server/group_manager.h b/src/v/kafka/server/group_manager.h index 877795ad70e4b..3c806c38e99cf 100644 --- a/src/v/kafka/server/group_manager.h +++ b/src/v/kafka/server/group_manager.h @@ -239,7 +239,7 @@ class group_manager { void handle_topic_delta(cluster::topic_table::delta_range_t); ss::future<> cleanup_removed_topic_partitions( - const std::vector&); + const chunked_vector&); ss::future<> handle_partition_leader_change( model::term_id, diff --git a/src/v/kafka/server/group_recovery_consumer.cc b/src/v/kafka/server/group_recovery_consumer.cc index ee44c150f6b09..ae9bb621036b0 100644 --- a/src/v/kafka/server/group_recovery_consumer.cc +++ b/src/v/kafka/server/group_recovery_consumer.cc @@ -73,6 +73,7 @@ group_recovery_consumer::operator()(model::record_batch batch) { if (_as.abort_requested()) { co_return ss::stop_iteration::yes; } + _state.last_read_offset = batch.last_offset(); if (batch.header().type == model::record_batch_type::raft_data) { _batch_base_offset = batch.base_offset(); co_await model::for_each_record(batch, [this](model::record& r) { diff --git a/src/v/kafka/server/group_recovery_consumer.h b/src/v/kafka/server/group_recovery_consumer.h index 9038533254af5..c4e9830e430f4 100644 --- a/src/v/kafka/server/group_recovery_consumer.h +++ b/src/v/kafka/server/group_recovery_consumer.h @@ -12,7 +12,7 @@ #include "kafka/server/group_metadata.h" #include "kafka/server/group_stm.h" -#include "kafka/types.h" +#include "model/fundamental.h" #include #include @@ -31,6 +31,7 @@ struct group_recovery_consumer_state { * retention feature is activated. see group::offset_metadata for more info. */ bool has_offset_retention_feature_fence{false}; + model::offset last_read_offset; }; class group_recovery_consumer { diff --git a/src/v/kafka/server/group_router.cc b/src/v/kafka/server/group_router.cc index c936633e19d46..5c434ccf5b30c 100644 --- a/src/v/kafka/server/group_router.cc +++ b/src/v/kafka/server/group_router.cc @@ -189,7 +189,7 @@ ss::future<> group_router::parallel_route_delete_groups( } ss::future> -group_router::delete_groups(std::vector groups) { +group_router::delete_groups(chunked_vector groups) { // partial results std::vector results; diff --git a/src/v/kafka/server/group_router.h b/src/v/kafka/server/group_router.h index da38251e37cb3..3dcd169ce72aa 100644 --- a/src/v/kafka/server/group_router.h +++ b/src/v/kafka/server/group_router.h @@ -93,7 +93,7 @@ class group_router final { ss::future describe_group(kafka::group_id g); ss::future> - delete_groups(std::vector groups); + delete_groups(chunked_vector groups); ss::sharded& coordinator_mapper() { return _coordinators; diff --git a/src/v/kafka/server/handlers/alter_configs.cc b/src/v/kafka/server/handlers/alter_configs.cc index 3dfc74ae2ae1e..407d80459666a 100644 --- a/src/v/kafka/server/handlers/alter_configs.cc +++ b/src/v/kafka/server/handlers/alter_configs.cc @@ -300,10 +300,10 @@ create_topic_properties_update( return update; } -static ss::future> +static ss::future> alter_topic_configuration( request_context& ctx, - std::vector resources, + chunked_vector resources, bool validate_only) { return do_alter_topics_configuration< alter_configs_resource, @@ -316,8 +316,8 @@ alter_topic_configuration( }); } -static ss::future> -alter_broker_configuartion(std::vector resources) { +static ss::future> +alter_broker_configuartion(chunked_vector resources) { return unsupported_broker_configuration< alter_configs_resource, alter_configs_resource_response>( @@ -346,13 +346,14 @@ ss::future alter_configs_handler::handle( alter_configs_resource_response, alter_configs_resource>( std::move(groupped), std::move(unauthorized_responsens)); + co_return co_await ctx.respond( assemble_alter_config_response< alter_configs_response, alter_configs_resource_response>(std::move(responses))); } - std::vector>> + std::vector>> futures; futures.reserve(2); futures.push_back(alter_topic_configuration( diff --git a/src/v/kafka/server/handlers/alter_partition_reassignments.cc b/src/v/kafka/server/handlers/alter_partition_reassignments.cc index 7a043eb098618..4ff9ab056217f 100644 --- a/src/v/kafka/server/handlers/alter_partition_reassignments.cc +++ b/src/v/kafka/server/handlers/alter_partition_reassignments.cc @@ -22,6 +22,7 @@ #include "kafka/server/protocol_utils.h" #include "model/namespace.h" #include "model/timeout_clock.h" +#include "utils/fragmented_vector.h" #include #include @@ -71,8 +72,7 @@ partitions_request_iterator validate_replicas( /** * @brief Validates partitions and places invalid partitions into @p resp_it - * @param begin starting position for a vector - * @param end stopping position for a vector + * @param topic the reassignable_topic to validate * @param resp_it a wrapper to std::back_inserter * @param topic_response a reassignable_topic_response to put errors into * @param alive_nodes list of RP nodes that are live @@ -83,10 +83,8 @@ partitions_request_iterator validate_replicas( */ template partitions_request_iterator validate_partitions( - partitions_request_iterator begin, - partitions_request_iterator end, + reassignable_topic& topic, std::back_insert_iterator resp_it, - reassignable_topic_response topic_response, std::vector alive_nodes, std::optional> tp_metadata) { @@ -95,6 +93,8 @@ partitions_request_iterator validate_partitions( // replicas.has_value are necessary. std::vector invalid_partitions; + auto begin = topic.partitions.begin(); + auto end = topic.partitions.end(); auto valid_partitions_end = validate_replicas( begin, @@ -210,9 +210,9 @@ partitions_request_iterator validate_partitions( // Store any invalid partitions in the response if (!invalid_partitions.empty()) { - topic_response.partitions = std::move(invalid_partitions); // resp_it is a wrapper to std::back_inserter - *resp_it = std::move(topic_response); + *resp_it = reassignable_topic_response{ + .name = topic.name, .partitions = std::move(invalid_partitions)}; } return valid_partitions_end; @@ -241,7 +241,9 @@ ss::future handle_partition( return octx.rctx.topics_frontend().move_partition_replicas( ntp, - std::move(*partition.replicas), + std::vector{ + std::make_move_iterator(partition.replicas->begin()), + std::make_move_iterator(partition.replicas->end())}, cluster::reconfiguration_policy::full_local_retention, model::timeout_clock::now() + octx.request.data.timeout_ms); @@ -289,10 +291,8 @@ ss::future do_handle_topic( model::topic_namespace_view{model::kafka_namespace, topic.name}); auto valid_partitions_end = validate_partitions( - topic.partitions.begin(), - topic.partitions.end(), + topic, std::back_inserter(octx.response.data.responses), - topic_response, std::move(alive_nodes), tp_metadata_ref); @@ -376,8 +376,8 @@ static ss::future do_handle(alter_op_context& octx) { return collect_alive_nodes(octx.rctx) .then([&octx](std::vector alive_nodes) { return ssx::parallel_transform( - octx.request.data.topics.begin(), - octx.request.data.topics.end(), + std::make_move_iterator(octx.request.data.topics.begin()), + std::make_move_iterator(octx.request.data.topics.end()), [&octx, alive_nodes = std::move(alive_nodes)]( reassignable_topic topic) mutable { return ss::do_with(topic, [&octx, alive_nodes](auto& topic) { @@ -388,7 +388,7 @@ static ss::future do_handle(alter_op_context& octx) { .then([&octx](std::vector topic_responses) { for (auto& t : topic_responses) { if (!t.partitions.empty()) { - octx.response.data.responses.push_back(t); + octx.response.data.responses.push_back(std::move(t)); } } return octx.rctx.respond(std::move(octx.response)); diff --git a/src/v/kafka/server/handlers/api_versions.cc b/src/v/kafka/server/handlers/api_versions.cc index 33268686430bb..453a08551790e 100644 --- a/src/v/kafka/server/handlers/api_versions.cc +++ b/src/v/kafka/server/handlers/api_versions.cc @@ -30,11 +30,11 @@ serialize_apis(type_list) { return apis; } -static std::vector +static chunked_vector get_supported_apis(bool is_idempotence_enabled, bool are_transactions_enabled) { auto all_api = serialize_apis(request_types{}); - std::vector filtered; + chunked_vector filtered; std::copy_if( all_api.begin(), all_api.end(), @@ -72,14 +72,14 @@ struct APIs { transactions = get_supported_apis(true, true); } - std::vector base; - std::vector idempotence; - std::vector transactions; + chunked_vector base; + chunked_vector idempotence; + chunked_vector transactions; }; static thread_local APIs supported_apis; -std::vector get_supported_apis() { +chunked_vector get_supported_apis() { return get_supported_apis( config::shard_local_cfg().enable_idempotence.value(), config::shard_local_cfg().enable_transactions.value()); @@ -106,11 +106,11 @@ api_versions_response api_versions_handler::handle_raw(request_context& ctx) { r.data.error_code == error_code::none || r.data.error_code == error_code::unsupported_version) { if (!ctx.is_idempotence_enabled()) { - r.data.api_keys = supported_apis.base; + r.data.api_keys = supported_apis.base.copy(); } else if (!ctx.are_transactions_enabled()) { - r.data.api_keys = supported_apis.idempotence; + r.data.api_keys = supported_apis.idempotence.copy(); } else { - r.data.api_keys = supported_apis.transactions; + r.data.api_keys = supported_apis.transactions.copy(); } } return r; diff --git a/src/v/kafka/server/handlers/api_versions.h b/src/v/kafka/server/handlers/api_versions.h index 7ab8921561ea8..c2d9006f3e52b 100644 --- a/src/v/kafka/server/handlers/api_versions.h +++ b/src/v/kafka/server/handlers/api_versions.h @@ -11,6 +11,7 @@ #pragma once #include "kafka/protocol/api_versions.h" #include "kafka/server/handlers/handler.h" +#include "utils/fragmented_vector.h" namespace kafka { @@ -24,6 +25,6 @@ struct api_versions_handler static api_versions_response handle_raw(request_context& ctx); }; -std::vector get_supported_apis(); +chunked_vector get_supported_apis(); } // namespace kafka diff --git a/src/v/kafka/server/handlers/configs/config_response_utils.cc b/src/v/kafka/server/handlers/configs/config_response_utils.cc new file mode 100644 index 0000000000000..92fc6231db002 --- /dev/null +++ b/src/v/kafka/server/handlers/configs/config_response_utils.cc @@ -0,0 +1,931 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "kafka/server/handlers/configs/config_response_utils.h" + +#include "cluster/metadata_cache.h" +#include "cluster/types.h" +#include "config/node_config.h" +#include "kafka/server/handlers/topics/types.h" + +#include + +namespace kafka { + +static bool config_property_requested( + const config_key_t& configuration_keys, + const std::string_view property_name) { + return !configuration_keys.has_value() + || std::find( + configuration_keys->begin(), + configuration_keys->end(), + property_name) + != configuration_keys->end(); +} + +template +static void add_config( + describe_configs_result& result, + std::string_view name, + T value, + describe_configs_source source) { + result.configs.push_back(describe_configs_resource_result{ + .name = ss::sstring(name), + .value = ssx::sformat("{}", value), + .config_source = source, + }); +} + +template +static void add_config_if_requested( + const config_key_t& configuration_keys, + describe_configs_result& result, + std::string_view name, + T value, + describe_configs_source source) { + if (config_property_requested(configuration_keys, name)) { + add_config(result, name, value, source); + } +} + +template +ss::sstring describe_as_string(const T& t) { + return ssx::sformat("{}", t); +} + +// Instantiate explicitly for unit testing +template ss::sstring describe_as_string(const int&); + +// Kafka protocol defines integral types by sizes. See +// https://kafka.apache.org/protocol.html +// Therefore we should also use type sizes for integrals and use Java type sizes +// as a guideline. See +// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html +template +constexpr auto num_bits = CHAR_BIT * sizeof(T); + +template +constexpr bool is_short = std::is_integral_v && !std::is_same_v + && num_bits <= 16; + +template +constexpr bool is_int = std::is_integral_v && num_bits > 16 + && num_bits <= 32; + +template +constexpr bool is_long = std::is_integral_v && num_bits > 32 + && num_bits <= 64; + +// property_config_type maps the datatype for a config property to +// describe_configs_type. Currently class_type and password are not used in +// Redpanda so we do not include checks for those types. You may find a similar +// mapping in Apache Kafka at +// https://github.com/apache/kafka/blob/be032735b39360df1a6de1a7feea8b4336e5bcc0/core/src/main/scala/kafka/server/ConfigHelper.scala +template +consteval describe_configs_type property_config_type() { + // clang-format off + constexpr auto is_string_type = std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + + constexpr auto is_long_type = is_long || + // Long type since seconds is atleast a 35-bit signed integral + // https://en.cppreference.com/w/cpp/chrono/duration + std::is_same_v || + // Long type since milliseconds is atleast a 45-bit signed integral + // https://en.cppreference.com/w/cpp/chrono/duration + std::is_same_v; + // clang-format on + + if constexpr ( + reflection::is_std_optional || reflection::is_tristate) { + return property_config_type(); + return property_config_type(); + } else if constexpr (std::is_same_v) { + return describe_configs_type::boolean; + } else if constexpr (is_string_type) { + return describe_configs_type::string; + } else if constexpr (is_short) { + return describe_configs_type::short_type; + } else if constexpr (is_int) { + return describe_configs_type::int_type; + } else if constexpr (is_long_type) { + return describe_configs_type::long_type; + } else if constexpr (std::is_floating_point_v) { + return describe_configs_type::double_type; + } else if constexpr (reflection::is_std_vector) { + return describe_configs_type::list; + } else { + static_assert( + utils::unsupported_type::value, + "Type name is not supported in describe_configs_type"); + } +} + +template +static void add_broker_config( + config_response_container_t& result, + std::string_view name, + const config::property& property, + bool include_synonyms, + std::optional documentation, + Func&& describe_f) { + describe_configs_source src + = property.is_overriden() ? describe_configs_source::static_broker_config + : describe_configs_source::default_config; + + std::vector synonyms; + if (include_synonyms) { + synonyms.reserve(2); + /** + * If value was overriden, include override + */ + if (src == describe_configs_source::static_broker_config) { + synonyms.push_back(describe_configs_synonym{ + .name = ss::sstring(property.name()), + .value = describe_f(property.value()), + .source = static_cast( + describe_configs_source::static_broker_config), + }); + } + /** + * If property is required it has no default + */ + if (!property.is_required()) { + synonyms.push_back(describe_configs_synonym{ + .name = ss::sstring(property.name()), + .value = describe_f(property.default_value()), + .source = static_cast( + describe_configs_source::default_config), + }); + } + } + + result.push_back(config_response{ + .name = ss::sstring(name), + .value = describe_f(property.value()), + .config_source = src, + .synonyms = std::move(synonyms), + .config_type = property_config_type(), + .documentation = documentation, + }); +} + +template +static void add_broker_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view name, + const config::property& property, + bool include_synonyms, + std::optional documentation, + Func&& describe_f) { + if (config_property_requested(config_keys, name)) { + add_broker_config( + result, + name, + property, + include_synonyms, + documentation, + std::forward(describe_f)); + } +} + +template +static void add_topic_config( + config_response_container_t& result, + std::string_view default_name, + const T& default_value, + std::string_view override_name, + const std::optional& overrides, + bool include_synonyms, + std::optional documentation, + Func&& describe_f) { + describe_configs_source src = overrides + ? describe_configs_source::topic + : describe_configs_source::default_config; + + std::vector synonyms; + if (include_synonyms) { + synonyms.reserve(2); + if (overrides) { + synonyms.push_back(describe_configs_synonym{ + .name = ss::sstring(override_name), + .value = describe_f(*overrides), + .source = static_cast(describe_configs_source::topic), + }); + } + synonyms.push_back(describe_configs_synonym{ + .name = ss::sstring(default_name), + .value = describe_f(default_value), + .source = static_cast( + describe_configs_source::default_config), + }); + } + + result.push_back(config_response{ + .name = ss::sstring(override_name), + .value = describe_f(overrides.value_or(default_value)), + .config_source = src, + .synonyms = std::move(synonyms), + .config_type = property_config_type(), + .documentation = documentation, + }); +} + +/** + * For faking DEFAULT_CONFIG status for properties that are actually + * topic overrides: cloud storage properties. We do not support cluster + * defaults for these, the values are always "sticky" to topics, but + * some Kafka clients insist that after an AlterConfig RPC, anything + * they didn't set should be DEFAULT_CONFIG. + * + * See https://github.com/redpanda-data/redpanda/issues/7451 + */ +template +std::optional +override_if_not_default(const std::optional& override, const T& def) { + if (override && override.value() != def) { + return override; + } else { + return std::nullopt; + } +} + +template +void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const T& default_value, + std::string_view override_name, + const std::optional& overrides, + bool include_synonyms, + std::optional documentation, + Func&& describe_f, + bool hide_default_override = false) { + if (config_property_requested(config_keys, override_name)) { + std::optional overrides_val; + if (hide_default_override) { + overrides_val = override_if_not_default(overrides, default_value); + } else { + overrides_val = overrides; + } + + add_topic_config( + result, + default_name, + default_value, + override_name, + overrides_val, + include_synonyms, + documentation, + std::forward(describe_f)); + } +} + +// Instantiate explicitly for unit testing +using describe_int_t = decltype(&describe_as_string); +template void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const int& default_value, + std::string_view override_name, + const std::optional& overrides, + bool include_synonyms, + std::optional documentation, + describe_int_t&& describe_f, + bool hide_default_override = false); + +template +static ss::sstring maybe_print_tristate(const tristate& tri) { + if (tri.is_disabled() || !tri.has_optional_value()) { + return "-1"; + } + return ssx::sformat("{}", tri.value()); +} + +template +static void add_topic_config( + config_response_container_t& result, + std::string_view default_name, + const std::optional& default_value, + std::string_view override_name, + const tristate& overrides, + bool include_synonyms, + std::optional documentation) { + // Wrap overrides in an optional because add_topic_config expects + // optional where S = tristate + std::optional> override_value; + if (overrides.is_disabled() || overrides.has_optional_value()) { + override_value = std::make_optional(overrides); + } + + add_topic_config( + result, + default_name, + tristate{default_value}, + override_name, + override_value, + include_synonyms, + documentation, + &maybe_print_tristate); +} + +template +void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const std::optional& default_value, + std::string_view override_name, + const tristate& overrides, + bool include_synonyms, + std::optional documentation) { + if (config_property_requested(config_keys, override_name)) { + add_topic_config( + result, + default_name, + default_value, + override_name, + overrides, + include_synonyms, + documentation); + } +} + +// Instantiate explicitly for unit testing +template void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const std::optional& default_value, + std::string_view override_name, + const tristate& overrides, + bool include_synonyms, + std::optional documentation); + +template +static void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view override_name, + const std::optional& overrides, + bool include_synonyms, + std::optional documentation, + Func&& describe_f) { + if (config_property_requested(config_keys, override_name)) { + add_topic_config( + result, + override_name, + overrides, + include_synonyms, + documentation, + std::forward(describe_f)); + } +} + +static ss::sstring +kafka_endpoint_format(const std::vector& endpoints) { + std::vector uris; + uris.reserve(endpoints.size()); + std::transform( + endpoints.cbegin(), + endpoints.cend(), + std::back_inserter(uris), + [](const model::broker_endpoint& ep) { + return ssx::sformat( + "{}://{}:{}", + (ep.name.empty() ? "plain" : ep.name), + ep.address.host(), + ep.address.port()); + }); + return ssx::sformat("{}", fmt::join(uris, ",")); +} + +static ss::sstring kafka_authn_endpoint_format( + const std::vector& endpoints) { + std::vector uris; + uris.reserve(endpoints.size()); + std::transform( + endpoints.cbegin(), + endpoints.cend(), + std::back_inserter(uris), + [](const config::broker_authn_endpoint& ep) { + return ssx::sformat( + "{}://{}:{}", + (ep.name.empty() ? "plain" : ep.name), + ep.address.host(), + ep.address.port()); + }); + return ssx::sformat("{}", fmt::join(uris, ",")); +} + +static inline std::optional maybe_make_documentation( + bool include_documentation, const std::string_view& docstring) { + return include_documentation ? std::make_optional(ss::sstring{docstring}) + : std::nullopt; +} + +config_response_container_t make_topic_configs( + const cluster::metadata_cache& metadata_cache, + const cluster::topic_properties& topic_properties, + const config_key_t& config_keys, + bool include_synonyms, + bool include_documentation) { + config_response_container_t result; + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().log_compression_type.name(), + metadata_cache.get_default_compression(), + topic_property_compression, + topic_properties.compression, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_compression_type.desc()), + &describe_as_string); + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().log_cleanup_policy.name(), + metadata_cache.get_default_cleanup_policy_bitflags(), + topic_property_cleanup_policy, + topic_properties.cleanup_policy_bitflags, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_cleanup_policy.desc()), + &describe_as_string, + true); + + const std::string_view docstring{ + topic_properties.is_compacted() + ? config::shard_local_cfg().compacted_log_segment_size.desc() + : config::shard_local_cfg().log_segment_size.desc()}; + add_topic_config_if_requested( + config_keys, + result, + topic_properties.is_compacted() + ? config::shard_local_cfg().compacted_log_segment_size.name() + : config::shard_local_cfg().log_segment_size.name(), + topic_properties.is_compacted() + ? metadata_cache.get_default_compacted_topic_segment_size() + : metadata_cache.get_default_segment_size(), + topic_property_segment_size, + topic_properties.segment_size, + include_synonyms, + maybe_make_documentation(include_documentation, docstring), + &describe_as_string); + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().log_retention_ms.name(), + metadata_cache.get_default_retention_duration(), + topic_property_retention_duration, + topic_properties.retention_duration, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_retention_ms.desc())); + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().retention_bytes.name(), + metadata_cache.get_default_retention_bytes(), + topic_property_retention_bytes, + topic_properties.retention_bytes, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().retention_bytes.desc())); + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().log_message_timestamp_type.name(), + metadata_cache.get_default_timestamp_type(), + topic_property_timestamp_type, + topic_properties.timestamp_type, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_message_timestamp_type.desc()), + &describe_as_string); + + add_topic_config_if_requested( + config_keys, + result, + config::shard_local_cfg().kafka_batch_max_bytes.name(), + metadata_cache.get_default_batch_max_bytes(), + topic_property_max_message_bytes, + topic_properties.batch_max_bytes, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().kafka_batch_max_bytes.desc()), + &describe_as_string); + + // Shadow indexing properties + add_topic_config_if_requested( + config_keys, + result, + topic_property_remote_read, + model::is_fetch_enabled( + metadata_cache.get_default_shadow_indexing_mode()), + topic_property_remote_read, + topic_properties.shadow_indexing.has_value() ? std::make_optional( + model::is_fetch_enabled(*topic_properties.shadow_indexing)) + : std::nullopt, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().cloud_storage_enable_remote_read.desc()), + &describe_as_string, + true); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_remote_write, + model::is_archival_enabled( + metadata_cache.get_default_shadow_indexing_mode()), + topic_property_remote_write, + topic_properties.shadow_indexing.has_value() ? std::make_optional( + model::is_archival_enabled(*topic_properties.shadow_indexing)) + : std::nullopt, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().cloud_storage_enable_remote_write.desc()), + &describe_as_string, + true); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_retention_local_target_bytes, + metadata_cache.get_default_retention_local_target_bytes(), + topic_property_retention_local_target_bytes, + topic_properties.retention_local_target_bytes, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().retention_local_target_bytes_default.desc())); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_retention_local_target_ms, + std::make_optional( + metadata_cache.get_default_retention_local_target_ms()), + topic_property_retention_local_target_ms, + topic_properties.retention_local_target_ms, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().retention_local_target_ms_default.desc())); + + if (config_property_requested(config_keys, topic_property_remote_delete)) { + add_topic_config( + result, + topic_property_remote_delete, + storage::ntp_config::default_remote_delete, + topic_property_remote_delete, + override_if_not_default( + std::make_optional(topic_properties.remote_delete), + storage::ntp_config::default_remote_delete), + true, + maybe_make_documentation( + include_documentation, + "Controls whether topic deletion should imply deletion in " + "S3"), + [](const bool& b) { return b ? "true" : "false"; }); + } + + add_topic_config_if_requested( + config_keys, + result, + topic_property_segment_ms, + metadata_cache.get_default_segment_ms(), + topic_property_segment_ms, + topic_properties.segment_ms, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_segment_ms.desc())); + + constexpr std::string_view key_validation + = "Enable validation of the schema id for keys on a record"; + constexpr std::string_view val_validation + = "Enable validation of the schema id for values on a record"; + constexpr bool validation_hide_default_override = true; + + switch (config::shard_local_cfg().enable_schema_id_validation()) { + case pandaproxy::schema_registry::schema_id_validation_mode::compat: { + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_key_schema_id_validation_compat, + metadata_cache.get_default_record_key_schema_id_validation(), + topic_property_record_key_schema_id_validation_compat, + topic_properties.record_key_schema_id_validation_compat, + include_synonyms, + maybe_make_documentation(include_documentation, key_validation), + &describe_as_string, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_key_subject_name_strategy_compat, + metadata_cache.get_default_record_key_subject_name_strategy(), + topic_property_record_key_subject_name_strategy_compat, + topic_properties.record_key_subject_name_strategy_compat, + include_synonyms, + maybe_make_documentation( + include_documentation, + fmt::format( + "The subject name strategy for keys if {} is enabled", + topic_property_record_key_schema_id_validation_compat)), + [](auto sns) { return ss::sstring(to_string_view_compat(sns)); }, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_value_schema_id_validation_compat, + metadata_cache.get_default_record_value_schema_id_validation(), + topic_property_record_value_schema_id_validation_compat, + topic_properties.record_value_schema_id_validation_compat, + include_synonyms, + maybe_make_documentation(include_documentation, val_validation), + &describe_as_string, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_value_subject_name_strategy_compat, + metadata_cache.get_default_record_value_subject_name_strategy(), + topic_property_record_value_subject_name_strategy_compat, + topic_properties.record_value_subject_name_strategy_compat, + include_synonyms, + maybe_make_documentation( + include_documentation, + fmt::format( + "The subject name strategy for values if {} is enabled", + topic_property_record_value_schema_id_validation_compat)), + [](auto sns) { return ss::sstring(to_string_view_compat(sns)); }, + validation_hide_default_override); + [[fallthrough]]; + } + case pandaproxy::schema_registry::schema_id_validation_mode::redpanda: { + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_key_schema_id_validation, + metadata_cache.get_default_record_key_schema_id_validation(), + topic_property_record_key_schema_id_validation, + topic_properties.record_key_schema_id_validation, + include_synonyms, + maybe_make_documentation(include_documentation, key_validation), + &describe_as_string, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_key_subject_name_strategy, + metadata_cache.get_default_record_key_subject_name_strategy(), + topic_property_record_key_subject_name_strategy, + topic_properties.record_key_subject_name_strategy, + include_synonyms, + maybe_make_documentation( + include_documentation, + fmt::format( + "The subject name strategy for keys if {} is enabled", + topic_property_record_key_schema_id_validation)), + &describe_as_string< + pandaproxy::schema_registry::subject_name_strategy>, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_value_schema_id_validation, + metadata_cache.get_default_record_value_schema_id_validation(), + topic_property_record_value_schema_id_validation, + topic_properties.record_value_schema_id_validation, + include_synonyms, + maybe_make_documentation(include_documentation, val_validation), + &describe_as_string, + validation_hide_default_override); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_record_value_subject_name_strategy, + metadata_cache.get_default_record_value_subject_name_strategy(), + topic_property_record_value_subject_name_strategy, + topic_properties.record_value_subject_name_strategy, + include_synonyms, + maybe_make_documentation( + include_documentation, + fmt::format( + "The subject name strategy for values if {} is enabled", + topic_property_record_value_schema_id_validation)), + &describe_as_string< + pandaproxy::schema_registry::subject_name_strategy>, + validation_hide_default_override); + [[fallthrough]]; + } + case pandaproxy::schema_registry::schema_id_validation_mode::none: { + break; + } + } + + add_topic_config_if_requested( + config_keys, + result, + topic_property_initial_retention_local_target_bytes, + metadata_cache.get_default_initial_retention_local_target_bytes(), + topic_property_initial_retention_local_target_bytes, + topic_properties.initial_retention_local_target_bytes, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg() + .initial_retention_local_target_bytes_default.desc())); + + add_topic_config_if_requested( + config_keys, + result, + topic_property_initial_retention_local_target_ms, + metadata_cache.get_default_initial_retention_local_target_ms(), + topic_property_initial_retention_local_target_ms, + topic_properties.initial_retention_local_target_ms, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg() + .initial_retention_local_target_ms_default.desc())); + + return result; +} + +config_response_container_t make_broker_configs( + const config_key_t& config_keys, + bool include_synonyms, + bool include_documentation) { + config_response_container_t result; + + add_broker_config_if_requested( + config_keys, + result, + "listeners", + config::node().kafka_api, + include_synonyms, + maybe_make_documentation( + include_documentation, config::node().kafka_api.desc()), + &kafka_authn_endpoint_format); + + add_broker_config_if_requested( + config_keys, + result, + "advertised.listeners", + config::node().advertised_kafka_api_property(), + include_synonyms, + maybe_make_documentation( + include_documentation, + config::node().advertised_kafka_api_property().desc()), + &kafka_endpoint_format); + + add_broker_config_if_requested( + config_keys, + result, + "log.segment.bytes", + config::shard_local_cfg().log_segment_size, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_segment_size.desc()), + &describe_as_string); + + add_broker_config_if_requested( + config_keys, + result, + "log.retention.bytes", + config::shard_local_cfg().retention_bytes, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().retention_bytes.desc()), + [](std::optional sz) { + return ssx::sformat("{}", sz ? sz.value() : -1); + }); + + add_broker_config_if_requested( + config_keys, + result, + "log.retention.ms", + config::shard_local_cfg().log_retention_ms, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().log_retention_ms.desc()), + [](const std::optional& ret) { + return ssx::sformat("{}", ret.value_or(-1ms).count()); + }); + + add_broker_config_if_requested( + config_keys, + result, + "num.partitions", + config::shard_local_cfg().default_topic_partitions, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().default_topic_partitions.desc()), + &describe_as_string); + + add_broker_config_if_requested( + config_keys, + result, + "default.replication.factor", + config::shard_local_cfg().default_topic_replication, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().default_topic_replication.desc()), + &describe_as_string); + + add_broker_config_if_requested( + config_keys, + result, + "log.dirs", + config::node().data_directory, + include_synonyms, + maybe_make_documentation( + include_documentation, config::node().data_directory.desc()), + [](const config::data_directory_path& path) { + return path.as_sstring(); + }); + + add_broker_config_if_requested( + config_keys, + result, + "auto.create.topics.enable", + config::shard_local_cfg().auto_create_topics_enabled, + include_synonyms, + maybe_make_documentation( + include_documentation, + config::shard_local_cfg().auto_create_topics_enabled.desc()), + &describe_as_string); + + return result; +} + +describe_configs_resource_result config_response::to_describe_config() { + return { + .name = name, + .value = value, + .read_only = read_only, + .is_default = is_default, + .config_source = config_source, + .is_sensitive = is_sensitive, + .synonyms = synonyms, + .config_type = config_type, + .documentation = documentation, + }; +}; + +creatable_topic_configs config_response::to_create_config() { + return { + .name = name, + .value = value, + .read_only = read_only, + .config_source = config_source, + .is_sensitive = is_sensitive, + }; +}; + +} // namespace kafka diff --git a/src/v/kafka/server/handlers/configs/config_response_utils.h b/src/v/kafka/server/handlers/configs/config_response_utils.h new file mode 100644 index 0000000000000..02e0091341f2e --- /dev/null +++ b/src/v/kafka/server/handlers/configs/config_response_utils.h @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "cluster/types.h" +#include "kafka/protocol/describe_configs.h" +#include "kafka/protocol/schemata/create_topics_response.h" +#include "utils/fragmented_vector.h" + +#include +#include + +namespace kafka { + +struct config_response { + ss::sstring name{}; + std::optional value{}; + bool read_only{}; + bool is_default{}; + kafka::describe_configs_source config_source{-1}; + bool is_sensitive{}; + std::vector synonyms{}; + kafka::describe_configs_type config_type{0}; + std::optional documentation{}; + + describe_configs_resource_result to_describe_config(); + creatable_topic_configs to_create_config(); +}; + +using config_response_container_t = chunked_vector; +using config_key_t = std::optional>; + +config_response_container_t make_topic_configs( + const cluster::metadata_cache& metadata_cache, + const cluster::topic_properties& topic_properties, + const config_key_t& config_keys, + bool include_synonyms, + bool include_documentation); + +config_response_container_t make_broker_configs( + const config_key_t& config_keys, + bool include_synonyms, + bool include_documentation); + +} // namespace kafka diff --git a/src/v/kafka/server/handlers/configs/config_utils.h b/src/v/kafka/server/handlers/configs/config_utils.h index 7d0f26690332f..d550c37819d05 100644 --- a/src/v/kafka/server/handlers/configs/config_utils.h +++ b/src/v/kafka/server/handlers/configs/config_utils.h @@ -27,6 +27,7 @@ #include "pandaproxy/schema_registry/schema_id_validation.h" #include "pandaproxy/schema_registry/subject_name_strategy.h" #include "security/acl.h" +#include "utils/fragmented_vector.h" #include #include @@ -39,12 +40,12 @@ namespace kafka { template struct groupped_resources { - std::vector topic_changes; - std::vector broker_changes; + chunked_vector topic_changes; + chunked_vector broker_changes; }; template -groupped_resources group_alter_config_resources(std::vector req) { +groupped_resources group_alter_config_resources(chunked_vector req) { groupped_resources ret; for (auto& res : req) { switch (config_resource_type(res.resource_type)) { @@ -59,7 +60,7 @@ groupped_resources group_alter_config_resources(std::vector req) { } template -T assemble_alter_config_response(std::vector> responses) { +T assemble_alter_config_response(std::vector> responses) { T response; for (auto& v : responses) { std::move( @@ -79,9 +80,9 @@ T make_error_alter_config_resource_response( } template -std::vector> make_audit_failure_response( - groupped_resources&& resources, std::vector unauthorized_responses) { - std::vector responses; +std::vector> make_audit_failure_response( + groupped_resources&& resources, chunked_vector unauthorized_responses) { + chunked_vector responses; auto gen_resp = [](const T& res) { return make_error_alter_config_resource_response( @@ -117,7 +118,9 @@ std::vector> make_audit_failure_response( unauthorized_responses.end(), std::back_inserter(responses)); - return {responses}; + std::vector> res; + res.push_back(std::move(responses)); + return res; } /** @@ -125,9 +128,9 @@ std::vector> make_audit_failure_response( * responsens and modifies passed in group_resources */ template -std::vector authorize_alter_config_resources( +chunked_vector authorize_alter_config_resources( request_context& ctx, groupped_resources& to_authorize) { - std::vector not_authorized; + chunked_vector not_authorized; /** * Check broker configuration authorization */ @@ -193,16 +196,18 @@ std::vector authorize_alter_config_resources( res, error_code::topic_authorization_failed); }); - to_authorize.topic_changes.erase( - unauthorized_it, to_authorize.topic_changes.end()); + to_authorize.topic_changes.erase_to_end(unauthorized_it); return not_authorized; } template -ss::future> do_alter_topics_configuration( - request_context& ctx, std::vector resources, bool validate_only, Func f) { - std::vector responses; +ss::future> do_alter_topics_configuration( + request_context& ctx, + chunked_vector resources, + bool validate_only, + Func f) { + chunked_vector responses; responses.reserve(resources.size()); absl::node_hash_set topic_names; @@ -256,9 +261,9 @@ ss::future> do_alter_topics_configuration( } template -ss::future> unsupported_broker_configuration( - std::vector resources, std::string_view const msg) { - std::vector responses; +ss::future> unsupported_broker_configuration( + chunked_vector resources, std::string_view const msg) { + chunked_vector responses; responses.reserve(resources.size()); std::transform( resources.begin(), @@ -269,7 +274,7 @@ ss::future> unsupported_broker_configuration( resource, error_code::invalid_config, ss::sstring(msg)); }); - return ss::make_ready_future>(std::move(responses)); + return ss::make_ready_future>(std::move(responses)); } class validation_error final : std::exception { diff --git a/src/v/kafka/server/handlers/create_partitions.cc b/src/v/kafka/server/handlers/create_partitions.cc index 5109da9319294..145b282b35b6c 100644 --- a/src/v/kafka/server/handlers/create_partitions.cc +++ b/src/v/kafka/server/handlers/create_partitions.cc @@ -22,6 +22,7 @@ #include "model/metadata.h" #include "model/namespace.h" #include "model/timeout_clock.h" +#include "utils/fragmented_vector.h" #include #include @@ -43,7 +44,7 @@ make_result(const create_partitions_topic& tp, error_code ec) { }; } -using request_iterator = std::vector::iterator; +using request_iterator = chunked_vector::iterator; template request_iterator validate_range_duplicates( @@ -152,7 +153,7 @@ ss::future create_partitions_handler::handle( create_partitions_response resp; if (request.data.topics.empty()) { - co_return co_await ctx.respond(resp); + co_return co_await ctx.respond(std::move(resp)); } resp.data.results.reserve(request.data.topics.size()); @@ -315,6 +316,7 @@ ss::future create_partitions_handler::handle( return create_partitions_topic_result{ .name = std::move(r.tp_ns.tp), .error_code = map_topic_error_code(r.ec), + .error_message = cluster::make_error_code(r.ec).message(), }; }); diff --git a/src/v/kafka/server/handlers/create_topics.cc b/src/v/kafka/server/handlers/create_topics.cc index 84a20dc71363d..79c81825888ee 100644 --- a/src/v/kafka/server/handlers/create_topics.cc +++ b/src/v/kafka/server/handlers/create_topics.cc @@ -12,9 +12,11 @@ #include "cluster/cluster_utils.h" #include "cluster/metadata_cache.h" #include "cluster/topics_frontend.h" +#include "cluster/types.h" #include "config/configuration.h" #include "kafka/protocol/errors.h" #include "kafka/protocol/timeout.h" +#include "kafka/server/handlers/configs/config_response_utils.h" #include "kafka/server/handlers/topics/topic_utils.h" #include "kafka/server/handlers/topics/types.h" #include "kafka/server/quota_manager.h" @@ -31,6 +33,7 @@ #include #include +#include #include namespace kafka { @@ -86,24 +89,6 @@ using validators = make_validator_types< subject_name_strategy_validator, replication_factor_must_be_greater_or_equal_to_minimum>; -static std::vector -properties_to_result_configs(config_map_t config_map) { - std::vector configs; - configs.reserve(config_map.size()); - std::transform( - config_map.begin(), - config_map.end(), - std::back_inserter(configs), - [](auto& cfg) { - return creatable_topic_configs{ - .name = cfg.first, - .value = {std::move(cfg.second)}, - .config_source = kafka::describe_configs_source::default_config, - }; - }); - return configs; -} - static void append_topic_configs(request_context& ctx, create_topics_response& response) { for (auto& ct_result : response.data.topics) { @@ -114,9 +99,8 @@ append_topic_configs(request_context& ctx, create_topics_response& response) { auto cfg = ctx.metadata_cache().get_topic_cfg( model::topic_namespace_view{model::kafka_namespace, ct_result.name}); if (cfg) { - auto config_map = from_cluster_type(cfg->properties); - ct_result.configs = { - properties_to_result_configs(std::move(config_map))}; + ct_result.configs = std::make_optional( + report_topic_configs(ctx.metadata_cache(), cfg->properties)); ct_result.topic_config_error_code = kafka::error_code::none; } else { // Topic was sucessfully created but metadata request did not @@ -210,7 +194,7 @@ ss::future create_topics_handler::handle( append_topic_configs(ctx, err_resp); } - co_return co_await ctx.respond(err_resp); + co_return co_await ctx.respond(std::move(err_resp)); } // fill in defaults if necessary @@ -276,8 +260,8 @@ ss::future create_topics_handler::handle( // topic creation and the real deal auto default_properties = ctx.metadata_cache().get_default_properties(); - result.configs = {properties_to_result_configs( - from_cluster_type(default_properties))}; + result.configs = std::make_optional(report_topic_configs( + ctx.metadata_cache(), default_properties)); } return result; }); @@ -334,7 +318,7 @@ ss::future create_topics_handler::handle( append_topic_configs(ctx, response); } - co_return co_await ctx.respond(response); + co_return co_await ctx.respond(std::move(response)); } } // namespace kafka diff --git a/src/v/kafka/server/handlers/delete_acls.cc b/src/v/kafka/server/handlers/delete_acls.cc index c2a8dbd58e9c4..0546f339bf438 100644 --- a/src/v/kafka/server/handlers/delete_acls.cc +++ b/src/v/kafka/server/handlers/delete_acls.cc @@ -10,6 +10,7 @@ */ #include "kafka/server/handlers/delete_acls.h" +#include "cluster/security_frontend.h" #include "kafka/protocol/errors.h" #include "kafka/protocol/schemata/delete_acls_response.h" #include "kafka/server/errors.h" @@ -17,6 +18,7 @@ #include "kafka/server/request_context.h" #include "kafka/server/response.h" #include "model/fundamental.h" +#include "utils/fragmented_vector.h" #include #include @@ -96,7 +98,11 @@ ss::future delete_acls_handler::handle( result.error_code = error_code::broker_not_available; result.error_message = "Broker not available - audit system failure"; delete_acls_response resp; - resp.data.filter_results.assign(request.data.filters.size(), result); + resp.data.filter_results.reserve(request.data.filters.size()); + std::fill_n( + std::back_inserter(resp.data.filter_results), + request.data.filters.size(), + result); co_return co_await ctx.respond(std::move(resp)); } @@ -104,7 +110,11 @@ ss::future delete_acls_handler::handle( delete_acls_filter_result result; result.error_code = error_code::cluster_authorization_failed; delete_acls_response resp; - resp.data.filter_results.assign(request.data.filters.size(), result); + resp.data.filter_results.reserve(request.data.filters.size()); + std::fill_n( + std::back_inserter(resp.data.filter_results), + request.data.filters.size(), + result); co_return co_await ctx.respond(std::move(resp)); } @@ -125,10 +135,12 @@ ss::future delete_acls_handler::handle( results.size(), num_filters); - response.data.filter_results.assign( - result_index.size(), - delete_acls_filter_result{ - .error_code = error_code::unknown_server_error}); + response.data.filter_results.reserve(result_index.size()); + for ([[maybe_unused]] auto _ : + boost::irange(result_index.size())) { + response.data.filter_results.push_back(delete_acls_filter_result{ + .error_code = error_code::unknown_server_error}); + } co_return co_await ctx.respond(std::move(response)); } @@ -143,7 +155,7 @@ ss::future delete_acls_handler::handle( .matching_acls = bindings_to_delete_result(results[i].bindings), }); }, - [&response](delete_acls_filter_result r) { + [&response](delete_acls_filter_result& r) { response.data.filter_results.push_back(std::move(r)); }); } diff --git a/src/v/kafka/server/handlers/delete_records.cc b/src/v/kafka/server/handlers/delete_records.cc index 68733d0f915bb..dcc2b9ef23116 100644 --- a/src/v/kafka/server/handlers/delete_records.cc +++ b/src/v/kafka/server/handlers/delete_records.cc @@ -17,6 +17,7 @@ #include "kafka/server/partition_proxy.h" #include "model/fundamental.h" #include "model/ktp.h" +#include "utils/fragmented_vector.h" #include @@ -32,9 +33,9 @@ constexpr auto invalid_low_watermark = model::offset(-1); /// indicate to truncate at the current partition high watermark constexpr auto at_current_high_watermark = model::offset(-1); -std::vector +chunked_vector make_partition_errors(const delete_records_topic& t, error_code ec) { - std::vector r; + chunked_vector r; for (const auto& p : t.partitions) { r.push_back(delete_records_partition_result{ .partition_index = p.partition_index, @@ -46,7 +47,7 @@ make_partition_errors(const delete_records_topic& t, error_code ec) { /// Performs validation of topics, any failures will result in a list of /// partitions that all contain the identical error codes -std::vector +chunked_vector validate_at_topic_level(request_context& ctx, const delete_records_topic& t) { if (ctx.recovery_mode_enabled()) { return make_partition_errors(t, error_code::policy_violation); @@ -72,7 +73,7 @@ validate_at_topic_level(request_context& ctx, const delete_records_topic& t) { return std::find_if( nodelete_topics.begin(), nodelete_topics.end(), - [t](const ss::sstring& name) { return name == t.name; }) + [&t](const ss::sstring& name) { return name == t.name; }) != nodelete_topics.end(); }; @@ -275,7 +276,7 @@ delete_records_handler::handle(request_context ctx, ss::smp_service_group) { auto results = co_await ss::when_all_succeed(fs.begin(), fs.end()); /// Group results by topic - using partition_results = std::vector; + using partition_results = chunked_vector; absl::flat_hash_map group_by_topic; for (auto& [name, partitions] : results) { group_by_topic[name].push_back(std::move(partitions)); diff --git a/src/v/kafka/server/handlers/describe_configs.cc b/src/v/kafka/server/handlers/describe_configs.cc index 021c4efad6e95..68ff3476d6b87 100644 --- a/src/v/kafka/server/handlers/describe_configs.cc +++ b/src/v/kafka/server/handlers/describe_configs.cc @@ -10,10 +10,12 @@ #include "kafka/server/handlers/describe_configs.h" #include "cluster/metadata_cache.h" +#include "cluster/types.h" #include "config/configuration.h" #include "config/data_directory_path.h" #include "config/node_config.h" #include "kafka/protocol/errors.h" +#include "kafka/server/handlers/configs/config_response_utils.h" #include "kafka/server/handlers/topics/topic_utils.h" #include "kafka/server/handlers/topics/types.h" #include "kafka/server/request_context.h" @@ -40,378 +42,26 @@ namespace kafka { -static bool config_property_requested( - const std::optional>& configuration_keys, - const std::string_view property_name) { - return !configuration_keys.has_value() - || std::find( - configuration_keys->begin(), - configuration_keys->end(), - property_name) - != configuration_keys->end(); -} - -template -static void add_config( - describe_configs_result& result, - std::string_view name, - T value, - describe_configs_source source) { - result.configs.push_back(describe_configs_resource_result{ - .name = ss::sstring(name), - .value = ssx::sformat("{}", value), - .config_source = source, - }); -} - -template -static void add_config_if_requested( - const describe_configs_resource& resource, - describe_configs_result& result, - std::string_view name, - T value, - describe_configs_source source) { - if (config_property_requested(resource.configuration_keys, name)) { - add_config(result, name, value, source); - } -} - -template -static ss::sstring describe_as_string(const T& t) { - return ssx::sformat("{}", t); -} - -// Kafka protocol defines integral types by sizes. See -// https://kafka.apache.org/protocol.html -// Therefore we should also use type sizes for integrals and use Java type sizes -// as a guideline. See -// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html -template -constexpr auto num_bits = CHAR_BIT * sizeof(T); - -template -constexpr bool is_short = std::is_integral_v && !std::is_same_v - && num_bits <= 16; - -template -constexpr bool is_int = std::is_integral_v && num_bits > 16 - && num_bits <= 32; - -template -constexpr bool is_long = std::is_integral_v && num_bits > 32 - && num_bits <= 64; - -// property_config_type maps the datatype for a config property to -// describe_configs_type. Currently class_type and password are not used in -// Redpanda so we do not include checks for those types. You may find a similar -// mapping in Apache Kafka at -// https://github.com/apache/kafka/blob/be032735b39360df1a6de1a7feea8b4336e5bcc0/core/src/main/scala/kafka/server/ConfigHelper.scala -template -consteval describe_configs_type property_config_type() { - // clang-format off - constexpr auto is_string_type = std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; - - constexpr auto is_long_type = is_long || - // Long type since seconds is atleast a 35-bit signed integral - // https://en.cppreference.com/w/cpp/chrono/duration - std::is_same_v || - // Long type since milliseconds is atleast a 45-bit signed integral - // https://en.cppreference.com/w/cpp/chrono/duration - std::is_same_v; - // clang-format on - - if constexpr ( - reflection::is_std_optional || reflection::is_tristate) { - return property_config_type(); - return property_config_type(); - } else if constexpr (std::is_same_v) { - return describe_configs_type::boolean; - } else if constexpr (is_string_type) { - return describe_configs_type::string; - } else if constexpr (is_short) { - return describe_configs_type::short_type; - } else if constexpr (is_int) { - return describe_configs_type::int_type; - } else if constexpr (is_long_type) { - return describe_configs_type::long_type; - } else if constexpr (std::is_floating_point_v) { - return describe_configs_type::double_type; - } else if constexpr (reflection::is_std_vector) { - return describe_configs_type::list; - } else { - static_assert( - utils::unsupported_type::value, - "Type name is not supported in describe_configs_type"); - } -} - -template -static void add_broker_config( - describe_configs_result& result, - std::string_view name, - const config::property& property, - bool include_synonyms, - std::optional documentation, - Func&& describe_f) { - describe_configs_source src - = property.is_overriden() ? describe_configs_source::static_broker_config - : describe_configs_source::default_config; - - std::vector synonyms; - if (include_synonyms) { - synonyms.reserve(2); - /** - * If value was overriden, include override - */ - if (src == describe_configs_source::static_broker_config) { - synonyms.push_back(describe_configs_synonym{ - .name = ss::sstring(property.name()), - .value = describe_f(property.value()), - .source = static_cast( - describe_configs_source::static_broker_config), - }); - } - /** - * If property is required it has no default - */ - if (!property.is_required()) { - synonyms.push_back(describe_configs_synonym{ - .name = ss::sstring(property.name()), - .value = describe_f(property.default_value()), - .source = static_cast( - describe_configs_source::default_config), - }); - } - } - - result.configs.push_back(describe_configs_resource_result{ - .name = ss::sstring(name), - .value = describe_f(property.value()), - .config_source = src, - .synonyms = std::move(synonyms), - .config_type = property_config_type(), - .documentation = documentation, - }); -} - -template -static void add_broker_config_if_requested( - const describe_configs_resource& resource, - describe_configs_result& result, - std::string_view name, - const config::property& property, - bool include_synonyms, - std::optional documentation, - Func&& describe_f) { - if (config_property_requested(resource.configuration_keys, name)) { - add_broker_config( - result, - name, - property, - include_synonyms, - documentation, - std::forward(describe_f)); - } -} - -template -static void add_topic_config( - describe_configs_result& result, - std::string_view default_name, - const T& default_value, - std::string_view override_name, - const std::optional& overrides, - bool include_synonyms, - std::optional documentation, - Func&& describe_f) { - describe_configs_source src = overrides - ? describe_configs_source::topic - : describe_configs_source::default_config; - - std::vector synonyms; - if (include_synonyms) { - synonyms.reserve(2); - if (overrides) { - synonyms.push_back(describe_configs_synonym{ - .name = ss::sstring(override_name), - .value = describe_f(*overrides), - .source = static_cast(describe_configs_source::topic), - }); - } - synonyms.push_back(describe_configs_synonym{ - .name = ss::sstring(default_name), - .value = describe_f(default_value), - .source = static_cast( - describe_configs_source::default_config), - }); - } - - result.configs.push_back(describe_configs_resource_result{ - .name = ss::sstring(override_name), - .value = describe_f(overrides.value_or(default_value)), - .config_source = src, - .synonyms = std::move(synonyms), - .config_type = property_config_type(), - .documentation = documentation, - }); -} - -/** - * For faking DEFAULT_CONFIG status for properties that are actually - * topic overrides: cloud storage properties. We do not support cluster - * defaults for these, the values are always "sticky" to topics, but - * some Kafka clients insist that after an AlterConfig RPC, anything - * they didn't set should be DEFAULT_CONFIG. - * - * See https://github.com/redpanda-data/redpanda/issues/7451 - */ -template -std::optional -override_if_not_default(const std::optional& override, const T& def) { - if (override && override.value() != def) { - return override; - } else { - return std::nullopt; - } -} - -template -static void add_topic_config_if_requested( +static void report_topic_config( const describe_configs_resource& resource, describe_configs_result& result, - std::string_view default_name, - const T& default_value, - std::string_view override_name, - const std::optional& overrides, + const cluster::metadata_cache& metadata_cache, + const cluster::topic_properties& topic_properties, bool include_synonyms, - std::optional documentation, - Func&& describe_f, - bool hide_default_override = false) { - if (config_property_requested(resource.configuration_keys, override_name)) { - std::optional overrides_val; - if (hide_default_override) { - overrides_val = override_if_not_default(overrides, default_value); - } else { - overrides_val = overrides; - } - - add_topic_config( - result, - default_name, - default_value, - override_name, - overrides_val, - include_synonyms, - documentation, - std::forward(describe_f)); - } -} - -template -static ss::sstring maybe_print_tristate(const tristate& tri) { - if (tri.is_disabled() || !tri.has_optional_value()) { - return "-1"; - } - return ssx::sformat("{}", tri.value()); -} - -template -static void add_topic_config( - describe_configs_result& result, - std::string_view default_name, - const std::optional& default_value, - std::string_view override_name, - const tristate& overrides, - bool include_synonyms, - std::optional documentation) { - // Wrap overrides in an optional because add_topic_config expects - // optional where S = tristate - std::optional> override_value; - if (overrides.is_disabled() || overrides.has_optional_value()) { - override_value = std::make_optional(overrides); - } - - add_topic_config( - result, - default_name, - tristate{default_value}, - override_name, - override_value, + bool include_documentation) { + auto res = make_topic_configs( + metadata_cache, + topic_properties, + resource.configuration_keys, include_synonyms, - documentation, - &maybe_print_tristate); -} + include_documentation); -template -static void add_topic_config_if_requested( - const describe_configs_resource& resource, - describe_configs_result& result, - std::string_view default_name, - const std::optional& default_value, - std::string_view override_name, - const tristate& overrides, - bool include_synonyms, - std::optional documentation) { - if (config_property_requested(resource.configuration_keys, override_name)) { - add_topic_config( - result, - default_name, - default_value, - override_name, - overrides, - include_synonyms, - documentation); + result.configs.reserve(res.size()); + for (auto& conf : res) { + result.configs.push_back(conf.to_describe_config()); } } -static ss::sstring -kafka_endpoint_format(const std::vector& endpoints) { - std::vector uris; - uris.reserve(endpoints.size()); - std::transform( - endpoints.cbegin(), - endpoints.cend(), - std::back_inserter(uris), - [](const model::broker_endpoint& ep) { - return ssx::sformat( - "{}://{}:{}", - (ep.name.empty() ? "plain" : ep.name), - ep.address.host(), - ep.address.port()); - }); - return ssx::sformat("{}", fmt::join(uris, ",")); -} - -static ss::sstring kafka_authn_endpoint_format( - const std::vector& endpoints) { - std::vector uris; - uris.reserve(endpoints.size()); - std::transform( - endpoints.cbegin(), - endpoints.cend(), - std::back_inserter(uris), - [](const config::broker_authn_endpoint& ep) { - return ssx::sformat( - "{}://{}:{}", - (ep.name.empty() ? "plain" : ep.name), - ep.address.host(), - ep.address.port()); - }); - return ssx::sformat("{}", fmt::join(uris, ",")); -} - -static inline std::optional maybe_make_documentation( - bool include_documentation, const std::string_view& docstring) { - return include_documentation ? std::make_optional(ss::sstring{docstring}) - : std::nullopt; -} - static void report_broker_config( const describe_configs_resource& resource, describe_configs_result& result, @@ -441,132 +91,13 @@ static void report_broker_config( } } - add_broker_config_if_requested( - resource, - result, - "listeners", - config::node().kafka_api, - include_synonyms, - maybe_make_documentation( - include_documentation, config::node().kafka_api.desc()), - &kafka_authn_endpoint_format); - - add_broker_config_if_requested( - resource, - result, - "advertised.listeners", - config::node().advertised_kafka_api_property(), - include_synonyms, - maybe_make_documentation( - include_documentation, - config::node().advertised_kafka_api_property().desc()), - &kafka_endpoint_format); - - add_broker_config_if_requested( - resource, - result, - "log.segment.bytes", - config::shard_local_cfg().log_segment_size, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().log_segment_size.desc()), - &describe_as_string); - - add_broker_config_if_requested( - resource, - result, - "log.retention.bytes", - config::shard_local_cfg().retention_bytes, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().retention_bytes.desc()), - [](std::optional sz) { - return ssx::sformat("{}", sz ? sz.value() : -1); - }); - - add_broker_config_if_requested( - resource, - result, - "log.retention.ms", - config::shard_local_cfg().log_retention_ms, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().log_retention_ms.desc()), - [](const std::optional& ret) { - return ssx::sformat("{}", ret.value_or(-1ms).count()); - }); - - add_broker_config_if_requested( - resource, - result, - "num.partitions", - config::shard_local_cfg().default_topic_partitions, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().default_topic_partitions.desc()), - &describe_as_string); - - add_broker_config_if_requested( - resource, - result, - "default.replication.factor", - config::shard_local_cfg().default_topic_replication, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().default_topic_replication.desc()), - &describe_as_string); + auto res = make_broker_configs( + resource.configuration_keys, include_synonyms, include_documentation); - add_broker_config_if_requested( - resource, - result, - "log.dirs", - config::node().data_directory, - include_synonyms, - maybe_make_documentation( - include_documentation, config::node().data_directory.desc()), - [](const config::data_directory_path& path) { - return path.as_sstring(); - }); - - add_broker_config_if_requested( - resource, - result, - "auto.create.topics.enable", - config::shard_local_cfg().auto_create_topics_enabled, - include_synonyms, - maybe_make_documentation( - include_documentation, - config::shard_local_cfg().auto_create_topics_enabled.desc()), - &describe_as_string); -} - -int64_t describe_retention_duration( - tristate& overrides, - std::optional def) { - if (overrides.is_disabled()) { - return -1; - } - if (overrides.has_optional_value()) { - return overrides.value().count(); + result.configs.reserve(res.size()); + for (auto& conf : res) { + result.configs.push_back(conf.to_describe_config()); } - - return def ? def->count() : -1; -} -int64_t describe_retention_bytes( - tristate& overrides, std::optional def) { - if (overrides.is_disabled()) { - return -1; - } - if (overrides.has_optional_value()) { - return overrides.value(); - } - - return def.value_or(-1); } template<> @@ -614,383 +145,13 @@ ss::future describe_configs_handler::handle( continue; } - /** - * Kafka properties - */ - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().log_compression_type.name(), - ctx.metadata_cache().get_default_compression(), - topic_property_compression, - topic_config->properties.compression, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().log_compression_type.desc()), - &describe_as_string); - - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().log_cleanup_policy.name(), - ctx.metadata_cache().get_default_cleanup_policy_bitflags(), - topic_property_cleanup_policy, - topic_config->properties.cleanup_policy_bitflags, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().log_cleanup_policy.desc()), - &describe_as_string); - - const std::string_view docstring{ - topic_config->properties.is_compacted() - ? config::shard_local_cfg().compacted_log_segment_size.desc() - : config::shard_local_cfg().log_segment_size.desc()}; - add_topic_config_if_requested( - resource, - result, - topic_config->properties.is_compacted() - ? config::shard_local_cfg().compacted_log_segment_size.name() - : config::shard_local_cfg().log_segment_size.name(), - topic_config->properties.is_compacted() - ? ctx.metadata_cache() - .get_default_compacted_topic_segment_size() - : ctx.metadata_cache().get_default_segment_size(), - topic_property_segment_size, - topic_config->properties.segment_size, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, docstring), - &describe_as_string); - - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().log_retention_ms.name(), - ctx.metadata_cache().get_default_retention_duration(), - topic_property_retention_duration, - topic_config->properties.retention_duration, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().log_retention_ms.desc())); - - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().retention_bytes.name(), - ctx.metadata_cache().get_default_retention_bytes(), - topic_property_retention_bytes, - topic_config->properties.retention_bytes, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().retention_bytes.desc())); - - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().log_message_timestamp_type.name(), - ctx.metadata_cache().get_default_timestamp_type(), - topic_property_timestamp_type, - topic_config->properties.timestamp_type, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().log_message_timestamp_type.desc()), - &describe_as_string); - - add_topic_config_if_requested( - resource, - result, - config::shard_local_cfg().kafka_batch_max_bytes.name(), - ctx.metadata_cache().get_default_batch_max_bytes(), - topic_property_max_message_bytes, - topic_config->properties.batch_max_bytes, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().kafka_batch_max_bytes.desc()), - &describe_as_string); - - // Shadow indexing properties - add_topic_config_if_requested( - resource, - result, - topic_property_remote_read, - model::is_fetch_enabled( - ctx.metadata_cache().get_default_shadow_indexing_mode()), - topic_property_remote_read, - topic_config->properties.shadow_indexing.has_value() - ? std::make_optional(model::is_fetch_enabled( - *topic_config->properties.shadow_indexing)) - : std::nullopt, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .cloud_storage_enable_remote_read.desc()), - &describe_as_string, - true); - - add_topic_config_if_requested( - resource, - result, - topic_property_remote_write, - model::is_archival_enabled( - ctx.metadata_cache().get_default_shadow_indexing_mode()), - topic_property_remote_write, - topic_config->properties.shadow_indexing.has_value() - ? std::make_optional(model::is_archival_enabled( - *topic_config->properties.shadow_indexing)) - : std::nullopt, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .cloud_storage_enable_remote_write.desc()), - &describe_as_string, - true); - - add_topic_config_if_requested( - resource, - result, - topic_property_retention_local_target_bytes, - ctx.metadata_cache().get_default_retention_local_target_bytes(), - topic_property_retention_local_target_bytes, - topic_config->properties.retention_local_target_bytes, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .retention_local_target_bytes_default.desc())); - - add_topic_config_if_requested( - resource, - result, - topic_property_retention_local_target_ms, - std::make_optional( - ctx.metadata_cache().get_default_retention_local_target_ms()), - topic_property_retention_local_target_ms, - topic_config->properties.retention_local_target_ms, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .retention_local_target_ms_default.desc())); - - if (config_property_requested( - resource.configuration_keys, topic_property_remote_delete)) { - add_topic_config( - result, - topic_property_remote_delete, - storage::ntp_config::default_remote_delete, - topic_property_remote_delete, - override_if_not_default( - std::make_optional( - topic_config->properties.remote_delete), - storage::ntp_config::default_remote_delete), - true, - maybe_make_documentation( - request.data.include_documentation, - "Controls whether topic deletion should imply deletion in " - "S3"), - [](const bool& b) { return b ? "true" : "false"; }); - } - - add_topic_config_if_requested( + report_topic_config( resource, result, - topic_property_segment_ms, - ctx.metadata_cache().get_default_segment_ms(), - topic_property_segment_ms, - topic_config->properties.segment_ms, + ctx.metadata_cache(), + topic_config->properties, request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg().log_segment_ms.desc())); - - constexpr std::string_view key_validation - = "Enable validation of the schema id for keys on a record"; - constexpr std::string_view val_validation - = "Enable validation of the schema id for values on a record"; - constexpr bool validation_hide_default_override = true; - - switch (config::shard_local_cfg().enable_schema_id_validation()) { - case pandaproxy::schema_registry::schema_id_validation_mode:: - compat: { - add_topic_config_if_requested( - resource, - result, - topic_property_record_key_schema_id_validation_compat, - ctx.metadata_cache() - .get_default_record_key_schema_id_validation(), - topic_property_record_key_schema_id_validation_compat, - topic_config->properties - .record_key_schema_id_validation_compat, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, key_validation), - &describe_as_string, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_key_subject_name_strategy_compat, - ctx.metadata_cache() - .get_default_record_key_subject_name_strategy(), - topic_property_record_key_subject_name_strategy_compat, - topic_config->properties - .record_key_subject_name_strategy_compat, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - fmt::format( - "The subject name strategy for keys if {} is enabled", - topic_property_record_key_schema_id_validation_compat)), - [](auto sns) { - return ss::sstring(to_string_view_compat(sns)); - }, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_value_schema_id_validation_compat, - ctx.metadata_cache() - .get_default_record_value_schema_id_validation(), - topic_property_record_value_schema_id_validation_compat, - topic_config->properties - .record_value_schema_id_validation_compat, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, val_validation), - &describe_as_string, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_value_subject_name_strategy_compat, - ctx.metadata_cache() - .get_default_record_value_subject_name_strategy(), - topic_property_record_value_subject_name_strategy_compat, - topic_config->properties - .record_value_subject_name_strategy_compat, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - fmt::format( - "The subject name strategy for values if {} is enabled", - topic_property_record_value_schema_id_validation_compat)), - [](auto sns) { - return ss::sstring(to_string_view_compat(sns)); - }, - validation_hide_default_override); - [[fallthrough]]; - } - case pandaproxy::schema_registry::schema_id_validation_mode:: - redpanda: { - add_topic_config_if_requested( - resource, - result, - topic_property_record_key_schema_id_validation, - ctx.metadata_cache() - .get_default_record_key_schema_id_validation(), - topic_property_record_key_schema_id_validation, - topic_config->properties.record_key_schema_id_validation, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, key_validation), - &describe_as_string, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_key_subject_name_strategy, - ctx.metadata_cache() - .get_default_record_key_subject_name_strategy(), - topic_property_record_key_subject_name_strategy, - topic_config->properties.record_key_subject_name_strategy, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - fmt::format( - "The subject name strategy for keys if {} is enabled", - topic_property_record_key_schema_id_validation)), - &describe_as_string< - pandaproxy::schema_registry::subject_name_strategy>, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_value_schema_id_validation, - ctx.metadata_cache() - .get_default_record_value_schema_id_validation(), - topic_property_record_value_schema_id_validation, - topic_config->properties.record_value_schema_id_validation, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, val_validation), - &describe_as_string, - validation_hide_default_override); - - add_topic_config_if_requested( - resource, - result, - topic_property_record_value_subject_name_strategy, - ctx.metadata_cache() - .get_default_record_value_subject_name_strategy(), - topic_property_record_value_subject_name_strategy, - topic_config->properties.record_value_subject_name_strategy, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - fmt::format( - "The subject name strategy for values if {} is enabled", - topic_property_record_value_schema_id_validation)), - &describe_as_string< - pandaproxy::schema_registry::subject_name_strategy>, - validation_hide_default_override); - [[fallthrough]]; - } - case pandaproxy::schema_registry::schema_id_validation_mode::none: { - break; - } - } - - add_topic_config_if_requested( - resource, - result, - topic_property_initial_retention_local_target_bytes, - ctx.metadata_cache() - .get_default_initial_retention_local_target_bytes(), - topic_property_initial_retention_local_target_bytes, - topic_config->properties.initial_retention_local_target_bytes, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .initial_retention_local_target_bytes_default.desc())); - - add_topic_config_if_requested( - resource, - result, - topic_property_initial_retention_local_target_ms, - ctx.metadata_cache() - .get_default_initial_retention_local_target_ms(), - topic_property_initial_retention_local_target_ms, - topic_config->properties.initial_retention_local_target_ms, - request.data.include_synonyms, - maybe_make_documentation( - request.data.include_documentation, - config::shard_local_cfg() - .initial_retention_local_target_ms_default.desc())); - + request.data.include_documentation); break; } diff --git a/src/v/kafka/server/handlers/describe_log_dirs.cc b/src/v/kafka/server/handlers/describe_log_dirs.cc index db2af8f54e143..416b3e5754d41 100644 --- a/src/v/kafka/server/handlers/describe_log_dirs.cc +++ b/src/v/kafka/server/handlers/describe_log_dirs.cc @@ -17,6 +17,7 @@ #include "kafka/server/response.h" #include "model/fundamental.h" #include "model/namespace.h" +#include "utils/fragmented_vector.h" #include #include @@ -32,7 +33,7 @@ struct partition_data { }; using partition_dir_set - = absl::flat_hash_map>; + = chunked_hash_map>; static partition_data describe_partition(cluster::partition& p) { auto result = partition_data{ @@ -99,16 +100,22 @@ static partition_dir_set collect_mapper( */ static ss::future collect( request_context& ctx, - std::optional> filter) { + std::optional> filter) { + std::optional> filter_v; + if (filter) { + filter_v.emplace( + std::make_move_iterator(filter->begin()), + std::make_move_iterator(filter->end())); + } return ctx.partition_manager().map_reduce0( - [filter = std::move(filter)](cluster::partition_manager& pm) { + [filter{std::move(filter_v)}](cluster::partition_manager& pm) { return collect_mapper(pm, filter); }, partition_dir_set{}, [](partition_dir_set acc, const partition_dir_set& update) { for (auto& topic : update) { for (auto partition : topic.second) { - acc[topic.first].push_back(partition); + acc[topic.first].push_back(std::move(partition)); } } return acc; @@ -159,9 +166,9 @@ ss::future describe_log_dirs_handler::handle( while (!partitions.empty()) { auto node = partitions.extract(partitions.begin()); - std::vector local_partitions; - std::vector remote_partitions; - for (const auto& i : node.mapped()) { + chunked_vector local_partitions; + chunked_vector remote_partitions; + for (const auto& i : node.second) { local_partitions.push_back(i.local); if (i.remote.has_value()) { remote_partitions.push_back(i.remote.value()); @@ -169,12 +176,12 @@ ss::future describe_log_dirs_handler::handle( } local_results.topics.push_back(describe_log_dirs_topic{ - .name = node.key(), + .name = node.first, .partitions = std::move(local_partitions), }); if (!remote_partitions.empty()) { remote_results.topics.push_back(describe_log_dirs_topic{ - .name = std::move(node.key()), + .name = std::move(node.first), .partitions = std::move(remote_partitions), }); } diff --git a/src/v/kafka/server/handlers/describe_producers.cc b/src/v/kafka/server/handlers/describe_producers.cc index fbeb362285bb3..17ec0cf6a4852 100644 --- a/src/v/kafka/server/handlers/describe_producers.cc +++ b/src/v/kafka/server/handlers/describe_producers.cc @@ -144,7 +144,7 @@ describe_producers_handler::handle(request_context ctx, ss::smp_service_group) { std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.topics.end())); - request.data.topics.erase(unauthorized_it, request.data.topics.end()); + request.data.topics.erase_to_end(unauthorized_it); for (const auto& topic : request.data.topics) { topic_response topic_resp; diff --git a/src/v/kafka/server/handlers/describe_transactions.cc b/src/v/kafka/server/handlers/describe_transactions.cc index 4486ddd9c34ae..e227283984a84 100644 --- a/src/v/kafka/server/handlers/describe_transactions.cc +++ b/src/v/kafka/server/handlers/describe_transactions.cc @@ -20,6 +20,7 @@ #include "model/fundamental.h" #include "model/namespace.h" #include "resource_mgmt/io_priority.h" +#include "utils/fragmented_vector.h" #include #include @@ -93,7 +94,7 @@ ss::future<> fill_info_about_tx( ss::future<> fill_info_about_transactions( cluster::tx_gateway_frontend& tx_frontend, describe_transactions_response& response, - std::vector tx_ids) { + chunked_vector tx_ids) { return ss::max_concurrent_for_each( tx_ids, 32, [&response, &tx_frontend](const auto tx_id) -> ss::future<> { return fill_info_about_tx(tx_frontend, response, tx_id); @@ -137,8 +138,7 @@ ss::future describe_transactions_handler::handle( std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.transactional_ids.end())); - request.data.transactional_ids.erase( - unauthorized_it, request.data.transactional_ids.end()); + request.data.transactional_ids.erase_to_end(unauthorized_it); auto& tx_frontend = ctx.tx_gateway_frontend(); co_await fill_info_about_transactions( diff --git a/src/v/kafka/server/handlers/details/security.h b/src/v/kafka/server/handlers/details/security.h index 155f9f85e9d9a..7af15cb27b487 100644 --- a/src/v/kafka/server/handlers/details/security.h +++ b/src/v/kafka/server/handlers/details/security.h @@ -12,6 +12,7 @@ #include "kafka/protocol/schemata/delete_acls_request.h" #include "kafka/protocol/schemata/describe_acls_request.h" #include "kafka/server/request_context.h" +#include "model/validation.h" #include "security/acl.h" namespace kafka::details { @@ -148,6 +149,17 @@ inline security::acl_binding to_acl_binding(const creatable_acl& acl) { fmt::format("Invalid cluster name: {}", pattern.name())); } + if (pattern.resource() == security::resource_type::topic) { + auto errc = model::validate_kafka_topic_name( + model::topic_view(pattern.name())); + if (pattern.name() != "*" && errc) { + throw acl_conversion_error(fmt::format( + "ACL topic {} does not conform to kafka topic schema: {}", + pattern.name(), + errc.message())); + } + } + security::acl_entry entry( to_acl_principal(acl.principal), to_acl_host(acl.host), diff --git a/src/v/kafka/server/handlers/fetch.cc b/src/v/kafka/server/handlers/fetch.cc index b555943d9af64..17a13e8a921f7 100644 --- a/src/v/kafka/server/handlers/fetch.cc +++ b/src/v/kafka/server/handlers/fetch.cc @@ -35,6 +35,7 @@ #include "resource_mgmt/io_priority.h" #include "ssx/semaphore.h" #include "storage/parser_utils.h" +#include "utils/fragmented_vector.h" #include "utils/to_string.h" #include @@ -423,7 +424,8 @@ static void fill_fetch_responses( op_context& octx, std::vector results, const std::vector& responses, - op_context::latency_point start_time) { + op_context::latency_point start_time, + bool record_latency = true) { auto range = boost::irange(0, results.size()); if (unlikely(results.size() != responses.size())) { // soft assert & recovery attempt @@ -499,7 +501,7 @@ static void fill_fetch_responses( * set aborted transactions if present */ if (!res.aborted_transactions.empty()) { - std::vector aborted; + chunked_vector aborted; aborted.reserve(res.aborted_transactions.size()); std::transform( res.aborted_transactions.begin(), @@ -519,10 +521,13 @@ static void fill_fetch_responses( } resp_it->set(std::move(resp)); - std::chrono::microseconds fetch_latency - = std::chrono::duration_cast( - op_context::latency_clock::now() - start_time); - octx.rctx.probe().record_fetch_latency(fetch_latency); + + if (record_latency) { + std::chrono::microseconds fetch_latency + = std::chrono::duration_cast( + op_context::latency_clock::now() - start_time); + octx.rctx.probe().record_fetch_latency(fetch_latency); + } } } @@ -707,6 +712,8 @@ class fetch_worker { std::vector read_results; // The total amount of bytes read across all results in `read_results`. size_t total_size; + // The time it took for the first `fetch_ntps_in_parallel` to complete + std::chrono::microseconds first_run_latency_result; }; ss::future run() { @@ -868,6 +875,7 @@ class fetch_worker { ss::future do_run() { bool first_run{true}; + std::chrono::microseconds first_run_latency_result{0}; // A map of indexes in `requests` to their corresponding index in // `_ctx.requests`. std::vector requests_map; @@ -894,6 +902,11 @@ class fetch_worker { _completed_waiter_count.current()); } + std::optional start_time; + if (first_run) { + start_time = op_context::latency_clock::now(); + } + auto q_results = co_await query_requests(std::move(requests)); if (first_run) { results = std::move(q_results.results); @@ -901,6 +914,9 @@ class fetch_worker { _last_visible_indexes = std::move( q_results.last_visible_indexes); + first_run_latency_result + = std::chrono::duration_cast( + op_context::latency_clock::now() - *start_time); } else { // Override the older results of the partitions with the newly // queried results. @@ -922,6 +938,7 @@ class fetch_worker { co_return worker_result{ .read_results = std::move(results), .total_size = total_size, + .first_run_latency_result = first_run_latency_result, }; } @@ -943,6 +960,7 @@ class fetch_worker { co_return worker_result{ .read_results = std::move(results), .total_size = total_size, + .first_run_latency_result = first_run_latency_result, }; } @@ -952,6 +970,7 @@ class fetch_worker { co_return worker_result{ .read_results = std::move(results), .total_size = total_size, + .first_run_latency_result = first_run_latency_result, }; } @@ -1145,7 +1164,11 @@ class nonpolling_fetch_plan_executor final : public fetch_plan_executor::impl { octx, std::move(results.read_results), fetch.responses, - fetch.start_time); + fetch.start_time, + false); + + octx.rctx.probe().record_fetch_latency( + results.first_run_latency_result); _last_result_size[fetch.shard] = results.total_size; _completed_shard_fetches.push_back(std::move(fetch)); @@ -1217,12 +1240,7 @@ void op_context::for_each_fetch_partition(Func&& f) const { request.cend(), [f = std::forward(f)]( const fetch_request::const_iterator::value_type& p) { - auto& part = *p.partition; - f(fetch_session_partition{ - .topic_partition = {p.topic->name, part.partition_index}, - .max_bytes = part.max_bytes, - .fetch_offset = part.fetch_offset, - }); + f(fetch_session_partition(p.topic->name, *p.partition)); }); } else { std::for_each( diff --git a/src/v/kafka/server/handlers/incremental_alter_configs.cc b/src/v/kafka/server/handlers/incremental_alter_configs.cc index 908763187f589..bf222e008fba0 100644 --- a/src/v/kafka/server/handlers/incremental_alter_configs.cc +++ b/src/v/kafka/server/handlers/incremental_alter_configs.cc @@ -299,9 +299,9 @@ create_topic_properties_update( return update; } -static ss::future> alter_topic_configuration( +static ss::future> alter_topic_configuration( request_context& ctx, - std::vector resources, + chunked_vector resources, bool validate_only) { return do_alter_topics_configuration( ctx, std::move(resources), validate_only, [&ctx](req_resource_t& r) { @@ -323,9 +323,9 @@ inline std::string_view map_config_name(std::string_view input) { .default_match(input); } -static ss::future> alter_broker_configuartion( - request_context& ctx, std::vector resources) { - std::vector responses; +static ss::future> alter_broker_configuartion( + request_context& ctx, chunked_vector resources) { + chunked_vector responses; responses.reserve(resources.size()); for (const auto& resource : resources) { cluster::config_update_request req; @@ -426,6 +426,13 @@ static ss::future> alter_broker_configuartion( continue; } + req_resource_t resource_c{ + .resource_type = resource.resource_type, + .resource_name = resource.resource_name, + .configs = resource.configs.copy(), + .unknown_tags = resource.unknown_tags, + }; + auto resp = co_await ctx.config_frontend() .local() @@ -433,7 +440,8 @@ static ss::future> alter_broker_configuartion( std::move(req), model::timeout_clock::now() + config::shard_local_cfg().alter_topic_cfg_timeout_ms()) - .then([resource](cluster::config_frontend::patch_result pr) { + .then([resource = std::move(resource_c)]( + cluster::config_frontend::patch_result pr) { std::error_code& ec = pr.errc; error_code kec = error_code::none; @@ -481,7 +489,7 @@ ss::future incremental_alter_configs_handler::handle( resp_resource_t>(std::move(responses))); } - std::vector>> futures; + std::vector>> futures; futures.reserve(2); futures.push_back(alter_topic_configuration( ctx, std::move(groupped.topic_changes), request.data.validate_only)); diff --git a/src/v/kafka/server/handlers/list_offsets.cc b/src/v/kafka/server/handlers/list_offsets.cc index 9e880cdab0c3e..a0628e6891f3f 100644 --- a/src/v/kafka/server/handlers/list_offsets.cc +++ b/src/v/kafka/server/handlers/list_offsets.cc @@ -19,8 +19,11 @@ #include "kafka/server/replicated_partition.h" #include "kafka/server/request_context.h" #include "kafka/server/response.h" +#include "model/fundamental.h" #include "model/namespace.h" #include "resource_mgmt/io_priority.h" +#include "ssx/when_all.h" +#include "utils/fragmented_vector.h" namespace kafka { @@ -128,9 +131,22 @@ static ss::future list_offsets_partition( offset, kafka_partition->leader_epoch()); } + auto min_offset = kafka_partition->start_offset(); + auto max_offset = model::prev_offset(offset); + + // Empty partition. + if (max_offset < min_offset) { + co_return list_offsets_response::make_partition( + ktp.get_partition(), + model::timestamp(-1), + model::offset(-1), + kafka_partition->leader_epoch()); + } + auto res = co_await kafka_partition->timequery(storage::timequery_config{ + min_offset, timestamp, - offset, + max_offset, kafka_read_priority(), {model::record_batch_type::raft_data}, octx.rctx.abort_source().local()}); @@ -178,7 +194,7 @@ static ss::future list_offsets_partition( static ss::future list_offsets_topic(list_offsets_ctx& octx, list_offset_topic& topic) { - std::vector> partitions; + chunked_vector> partitions; partitions.reserve(topic.partitions.size()); const auto* disabled_set @@ -217,19 +233,22 @@ list_offsets_topic(list_offsets_ctx& octx, list_offset_topic& topic) { partitions.push_back(std::move(pr)); } - return when_all_succeed(partitions.begin(), partitions.end()) + return ssx::when_all_succeed< + chunked_vector>( + std::move(partitions)) .then([name = std::move(topic.name)]( - std::vector parts) mutable { + chunked_vector parts) mutable { return list_offset_topic_response{ .name = std::move(name), - .partitions = std::move(parts), - }; + .partitions = chunked_vector{ + std::make_move_iterator(parts.begin()), + std::make_move_iterator(parts.end())}}; }); } -static std::vector> +static chunked_vector> list_offsets_topics(list_offsets_ctx& octx) { - std::vector> topics; + chunked_vector> topics; topics.reserve(octx.request.data.topics.size()); for (auto& topic : octx.request.data.topics) { @@ -247,7 +266,7 @@ static void handle_unauthorized(list_offsets_ctx& octx) { octx.response.data.topics.reserve( octx.response.data.topics.size() + octx.unauthorized_topics.size()); for (auto& topic : octx.unauthorized_topics) { - std::vector partitions; + chunked_vector partitions; partitions.reserve(topic.partitions.size()); for (auto& partition : topic.partitions) { partitions.push_back(list_offset_partition_response( @@ -274,7 +293,7 @@ list_offsets_handler::handle(request_context ctx, ss::smp_service_group ssg) { list_offsets_response response; response.data.topics.reserve(request.data.topics.size()); for (const auto& t : request.data.topics) { - std::vector partitions; + chunked_vector partitions; partitions.reserve(t.partitions.size()); for (const auto& p : t.partitions) { partitions.push_back(list_offsets_response::make_partition( @@ -300,7 +319,7 @@ list_offsets_handler::handle(request_context ctx, ss::smp_service_group ssg) { request.data.topics.end(), std::back_inserter(resp.data.topics), [](const list_offset_topic& t) { - std::vector resp; + chunked_vector resp; resp.reserve(t.partitions.size()); for (const auto& p : t.partitions) { resp.emplace_back(list_offset_partition_response{ @@ -318,16 +337,19 @@ list_offsets_handler::handle(request_context ctx, ss::smp_service_group ssg) { std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.topics.end())); - request.data.topics.erase(unauthorized_it, request.data.topics.end()); + request.data.topics.erase_to_end(unauthorized_it); list_offsets_ctx octx( std::move(ctx), std::move(request), ssg, std::move(unauthorized_topics)); return ss::do_with(std::move(octx), [](list_offsets_ctx& octx) { auto topics = list_offsets_topics(octx); - return when_all_succeed(topics.begin(), topics.end()) - .then([&octx](std::vector topics) { - octx.response.data.topics = std::move(topics); + return ssx::when_all_succeed< + chunked_vector>(std::move(topics)) + .then([&octx](chunked_vector topics) { + octx.response.data.topics = { + std::make_move_iterator(topics.begin()), + std::make_move_iterator(topics.end())}; handle_unauthorized(octx); return octx.rctx.respond(std::move(octx.response)); }); diff --git a/src/v/kafka/server/handlers/list_partition_reassignments.cc b/src/v/kafka/server/handlers/list_partition_reassignments.cc index 29c1456a3bbba..a7abf3333dcee 100644 --- a/src/v/kafka/server/handlers/list_partition_reassignments.cc +++ b/src/v/kafka/server/handlers/list_partition_reassignments.cc @@ -155,7 +155,9 @@ ss::future list_partition_reassignments_handler::handle( all_in_progress_reassignments.begin(), all_in_progress_reassignments.end(), [&resp](const ongoing_topic_reassignment& topic_reassignment) { - resp.data.topics.push_back(topic_reassignment); + resp.data.topics.emplace_back(ongoing_topic_reassignment{ + .name = topic_reassignment.name, + .partitions = topic_reassignment.partitions.copy()}); }); co_return co_await ctx.respond(std::move(resp)); } @@ -181,7 +183,7 @@ ss::future list_partition_reassignments_handler::handle( } if (!topic_reassignment.partitions.empty()) { - resp.data.topics.push_back(topic_reassignment); + resp.data.topics.push_back(std::move(topic_reassignment)); } } diff --git a/src/v/kafka/server/handlers/metadata.cc b/src/v/kafka/server/handlers/metadata.cc index a9fe5049c4179..1a57f1a8f67c6 100644 --- a/src/v/kafka/server/handlers/metadata.cc +++ b/src/v/kafka/server/handlers/metadata.cc @@ -27,6 +27,7 @@ #include "model/metadata.h" #include "model/namespace.h" #include "model/timeout_clock.h" +#include "utils/fragmented_vector.h" #include "utils/to_string.h" #include diff --git a/src/v/kafka/server/handlers/offset_for_leader_epoch.cc b/src/v/kafka/server/handlers/offset_for_leader_epoch.cc index ea13468b7fd6f..83a7ea8895ed9 100644 --- a/src/v/kafka/server/handlers/offset_for_leader_epoch.cc +++ b/src/v/kafka/server/handlers/offset_for_leader_epoch.cc @@ -20,6 +20,7 @@ #include "model/namespace.h" #include "security/acl.h" #include "ssx/future-util.h" +#include "utils/fragmented_vector.h" #include #include @@ -120,10 +121,10 @@ static ss::future<> fetch_offsets_from_shards( }); } -static ss::future> +static ss::future> get_offsets_for_leader_epochs( - request_context& ctx, std::vector topics) { - std::vector result; + request_context& ctx, chunked_vector topics) { + chunked_vector result; result.reserve(topics.size()); absl::flat_hash_map requests_per_shard; @@ -241,7 +242,7 @@ ss::future offset_for_leader_epoch_handler::handle( return res; }); // remove unauthorized topics - request.data.topics.erase(it, request.data.topics.end()); + request.data.topics.erase_to_end(it); } if (!ctx.audit()) { diff --git a/src/v/kafka/server/handlers/produce.cc b/src/v/kafka/server/handlers/produce.cc index 2a1b182790f77..2ccd0cb1e8c03 100644 --- a/src/v/kafka/server/handlers/produce.cc +++ b/src/v/kafka/server/handlers/produce.cc @@ -763,7 +763,7 @@ produce_handler::handle(request_context ctx, ss::smp_service_group ssg) { return r; }); - request.data.topics.erase(unauthorized_it, request.data.topics.end()); + request.data.topics.erase_to_end(unauthorized_it); ss::promise<> dispatched_promise; auto dispatched_f = dispatched_promise.get_future(); diff --git a/src/v/kafka/server/handlers/topics/topic_utils.cc b/src/v/kafka/server/handlers/topics/topic_utils.cc index 026ae633c6372..ad30fbeb23bd9 100644 --- a/src/v/kafka/server/handlers/topics/topic_utils.cc +++ b/src/v/kafka/server/handlers/topics/topic_utils.cc @@ -24,7 +24,7 @@ namespace kafka { void append_cluster_results( const std::vector& cluster_results, - std::vector& kafka_results) { + chunked_vector& kafka_results) { std::transform( cluster_results.begin(), cluster_results.end(), diff --git a/src/v/kafka/server/handlers/topics/topic_utils.h b/src/v/kafka/server/handlers/topics/topic_utils.h index 177040151a86f..e39c7d61e303b 100644 --- a/src/v/kafka/server/handlers/topics/topic_utils.h +++ b/src/v/kafka/server/handlers/topics/topic_utils.h @@ -16,6 +16,7 @@ #include "kafka/server/handlers/topics/validators.h" #include "model/timeout_clock.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include @@ -43,7 +44,7 @@ concept TopicResultIterator template requires TopicRequestItem creatable_topic_result -generate_error(T item, error_code code, const ss::sstring& msg) { +generate_error(const T& item, error_code code, const ss::sstring& msg) { return creatable_topic_result{ .name = item.name, .error_code = code, @@ -54,7 +55,7 @@ generate_error(T item, error_code code, const ss::sstring& msg) { /// Generates successfull creatable_topic_result for single topic request item template requires TopicRequestItem -creatable_topic_result generate_successfull_result(T item) { +creatable_topic_result generate_successfull_result(const T& item) { return creatable_topic_result{ .name = item.name, .error_code = error_code::none}; } @@ -110,7 +111,7 @@ Iter validate_requests_range( // Kafka protocol error message void append_cluster_results( const std::vector&, - std::vector&); + chunked_vector&); // Converts objects representing KafkaAPI message to objects consumed // by cluster::controller API diff --git a/src/v/kafka/server/handlers/topics/types.cc b/src/v/kafka/server/handlers/topics/types.cc index ad403168950a6..317585c12a518 100644 --- a/src/v/kafka/server/handlers/topics/types.cc +++ b/src/v/kafka/server/handlers/topics/types.cc @@ -11,6 +11,7 @@ #include "cluster/types.h" #include "config/configuration.h" +#include "kafka/server/handlers/configs/config_response_utils.h" #include "kafka/server/handlers/configs/config_utils.h" #include "model/compression.h" #include "model/fundamental.h" @@ -18,6 +19,7 @@ #include "model/timestamp.h" #include "pandaproxy/schema_registry/subject_name_strategy.h" #include "units.h" +#include "utils/fragmented_vector.h" #include "utils/string_switch.h" #include @@ -39,8 +41,13 @@ template concept CreatableTopicCfg = std::is_same_v || std::is_same_v; -template -config_map_t make_config_map(const std::vector& config) { +template +concept CreatableTopicCfgContainer = requires(Container c) { + requires CreatableTopicCfg; +}; + +template +config_map_t make_config_map(const T& config) { config_map_t ret; ret.reserve(config.size()); for (const auto& c : config) { @@ -219,153 +226,32 @@ to_cluster_type(const creatable_topic& t) { ret.custom_assignments.push_back( cluster::custom_partition_assignment{ .id = assignment.partition_index, - .replicas = assignment.broker_ids}); + .replicas = std::vector{ + assignment.broker_ids.begin(), assignment.broker_ids.end()}}); } } return ret; } -template -static ss::sstring from_config_type(const T& v) { - if constexpr (std::is_enum_v) { - return ssx::sformat("{}", v); - } else if constexpr (std::is_same_v) { - return v ? "true" : "false"; - } else if constexpr (std::is_same_v) { - return ss::to_sstring( - std::chrono::duration_cast(v).count()); - } else { - return ss::to_sstring(v); - } -} - -config_map_t from_cluster_type(const cluster::topic_properties& properties) { - config_map_t config_entries; - if (properties.compression) { - config_entries[topic_property_compression] = from_config_type( - *properties.compression); - } - if (properties.cleanup_policy_bitflags) { - config_entries[topic_property_cleanup_policy] = from_config_type( - *properties.cleanup_policy_bitflags); - } - if (properties.compaction_strategy) { - config_entries[topic_property_compaction_strategy] = from_config_type( - *properties.compaction_strategy); - } - if (properties.timestamp_type) { - config_entries[topic_property_timestamp_type] = from_config_type( - *properties.timestamp_type); - } - if (properties.segment_size) { - config_entries[topic_property_segment_size] = from_config_type( - *properties.segment_size); - } - if (properties.retention_bytes.has_optional_value()) { - config_entries[topic_property_retention_bytes] = from_config_type( - properties.retention_bytes.value()); - } - if (properties.retention_duration.has_optional_value()) { - config_entries[topic_property_retention_duration] = from_config_type( - *properties.retention_duration); - } - if (properties.recovery) { - config_entries[topic_property_recovery] = from_config_type( - *properties.recovery); - } - if (properties.batch_max_bytes) { - config_entries[topic_property_max_message_bytes] = from_config_type( - *properties.batch_max_bytes); - } - if (properties.shadow_indexing) { - config_entries[topic_property_remote_write] = "false"; - config_entries[topic_property_remote_read] = "false"; - - switch (*properties.shadow_indexing) { - case model::shadow_indexing_mode::archival: - config_entries[topic_property_remote_write] = "true"; - break; - case model::shadow_indexing_mode::fetch: - config_entries[topic_property_remote_read] = "true"; - break; - case model::shadow_indexing_mode::full: - config_entries[topic_property_remote_write] = "true"; - config_entries[topic_property_remote_read] = "true"; - break; - default: - break; - } - } - if (properties.read_replica_bucket) { - config_entries[topic_property_read_replica] = from_config_type( - *properties.read_replica_bucket); - } +static std::vector +convert_topic_configs(config_response_container_t&& topic_cfgs) { + auto configs = std::vector(); + configs.reserve(topic_cfgs.size()); - if (properties.retention_local_target_bytes.has_optional_value()) { - config_entries[topic_property_retention_local_target_bytes] - = from_config_type(*properties.retention_local_target_bytes); + for (auto& conf : topic_cfgs) { + configs.push_back(conf.to_create_config()); } - if (properties.retention_local_target_ms.has_optional_value()) { - config_entries[topic_property_retention_local_target_ms] - = from_config_type(*properties.retention_local_target_ms); - } - - config_entries[topic_property_remote_delete] = from_config_type( - properties.remote_delete); - - if (properties.segment_ms.has_optional_value()) { - config_entries[topic_property_segment_ms] = from_config_type( - properties.segment_ms.value()); - } + return configs; +} - if (properties.record_key_schema_id_validation) { - config_entries[topic_property_record_key_schema_id_validation] - = from_config_type(properties.record_key_schema_id_validation); - } - if (properties.record_key_schema_id_validation_compat) { - config_entries[topic_property_record_key_schema_id_validation_compat] - = from_config_type(properties.record_key_schema_id_validation_compat); - } - if (properties.record_key_subject_name_strategy) { - config_entries[topic_property_record_key_subject_name_strategy] - = from_config_type(properties.record_key_subject_name_strategy); - } - if (properties.record_key_subject_name_strategy_compat) { - config_entries[topic_property_record_key_subject_name_strategy_compat] - = from_config_type( - properties.record_key_subject_name_strategy_compat); - } - if (properties.record_value_schema_id_validation) { - config_entries[topic_property_record_value_schema_id_validation] - = from_config_type(properties.record_value_schema_id_validation); - } - if (properties.record_value_schema_id_validation_compat) { - config_entries[topic_property_record_value_schema_id_validation_compat] - = from_config_type( - properties.record_value_schema_id_validation_compat); - } - if (properties.record_value_subject_name_strategy) { - config_entries[topic_property_record_value_subject_name_strategy] - = from_config_type(properties.record_value_subject_name_strategy); - } - if (properties.record_value_subject_name_strategy_compat) { - config_entries[topic_property_record_value_subject_name_strategy_compat] - = from_config_type( - properties.record_value_subject_name_strategy_compat); - } - if (properties.initial_retention_local_target_bytes.has_optional_value()) { - config_entries[topic_property_initial_retention_local_target_bytes] - = from_config_type(*properties.initial_retention_local_target_bytes); - } - if (properties.initial_retention_local_target_ms.has_optional_value()) { - config_entries[topic_property_initial_retention_local_target_ms] - = from_config_type(*properties.initial_retention_local_target_ms); - } +std::vector report_topic_configs( + const cluster::metadata_cache& metadata_cache, + const cluster::topic_properties& topic_properties) { + auto topic_cfgs = make_topic_configs( + metadata_cache, topic_properties, std::nullopt, false, false); - /// Final topic_property not encoded here is \ref remote_topic_properties, - /// is more of an implementation detail no need to ever show user - return config_entries; + return convert_topic_configs(std::move(topic_cfgs)); } } // namespace kafka diff --git a/src/v/kafka/server/handlers/topics/types.h b/src/v/kafka/server/handlers/topics/types.h index f95fca2d58440..328852b728982 100644 --- a/src/v/kafka/server/handlers/topics/types.h +++ b/src/v/kafka/server/handlers/topics/types.h @@ -14,9 +14,11 @@ #include "kafka/protocol/schemata/create_topics_request.h" #include "kafka/protocol/schemata/create_topics_response.h" #include "kafka/server/errors.h" +#include "kafka/server/handlers/configs/config_response_utils.h" #include "model/fundamental.h" #include "model/namespace.h" #include "utils/absl_sstring_hash.h" +#include "utils/fragmented_vector.h" #include #include @@ -137,7 +139,10 @@ struct topic_op_result { inline creatable_topic_result from_cluster_topic_result(const cluster::topic_result& err) { - return {.name = err.tp_ns.tp, .error_code = map_topic_error_code(err.ec)}; + return { + .name = err.tp_ns.tp, + .error_code = map_topic_error_code(err.ec), + .error_message = cluster::make_error_code(err.ec).message()}; } config_map_t config_map(const std::vector& config); @@ -146,5 +151,8 @@ config_map_t config_map(const std::vector& config); cluster::custom_assignable_topic_configuration to_cluster_type(const creatable_topic& t); -config_map_t from_cluster_type(const cluster::topic_properties&); +std::vector report_topic_configs( + const cluster::metadata_cache& metadata_cache, + const cluster::topic_properties& topic_properties); + } // namespace kafka diff --git a/src/v/kafka/server/member.h b/src/v/kafka/server/member.h index bff5f4a4ec31f..d1d9a6993f474 100644 --- a/src/v/kafka/server/member.h +++ b/src/v/kafka/server/member.h @@ -17,6 +17,7 @@ #include "kafka/protocol/sync_group.h" #include "kafka/server/group_metadata.h" #include "kafka/types.h" +#include "utils/fragmented_vector.h" #include #include @@ -49,7 +50,7 @@ class group_member { duration_type session_timeout, duration_type rebalance_timeout, kafka::protocol_type protocol_type, - std::vector protocols) + chunked_vector protocols) : group_member( member_state{ .id = std::move(member_id), @@ -73,7 +74,7 @@ class group_member { kafka::member_state state, kafka::group_id group_id, kafka::protocol_type protocol_type, - std::vector protocols) + chunked_vector protocols) : _state(std::move(state)) , _group_id(std::move(group_id)) , _is_new(false) @@ -87,6 +88,22 @@ class group_member { void replace_id(member_id new_id) { _state.id = std::move(new_id); } + /// Get the member's client_id. + const kafka::client_id& client_id() const { return _state.client_id; } + + /// Replace the member's client_id. + void replace_client_id(kafka::client_id new_client_id) { + _state.client_id = std::move(new_client_id); + } + + /// Get the member's client_host. + const kafka::client_host& client_host() const { return _state.client_host; } + + /// Replace the member's client_host. + void replace_client_host(kafka::client_host new_client_host) { + _state.client_host = std::move(new_client_host); + } + /// Get the id of the member's group. const kafka::group_id& group_id() const { return _group_id; } @@ -98,9 +115,21 @@ class group_member { /// Get the member's session timeout. duration_type session_timeout() const { return _state.session_timeout; } + /// Replace the member's session timeout. + void + replace_session_timeout(std::chrono::milliseconds new_session_timeout) { + _state.session_timeout = new_session_timeout; + } + /// Get the member's rebalance timeout. duration_type rebalance_timeout() const { return _state.rebalance_timeout; } + /// Replace the member's rebalance timeout. + void + replace_rebalance_timeout(std::chrono::milliseconds new_rebalance_timeout) { + _state.rebalance_timeout = new_rebalance_timeout; + } + /// Get the member's protocol type. const kafka::protocol_type& protocol_type() const { return _protocol_type; } @@ -115,10 +144,12 @@ class group_member { /// Clear the member's assignment. void clear_assignment() { _state.assignment.clear(); } - const std::vector& protocols() const { return _protocols; } + const chunked_vector& protocols() const { + return _protocols; + } /// Update the set of protocols supported by the member. - void set_protocols(std::vector protocols) { + void set_protocols(chunked_vector protocols) { _protocols = std::move(protocols); } @@ -215,7 +246,7 @@ class group_member { clock_type::time_point _latest_heartbeat; ss::timer _expire_timer; kafka::protocol_type _protocol_type; - std::vector _protocols; + chunked_vector _protocols; // external shutdown synchronization std::unique_ptr _sync_promise; diff --git a/src/v/kafka/server/quota_manager.cc b/src/v/kafka/server/quota_manager.cc index 3b2425a0fa8ef..3af384d219910 100644 --- a/src/v/kafka/server/quota_manager.cc +++ b/src/v/kafka/server/quota_manager.cc @@ -75,7 +75,6 @@ quota_manager::maybe_add_and_retrieve_quota( ss::sstring(qid), client_quota{ now, - clock::duration(0), {static_cast(_default_num_windows()), _default_window_width()}, {static_cast(_default_num_windows()), _default_window_width()}, /// pm_rate is only non-nullopt on the qm home shard @@ -245,12 +244,7 @@ throttle_delay quota_manager::record_produce_tp_and_throttle( auto target_tp_rate = get_client_target_produce_tp_rate(quota_id); auto delay_ms = throttle( quota_id, target_tp_rate, now, it->second.tp_produce_rate); - auto prev = it->second.delay; - it->second.delay = delay_ms; - throttle_delay res{}; - res.enforce = prev.count() > 0; - res.duration = it->second.delay; - return res; + return {.duration = delay_ms}; } void quota_manager::record_fetch_tp( @@ -276,10 +270,7 @@ throttle_delay quota_manager::throttle_fetch_tp( it->second.tp_fetch_rate.maybe_advance_current(now); auto delay_ms = throttle( quota_id, *target_tp_rate, now, it->second.tp_fetch_rate); - throttle_delay res{}; - res.enforce = true; - res.duration = delay_ms; - return res; + return {.duration = delay_ms}; } // erase inactive tracked quotas. windows are considered inactive if diff --git a/src/v/kafka/server/quota_manager.h b/src/v/kafka/server/quota_manager.h index 8c023a0df80d6..1f93f6ddf90c3 100644 --- a/src/v/kafka/server/quota_manager.h +++ b/src/v/kafka/server/quota_manager.h @@ -55,15 +55,7 @@ class quota_manager : public ss::peering_sharded_service { using clock = ss::lowres_clock; struct throttle_delay { - bool enforce{false}; clock::duration duration{0}; - clock::duration enforce_duration() const { - if (enforce) { - return duration; - } else { - return clock::duration::zero(); - } - } }; quota_manager(); @@ -125,7 +117,6 @@ class quota_manager : public ss::peering_sharded_service { // pm_rate: partition mutation quota tracking - only on home shard struct client_quota { clock::time_point last_seen; - clock::duration delay; rate_tracker tp_produce_rate; rate_tracker tp_fetch_rate; std::optional pm_rate; diff --git a/src/v/kafka/server/replicated_partition.cc b/src/v/kafka/server/replicated_partition.cc index a3c53aa1f4749..b86c28e551910 100644 --- a/src/v/kafka/server/replicated_partition.cc +++ b/src/v/kafka/server/replicated_partition.cc @@ -247,43 +247,7 @@ ss::future> replicated_partition::timequery(storage::timequery_config cfg) { // cluster::partition::timequery returns a result in Kafka offsets, // no further offset translation is required here. - auto res = co_await _partition->timequery(cfg); - if (!res.has_value()) { - co_return std::nullopt; - } - const auto kafka_start_override = _partition->kafka_start_offset_override(); - if ( - !kafka_start_override.has_value() - || kafka_start_override.value() <= res.value().offset) { - // The start override doesn't affect the result of the timequery. - co_return res; - } - vlog( - klog.debug, - "{} timequery result {} clamped by start override, fetching result at " - "start {}", - ntp(), - res->offset, - kafka_start_override.value()); - storage::log_reader_config config( - kafka_start_override.value(), - cfg.max_offset, - 0, - 2048, // We just need one record batch - cfg.prio, - cfg.type_filter, - std::nullopt, // No timestamp, just use the offset - cfg.abort_source); - auto translating_reader = co_await make_reader(config, std::nullopt); - auto ot_state = std::move(translating_reader.ot_state); - model::record_batch_reader::storage_t data - = co_await model::consume_reader_to_memory( - std::move(translating_reader.reader), model::no_timeout); - auto& batches = std::get(data); - if (batches.empty()) { - co_return std::nullopt; - } - co_return storage::batch_timequery(*(batches.begin()), cfg.time); + return _partition->timequery(cfg); } ss::future> replicated_partition::replicate( @@ -473,13 +437,10 @@ ss::future replicated_partition::validate_fetch_offset( if (reading_from_follower && !_partition->is_leader()) { auto ec = error_code::none; - const std::pair bounds = std::minmax( + const auto available_to_read = std::min( leader_high_watermark(), log_end_offset()); - const auto effective_log_end_offset = bounds.second; - const auto available_to_read = bounds.first; - if ( - fetch_offset < start_offset() - || fetch_offset > effective_log_end_offset) { + + if (fetch_offset < start_offset()) { ec = error_code::offset_out_of_range; } else if (fetch_offset > available_to_read) { /** diff --git a/src/v/kafka/server/replicated_partition.h b/src/v/kafka/server/replicated_partition.h index 646c5b52143e3..aa9b75872fc06 100644 --- a/src/v/kafka/server/replicated_partition.h +++ b/src/v/kafka/server/replicated_partition.h @@ -108,8 +108,8 @@ class replicated_partition final : public kafka::partition_proxy::impl { /** * By default we return a dirty_offset + 1 */ - return model::next_offset( - _translator->from_log_offset(_partition->dirty_offset())); + return _translator->from_log_offset( + model::next_offset(_partition->dirty_offset())); } model::offset leader_high_watermark() const { diff --git a/src/v/kafka/server/server.cc b/src/v/kafka/server/server.cc index c737304e5b176..b988849dd4da9 100644 --- a/src/v/kafka/server/server.cc +++ b/src/v/kafka/server/server.cc @@ -79,6 +79,7 @@ #include #include +#include #include #include #include @@ -333,6 +334,10 @@ ss::future<> server::apply(ss::lw_shared_ptr conn) { std::exception_ptr eptr; try { + co_await ctx->start(); + // Must call start() to ensure `ctx` is inserted into the `_connections` + // list. Otherwise if enqueing the audit message fails and `stop()` is + // called, this will result in a segfault. if (authn_method == config::broker_authn_method::mtls_identity) { auto authn_event = make_auth_event_options(mtls_state.value(), ctx); if (!ctx->server().audit_mgr().enqueue_authn_event( @@ -342,7 +347,6 @@ ss::future<> server::apply(ss::lw_shared_ptr conn) { "system error"); } } - co_await ctx->start(); co_await ctx->process(); } catch (...) { eptr = std::current_exception(); @@ -657,12 +661,12 @@ ss::future sasl_handshake_handler::handle( */ auto error = error_code::none; - std::vector supported_sasl_mechanisms; + chunked_vector supported_sasl_mechanisms; if (supports("SCRAM")) { - supported_sasl_mechanisms.insert( - supported_sasl_mechanisms.end(), - {security::scram_sha256_authenticator::name, - security::scram_sha512_authenticator::name}); + supported_sasl_mechanisms.emplace_back( + security::scram_sha256_authenticator::name); + supported_sasl_mechanisms.emplace_back( + security::scram_sha512_authenticator::name); if ( request.data.mechanism @@ -798,8 +802,7 @@ ss::future delete_groups_handler::handle( std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.groups_names.end())); - request.data.groups_names.erase( - unauthorized_it, request.data.groups_names.end()); + request.data.groups_names.erase_to_end(unauthorized_it); std::vector results; @@ -1005,7 +1008,7 @@ ss::future add_partitions_to_txn_handler::handle( ? error_code::topic_authorization_failed : error_code::operation_not_attempted; }}; - return ctx.respond(response); + return ctx.respond(std::move(response)); } cluster::add_paritions_tx_request tx_request{ @@ -1074,7 +1077,7 @@ ss::future add_partitions_to_txn_handler::handle( } add_partitions_to_txn_response response; - response.data = data; + response.data = std::move(data); return ctx.respond(std::move(response)); }); }); @@ -1127,7 +1130,7 @@ offset_fetch_handler::handle(request_context ctx, ss::smp_service_group) { co_return co_await ctx.respond(std::move(resp)); } - resp.data.topics.erase(unauthorized, resp.data.topics.end()); + resp.data.topics.erase_to_end(unauthorized); co_return co_await ctx.respond(std::move(resp)); } @@ -1202,7 +1205,7 @@ offset_delete_handler::handle(request_context ctx, ss::smp_service_group) { std::vector unauthorized( std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.topics.end())); - request.data.topics.erase(unauthorized_it, request.data.topics.end()); + request.data.topics.erase_to_end(unauthorized_it); /// Remove unknown topic_partitions from request std::vector unknowns; @@ -1304,8 +1307,7 @@ delete_topics_handler::handle(request_context ctx, ss::smp_service_group) { std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.topic_names.end())); - request.data.topic_names.erase( - unauthorized_it, request.data.topic_names.end()); + request.data.topic_names.erase_to_end(unauthorized_it); auto kafka_nodelete_topics = config::shard_local_cfg().kafka_nodelete_topics(); @@ -1329,7 +1331,7 @@ delete_topics_handler::handle(request_context ctx, ss::smp_service_group) { std::make_move_iterator(nodelete_it), std::make_move_iterator(request.data.topic_names.end())); - request.data.topic_names.erase(nodelete_it, request.data.topic_names.end()); + request.data.topic_names.erase_to_end(nodelete_it); std::vector res; @@ -1356,8 +1358,7 @@ delete_topics_handler::handle(request_context ctx, ss::smp_service_group) { std::make_move_iterator(quota_exceeded_it), std::make_move_iterator(request.data.topic_names.end())); - request.data.topic_names.erase( - quota_exceeded_it, request.data.topic_names.end()); + request.data.topic_names.erase_to_end(quota_exceeded_it); if (!request.data.topic_names.empty()) { // construct namespaced topic set from request @@ -1917,7 +1918,7 @@ describe_groups_handler::handle(request_context ctx, ss::smp_service_group) { std::make_move_iterator(unauthorized_it), std::make_move_iterator(request.data.groups.end())); - request.data.groups.erase(unauthorized_it, request.data.groups.end()); + request.data.groups.erase_to_end(unauthorized_it); describe_groups_response response; @@ -1934,8 +1935,12 @@ describe_groups_handler::handle(request_context ctx, ss::smp_service_group) { return res; })); } - response.data.groups = co_await ss::when_all_succeed( + auto group_v = co_await ss::when_all_succeed( described.begin(), described.end()); + + response.data.groups = { + std::make_move_iterator(group_v.begin()), + std::make_move_iterator(group_v.end())}; } for (auto& group : unauthorized) { diff --git a/src/v/kafka/server/snc_quota_manager.cc b/src/v/kafka/server/snc_quota_manager.cc index a4364e72e31b7..6f85ced636064 100644 --- a/src/v/kafka/server/snc_quota_manager.cc +++ b/src/v/kafka/server/snc_quota_manager.cc @@ -346,13 +346,12 @@ void snc_quota_manager::get_or_create_quota_context( } } -snc_quota_manager::delays_t snc_quota_manager::get_shard_delays( - snc_quota_context& ctx, const clock::time_point now) const { +snc_quota_manager::delays_t +snc_quota_manager::get_shard_delays(const snc_quota_context& ctx) const { delays_t res; - // force throttle whatever the client did not do on its side - if (now < ctx._throttled_until) { - res.enforce = ctx._throttled_until - now; + if (ctx._exempt) { + return res; } // throttling delay the connection should be requested to throttle @@ -367,7 +366,6 @@ snc_quota_manager::delays_t snc_quota_manager::get_shard_delays( _max_kafka_throttle_delay(), std::max(eval_delay(_shard_quota.in), eval_delay(_shard_quota.eg))); } - ctx._throttled_until = now + res.request; _probe.record_throttle_time( std::chrono::duration_cast(res.request)); @@ -376,7 +374,7 @@ snc_quota_manager::delays_t snc_quota_manager::get_shard_delays( } void snc_quota_manager::record_request_receive( - snc_quota_context& ctx, + const snc_quota_context& ctx, const size_t request_size, const clock::time_point now) noexcept { if (ctx._exempt) { @@ -393,7 +391,7 @@ void snc_quota_manager::record_request_receive( } void snc_quota_manager::record_request_intake( - snc_quota_context& ctx, const size_t request_size) noexcept { + const snc_quota_context& ctx, const size_t request_size) noexcept { if (ctx._exempt) { return; } @@ -401,7 +399,7 @@ void snc_quota_manager::record_request_intake( } void snc_quota_manager::record_response( - snc_quota_context& ctx, + const snc_quota_context& ctx, const size_t request_size, const clock::time_point now) noexcept { if (ctx._exempt) { diff --git a/src/v/kafka/server/snc_quota_manager.h b/src/v/kafka/server/snc_quota_manager.h index b88ae43d27c74..10c9274537f9e 100644 --- a/src/v/kafka/server/snc_quota_manager.h +++ b/src/v/kafka/server/snc_quota_manager.h @@ -92,12 +92,6 @@ class snc_quota_context { /// Whether the connection belongs to an exempt tput control group bool _exempt{false}; - - // Operating - - /// What time the client on this conection should throttle (be throttled) - /// until - ss::lowres_clock::time_point _throttled_until; }; /// Isolates \ref quota_manager functionality related to @@ -125,10 +119,8 @@ class snc_quota_manager ss::future<> start(); ss::future<> stop(); - /// @p enforce delay to enforce in this call /// @p request delay to request from the client via throttle_ms struct delays_t { - clock::duration enforce{0}; clock::duration request{0}; }; @@ -144,24 +136,24 @@ class snc_quota_manager std::optional client_id); /// Determine throttling required by shard level TP quotas. - delays_t get_shard_delays(snc_quota_context&, clock::time_point now) const; + delays_t get_shard_delays(const snc_quota_context&) const; /// Record the request size when it has arrived from the transport. /// This should be done before calling \ref get_shard_delays because the /// recorded request size is used to calculate throttling parameters. void record_request_receive( - snc_quota_context&, + const snc_quota_context&, size_t request_size, clock::time_point now = clock::now()) noexcept; /// Record the request size when the request data is about to be consumed. /// This data is used to represent throttled throughput. - void - record_request_intake(snc_quota_context&, size_t request_size) noexcept; + void record_request_intake( + const snc_quota_context&, size_t request_size) noexcept; /// Record the response size for all purposes void record_response( - snc_quota_context&, + const snc_quota_context&, size_t request_size, clock::time_point now = clock::now()) noexcept; diff --git a/src/v/kafka/server/tests/CMakeLists.txt b/src/v/kafka/server/tests/CMakeLists.txt index c6563f6a1765f..558a776ad5c0e 100644 --- a/src/v/kafka/server/tests/CMakeLists.txt +++ b/src/v/kafka/server/tests/CMakeLists.txt @@ -11,6 +11,7 @@ rp_test( validator_tests.cc fetch_unit_test.cc config_utils_test.cc + config_response_utils_test.cc DEFINITIONS BOOST_TEST_DYN_LINK LIBRARIES Boost::unit_test_framework v::kafka LABELS kafka @@ -55,7 +56,8 @@ set(srcs alter_config_test.cc produce_consume_test.cc group_metadata_serialization_test.cc - partition_reassignments_test.cc) + partition_reassignments_test.cc + replicated_partition_test.cc) rp_test( FIXTURE_TEST diff --git a/src/v/kafka/server/tests/alter_config_test.cc b/src/v/kafka/server/tests/alter_config_test.cc index 13abbe9d0c00e..c2e1ce908e266 100644 --- a/src/v/kafka/server/tests/alter_config_test.cc +++ b/src/v/kafka/server/tests/alter_config_test.cc @@ -25,6 +25,7 @@ #include "model/metadata.h" #include "model/namespace.h" #include "redpanda/tests/fixture.h" +#include "utils/fragmented_vector.h" #include #include @@ -74,7 +75,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { kafka::alter_configs_resource make_alter_topic_config_resource( const model::topic& topic, const absl::flat_hash_map& properties) { - std::vector cfg_list; + chunked_vector cfg_list; cfg_list.reserve(properties.size()); for (auto& [k, v] : properties) { cfg_list.push_back(kafka::alterable_config{.name = k, .value = v}); @@ -88,6 +89,15 @@ class alter_config_test_fixture : public redpanda_thread_fixture { }; } + chunked_vector + make_alter_topic_config_resource_cv( + const model::topic& topic, + const absl::flat_hash_map& properties) { + chunked_vector cv; + cv.push_back(make_alter_topic_config_resource(topic, properties)); + return cv; + } + kafka::incremental_alter_configs_resource make_incremental_alter_topic_config_resource( const model::topic& topic, @@ -96,7 +106,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { std:: pair, kafka::config_resource_operation>>& operations) { - std::vector cfg_list; + chunked_vector cfg_list; cfg_list.reserve(operations.size()); for (auto& [k, v] : operations) { cfg_list.push_back(kafka::incremental_alterable_config{ @@ -114,9 +124,24 @@ class alter_config_test_fixture : public redpanda_thread_fixture { }; } + chunked_vector + make_incremental_alter_topic_config_resource_cv( + const model::topic& topic, + const absl::flat_hash_map< + ss::sstring, + std:: + pair, kafka::config_resource_operation>>& + operations) { + chunked_vector cv; + cv.push_back( + make_incremental_alter_topic_config_resource(topic, operations)); + return cv; + } + kafka::describe_configs_response describe_configs( const ss::sstring& resource_name, - std::optional> configuration_keys = std::nullopt, + std::optional> configuration_keys + = std::nullopt, kafka::config_resource_type resource_type = kafka::config_resource_type::topic) { kafka::describe_configs_request req; @@ -124,7 +149,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { kafka::describe_configs_resource res{ .resource_type = resource_type, .resource_name = resource_name, - .configuration_keys = configuration_keys, + .configuration_keys = std::move(configuration_keys), }; req.data.resources.push_back(std::move(res)); return do_with_client([req = std::move(req)]( @@ -136,7 +161,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { } kafka::alter_configs_response - alter_configs(std::vector resources) { + alter_configs(chunked_vector resources) { kafka::alter_configs_request req; req.data.resources = std::move(resources); return do_with_client([req = std::move(req)]( @@ -148,7 +173,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { } kafka::incremental_alter_configs_response incremental_alter_configs( - std::vector resources) { + chunked_vector resources) { kafka::incremental_alter_configs_request req; req.data.resources = std::move(resources); return do_with_client([req = std::move(req)]( @@ -162,7 +187,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { void assert_property_presented( const ss::sstring& resource_name, const ss::sstring& key, - const kafka::describe_configs_response resp, + const kafka::describe_configs_response& resp, const bool presented) { auto it = std::find_if( resp.data.results.begin(), @@ -187,7 +212,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { void assert_properties_amount( const ss::sstring& resource_name, - const kafka::describe_configs_response resp, + const kafka::describe_configs_response& resp, const size_t amount) { auto it = std::find_if( resp.data.results.begin(), @@ -206,7 +231,7 @@ class alter_config_test_fixture : public redpanda_thread_fixture { const model::topic& topic, const ss::sstring& key, const ss::sstring& value, - const kafka::describe_configs_response resp) { + const kafka::describe_configs_response& resp) { auto it = std::find_if( resp.data.results.begin(), resp.data.results.end(), @@ -238,7 +263,7 @@ FIXTURE_TEST( req.data.topics = std::nullopt; auto client = make_kafka_client().get0(); client.connect().get(); - auto resp = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(1)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); auto broker_id = std::to_string(resp.data.brokers[0].node_id()); @@ -264,10 +289,10 @@ FIXTURE_TEST( // Single properies_request for (const auto& request_property : all_properties) { - std::vector request_properties = {request_property}; + chunked_vector request_properties{request_property}; auto single_describe_resp = describe_configs( broker_id, - std::make_optional(request_properties), + std::make_optional(std::move(request_properties)), kafka::config_resource_type::broker); assert_properties_amount(broker_id, single_describe_resp, 1); for (const auto& property : all_properties) { @@ -280,14 +305,14 @@ FIXTURE_TEST( } // Group properties_request - std::vector first_group_config_properties = { + chunked_vector first_group_config_properties = { "listeners", "advertised.listeners", "log.segment.bytes", "log.retention.bytes", "log.retention.ms"}; - std::vector second_group_config_properties = { + chunked_vector second_group_config_properties = { "num.partitions", "default.replication.factor", "log.dirs", @@ -295,7 +320,7 @@ FIXTURE_TEST( auto first_group_describe_resp = describe_configs( broker_id, - std::make_optional(first_group_config_properties), + std::make_optional(first_group_config_properties.copy()), kafka::config_resource_type::broker); assert_properties_amount( broker_id, @@ -312,7 +337,7 @@ FIXTURE_TEST( auto second_group_describe_resp = describe_configs( broker_id, - std::make_optional(second_group_config_properties), + std::make_optional(second_group_config_properties.copy()), kafka::config_resource_type::broker); assert_properties_amount( broker_id, @@ -377,9 +402,9 @@ FIXTURE_TEST( // Single properties_request for (const auto& request_property : all_properties) { - std::vector request_properties = {request_property}; + chunked_vector request_properties = {request_property}; auto single_describe_resp = describe_configs( - test_tp, std::make_optional(request_properties)); + test_tp, std::make_optional(std::move(request_properties))); assert_properties_amount(test_tp, single_describe_resp, 1); for (const auto& property : all_properties) { assert_property_presented( @@ -391,18 +416,18 @@ FIXTURE_TEST( } // Group properties_request - std::vector first_group_config_properties = { + chunked_vector first_group_config_properties = { "retention.ms", "retention.bytes", "segment.bytes", "redpanda.remote.read", "redpanda.remote.write"}; - std::vector second_group_config_properties = { + chunked_vector second_group_config_properties = { "cleanup.policy", "compression.type", "message.timestamp.type"}; auto first_group_describe_resp = describe_configs( - test_tp, std::make_optional(first_group_config_properties)); + test_tp, std::make_optional(first_group_config_properties.copy())); vlog( test_log.debug, "first_group_describe_resp: {}", @@ -419,7 +444,7 @@ FIXTURE_TEST( } auto second_group_describe_resp = describe_configs( - test_tp, std::make_optional(second_group_config_properties)); + test_tp, std::make_optional(second_group_config_properties.copy())); vlog( test_log.debug, "second_group_describe_resp: {}", @@ -450,7 +475,7 @@ FIXTURE_TEST(test_alter_single_topic_config, alter_config_test_fixture) { properties.emplace("replication.factor", "1"); auto resp = alter_configs( - {make_alter_topic_config_resource(test_tp, properties)}); + make_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 1); BOOST_REQUIRE_EQUAL( @@ -481,10 +506,9 @@ FIXTURE_TEST(test_alter_multiple_topics_config, alter_config_test_fixture) { properties_2.emplace("retention.bytes", "4096"); properties_2.emplace("replication.factor", "1"); - auto resp = alter_configs({ - make_alter_topic_config_resource(topic_1, properties_1), - make_alter_topic_config_resource(topic_2, properties_2), - }); + auto cv = make_alter_topic_config_resource_cv(topic_1, properties_1); + cv.push_back(make_alter_topic_config_resource(topic_2, properties_2)); + auto resp = alter_configs(std::move(cv)); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 2); BOOST_REQUIRE_EQUAL( @@ -515,7 +539,7 @@ FIXTURE_TEST( properties.emplace("replication.factor", "1"); auto resp = alter_configs( - {make_alter_topic_config_resource(test_tp, properties)}); + make_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL( resp.data.responses[0].error_code, kafka::error_code::none); @@ -530,7 +554,7 @@ FIXTURE_TEST(test_alter_topic_error, alter_config_test_fixture) { properties.emplace("not.exists", "1234"); auto resp = alter_configs( - {make_alter_topic_config_resource(test_tp, properties)}); + {make_alter_topic_config_resource_cv(test_tp, properties)}); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 1); BOOST_REQUIRE_EQUAL( @@ -551,7 +575,7 @@ FIXTURE_TEST( properties.emplace("replication.factor", "1"); auto resp = alter_configs( - {make_alter_topic_config_resource(test_tp, properties)}); + make_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 1); BOOST_REQUIRE_EQUAL( @@ -569,7 +593,7 @@ FIXTURE_TEST( new_properties.emplace("retention.bytes", "4096"); new_properties.emplace("replication.factor", "1"); - alter_configs({make_alter_topic_config_resource(test_tp, new_properties)}); + alter_configs(make_alter_topic_config_resource_cv(test_tp, new_properties)); auto new_describe_resp = describe_configs(test_tp); // retention.ms should be set back to default @@ -597,7 +621,7 @@ FIXTURE_TEST(test_incremental_alter_config, alter_config_test_fixture) { std::make_pair("1234", kafka::config_resource_operation::set)); auto resp = incremental_alter_configs( - {make_incremental_alter_topic_config_resource(test_tp, properties)}); + make_incremental_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 1); BOOST_REQUIRE_EQUAL( @@ -620,7 +644,7 @@ FIXTURE_TEST(test_incremental_alter_config, alter_config_test_fixture) { std::pair{"4096", kafka::config_resource_operation::set}); incremental_alter_configs( - {make_incremental_alter_topic_config_resource(test_tp, new_properties)}); + make_incremental_alter_topic_config_resource_cv(test_tp, new_properties)); auto new_describe_resp = describe_configs(test_tp); // retention.ms should stay untouched @@ -646,7 +670,7 @@ FIXTURE_TEST( std::pair{"true", kafka::config_resource_operation::set}); auto resp = incremental_alter_configs( - {make_incremental_alter_topic_config_resource(test_tp, properties)}); + make_incremental_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL( resp.data.responses[0].error_code, kafka::error_code::none); @@ -666,7 +690,7 @@ FIXTURE_TEST(test_incremental_alter_config_remove, alter_config_test_fixture) { std::make_pair("1234", kafka::config_resource_operation::set)); auto resp = incremental_alter_configs( - {make_incremental_alter_topic_config_resource(test_tp, properties)}); + make_incremental_alter_topic_config_resource_cv(test_tp, properties)); BOOST_REQUIRE_EQUAL(resp.data.responses.size(), 1); BOOST_REQUIRE_EQUAL( @@ -688,8 +712,8 @@ FIXTURE_TEST(test_incremental_alter_config_remove, alter_config_test_fixture) { "retention.ms", std::pair{std::nullopt, kafka::config_resource_operation::remove}); - incremental_alter_configs( - {make_incremental_alter_topic_config_resource(test_tp, new_properties)}); + incremental_alter_configs({make_incremental_alter_topic_config_resource_cv( + test_tp, new_properties)}); auto new_describe_resp = describe_configs(test_tp); // retention.ms should be set back to default diff --git a/src/v/kafka/server/tests/config_response_utils_test.cc b/src/v/kafka/server/tests/config_response_utils_test.cc new file mode 100644 index 0000000000000..0dc79bc6febfb --- /dev/null +++ b/src/v/kafka/server/tests/config_response_utils_test.cc @@ -0,0 +1,139 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "kafka/server/tests/config_response_utils_test_help.h" +#include "utils/to_string.h" + +#include +#include +#include + +#include +#include +#include +#include + +std::optional get_config( + const kafka::config_response_container_t& result, std::string_view key) { + for (const auto& config : result) { + if (config.name == key) { + return config.value; + } + } + throw std::runtime_error(fmt::format("Key not found: {}", key)); +} + +kafka::describe_configs_source get_config_source( + const kafka::config_response_container_t& result, std::string_view key) { + for (const auto& config : result) { + if (config.name == key) { + return config.config_source; + } + } + throw std::runtime_error(fmt::format("Key not found: {}", key)); +} + +BOOST_AUTO_TEST_CASE(add_topic_config_if_requested_tristate) { + using namespace kafka; + auto verify_tristate_config = []( + std::optional default_value, + tristate override_value, + std::optional expected_value) { + config_response_container_t result; + + add_topic_config_if_requested( + std::nullopt, + result, + "test-global-broker-config-name", + default_value, + "test-topic-override-name", + override_value, + false, + std::nullopt); + + BOOST_CHECK_EQUAL( + get_config(result, "test-topic-override-name"), expected_value); + }; + + // clang-format off + verify_tristate_config(std::make_optional(2), tristate(1), std::make_optional("1")); + verify_tristate_config(std::make_optional(2), tristate(std::nullopt), std::make_optional("2")); + verify_tristate_config(std::make_optional(2), tristate(), std::make_optional("-1")); + + verify_tristate_config(std::nullopt, tristate(1), std::make_optional("1")); + verify_tristate_config(std::nullopt, tristate(std::nullopt), std::make_optional("-1")); + verify_tristate_config(std::nullopt, tristate(), std::make_optional("-1")); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(add_topic_config_if_requested_optional) { + using namespace kafka; + auto verify_optional_config = []( + int default_value, + std::optional override_value, + std::optional expected_value, + bool hide_default_override) { + config_response_container_t result; + + add_topic_config_if_requested( + std::nullopt, + result, + "test-global-broker-config-name", + default_value, + "test-topic-override-name", + override_value, + false, + std::nullopt, + &describe_as_string, + hide_default_override); + + BOOST_CHECK_EQUAL( + get_config(result, "test-topic-override-name"), expected_value); + }; + + // clang-format off + verify_optional_config(2, std::make_optional(1), std::make_optional("1"), false); + verify_optional_config(2, std::nullopt, std::make_optional("2"), false); + // clang-format on +} + +BOOST_AUTO_TEST_CASE(add_topic_config_if_requested_optional_hide_default) { + using namespace kafka; + + auto verify_optional_config_with_hide_override = + []( + bool hide_default_override, + kafka::describe_configs_source expected_source) { + config_response_container_t result; + + add_topic_config_if_requested( + std::nullopt, + result, + "test-global-broker-config-name", + 2, + "test-topic-override-name", + std::make_optional(2), + false, + std::nullopt, + &describe_as_string, + hide_default_override); + + BOOST_CHECK_EQUAL( + get_config(result, "test-topic-override-name"), + std::make_optional("2")); + BOOST_CHECK_EQUAL( + get_config_source(result, "test-topic-override-name"), + expected_source); + }; + + // clang-format off + verify_optional_config_with_hide_override(false, kafka::describe_configs_source::topic); + verify_optional_config_with_hide_override(true, kafka::describe_configs_source::default_config); + // clang-format on +} diff --git a/src/v/kafka/server/tests/config_response_utils_test_help.h b/src/v/kafka/server/tests/config_response_utils_test_help.h new file mode 100644 index 0000000000000..d85e553f2639e --- /dev/null +++ b/src/v/kafka/server/tests/config_response_utils_test_help.h @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "kafka/protocol/describe_configs.h" +#include "kafka/server/handlers/configs/config_response_utils.h" + +namespace kafka { + +template +ss::sstring describe_as_string(const T& t); + +template +void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const T& default_value, + std::string_view override_name, + const std::optional& overrides, + bool include_synonyms, + std::optional documentation, + Func&& describe_f, + bool hide_default_override = false); + +template +void add_topic_config_if_requested( + const config_key_t& config_keys, + config_response_container_t& result, + std::string_view default_name, + const std::optional& default_value, + std::string_view override_name, + const tristate& overrides, + bool include_synonyms, + std::optional documentation); + +} // namespace kafka diff --git a/src/v/kafka/server/tests/consumer_groups_test.cc b/src/v/kafka/server/tests/consumer_groups_test.cc index 02a419838aacc..50afa0058104e 100644 --- a/src/v/kafka/server/tests/consumer_groups_test.cc +++ b/src/v/kafka/server/tests/consumer_groups_test.cc @@ -14,6 +14,7 @@ #include "kafka/protocol/errors.h" #include "kafka/protocol/find_coordinator.h" #include "kafka/protocol/join_group.h" +#include "kafka/protocol/offset_commit.h" #include "kafka/protocol/schemata/join_group_request.h" #include "kafka/types.h" #include "model/fundamental.h" @@ -21,6 +22,8 @@ #include "model/timeout_clock.h" #include "redpanda/tests/fixture.h" #include "test_utils/async.h" +#include "test_utils/scoped_config.h" +#include "utils/base64.h" #include #include @@ -100,6 +103,74 @@ FIXTURE_TEST(join_empty_group_static_member, consumer_offsets_fixture) { }).get(); } +FIXTURE_TEST(conditional_retention_test, consumer_offsets_fixture) { + scoped_config cfg; + cfg.get("group_topic_partitions").set_value(1); + // setting to true to begin with, so log_eviction_stm is attached to + // the partition. + cfg.get("unsafe_enable_consumer_offsets_delete_retention").set_value(true); + add_topic( + model::topic_namespace_view{model::kafka_namespace, model::topic{"foo"}}) + .get(); + kafka::group_instance_id gr("instance-1"); + wait_for_consumer_offsets_topic(gr); + // load some data into the topic via offset_commit requests. + auto client = make_kafka_client().get0(); + auto deferred = ss::defer([&client] { + client.stop().then([&client] { client.shutdown(); }).get(); + }); + client.connect().get(); + auto offset = 0; + auto rand_offset_commit = [&] { + auto req_part = offset_commit_request_partition{ + .partition_index = model::partition_id{0}, + .committed_offset = model::offset{offset++}}; + auto topic = offset_commit_request_topic{ + .name = model::topic{"foo"}, .partitions = {std::move(req_part)}}; + + return offset_commit_request{.data{ + .group_id = kafka::group_id{fmt::format("foo-{}", offset)}, + .topics = {std::move(topic)}}}; + }; + for (int i = 0; i < 10; i++) { + auto req = rand_offset_commit(); + req.data.group_instance_id = gr; + auto resp = client.dispatch(std::move(req)).get(); + BOOST_REQUIRE(!resp.data.errored()); + } + auto part = app.partition_manager.local().get(model::ntp{ + model::kafka_namespace, + model::kafka_consumer_offsets_topic, + model::partition_id{0}}); + BOOST_REQUIRE(part); + auto log = part->log(); + storage::ntp_config::default_overrides ov; + ov.cleanup_policy_bitflags = model::cleanup_policy_bitflags::deletion + | model::cleanup_policy_bitflags::compaction; + log->update_configuration(ov).get(); + log->flush().get(); + log->force_roll(ss::default_priority_class()).get(); + for (auto retention_enabled : {false, true}) { + // number of partitions of CO topic. + cfg.get("unsafe_enable_consumer_offsets_delete_retention") + .set_value(retention_enabled); + // attempt a GC on the partition log. + // evict the first segment. + storage::gc_config gc_cfg{model::timestamp::max(), 1}; + log->gc(gc_cfg).get(); + // Check if retention works + try { + tests::cooperative_spin_wait_with_timeout(5s, [&] { + return log.get()->offsets().start_offset > model::offset{0}; + }).get(); + } catch (const ss::timed_out_error& e) { + if (retention_enabled) { + std::rethrow_exception(std::make_exception_ptr(e)); + } + } + } +} + SEASTAR_THREAD_TEST_CASE(consumer_group_decode) { { // snatched from a log message after a franz-go client joined diff --git a/src/v/kafka/server/tests/create_topics_test.cc b/src/v/kafka/server/tests/create_topics_test.cc index c10883be6ae3a..2c735df1af484 100644 --- a/src/v/kafka/server/tests/create_topics_test.cc +++ b/src/v/kafka/server/tests/create_topics_test.cc @@ -9,9 +9,11 @@ #include "kafka/protocol/create_topics.h" #include "kafka/protocol/metadata.h" +#include "kafka/server/handlers/configs/config_response_utils.h" #include "kafka/server/handlers/topics/types.h" #include "redpanda/tests/fixture.h" #include "resource_mgmt/io_priority.h" +#include "utils/fragmented_vector.h" #include #include @@ -135,8 +137,10 @@ class create_topic_fixture : public redpanda_thread_fixture { /// Server should return default configs BOOST_TEST(topic_res.configs, "empty config response"); auto cfg_map = config_map(*topic_res.configs); - const auto default_topic_properties = kafka::from_cluster_type( - app.metadata_cache.local().get_default_properties()); + const auto default_topic_properties = config_map( + kafka::report_topic_configs( + app.metadata_cache.local(), + app.metadata_cache.local().get_default_properties())); BOOST_TEST( cfg_map == default_topic_properties, "incorrect default properties"); @@ -153,8 +157,9 @@ class create_topic_fixture : public redpanda_thread_fixture { auto cfg = app.metadata_cache.local().get_topic_cfg( model::topic_namespace_view{model::kafka_namespace, topic_res.name}); BOOST_TEST(cfg, "missing topic config"); - auto config_map = kafka::from_cluster_type(cfg->properties); - BOOST_TEST(config_map == resp_cfgs, "configs didn't match"); + auto cfg_map = config_map(kafka::report_topic_configs( + app.metadata_cache.local(), cfg->properties)); + BOOST_TEST(cfg_map == resp_cfgs, "configs didn't match"); BOOST_CHECK_EQUAL( topic_res.topic_config_error_code, kafka::error_code::none); } @@ -166,11 +171,12 @@ class create_topic_fixture : public redpanda_thread_fixture { // query the server for this topic's metadata kafka::metadata_request metadata_req; metadata_req.data.topics - = std::make_optional>(); + = std::make_optional>(); metadata_req.data.topics->push_back( kafka::metadata_request_topic{request_topic.name}); auto metadata_resp - = client.dispatch(metadata_req, kafka::api_version(1)).get0(); + = client.dispatch(std::move(metadata_req), kafka::api_version(1)) + .get0(); // yank out the metadata for the topic from the response auto topic_metadata = std::find_if( diff --git a/src/v/kafka/server/tests/delete_topics_test.cc b/src/v/kafka/server/tests/delete_topics_test.cc index 72dc3670d51fb..c4e80fcd5db9b 100644 --- a/src/v/kafka/server/tests/delete_topics_test.cc +++ b/src/v/kafka/server/tests/delete_topics_test.cc @@ -17,6 +17,7 @@ #include "redpanda/application.h" #include "redpanda/tests/fixture.h" #include "test_utils/async.h" +#include "utils/fragmented_vector.h" #include #include @@ -78,12 +79,13 @@ class delete_topics_request_fixture : public redpanda_thread_fixture { kafka::metadata_response get_topic_metadata(const model::topic& tp) { auto client = make_kafka_client().get0(); client.connect().get0(); - std::vector topics; + chunked_vector topics; topics.push_back(kafka::metadata_request_topic{tp}); kafka::metadata_request md_req{ - .data = {.topics = topics, .allow_auto_topic_creation = false}, + .data + = {.topics = std::move(topics), .allow_auto_topic_creation = false}, .list_all_topics = false}; - return client.dispatch(md_req).get0(); + return client.dispatch(std::move(md_req)).get0(); } ss::future get_all_metadata() { @@ -116,7 +118,7 @@ class delete_topics_request_fixture : public redpanda_thread_fixture { } kafka::delete_topics_request make_delete_topics_request( - std::vector topics, std::chrono::milliseconds timeout) { + chunked_vector topics, std::chrono::milliseconds timeout) { kafka::delete_topics_request req; req.data.topic_names = std::move(topics); req.data.timeout_ms = timeout; @@ -145,14 +147,16 @@ FIXTURE_TEST(delete_valid_topics, delete_topics_request_fixture) { create_topic("topic-1", 1, 1); // Single topic - validate_valid_delete_topics_request( - make_delete_topics_request({model::topic("topic-1")}, 10s)); + validate_valid_delete_topics_request(make_delete_topics_request( + chunked_vector{{model::topic("topic-1")}}, 10s)); create_topic("topic-2", 5, 1); create_topic("topic-3", 1, 1); // Multi topic validate_valid_delete_topics_request(make_delete_topics_request( - {model::topic("topic-2"), model::topic("topic-3")}, 10s)); + chunked_vector{ + {model::topic("topic-2"), model::topic("topic-3")}}, + 10s)); } #if 0 @@ -164,7 +168,7 @@ FIXTURE_TEST(error_delete_topics_request, delete_topics_request_fixture) { // Basic validate_error_delete_topic_request( - make_delete_topics_request({model::topic("invalid-topic")}, 10s), + make_delete_topics_request(chunked_vector{{model::topic("invalid-topic")}}, 10s), {{model::topic("invalid-topic"), kafka::error_code::unknown_topic_or_partition}}); @@ -173,8 +177,8 @@ FIXTURE_TEST(error_delete_topics_request, delete_topics_request_fixture) { validate_error_delete_topic_request( make_delete_topics_request( - {model::topic("partial-topic-1"), - model::topic("partial-invalid-topic")}, + chunked_vector{{model::topic("partial-topic-1"), + model::topic("partial-invalid-topic")}}, 10s), {{model::topic("partial-topic-1"), kafka::error_code::none}, {model::topic("partial-invalid-topic"), @@ -184,7 +188,7 @@ FIXTURE_TEST(error_delete_topics_request, delete_topics_request_fixture) { create_topic("timeout-topic", 1, 1); auto tp = model::topic("timeout-topic"); validate_error_delete_topic_request( - make_delete_topics_request({tp}, 0ms), + make_delete_topics_request(chunked_vector{{tp}}, 0ms), {{tp, kafka::error_code::request_timed_out}}); tests::cooperative_spin_wait_with_timeout(5s, [this, tp] { diff --git a/src/v/kafka/server/tests/error_mapping_test.cc b/src/v/kafka/server/tests/error_mapping_test.cc index 04b08f5b9fe04..1d0534867e1f3 100644 --- a/src/v/kafka/server/tests/error_mapping_test.cc +++ b/src/v/kafka/server/tests/error_mapping_test.cc @@ -22,6 +22,22 @@ BOOST_AUTO_TEST_CASE(error_mapping_test) { BOOST_REQUIRE_EQUAL( kafka::map_topic_error_code(cluster::errc::topic_invalid_partitions), kafka::error_code::invalid_partitions); + BOOST_REQUIRE_EQUAL( + kafka::map_topic_error_code( + cluster::errc::topic_invalid_partitions_core_limit), + kafka::error_code::invalid_partitions); + BOOST_REQUIRE_EQUAL( + kafka::map_topic_error_code( + cluster::errc::topic_invalid_partitions_memory_limit), + kafka::error_code::invalid_partitions); + BOOST_REQUIRE_EQUAL( + kafka::map_topic_error_code( + cluster::errc::topic_invalid_partitions_fd_limit), + kafka::error_code::invalid_partitions); + BOOST_REQUIRE_EQUAL( + kafka::map_topic_error_code( + cluster::errc::topic_invalid_partitions_decreased), + kafka::error_code::invalid_partitions); BOOST_REQUIRE_EQUAL( kafka::map_topic_error_code( cluster::errc::topic_invalid_replication_factor), diff --git a/src/v/kafka/server/tests/fetch_plan_bench.cc b/src/v/kafka/server/tests/fetch_plan_bench.cc index f971cb00fd53f..1b2ccdff4ae7b 100644 --- a/src/v/kafka/server/tests/fetch_plan_bench.cc +++ b/src/v/kafka/server/tests/fetch_plan_bench.cc @@ -39,15 +39,6 @@ static ss::logger fpt_logger("fpt_test"); using namespace std::chrono_literals; // NOLINT struct fixture { - static kafka::fetch_session_partition make_fetch_partition( - model::topic topic, model::partition_id p_id, model::offset offset) { - return kafka::fetch_session_partition{ - .topic_partition = {std::move(topic), p_id}, - .max_bytes = 1_MiB, - .fetch_offset = offset, - .high_watermark = offset}; - } - static kafka::fetch_request::topic make_fetch_request_topic(model::topic tp, int partitions_count) { kafka::fetch_request::topic fetch_topic{ @@ -94,35 +85,39 @@ struct fetch_plan_fixture : redpanda_thread_fixture { }; PERF_TEST_F(fetch_plan_fixture, test_fetch_plan) { - // make the fetch topic - kafka::fetch_topic ft; - ft.name = t; - - // add the partitions to the fetch request - for (int pid = 0; pid < session_partition_count; pid++) { - kafka::fetch_partition fp; - fp.partition_index = model::partition_id(pid); - fp.fetch_offset = model::offset(0); - fp.current_leader_epoch = kafka::leader_epoch(-1); - fp.log_start_offset = model::offset(-1); - fp.max_bytes = 1048576; - ft.fetch_partitions.push_back(std::move(fp)); - } + auto make_fetch_req = [this]() { + // make the fetch topic + kafka::fetch_topic ft; + ft.name = t; + + // add the partitions to the fetch request + for (int pid = 0; pid < session_partition_count; pid++) { + kafka::fetch_partition fp; + fp.partition_index = model::partition_id(pid); + fp.fetch_offset = model::offset(0); + fp.current_leader_epoch = kafka::leader_epoch(-1); + fp.log_start_offset = model::offset(-1); + fp.max_bytes = 1048576; + ft.fetch_partitions.push_back(std::move(fp)); + } - BOOST_TEST_CHECKPOINT("HERE"); + BOOST_TEST_CHECKPOINT("HERE"); + + // create a request + kafka::fetch_request_data frq_data; + frq_data.replica_id = kafka::client::consumer_replica_id; + frq_data.max_wait_ms = 500ms; + frq_data.min_bytes = 1; + frq_data.max_bytes = 52428800; + frq_data.isolation_level = model::isolation_level::read_uncommitted; + frq_data.session_id = kafka::invalid_fetch_session_id; + frq_data.session_epoch = kafka::initial_fetch_session_epoch; + frq_data.topics.push_back(std::move(ft)); - // create a request - kafka::fetch_request_data frq_data; - frq_data.replica_id = kafka::client::consumer_replica_id; - frq_data.max_wait_ms = 500ms; - frq_data.min_bytes = 1; - frq_data.max_bytes = 52428800; - frq_data.isolation_level = model::isolation_level::read_uncommitted; - frq_data.session_id = kafka::invalid_fetch_session_id; - frq_data.session_epoch = kafka::initial_fetch_session_epoch; - frq_data.topics.push_back(std::move(ft)); + return kafka::fetch_request{std::move(frq_data)}; + }; - kafka::fetch_request fetch_req{frq_data}; + auto fetch_req = make_fetch_req(); BOOST_TEST_CHECKPOINT("HERE"); @@ -136,7 +131,7 @@ PERF_TEST_F(fetch_plan_fixture, test_fetch_plan) { // in the session cache kafka::fetch_session_id sess_id; { - auto rctx = make_request_context(fetch_req, conn); + auto rctx = make_request_context(make_fetch_req(), conn); // set up a fetch session auto ctx = rctx.fetch_sessions().maybe_get_session(fetch_req); BOOST_REQUIRE_EQUAL(ctx.has_error(), false); @@ -157,7 +152,7 @@ PERF_TEST_F(fetch_plan_fixture, test_fetch_plan) { fetch_req.data.session_epoch = 1; fetch_req.data.topics.clear(); - auto rctx = make_request_context(fetch_req); + auto rctx = make_request_context(std::move(fetch_req)); BOOST_REQUIRE_EQUAL(rctx.fetch_sessions().size(), 1); // add all partitions to fetch metadata diff --git a/src/v/kafka/server/tests/fetch_session_test.cc b/src/v/kafka/server/tests/fetch_session_test.cc index 7e5816a8d90d0..7099e3b8253c1 100644 --- a/src/v/kafka/server/tests/fetch_session_test.cc +++ b/src/v/kafka/server/tests/fetch_session_test.cc @@ -9,6 +9,7 @@ * by the Apache License, Version 2.0 */ #include "kafka/protocol/fetch.h" +#include "kafka/protocol/schemata/fetch_request.h" #include "kafka/server/fetch_session.h" #include "kafka/server/fetch_session_cache.h" #include "kafka/types.h" @@ -27,13 +28,15 @@ using namespace std::chrono_literals; // NOLINT struct fixture { - static kafka::fetch_session_partition make_fetch_partition( - model::topic topic, model::partition_id p_id, model::offset offset) { - return kafka::fetch_session_partition{ - .topic_partition = {topic, p_id}, - .max_bytes = 1_MiB, - .fetch_offset = offset, - .high_watermark = offset}; + static kafka::fetch_partition + make_fetch_partition(const model::partition_id p_id) { + return { + .partition_index = p_id, + .current_leader_epoch = kafka::leader_epoch( + random_generators::get_int(100)), + .fetch_offset = model::offset(random_generators::get_int(10000)), + .max_bytes = random_generators::get_int(1024, 1024 * 1024), + }; } static kafka::fetch_request::topic @@ -45,11 +48,7 @@ struct fixture { for (int i = 0; i < partitions_count; ++i) { fetch_topic.fetch_partitions.push_back( - kafka::fetch_request::partition{ - .partition_index = model::partition_id(i), - .fetch_offset = model::offset(i * 10), - .max_bytes = 100_KiB, - }); + make_fetch_partition(model::partition_id(i))); } return fetch_topic; } @@ -79,16 +78,18 @@ FIXTURE_TEST(test_fetch_session_basic_operations, fixture) { expected.reserve(20); for (int i = 0; i < 20; ++i) { + auto req = make_fetch_request_topic( + model::topic(random_generators::gen_alphanum_string(5)), 1); + req.fetch_partitions[0].partition_index = model::partition_id( + random_generators::get_int(i * 10, ((i + 1) * 10) - 1)); + expected.push_back(tpo{ model::ktp{ - model::topic(random_generators::gen_alphanum_string(5)), - model::partition_id( - random_generators::get_int(i * 10, ((i + 1) * 10) - 1))}, - model::offset(random_generators::get_int(10000))}); - - auto& t = expected.back(); - session.partitions().emplace(fixture::make_fetch_partition( - t.ktp.get_topic(), t.ktp.get_partition(), t.offset)); + model::topic(req.name), + model::partition_id(req.fetch_partitions[0].partition_index)}, + model::offset(req.fetch_partitions[0].fetch_offset)}); + session.partitions().emplace( + kafka::fetch_session_partition(req.name, req.fetch_partitions[0])); } BOOST_TEST_MESSAGE("test insertion order iteration"); @@ -134,7 +135,8 @@ FIXTURE_TEST(test_session_operations, fixture) { kafka::fetch_request req; req.data.session_epoch = kafka::initial_fetch_session_epoch; req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = {make_fetch_request_topic(model::topic("test"), 3)}; + req.data.topics.emplace_back( + make_fetch_request_topic(model::topic("test"), 3)); { BOOST_TEST_MESSAGE("create new session"); auto ctx = cache.maybe_get_session(req); @@ -156,6 +158,9 @@ FIXTURE_TEST(test_session_operations, fixture) { BOOST_REQUIRE_EQUAL( fp.topic_partition.get_partition(), req.data.topics[0].fetch_partitions[i].partition_index); + BOOST_REQUIRE_EQUAL( + fp.current_leader_epoch, + req.data.topics[0].fetch_partitions[i].current_leader_epoch); BOOST_REQUIRE_EQUAL( fp.fetch_offset, req.data.topics[0].fetch_partitions[i].fetch_offset); @@ -170,13 +175,23 @@ FIXTURE_TEST(test_session_operations, fixture) { BOOST_TEST_MESSAGE("test updating session"); { - req.data.topics[0].fetch_partitions.erase( - std::next(req.data.topics[0].fetch_partitions.begin())); - // add 2 partitons from new topic, forget one from the first topic - req.data.topics.push_back( - make_fetch_request_topic(model::topic("test-new"), 2)); + // Remove and forget about the first partition. + auto fp_v = std::vector< + decltype(req.data.topics[0].fetch_partitions)::value_type>{ + req.data.topics[0].fetch_partitions.begin(), + req.data.topics[0].fetch_partitions.end()}; + fp_v.erase(std::next(fp_v.begin())); + req.data.topics[0].fetch_partitions = { + std::make_move_iterator(fp_v.begin()), + std::make_move_iterator(fp_v.end())}; req.data.forgotten.push_back(kafka::fetch_request::forgotten_topic{ .name = model::topic("test"), .forgotten_partition_indexes = {1}}); + // Update the second partition. + req.data.topics[0].fetch_partitions[0] = make_fetch_partition( + req.data.topics[0].fetch_partitions[0].partition_index); + // Add 2 partitions from new topic. + req.data.topics.push_back( + make_fetch_request_topic(model::topic("test-new"), 2)); auto ctx = cache.maybe_get_session(req); @@ -203,6 +218,11 @@ FIXTURE_TEST(test_session_operations, fixture) { BOOST_REQUIRE_EQUAL( fp.topic_partition.get_partition(), req.data.topics[t_idx].fetch_partitions[p_idx].partition_index); + BOOST_REQUIRE_EQUAL( + fp.current_leader_epoch, + req.data.topics[t_idx] + .fetch_partitions[p_idx] + .current_leader_epoch); BOOST_REQUIRE_EQUAL( fp.fetch_offset, req.data.topics[t_idx].fetch_partitions[p_idx].fetch_offset); diff --git a/src/v/kafka/server/tests/fetch_test.cc b/src/v/kafka/server/tests/fetch_test.cc index a655d1755e1b6..f089a51168bef 100644 --- a/src/v/kafka/server/tests/fetch_test.cc +++ b/src/v/kafka/server/tests/fetch_test.cc @@ -8,6 +8,7 @@ // by the Apache License, Version 2.0 #include "kafka/protocol/batch_consumer.h" +#include "kafka/protocol/types.h" #include "kafka/server/handlers/fetch.h" #include "kafka/types.h" #include "model/fundamental.h" @@ -17,6 +18,7 @@ #include +#include #include #include @@ -257,17 +259,18 @@ FIXTURE_TEST(fetch_one, redpanda_thread_fixture) { // disable incremental fetches req.data.session_id = kafka::invalid_fetch_session_id; req.data.session_epoch = kafka::final_fetch_session_epoch; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::fetch_topic{ .name = topic, .fetch_partitions = {{ .partition_index = pid, .fetch_offset = offset, }}, - }}; + }); auto client = make_kafka_client().get0(); client.connect().get(); - auto resp = client.dispatch(req, kafka::api_version(version)).get0(); + auto resp + = client.dispatch(std::move(req), kafka::api_version(version)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE(resp.data.topics.size() == 1); @@ -358,7 +361,8 @@ FIXTURE_TEST(fetch_empty, redpanda_thread_fixture) { auto client = make_kafka_client().get0(); client.connect().get(); - auto resp_1 = client.dispatch(no_topics, kafka::api_version(6)).get0(); + auto resp_1 + = client.dispatch(std::move(no_topics), kafka::api_version(6)).get0(); BOOST_REQUIRE(resp_1.data.topics.empty()); @@ -366,14 +370,89 @@ FIXTURE_TEST(fetch_empty, redpanda_thread_fixture) { no_partitions.data.max_bytes = std::numeric_limits::max(); no_partitions.data.min_bytes = 1; no_partitions.data.max_wait_ms = std::chrono::milliseconds(1000); - no_partitions.data.topics = {{.name = topic, .fetch_partitions = {}}}; + no_partitions.data.topics.emplace_back( + kafka::fetch_topic{.name = topic, .fetch_partitions = {}}); - auto resp_2 = client.dispatch(no_topics, kafka::api_version(6)).get0(); + // NOTE(oren): this looks like it was ill-formed before? see surrounding + // code + auto resp_2 + = client.dispatch(std::move(no_partitions), kafka::api_version(6)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE(resp_2.data.topics.empty()); } +FIXTURE_TEST(fetch_leader_epoch, redpanda_thread_fixture) { + // create a topic partition with some data + model::topic topic("foo"); + model::partition_id pid(0); + auto ntp = make_default_ntp(topic, pid); + auto log_config = make_default_config(); + wait_for_controller_leadership().get0(); + add_topic(model::topic_namespace_view(ntp)).get(); + + wait_for_partition_offset(ntp, model::offset(0)).get0(); + + const auto shard = app.shard_table.local().shard_for(ntp); + app.partition_manager + .invoke_on( + *shard, + [ntp, this](cluster::partition_manager& mgr) { + auto partition = mgr.get(ntp); + { + auto batches = model::test::make_random_batches( + model::offset(0), 5); + auto rdr = model::make_memory_record_batch_reader( + std::move(batches)); + partition->raft() + ->replicate( + std::move(rdr), + raft::replicate_options( + raft::consistency_level::quorum_ack)) + .discard_result() + .get0(); + } + partition->raft()->step_down("trigger epoch change").get0(); + wait_for_leader(ntp, 10s).get0(); + { + auto batches = model::test::make_random_batches( + model::offset(0), 5); + auto rdr = model::make_memory_record_batch_reader( + std::move(batches)); + partition->raft() + ->replicate( + std::move(rdr), + raft::replicate_options( + raft::consistency_level::quorum_ack)) + .discard_result() + .get0(); + } + }) + .get0(); + + kafka::fetch_request req; + req.data.max_bytes = std::numeric_limits::max(); + req.data.min_bytes = 1; + req.data.max_wait_ms = std::chrono::milliseconds(1000); + req.data.topics.emplace_back(kafka::fetch_topic{ + .name = topic, + .fetch_partitions = {{ + .partition_index = pid, + .current_leader_epoch = kafka::leader_epoch(1), + .fetch_offset = model::offset(6), + }}}); + + auto client = make_kafka_client().get0(); + client.connect().get(); + auto resp = client.dispatch(std::move(req), kafka::api_version(9)).get0(); + client.stop().then([&client] { client.shutdown(); }).get(); + + BOOST_REQUIRE_MESSAGE( + resp.data.topics[0].partitions[0].error_code + == kafka::error_code::fenced_leader_epoch, + fmt::format("error: {}", resp.data.topics[0].partitions[0].error_code)); +} + FIXTURE_TEST(fetch_multi_partitions_debounce, redpanda_thread_fixture) { // create a topic partition with some data model::topic topic("foo"); @@ -393,10 +472,10 @@ FIXTURE_TEST(fetch_multi_partitions_debounce, redpanda_thread_fixture) { req.data.min_bytes = 1; req.data.max_wait_ms = std::chrono::milliseconds(3000); req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::fetch_topic{ .name = topic, .fetch_partitions = {}, - }}; + }); for (int i = 0; i < 6; ++i) { kafka::fetch_request::partition p; p.partition_index = model::partition_id(i); @@ -407,7 +486,7 @@ FIXTURE_TEST(fetch_multi_partitions_debounce, redpanda_thread_fixture) { } auto client = make_kafka_client().get0(); client.connect().get(); - auto fresp = client.dispatch(req, kafka::api_version(4)); + auto fresp = client.dispatch(std::move(req), kafka::api_version(4)); for (int i = 0; i < 6; ++i) { model::partition_id partition_id(i); @@ -468,17 +547,17 @@ FIXTURE_TEST(fetch_leader_ack, redpanda_thread_fixture) { req.data.min_bytes = 1; req.data.max_wait_ms = std::chrono::milliseconds(5000); req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::fetch_topic{ .name = topic, .fetch_partitions = {{ .partition_index = pid, .fetch_offset = offset, }}, - }}; + }); auto client = make_kafka_client().get0(); client.connect().get(); - auto fresp = client.dispatch(req, kafka::api_version(4)); + auto fresp = client.dispatch(std::move(req), kafka::api_version(4)); auto shard = app.shard_table.local().shard_for(ntp); app.partition_manager .invoke_on( @@ -525,17 +604,17 @@ FIXTURE_TEST(fetch_one_debounce, redpanda_thread_fixture) { req.data.min_bytes = 1; req.data.max_wait_ms = std::chrono::milliseconds(5000); req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::fetch_topic{ .name = topic, .fetch_partitions = {{ .partition_index = pid, .fetch_offset = offset, }}, - }}; + }); auto client = make_kafka_client().get0(); client.connect().get(); - auto fresp = client.dispatch(req, kafka::api_version(4)); + auto fresp = client.dispatch(std::move(req), kafka::api_version(4)); auto shard = app.shard_table.local().shard_for(ntp); app.partition_manager .invoke_on( @@ -592,15 +671,14 @@ FIXTURE_TEST(fetch_multi_topics, redpanda_thread_fixture) { req.data.min_bytes = 1; req.data.max_wait_ms = std::chrono::milliseconds(3000); req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = { - { - .name = topic_1, - .fetch_partitions = {}, - }, - { - .name = topic_2, - .fetch_partitions = {}, - }}; + req.data.topics.emplace_back(kafka::fetch_topic{ + .name = topic_1, + .fetch_partitions = {}, + }); + req.data.topics.emplace_back(kafka::fetch_topic{ + .name = topic_2, + .fetch_partitions = {}, + }); for (auto& ntp : ntps) { kafka::fetch_request::partition p; @@ -634,7 +712,7 @@ FIXTURE_TEST(fetch_multi_topics, redpanda_thread_fixture) { .get0(); } - auto resp = client.dispatch(req, kafka::api_version(4)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(4)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE_EQUAL(resp.data.topics.size(), 2); @@ -689,17 +767,17 @@ FIXTURE_TEST(fetch_request_max_bytes, redpanda_thread_fixture) { req.data.min_bytes = 1; req.data.max_wait_ms = std::chrono::milliseconds(0); req.data.session_id = kafka::invalid_fetch_session_id; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::fetch_topic{ .name = topic, .fetch_partitions = {{ .partition_index = pid, .fetch_offset = model::offset(0), }}, - }}; + }); auto client = make_kafka_client().get(); client.connect().get(); - auto fresp = client.dispatch(req, kafka::api_version(4)); + auto fresp = client.dispatch(std::move(req), kafka::api_version(4)); auto resp = fresp.get(); client.stop().then([&client] { client.shutdown(); }).get(); diff --git a/src/v/kafka/server/tests/group_test.cc b/src/v/kafka/server/tests/group_test.cc index 9c40ef18b62b3..c799a2ac6be92 100644 --- a/src/v/kafka/server/tests/group_test.cc +++ b/src/v/kafka/server/tests/group_test.cc @@ -8,8 +8,10 @@ // by the Apache License, Version 2.0 #include "config/configuration.h" +#include "kafka/protocol/types.h" #include "kafka/server/group.h" #include "kafka/server/group_metadata.h" +#include "utils/fragmented_vector.h" #include "utils/to_string.h" #include @@ -19,6 +21,9 @@ #include #include +#include + +using namespace std::chrono_literals; namespace kafka { static auto split_member_id(const ss::sstring& m) { @@ -75,7 +80,7 @@ static member_ptr get_group_member( std::chrono::seconds(1), std::chrono::milliseconds(2), kafka::protocol_type("p"), - protos); + chunked_vector{protos.begin(), protos.end()}); } static join_group_response join_resp() { @@ -179,7 +184,8 @@ SEASTAR_THREAD_TEST_CASE(rebalance_timeout) { std::chrono::seconds(1), std::chrono::milliseconds(2), kafka::protocol_type("p"), - test_group_protos); + chunked_vector{ + test_group_protos.begin(), test_group_protos.end()}); auto m1 = ss::make_lw_shared( kafka::member_id("n"), @@ -190,7 +196,8 @@ SEASTAR_THREAD_TEST_CASE(rebalance_timeout) { std::chrono::seconds(1), std::chrono::seconds(3), kafka::protocol_type("p"), - test_group_protos); + chunked_vector{ + test_group_protos.begin(), test_group_protos.end()}); (void)g.add_member(m0); BOOST_TEST(g.rebalance_timeout() == std::chrono::milliseconds(2)); @@ -369,7 +376,7 @@ SEASTAR_THREAD_TEST_CASE(supports_protocols) { // empty group -> request needs protocol type r.data.protocol_type = kafka::protocol_type(""); - r.data.protocols = std::vector{ + r.data.protocols = chunked_vector{ {kafka::protocol_name(""), bytes()}}; BOOST_TEST(!g.supports_protocols(r)); @@ -380,7 +387,7 @@ SEASTAR_THREAD_TEST_CASE(supports_protocols) { // group is empty and request can init group state r.data.protocol_type = kafka::protocol_type("p"); - r.data.protocols = std::vector{ + r.data.protocols = chunked_vector{ {kafka::protocol_name(""), bytes()}}; BOOST_TEST(g.supports_protocols(r)); @@ -394,14 +401,15 @@ SEASTAR_THREAD_TEST_CASE(supports_protocols) { std::chrono::seconds(1), std::chrono::seconds(3), kafka::protocol_type("p"), - test_group_protos); + chunked_vector{ + test_group_protos.begin(), test_group_protos.end()}); (void)g.add_member(m); g.set_state(group_state::preparing_rebalance); // protocol type doesn't match the group's protocol type r.data.protocol_type = kafka::protocol_type("x"); - r.data.protocols = std::vector{ + r.data.protocols = chunked_vector{ {kafka::protocol_name(""), bytes()}}; BOOST_TEST(!g.supports_protocols(r)); @@ -410,7 +418,7 @@ SEASTAR_THREAD_TEST_CASE(supports_protocols) { BOOST_TEST(!g.supports_protocols(r)); // now it contains a matching protocol - r.data.protocols = std::vector{ + r.data.protocols = chunked_vector{ {kafka::protocol_name("n0"), bytes()}}; BOOST_TEST(g.supports_protocols(r)); @@ -424,11 +432,11 @@ SEASTAR_THREAD_TEST_CASE(supports_protocols) { std::chrono::seconds(1), std::chrono::seconds(3), kafka::protocol_type("p"), - std::vector{{kafka::protocol_name("n2"), "d0"}}); + chunked_vector{{kafka::protocol_name("n2"), "d0"}}); (void)g.add_member(m2); // n2 is not supported bc the first member doesn't support it - r.data.protocols = std::vector{ + r.data.protocols = chunked_vector{ {kafka::protocol_name("n2"), bytes()}}; BOOST_TEST(!g.supports_protocols(r)); } @@ -445,8 +453,8 @@ SEASTAR_THREAD_TEST_CASE(leader_rejoined) { // leader is joining BOOST_TEST(g.leader_rejoined()); - // simulate that the leader is now not joining for some reason. since there - // is only one member, a replacement can't be chosen. + // simulate that the leader is now not joining for some reason. since + // there is only one member, a replacement can't be chosen. m0->set_join_response(join_resp()); BOOST_TEST(!g.leader_rejoined()); @@ -489,4 +497,72 @@ SEASTAR_THREAD_TEST_CASE(group_state_output) { BOOST_TEST(s == "PreparingRebalance"); } +SEASTAR_THREAD_TEST_CASE(add_new_static_member) { + auto g = get(); + const kafka::group_id common_group_id = g.id(); + const kafka::group_instance_id common_instance_id{"0-0"}; + + const kafka::member_id m1_id{"m1"}; + const kafka::client_id m1_client_id{"client-id-1"}; + const kafka::client_host m1_client_host{"client-host-1"}; + const std::chrono::milliseconds m1_session_timeout{30001}; + const std::chrono::milliseconds m1_rebalance_timeout{45001}; + + // Create request for first member + join_group_request r1; + r1.client_id = m1_client_id; + r1.client_host = m1_client_host; + r1.data.group_id = common_group_id; + r1.data.group_instance_id = common_instance_id; + r1.data.session_timeout_ms = m1_session_timeout; + r1.data.rebalance_timeout_ms = m1_rebalance_timeout; + + // adding first static member will call "add_member_and_rebalance" + g.add_new_static_member(m1_id, std::move(r1)); + + // validate group state + BOOST_TEST(g.contains_member(m1_id)); + + // validate new member + const auto m1 = g.get_member(m1_id); + BOOST_TEST(m1->group_id() == common_group_id); + BOOST_TEST(m1->id() == m1_id); + BOOST_TEST(m1->client_id() == m1_client_id); + BOOST_TEST(m1->client_host() == m1_client_host); + BOOST_TEST(m1->session_timeout() == m1_session_timeout); + BOOST_TEST(m1->rebalance_timeout() == m1_rebalance_timeout); + + const kafka::member_id m2_id{"m2"}; + const kafka::client_id m2_client_id{"client-id-2"}; + const kafka::client_host m2_client_host{"client-host-2"}; + const std::chrono::milliseconds m2_session_timeout{30002}; + const std::chrono::milliseconds m2_rebalance_timeout{45002}; + + // Create request for second member to update m1 + join_group_request r2; + r2.client_id = m2_client_id; + r2.client_host = m2_client_host; + r2.data.group_id = common_group_id; + r2.data.group_instance_id = common_instance_id; + r2.data.session_timeout_ms = m2_session_timeout; + r2.data.rebalance_timeout_ms = m2_rebalance_timeout; + + // adding second static member will call + // "update_static_member_and_rebalance" + g.add_new_static_member(m2_id, std::move(r2)); + + // validate group state + BOOST_TEST(!g.contains_member(m1_id)); + BOOST_TEST(g.contains_member(m2_id)); + + // validate updated member + const auto m2 = g.get_member(m2_id); + BOOST_TEST(m2->group_id() == common_group_id); + BOOST_TEST(m2->id() == m2_id); + BOOST_TEST(m2->client_id() == m2_client_id); + BOOST_TEST(m2->client_host() == m2_client_host); + BOOST_TEST(m2->session_timeout() == m2_session_timeout); + BOOST_TEST(m2->rebalance_timeout() == m2_rebalance_timeout); +} + } // namespace kafka diff --git a/src/v/kafka/server/tests/list_offsets_test.cc b/src/v/kafka/server/tests/list_offsets_test.cc index 72157ffb24dfa..c5d1d484459a2 100644 --- a/src/v/kafka/server/tests/list_offsets_test.cc +++ b/src/v/kafka/server/tests/list_offsets_test.cc @@ -15,6 +15,7 @@ #include "redpanda/tests/fixture.h" #include "resource_mgmt/io_priority.h" #include "test_utils/async.h" +#include "utils/fragmented_vector.h" #include @@ -53,15 +54,15 @@ FIXTURE_TEST(list_offsets, redpanda_thread_fixture) { client.connect().get(); kafka::list_offsets_request req; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.get_topic(), .partitions = {{ .partition_index = ntp.get_partition(), .timestamp = base_ts, }}, - }}; + }); - auto resp = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(1)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE_EQUAL(resp.data.topics.size(), 1); @@ -87,15 +88,15 @@ FIXTURE_TEST(list_offsets_earliest, redpanda_thread_fixture) { client.connect().get(); kafka::list_offsets_request req; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.get_topic(), .partitions = {{ .partition_index = ntp.get_partition(), .timestamp = kafka::list_offsets_request::earliest_timestamp, }}, - }}; + }); - auto resp = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(1)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE_EQUAL(resp.data.topics.size(), 1); @@ -122,15 +123,15 @@ FIXTURE_TEST(list_offsets_latest, redpanda_thread_fixture) { client.connect().get(); kafka::list_offsets_request req; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.get_topic(), .partitions = {{ .partition_index = ntp.get_partition(), .timestamp = kafka::list_offsets_request::latest_timestamp, }}, - }}; + }); - auto resp = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(1)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE_EQUAL(resp.data.topics.size(), 1); @@ -161,15 +162,15 @@ FIXTURE_TEST(list_offsets_not_found, redpanda_thread_fixture) { client.connect().get(); kafka::list_offsets_request req; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.get_topic(), .partitions = {{ .partition_index = ntp.get_partition(), .timestamp = future_ts, }}, - }}; + }); - auto resp = client.dispatch(req, kafka::api_version(4)).get(); + auto resp = client.dispatch(std::move(req), kafka::api_version(4)).get(); client.stop().then([&client] { client.shutdown(); }).get(); // request is asking for messages with timestamp far ahead of the produced @@ -186,12 +187,12 @@ FIXTURE_TEST(list_offsets_not_found, redpanda_thread_fixture) { kafka::produce_request make_produce_request(model::topic_partition tp, model::record_batch&& batch) { - std::vector partitions; + chunked_vector partitions; partitions.emplace_back(kafka::produce_request::partition{ .partition_index{tp.partition}, .records = kafka::produce_request_record_data(std::move(batch))}); - std::vector topics; + chunked_vector topics; topics.emplace_back(kafka::produce_request::topic{ .name{std::move(tp.topic)}, .partitions{std::move(partitions)}}); std::optional t_id; @@ -243,15 +244,17 @@ FIXTURE_TEST(list_offsets_by_time, redpanda_thread_fixture) { for (long i = 0; i < batch_count; ++i) { // fetch timestamp i, expect offset 2 * i. kafka::list_offsets_request req; - req.data.topics = {{ + + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.tp.topic, .partitions = {{ .partition_index = ntp.tp.partition, .timestamp = model::timestamp(base_timestamp + i * record_count), }}, - }}; + }); - auto resp = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp + = client.dispatch(std::move(req), kafka::api_version(1)).get0(); BOOST_REQUIRE_EQUAL(resp.data.topics.size(), 1); BOOST_REQUIRE_EQUAL(resp.data.topics[0].partitions.size(), 1); @@ -266,17 +269,19 @@ FIXTURE_TEST(list_offsets_by_time, redpanda_thread_fixture) { // compressed batches we should still get a result pointing // to the start of the batch. auto record_offset = 1; // Offset into batch which we will read - req.data.topics = {{ + kafka::list_offsets_request req2; + req2.data.topics.emplace_back(kafka::list_offset_topic{ .name = ntp.tp.topic, .partitions = {{ .partition_index = ntp.tp.partition, .timestamp = model::timestamp( base_timestamp + i * record_count + record_offset), }}, - }}; + }); const auto& batch = batches[i]; - auto resp_midbatch = client.dispatch(req, kafka::api_version(1)).get0(); + auto resp_midbatch + = client.dispatch(std::move(req2), kafka::api_version(1)).get0(); BOOST_REQUIRE_EQUAL(resp_midbatch.data.topics.size(), 1); BOOST_REQUIRE_EQUAL(resp_midbatch.data.topics[0].partitions.size(), 1); if (batch.compressed()) { diff --git a/src/v/kafka/server/tests/list_offsets_utils.cc b/src/v/kafka/server/tests/list_offsets_utils.cc index 4cea921887a9c..7f5b7fa730492 100644 --- a/src/v/kafka/server/tests/list_offsets_utils.cc +++ b/src/v/kafka/server/tests/list_offsets_utils.cc @@ -22,9 +22,9 @@ kafka_list_offsets_transport::list_offsets( model::topic topic_name, pid_to_timestamp_map_t ts_per_partition) { kafka::list_offsets_request req; - req.data.topics = {{ + req.data.topics.emplace_back(kafka::list_offset_topic{ .name = std::move(topic_name), - }}; + }); for (const auto& [pid, ts] : ts_per_partition) { req.data.topics[0].partitions.emplace_back(kafka::list_offset_partition{ .partition_index = pid, .timestamp = ts}); diff --git a/src/v/kafka/server/tests/member_test.cc b/src/v/kafka/server/tests/member_test.cc index d5096c9b07fed..af581dd34cb5c 100644 --- a/src/v/kafka/server/tests/member_test.cc +++ b/src/v/kafka/server/tests/member_test.cc @@ -11,6 +11,7 @@ #include "kafka/server/group_manager.h" #include "kafka/server/member.h" #include "kafka/types.h" +#include "utils/fragmented_vector.h" #include "utils/to_string.h" #include @@ -21,7 +22,7 @@ namespace kafka { -static const std::vector test_protos = { +static const chunked_vector test_protos = { {kafka::protocol_name("n0"), "d0"}, {kafka::protocol_name("n1"), "d1"}}; static group_member get_member() { @@ -34,7 +35,7 @@ static group_member get_member() { std::chrono::seconds(1), std::chrono::milliseconds(2), kafka::protocol_type("p"), - test_protos); + test_protos.copy()); } static join_group_response make_join_response() { @@ -92,12 +93,12 @@ SEASTAR_THREAD_TEST_CASE(protocols) { BOOST_TEST(m.protocols() == test_protos); // and the negative test - auto protos = test_protos; + auto protos = test_protos.copy(); protos[0].name = kafka::protocol_name("x"); BOOST_TEST(m.protocols() != protos); // can set new protocols - m.set_protocols(protos); + m.set_protocols(protos.copy()); BOOST_TEST(m.protocols() != test_protos); BOOST_TEST(m.protocols() == protos); } @@ -177,7 +178,7 @@ SEASTAR_THREAD_TEST_CASE(member_serde) { std::move(m1_state), m0.group_id(), kafka::protocol_type("p"), - test_protos); + test_protos.copy()); BOOST_REQUIRE(m1.state() == m0.state()); } diff --git a/src/v/kafka/server/tests/partition_reassignments_test.cc b/src/v/kafka/server/tests/partition_reassignments_test.cc index 4536b12c92286..580912a13c505 100644 --- a/src/v/kafka/server/tests/partition_reassignments_test.cc +++ b/src/v/kafka/server/tests/partition_reassignments_test.cc @@ -17,6 +17,7 @@ #include "kafka/types.h" #include "model/namespace.h" #include "redpanda/tests/fixture.h" +#include "utils/fragmented_vector.h" #include #include @@ -54,7 +55,7 @@ class partition_reassignments_test_fixture : public redpanda_thread_fixture { kafka::alter_partition_reassignments_request req; if (topic_name.has_value()) { - req.data.topics = std::vector{ + req.data.topics = chunked_vector{ kafka::reassignable_topic{ .name = *topic_name, .partitions = std::move(reassignable_partitions)}}; @@ -64,11 +65,11 @@ class partition_reassignments_test_fixture : public redpanda_thread_fixture { kafka::list_partition_reassignments_response list_partition_reassignments( kafka::client::transport& client, - std::optional> + std::optional> topics = std::nullopt) { kafka::list_partition_reassignments_request req; - req.data.topics = topics; + req.data.topics = std::move(topics); return client.dispatch(std::move(req), kafka::api_version(0)).get(); } }; @@ -267,12 +268,13 @@ FIXTURE_TEST( test_log.info( "List partition assignments {} expect an empty response", test_tp); std::vector pids; + pids.reserve(num_partitions); for (int pid = 0; pid < num_partitions; ++pid) { pids.emplace_back(pid); } auto resp = list_partition_reassignments( client, - std::vector{ + chunked_vector{ kafka::list_partition_reassignments_topics{ .name = test_tp, .partition_indexes = pids}}); BOOST_CHECK_EQUAL(resp.data.topics.size(), 0); diff --git a/src/v/kafka/server/tests/produce_consume_test.cc b/src/v/kafka/server/tests/produce_consume_test.cc index c9f5061b5da03..cd0dec84ff891 100644 --- a/src/v/kafka/server/tests/produce_consume_test.cc +++ b/src/v/kafka/server/tests/produce_consume_test.cc @@ -23,6 +23,7 @@ #include "storage/record_batch_builder.h" #include "test_utils/async.h" #include "test_utils/fixture.h" +#include "utils/fragmented_vector.h" #include #include @@ -74,7 +75,8 @@ struct prod_consume_fixture : public redpanda_thread_fixture { }).get(); } - std::vector small_batches(size_t count) { + chunked_vector + small_batches(size_t count) { storage::record_batch_builder builder( model::record_batch_type::raft_data, model::offset(0)); @@ -84,7 +86,7 @@ struct prod_consume_fixture : public redpanda_thread_fixture { builder.add_raw_kv(iobuf{}, std::move(v)); } - std::vector res; + chunked_vector res; kafka::produce_request::partition partition; partition.partition_index = model::partition_id(0); @@ -95,11 +97,11 @@ struct prod_consume_fixture : public redpanda_thread_fixture { ss::future produce_raw( kafka::client::transport& producer, - std::vector&& partitions) { + chunked_vector&& partitions) { kafka::produce_request::topic tp; tp.partitions = std::move(partitions); tp.name = test_topic; - std::vector topics; + chunked_vector topics; topics.push_back(std::move(tp)); kafka::produce_request req(std::nullopt, 1, std::move(topics)); req.data.timeout_ms = std::chrono::seconds(2); @@ -108,8 +110,8 @@ struct prod_consume_fixture : public redpanda_thread_fixture { return producer.dispatch(std::move(req)); } - ss::future - produce_raw(std::vector&& partitions) { + ss::future produce_raw( + chunked_vector&& partitions) { return produce_raw(producers.front(), std::move(partitions)); } @@ -209,7 +211,7 @@ FIXTURE_TEST(test_produce_consume_small_batches, prod_consume_fixture) { FIXTURE_TEST(test_version_handler, prod_consume_fixture) { wait_for_controller_leadership().get(); start(); - std::vector topics; + chunked_vector topics; topics.push_back(kafka::produce_request::topic{ .name = model::topic{"abc123"}, .partitions = small_batches(10)}); @@ -225,7 +227,7 @@ FIXTURE_TEST(test_version_handler, prod_consume_fixture) { kafka::client::kafka_request_disconnected_exception); } -static std::vector +static chunked_vector single_batch(model::partition_id p_id, const size_t volume) { storage::record_batch_builder builder( model::record_batch_type::raft_data, model::offset(0)); @@ -240,7 +242,7 @@ single_batch(model::partition_id p_id, const size_t volume) { partition.partition_index = p_id; partition.records.emplace(std::move(builder).build()); - std::vector res; + chunked_vector res; res.push_back(std::move(partition)); return res; } @@ -999,7 +1001,7 @@ FIXTURE_TEST(test_offset_for_leader_epoch, prod_consume_fixture) { {}, }; req.data.topics.emplace_back(std::move(t)); - auto resp = client.dispatch(req, kafka::api_version(2)).get0(); + auto resp = client.dispatch(std::move(req), kafka::api_version(2)).get0(); client.stop().then([&client] { client.shutdown(); }).get(); BOOST_REQUIRE_EQUAL(1, resp.data.topics.size()); const auto& topic_resp = resp.data.topics[0]; diff --git a/src/v/kafka/server/tests/produce_consume_utils.cc b/src/v/kafka/server/tests/produce_consume_utils.cc index 3e786da439610..68c0395ab7185 100644 --- a/src/v/kafka/server/tests/produce_consume_utils.cc +++ b/src/v/kafka/server/tests/produce_consume_utils.cc @@ -14,6 +14,7 @@ #include "kafka/protocol/produce.h" #include "kafka/protocol/schemata/produce_request.h" #include "storage/record_batch_builder.h" +#include "utils/fragmented_vector.h" #include "vlog.h" #include @@ -37,7 +38,7 @@ kafka_produce_transport::produce( kafka::produce_request::topic tp; tp.name = topic_name; tp.partitions = produce_partition_requests(records_per_partition, ts); - std::vector topics; + chunked_vector topics; topics.push_back(std::move(tp)); kafka::produce_request req(std::nullopt, -1, std::move(topics)); req.data.timeout_ms = std::chrono::seconds(10); @@ -60,11 +61,11 @@ kafka_produce_transport::produce( co_return ret; } -std::vector +chunked_vector kafka_produce_transport::produce_partition_requests( const pid_to_kvs_map_t& records_per_partition, std::optional ts) { - std::vector ret; + chunked_vector ret; ret.reserve(records_per_partition.size()); for (const auto& [pid, records] : records_per_partition) { storage::record_batch_builder builder( diff --git a/src/v/kafka/server/tests/produce_consume_utils.h b/src/v/kafka/server/tests/produce_consume_utils.h index 6ba2711b5b1a4..c18e24eea4885 100644 --- a/src/v/kafka/server/tests/produce_consume_utils.h +++ b/src/v/kafka/server/tests/produce_consume_utils.h @@ -14,6 +14,7 @@ #include "kafka/protocol/produce.h" #include "kafka/protocol/schemata/produce_request.h" #include "storage/record_batch_builder.h" +#include "utils/fragmented_vector.h" namespace tests { @@ -102,7 +103,7 @@ class kafka_produce_transport { // produce requests. Each request, once sent, will correspond to a // replicated Raft batch. // NOTE: input must remain valid for the lifetimes of the returned requests. - static std::vector + static chunked_vector produce_partition_requests( const pid_to_kvs_map_t& records_per_partition, std::optional ts); diff --git a/src/v/kafka/server/tests/replicated_partition_test.cc b/src/v/kafka/server/tests/replicated_partition_test.cc new file mode 100644 index 0000000000000..81073ebdc6400 --- /dev/null +++ b/src/v/kafka/server/tests/replicated_partition_test.cc @@ -0,0 +1,77 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "cluster/fwd.h" +#include "cluster/types.h" +#include "kafka/server/partition_proxy.h" +#include "kafka/server/replicated_partition.h" +#include "model/fundamental.h" +#include "model/metadata.h" +#include "model/namespace.h" +#include "model/record_batch_reader.h" +#include "model/record_batch_types.h" +#include "model/timeout_clock.h" +#include "redpanda/tests/fixture.h" +#include "storage/record_batch_builder.h" +#include "test_utils/async.h" + +FIXTURE_TEST(test_replicated_partition_end_offset, redpanda_thread_fixture) { + wait_for_controller_leadership().get(); + + model::topic_namespace tp_ns( + model::kafka_namespace, model::topic("test-topic")); + + add_topic(tp_ns).get(); + model::ntp ntp(tp_ns.ns, tp_ns.tp, model::partition_id(0)); + auto shard = app.shard_table.local().shard_for(ntp); + + tests::cooperative_spin_wait_with_timeout(10s, [this, shard, &ntp] { + return app.partition_manager.invoke_on( + *shard, [&ntp](cluster::partition_manager& pm) { + auto p = pm.get(ntp); + return p->get_leader_id().has_value(); + }); + }).get(); + + app.partition_manager + .invoke_on( + *shard, + [&ntp](cluster::partition_manager& pm) { + auto p = pm.get(ntp); + kafka::replicated_partition rp(p); + auto p_info = rp.get_partition_info(); + /** + * Since log is empty from Kafka client perspective (no data + * batches), the end offset which is exclusive must be equal to 0 + */ + BOOST_REQUIRE_EQUAL(rp.log_end_offset(), model::offset{0}); + BOOST_REQUIRE_EQUAL(rp.high_watermark(), model::offset{0}); + + storage::record_batch_builder builder( + model::record_batch_type::version_fence, model::offset(0)); + builder.add_raw_kv(iobuf{}, iobuf{}); + builder.add_raw_kv(iobuf{}, iobuf{}); + builder.add_raw_kv(iobuf{}, iobuf{}); + + // replicate a batch that is subjected to offset translation + return p + ->replicate( + model::make_memory_record_batch_reader( + {std::move(builder).build()}), + raft::replicate_options(raft::consistency_level::quorum_ack)) + .then([p, rp](result rr) { + BOOST_REQUIRE(rr.has_value()); + BOOST_REQUIRE_GT(p->dirty_offset(), model::offset{0}); + + BOOST_REQUIRE_EQUAL(rp.log_end_offset(), model::offset{0}); + BOOST_REQUIRE_EQUAL(rp.high_watermark(), model::offset{0}); + }); + }) + .get(); +} diff --git a/src/v/kafka/server/tests/topic_recreate_test.cc b/src/v/kafka/server/tests/topic_recreate_test.cc index c209241c1bd2a..4b8957a5b0079 100644 --- a/src/v/kafka/server/tests/topic_recreate_test.cc +++ b/src/v/kafka/server/tests/topic_recreate_test.cc @@ -22,6 +22,7 @@ #include "redpanda/application.h" #include "redpanda/tests/fixture.h" #include "test_utils/async.h" +#include "utils/fragmented_vector.h" #include #include @@ -58,7 +59,7 @@ class recreate_test_fixture : public redpanda_thread_fixture { = client.dispatch(std::move(req), kafka::api_version(2)).get0(); } kafka::delete_topics_request make_delete_topics_request( - std::vector topics, std::chrono::milliseconds timeout) { + chunked_vector topics, std::chrono::milliseconds timeout) { kafka::delete_topics_request req; req.data.topic_names = std::move(topics); req.data.timeout_ms = timeout; @@ -67,8 +68,11 @@ class recreate_test_fixture : public redpanda_thread_fixture { kafka::delete_topics_response delete_topics(std::vector topics) { - return send_delete_topics_request( - make_delete_topics_request(std::move(topics), 5s)); + return send_delete_topics_request(make_delete_topics_request( + chunked_vector{ + std::make_move_iterator(topics.begin()), + std::make_move_iterator(topics.end())}, + 5s)); } kafka::delete_topics_response @@ -97,12 +101,13 @@ class recreate_test_fixture : public redpanda_thread_fixture { ss::future get_topic_metadata(const model::topic& tp) { return do_with_client([tp](kafka::client::transport& client) { - std::vector topics; + chunked_vector topics; topics.push_back(kafka::metadata_request_topic{tp}); kafka::metadata_request md_req{ - .data = {.topics = topics, .allow_auto_topic_creation = false}, + .data + = {.topics = std::make_optional(std::move(topics)), .allow_auto_topic_creation = false}, .list_all_topics = false}; - return client.dispatch(md_req); + return client.dispatch(std::move(md_req)); }); } diff --git a/src/v/model/offset_interval.h b/src/v/model/offset_interval.h new file mode 100644 index 0000000000000..5282291b346ca --- /dev/null +++ b/src/v/model/offset_interval.h @@ -0,0 +1,80 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#pragma once + +#include "model/fundamental.h" +#include "vassert.h" + +namespace model { +/// A non-empty, bounded, closed interval of offsets [min offset, max offset]. +/// +/// This property helps to simplify the logic and the instructions required to +/// check for overlaps, containment, etc. It is the responsibility of the caller +/// to ensure these properties hold before constructing an instance of this +/// class. +/// +/// To represent a potentially empty range, wrap it in an optional. +class bounded_offset_interval { +public: + static bounded_offset_interval + unchecked(model::offset min, model::offset max) noexcept { + return {min, max}; + } + + static bounded_offset_interval + checked(model::offset min, model::offset max) { + if (min < model::offset(0) || max < model::offset(0) || min > max) { + throw std::invalid_argument(fmt::format( + "Invalid arguments for constructing a non-empty bounded offset " + "interval: min({}) <= max({})", + min, + max)); + } + + return {min, max}; + } + + inline bool overlaps(const bounded_offset_interval& other) const noexcept { + return _min <= other._max && _max >= other._min; + } + + inline bool contains(model::offset o) const noexcept { + return _min <= o && o <= _max; + } + + friend std::ostream& + operator<<(std::ostream& o, const bounded_offset_interval& r) { + fmt::print(o, "{{min: {}, max: {}}}", r._min, r._max); + return o; + } + + inline model::offset min() const noexcept { return _min; } + inline model::offset max() const noexcept { return _max; } + +private: + bounded_offset_interval(model::offset min, model::offset max) noexcept + : _min(min) + , _max(max) { +#ifndef NDEBUG + vassert( + min >= model::offset(0), "Offset interval min({}) must be >= 0", min); + vassert( + min <= max, + "Offset interval invariant not satisfied: min({}) <= max({})", + min, + max); +#endif + } + + model::offset _min; + model::offset _max; +}; + +} // namespace model diff --git a/src/v/model/record_batch_reader.cc b/src/v/model/record_batch_reader.cc index 34ada70e2066a..e4a677c793453 100644 --- a/src/v/model/record_batch_reader.cc +++ b/src/v/model/record_batch_reader.cc @@ -243,6 +243,25 @@ make_fragmented_memory_storage_batches(Container batches) { } return data; } + +template +ss::future consume_reader_to_fragmented_memory( + record_batch_reader reader, timeout_clock::time_point timeout) { + class fragmented_memory_batch_consumer { + public: + ss::future operator()(model::record_batch b) { + _result.push_back(std::move(b)); + return ss::make_ready_future( + ss::stop_iteration::no); + } + Container end_of_stream() { return std::move(_result); } + + private: + Container _result; + }; + return std::move(reader).consume( + fragmented_memory_batch_consumer{}, timeout); +} } // namespace record_batch_reader make_fragmented_memory_record_batch_reader( @@ -253,6 +272,14 @@ record_batch_reader make_fragmented_memory_record_batch_reader( fragmented_vector>(std::move(batches))); } +record_batch_reader make_fragmented_memory_record_batch_reader( + chunked_vector batches) { + return make_fragmented_memory_record_batch_reader( + make_fragmented_memory_storage_batches< + false, + chunked_vector>(std::move(batches))); +} + record_batch_reader make_foreign_fragmented_memory_record_batch_reader( fragmented_vector batches) { return make_fragmented_memory_record_batch_reader( @@ -261,6 +288,14 @@ record_batch_reader make_foreign_fragmented_memory_record_batch_reader( fragmented_vector>(std::move(batches))); } +record_batch_reader make_foreign_fragmented_memory_record_batch_reader( + chunked_vector batches) { + return make_fragmented_memory_record_batch_reader( + make_fragmented_memory_storage_batches< + true, + chunked_vector>(std::move(batches))); +} + record_batch_reader make_foreign_fragmented_memory_record_batch_reader( ss::chunked_fifo batches) { return make_fragmented_memory_record_batch_reader( @@ -297,22 +332,15 @@ ss::future consume_reader_to_memory( ss::future> consume_reader_to_fragmented_memory( record_batch_reader reader, timeout_clock::time_point timeout) { - class fragmented_memory_batch_consumer { - public: - ss::future operator()(model::record_batch b) { - _result.push_back(std::move(b)); - return ss::make_ready_future( - ss::stop_iteration::no); - } - fragmented_vector end_of_stream() { - return std::move(_result); - } + return consume_reader_to_fragmented_memory< + fragmented_vector>(std::move(reader), timeout); +} - private: - fragmented_vector _result; - }; - return std::move(reader).consume( - fragmented_memory_batch_consumer{}, timeout); +ss::future> +consume_reader_to_chunked_vector( + record_batch_reader reader, timeout_clock::time_point timeout) { + return consume_reader_to_fragmented_memory< + chunked_vector>(std::move(reader), timeout); } std::ostream& operator<<(std::ostream& os, const record_batch_reader& r) { diff --git a/src/v/model/record_batch_reader.h b/src/v/model/record_batch_reader.h index be584f36f1a1e..84bc825533446 100644 --- a/src/v/model/record_batch_reader.h +++ b/src/v/model/record_batch_reader.h @@ -98,6 +98,12 @@ class record_batch_reader final { return do_consume(consumer, timeout); }); } + template + auto peek_each_ref(ReferenceConsumer c, timeout_clock::time_point tm) { + return ss::do_with(std::move(c), [this, tm](ReferenceConsumer& c) { + return do_peek_each_ref(c, tm); + }); + } private: record_batch pop_batch() { @@ -142,6 +148,31 @@ class record_batch_reader final { return c(pop_batch()); }); } + template + auto do_peek_each_ref( + ReferenceConsumer& refc, timeout_clock::time_point timeout) { + return do_action(refc, timeout, [this](ReferenceConsumer& c) { + return ss::visit( + _slice, + [&c](data_t& d) { + return c(d.front()).then([&](ss::stop_iteration stop) { + if (!stop) { + d.pop_front(); + } + return stop; + }); + }, + [&c](foreign_data_t& d) { + return c((*d.buffer)[d.index]) + .then([&](ss::stop_iteration stop) { + if (!stop) { + ++d.index; + } + return stop; + }); + }); + }); + } template auto do_action( ConsumerType& consumer, @@ -249,6 +280,16 @@ class record_batch_reader final { }); } + /// Similar to for_each_ref, but advances only if the consumer returns + /// ss::stop_iteration::no. I.e. the batch where the consumer stopped + /// remains available for reading by subsequent consumers. + template + requires ReferenceBatchReaderConsumer + auto peek_each_ref( + ReferenceConsumer consumer, timeout_clock::time_point timeout) & { + return _impl->peek_each_ref(std::move(consumer), timeout); + } + std::unique_ptr release() && { return std::move(_impl); } private: @@ -274,6 +315,9 @@ record_batch_reader record_batch_reader make_fragmented_memory_record_batch_reader( fragmented_vector); +record_batch_reader make_fragmented_memory_record_batch_reader( + chunked_vector); + inline record_batch_reader make_memory_record_batch_reader(model::record_batch b) { record_batch_reader::data_t batches; @@ -318,6 +362,9 @@ record_batch_reader record_batch_reader make_foreign_fragmented_memory_record_batch_reader( fragmented_vector); +record_batch_reader make_foreign_fragmented_memory_record_batch_reader( + chunked_vector); + record_batch_reader make_foreign_fragmented_memory_record_batch_reader( ss::chunked_fifo); @@ -334,6 +381,10 @@ ss::future> consume_reader_to_fragmented_memory( record_batch_reader, timeout_clock::time_point timeout); +ss::future> +consume_reader_to_chunked_vector( + record_batch_reader reader, timeout_clock::time_point timeout); + /// \brief wraps a reader into a foreign_ptr record_batch_reader make_foreign_record_batch_reader(record_batch_reader&&); diff --git a/src/v/model/tests/random_batch.cc b/src/v/model/tests/random_batch.cc index 94b4e8881d6bc..2bbb471ebd23e 100644 --- a/src/v/model/tests/random_batch.cc +++ b/src/v/model/tests/random_batch.cc @@ -118,7 +118,10 @@ make_random_batch(model::offset o, int num_records, bool allow_compression) { model::record_batch make_random_batch(record_batch_spec spec) { auto ts = spec.timestamp.value_or(model::timestamp::now()); - auto max_ts = model::timestamp(ts() + spec.count - 1); + auto max_ts = ts; + if (!spec.all_records_have_same_timestamp) { + max_ts = model::timestamp(ts() + spec.count - 1); + } auto header = model::record_batch_header{ .size_bytes = 0, // computed later .base_offset = spec.offset, @@ -234,7 +237,9 @@ make_random_batches(record_batch_spec spec) { auto num_records = spec.records ? *spec.records : get_int(2, 30); auto batch_spec = spec; batch_spec.timestamp = ts; - ts = model::timestamp(ts() + num_records); + if (!batch_spec.all_records_have_same_timestamp) { + ts = model::timestamp(ts() + num_records); + } batch_spec.offset = o; batch_spec.count = num_records; if (spec.enable_idempotence) { diff --git a/src/v/model/tests/random_batch.h b/src/v/model/tests/random_batch.h index c7dbccad0443e..d9df64dba393a 100644 --- a/src/v/model/tests/random_batch.h +++ b/src/v/model/tests/random_batch.h @@ -34,6 +34,7 @@ struct record_batch_spec { bool is_transactional{false}; std::optional> record_sizes; std::optional timestamp; + bool all_records_have_same_timestamp{false}; }; model::record make_random_record(int, iobuf); diff --git a/src/v/net/connection.cc b/src/v/net/connection.cc index 388a1501b87ad..5ec8d68f61423 100644 --- a/src/v/net/connection.cc +++ b/src/v/net/connection.cc @@ -20,6 +20,8 @@ #include #include +#include + namespace net { /** @@ -44,7 +46,9 @@ bool is_reconnect_error(const std::system_error& e) { default: return false; } - } else { + } else if ( + e.code().category() == std::system_category() + || e.code().category() == std::generic_category()) { switch (v) { case ECONNREFUSED: case ENETUNREACH: @@ -62,6 +66,9 @@ bool is_reconnect_error(const std::system_error& e) { default: return false; } + } else { + // We don't know what the error category is at this point + return false; } __builtin_unreachable(); } diff --git a/src/v/net/probes.cc b/src/v/net/probes.cc index 64f7579c64473..7be4fac0fb89a 100644 --- a/src/v/net/probes.cc +++ b/src/v/net/probes.cc @@ -8,6 +8,7 @@ // by the Apache License, Version 2.0 #include "config/configuration.h" +#include "hashing/crc32.h" #include "metrics/metrics.h" #include "net/client_probe.h" #include "net/server_probe.h" @@ -340,15 +341,17 @@ ss::future> build_reloadable_credentials_with_probe( ss::sstring detail, ss::tls::reload_callback cb) { auto probe = ss::make_lw_shared(); - auto wrap_cb = [probe, cb = std::move(cb)]( - const std::unordered_set& updated, - const ss::tls::certificate_credentials& creds, - const std::exception_ptr& eptr) { - if (cb) { - cb(updated, eptr); - } - probe->loaded(creds, eptr); - }; + auto wrap_cb = + [probe, cb = std::move(cb)]( + const std::unordered_set& updated, + const ss::tls::certificate_credentials& creds, + const std::exception_ptr& eptr, + std::optional trust_file_contents = std::nullopt) { + if (cb) { + cb(updated, eptr); + } + probe->loaded(creds, eptr, trust_file_contents); + }; ss::shared_ptr cred; if constexpr (std::is_same::value) { @@ -359,7 +362,7 @@ ss::future> build_reloadable_credentials_with_probe( } probe->setup_metrics(std::move(area), std::move(detail)); - probe->loaded(*cred, nullptr); + probe->loaded(*cred, nullptr, builder.get_trust_file_blob()); co_return cred; } @@ -378,11 +381,13 @@ build_reloadable_credentials_with_probe( ss::tls::reload_callback cb); void tls_certificate_probe::loaded( - const ss::tls::certificate_credentials& creds, std::exception_ptr ex) { + const ss::tls::certificate_credentials& creds, + std::exception_ptr ex, + std::optional trust_file_contents) { _load_time = clock_type::now(); + reset(); if (ex) { - reset(); return; } @@ -394,33 +399,41 @@ void tls_certificate_probe::loaded( return tls_serial_number{result}; }; - _cert_loaded = true; - auto certs_info = creds.get_cert_info(); auto ts_info = creds.get_trust_list_info(); if (!certs_info.has_value() || !ts_info.has_value()) { - reset(); return; } + _cert_loaded = true; + for (auto& info : certs_info.value()) { auto exp = clock_type::from_time_t(info.expiry); auto srl = to_tls_serial(info.serial); - if (exp < _cert_expiry_time) { - _cert_expiry_time = exp; - _cert_serial = srl; + if (!_cert || exp < _cert->expiry) { + _cert.emplace(cert{.expiry = exp, .serial = srl}); } } for (auto& info : ts_info.value()) { auto exp = clock_type::from_time_t(info.expiry); auto srl = to_tls_serial(info.serial); - if (exp < _ca_expiry_time) { - _ca_expiry_time = exp; - _ca_serial = srl; + if (!_ca || exp < _ca->expiry) { + _ca.emplace(cert{.expiry = exp, .serial = srl}); } } + + _trust_file_crc32c = [&trust_file_contents]() -> uint32_t { + if (!trust_file_contents.has_value()) { + return 0u; + } + crc::crc32c hash; + hash.extend( + trust_file_contents.value().data(), + trust_file_contents.value().size()); + return hash.value(); + }(); } void tls_certificate_probe::setup_metrics( @@ -443,7 +456,9 @@ void tls_certificate_probe::setup_metrics( defs.emplace_back( sm::make_gauge( "truststore_expires_at_timestamp_seconds", - [this] { return _ca_expiry_time.time_since_epoch() / 1s; }, + [this] { + return _ca.value_or(cert{}).expiry.time_since_epoch() / 1s; + }, sm::description( "Expiry time of the shortest-lived CA in the truststore" "(seconds since epoch)"), @@ -452,7 +467,9 @@ void tls_certificate_probe::setup_metrics( defs.emplace_back( sm::make_gauge( "certificate_expires_at_timestamp_seconds", - [this] { return _cert_expiry_time.time_since_epoch() / 1s; }, + [this] { + return _cert.value_or(cert{}).expiry.time_since_epoch() / 1s; + }, sm::description( "Expiry time of the server certificate (seconds since epoch)"), labels) @@ -460,7 +477,7 @@ void tls_certificate_probe::setup_metrics( defs.emplace_back( sm::make_gauge( "certificate_serial", - [this] { return _cert_serial; }, + [this] { return _cert.value_or(cert{}).serial; }, sm::description("Least significant four bytes of the server " "certificate serial number"), labels) @@ -481,6 +498,16 @@ void tls_certificate_probe::setup_metrics( "the given truststore, otherwise zero."), labels) .aggregate(aggregate_labels)); + defs.emplace_back( + sm::make_gauge( + "trust_file_crc32c", + [this] { return cert_valid() ? _trust_file_crc32c : 0; }, + sm::description( + "crc32c calculated from the contents of the trust " + "file if loaded certificate is valid and trust store " + "is present, otherwise zero."), + labels) + .aggregate(aggregate_labels)); return defs; }; diff --git a/src/v/net/tls_certificate_probe.h b/src/v/net/tls_certificate_probe.h index e67577ce135ac..3237adf8a3d02 100644 --- a/src/v/net/tls_certificate_probe.h +++ b/src/v/net/tls_certificate_probe.h @@ -39,31 +39,39 @@ class tls_certificate_probe { ~tls_certificate_probe() = default; void loaded( - const ss::tls::certificate_credentials& creds, std::exception_ptr ex); + const ss::tls::certificate_credentials& creds, + std::exception_ptr ex, + std::optional trust_file_contents); void setup_metrics(std::string_view area, std::string_view detail); private: + struct cert { + const clock_type::time_point expiry{clock_type::time_point::min()}; + const tls_serial_number serial{}; + bool expired(clock_type::time_point now) const { return expiry <= now; } + }; metrics::internal_metric_groups _metrics; metrics::public_metric_groups _public_metrics; clock_type::time_point _load_time{}; - clock_type::time_point _cert_expiry_time{clock_type::time_point::max()}; - clock_type::time_point _ca_expiry_time{clock_type::time_point::max()}; - tls_serial_number _cert_serial; - tls_serial_number _ca_serial; + std::optional _cert; + std::optional _ca; bool _cert_loaded{false}; + uint32_t _trust_file_crc32c; bool cert_valid() const { auto now = clock_type::now(); - return _cert_loaded && now < _ca_expiry_time && now < _cert_expiry_time; + return ( + _cert_loaded // + && (!_cert.value_or(cert{}).expired(now)) + && (!_ca.value_or(cert{}).expired(now))); } void reset() { _cert_loaded = false; - _cert_expiry_time = clock_type::time_point::max(); - _ca_expiry_time = clock_type::time_point::max(); - _cert_serial = {}; - _ca_serial = {}; + _trust_file_crc32c = 0; + _cert.reset(); + _ca.reset(); } friend std::ostream& diff --git a/src/v/pandaproxy/api/api-doc/schema_registry.json b/src/v/pandaproxy/api/api-doc/schema_registry.json index 8e0f788ac6eb0..f76f5b6df8c28 100644 --- a/src/v/pandaproxy/api/api-doc/schema_registry.json +++ b/src/v/pandaproxy/api/api-doc/schema_registry.json @@ -1,31 +1,32 @@ -"/config/{subject}": { + + "/config/{subject}": { "get": { "summary": "Get the compatibility level for a subject.", "operationId": "get_config_subject", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "subject", "in": "path", "required": true, "type": "string" + }, + { + "name": "defaultToGlobal", + "in": "query", + "required": false, + "type": "boolean" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "compatibilityLevel": { - "type": "string" - } - } + "$ref": "#/definitions/get_compatibility" } }, "500": { @@ -55,26 +56,20 @@ "name": "config", "in": "body", "schema": { - "type": "object", - "properties": { - "compatibility": { - "type": "string" - } - } + "$ref": "#/definitions/put_compatibility" } } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "compatibility": { - "type": "string" - } - } + "$ref": "#/definitions/put_compatibility" } }, "500": { @@ -88,11 +83,6 @@ "delete": { "summary": "Delete the compatibility level for a subject.", "operationId": "delete_config_subject", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "subject", @@ -101,17 +91,16 @@ "type": "string" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "compatibility": { - "type": "string" - } - } + "$ref": "#/definitions/get_compatibility" } }, "404": { @@ -133,23 +122,17 @@ "get": { "summary": "Get the global compatibility level.", "operationId": "get_config", - "consumes": [ + "parameters": [], + "produces": [ "application/vnd.schemaregistry.v1+json", "application/vnd.schemaregistry+json", "application/json" ], - "parameters": [], - "produces": ["application/vnd.schemaregistry.v1+json"], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "compatibilityLevel": { - "type": "string" - } - } + "$ref": "#/definitions/get_compatibility" } }, "500": { @@ -173,26 +156,20 @@ "name": "config", "in": "body", "schema": { - "type": "object", - "properties": { - "compatibility": { - "type": "string" - } - } + "$ref": "#/definitions/put_compatibility" } } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "compatibility": { - "type": "string" - } - } + "$ref": "#/definitions/put_compatibility" } }, "500": { @@ -208,23 +185,201 @@ "get": { "summary": "Get the global mode.", "operationId": "get_mode", + "parameters": [], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mode" + } + }, + "500": { + "description": "Internal Server error", + "schema": { + "$ref": "#/definitions/error_body" + } + } + } + }, + "put": { + "summary": "Set the global mode.", + "operationId": "put_mode", "consumes": [ "application/vnd.schemaregistry.v1+json", "application/vnd.schemaregistry+json", "application/json" ], - "parameters": [], - "produces": ["application/vnd.schemaregistry.v1+json"], + "parameters": [ + { + "name": "mode", + "in": "body", + "schema": { + "$ref": "#/definitions/mode" + } + } + ], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "mode": { - "type": "string" - } - } + "$ref": "#/definitions/mode" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/error_body" + } + }, + "500": { + "description": "Internal Server error", + "schema": { + "$ref": "#/definitions/error_body" + } + } + } + } + }, + "/mode/{subject}": { + "get": { + "summary": "Get the mode for a subject.", + "operationId": "get_mode_subject", + "parameters": [ + { + "name": "subject", + "description": "The subject to get the mode for.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "defaultToGlobal", + "description": "If true, return the global mode if the subject doesn't have a mode set.", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mode" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/error_body" + } + }, + "500": { + "description": "Internal Server error", + "schema": { + "$ref": "#/definitions/error_body" + } + } + } + }, + "put": { + "summary": "Set the mode for a subject.", + "operationId": "put_mode_subject", + "consumes": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], + "parameters": [ + { + "name": "subject", + "description": "The subject to set the mode for.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "mode", + "in": "body", + "schema": { + "$ref": "#/definitions/mode" + } + } + ], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mode" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/error_body" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/error_body" + } + }, + "500": { + "description": "Internal Server error", + "schema": { + "$ref": "#/definitions/error_body" + } + } + } + }, + "delete": { + "summary": "Delete the mode for a subject.", + "operationId": "delete_mode_subject", + "parameters": [ + { + "name": "subject", + "description": "The subject to delete the mode for.", + "in": "path", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mode" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/error_body" } }, "500": { @@ -240,13 +395,12 @@ "get": { "summary": "Get the supported schema types.", "operationId": "get_schemas_types", - "consumes": [ + "parameters": [], + "produces": [ "application/vnd.schemaregistry.v1+json", "application/vnd.schemaregistry+json", "application/json" ], - "parameters": [], - "produces": ["application/vnd.schemaregistry.v1+json"], "responses": { "200": { "description": "OK", @@ -268,13 +422,8 @@ }, "/schemas/ids/{id}": { "get": { - "summary": "Get a schema by id.", + "summary": "Get a schema by ID.", "operationId": "get_schemas_ids_id", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "id", @@ -283,7 +432,11 @@ "type": "integer" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -316,13 +469,8 @@ }, "/schemas/ids/{id}/versions": { "get": { - "summary": "Get a list of subject-version for the schema id.", + "summary": "Get a list of subject-version for the schema ID.", "operationId": "get_schemas_ids_id_versions", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "id", @@ -331,7 +479,11 @@ "type": "integer" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -369,11 +521,6 @@ "get": { "summary": "Retrieve a list of subjects associated with some schema ID.", "operationId": "get_schemas_ids_id_subjects", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "id", @@ -388,7 +535,11 @@ "type": "boolean" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -424,6 +575,12 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "subjectPrefix", + "in": "query", + "required": false, + "type": "string" } ], "produces": [ @@ -437,7 +594,7 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string" } } }, @@ -452,7 +609,7 @@ }, "/subjects/{subject}": { "post": { - "summary": "Check if a schema is already registred for the subject.", + "summary": "Check if a schema is already registered for the subject.", "operationId": "post_subject", "consumes": [ "application/vnd.schemaregistry.v1+json", @@ -466,15 +623,31 @@ "required": true, "type": "string" }, + { + "name": "normalize", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "deleted", + "in": "query", + "required": false, + "type": "boolean" + }, { "name": "schema_def", "in": "body", - "schema": { + "schema": { "$ref": "#/definitions/schema_def" } } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -482,6 +655,12 @@ "$ref": "#/definitions/subject_schema" } }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/error_body" + } + }, "409": { "description": "Incompatible schema", "schema": { @@ -505,11 +684,6 @@ "delete": { "summary": "Delete all schemas for the subject.", "operationId": "delete_subject", - "consumes": [ - "application/vnd.schemaregistry.v1+json", - "application/vnd.schemaregistry+json", - "application/json" - ], "parameters": [ { "name": "subject", @@ -524,7 +698,11 @@ "type": "boolean" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -579,7 +757,7 @@ "schema": { "type": "array", "items": { - "type": "integer" + "type": "integer" } } }, @@ -612,15 +790,25 @@ "required": true, "type": "string" }, + { + "name": "normalize", + "in": "query", + "required": false, + "type": "boolean" + }, { "name": "schema_def", "in": "body", - "schema": { + "schema": { "$ref": "#/definitions/schema_def" } } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", @@ -825,7 +1013,7 @@ }, "/subjects/{subject}/versions/{version}/referencedby": { "get": { - "summary": "Retrieve a list of schema ids that reference the subject and version.", + "summary": "Retrieve a list of schema IDs that reference the subject and version.", "operationId": "get_subject_versions_version_referenced_by", "parameters": [ { @@ -852,7 +1040,7 @@ "schema": { "type": "array", "items": { - "type": "integer" + "type": "integer" } } }, @@ -907,7 +1095,7 @@ "schema": { "type": "array", "items": { - "type": "integer" + "type": "integer" } } }, @@ -957,22 +1145,27 @@ { "name": "schema_def", "in": "body", - "schema": { + "schema": { "$ref": "#/definitions/schema_def" } + }, + { + "name": "verbose", + "in": "query", + "required": false, + "type": "boolean" } ], - "produces": ["application/vnd.schemaregistry.v1+json"], + "produces": [ + "application/vnd.schemaregistry.v1+json", + "application/vnd.schemaregistry+json", + "application/json" + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer" - } - } + "$ref": "#/definitions/is_compatibile" } }, "409": { @@ -1010,3 +1203,4 @@ } } } + \ No newline at end of file diff --git a/src/v/pandaproxy/api/api-doc/schema_registry_definitions.def.json b/src/v/pandaproxy/api/api-doc/schema_registry_definitions.def.json index 0d837c9cf2a87..58f8937f9210d 100644 --- a/src/v/pandaproxy/api/api-doc/schema_registry_definitions.def.json +++ b/src/v/pandaproxy/api/api-doc/schema_registry_definitions.def.json @@ -31,7 +31,7 @@ "type": "string" }, "version": { - "type": "integer" + "type": "integer" } } } @@ -65,7 +65,7 @@ "type": "string" }, "version": { - "type": "integer" + "type": "integer" } } } @@ -74,4 +74,46 @@ "type": "string" } } - } \ No newline at end of file + }, + "get_compatibility": { + "type": "object", + "properties": { + "compatibilityLevel": { + "type": "string" + } + } + }, + "put_compatibility": { + "type": "object", + "properties": { + "compatibility": { + "type": "string" + } + } + }, + "mode": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "READWRITE", + "READONLY" + ] + } + } + }, + "is_compatibile": { + "type": "object", + "properties": { + "is_compatible": { + "type": "boolean" + }, + "messages": { + "type": "array", + "items": { + "type": "string" + } + }, + } + } diff --git a/src/v/pandaproxy/api/api-doc/schema_registry_header.json b/src/v/pandaproxy/api/api-doc/schema_registry_header.json index 6f807a01b4164..d0c431ec38803 100644 --- a/src/v/pandaproxy/api/api-doc/schema_registry_header.json +++ b/src/v/pandaproxy/api/api-doc/schema_registry_header.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "title": "Pandaproxy Schema Registry", - "version": "1.0.3" + "version": "1.0.5" }, "host": "{{Host}}", "basePath": "/", diff --git a/src/v/pandaproxy/auth_utils.h b/src/v/pandaproxy/auth_utils.h index 18d0a984432b6..e647cc31d83a2 100644 --- a/src/v/pandaproxy/auth_utils.h +++ b/src/v/pandaproxy/auth_utils.h @@ -41,4 +41,47 @@ inline credential_t maybe_authenticate_request( return user; } + +enum class auth_level { + // Unauthenticated endpoint (not a typo, 'public' is a keyword) + publik = 0, + // Requires authentication (if enabled) but not superuser status + user = 1, + // Requires authentication (if enabled) and superuser status + superuser = 2 +}; + +inline credential_t maybe_authorize_request( + config::rest_authn_method authn_method, + auth_level lvl, + request_authenticator& authenticator, + const ss::http::request& req) { + credential_t user; + + if (authn_method != config::rest_authn_method::none) { + // Will throw 400 & 401 if auth fails + auto auth_result = authenticator.authenticate(req); + // Will throw 403 if user enabled HTTP Basic Auth but + // did not give the authorization header. + switch (lvl) { + case auth_level::superuser: + auth_result.require_superuser(); + break; + case auth_level::user: + auth_result.require_authenticated(); + break; + case auth_level::publik: + auth_result.pass(); + break; + } + + user = credential_t{ + auth_result.get_username(), + auth_result.get_password(), + auth_result.get_sasl_mechanism()}; + } + + return user; +} + } // namespace pandaproxy diff --git a/src/v/pandaproxy/error.cc b/src/v/pandaproxy/error.cc index 33a4c074b7092..4d628f9be0a74 100644 --- a/src/v/pandaproxy/error.cc +++ b/src/v/pandaproxy/error.cc @@ -129,6 +129,8 @@ struct reply_error_category final : std::error_category { return "subject_version_not_deleted"; case reply_error_code::compatibility_not_found: return "compatibility_not_found"; + case reply_error_code::mode_not_found: + return "mode_not_found"; case reply_error_code::serialization_error: return "serialization_error"; case reply_error_code::consumer_already_exists: @@ -140,7 +142,9 @@ struct reply_error_category final : std::error_category { return "Invalid schema version"; case reply_error_code::compatibility_level_invalid: return "Invalid compatibility level"; - case reply_error_code::subject_version_operaton_not_permitted: + case reply_error_code::mode_invalid: + return "Invalid mode"; + case reply_error_code::subject_version_operation_not_permitted: return "Overwrite new schema is not permitted."; case reply_error_code::subject_version_has_references: return "One or more references exist to the schema"; diff --git a/src/v/pandaproxy/error.h b/src/v/pandaproxy/error.h index b1d26c7211f0a..ab78f0c0fd028 100644 --- a/src/v/pandaproxy/error.h +++ b/src/v/pandaproxy/error.h @@ -79,12 +79,14 @@ enum class reply_error_code : uint16_t { subject_version_soft_deleted = 40406, subject_version_not_deleted = 40407, compatibility_not_found = 40408, + mode_not_found = 40409, serialization_error = 40801, consumer_already_exists = 40902, schema_empty = 42201, schema_version_invalid = 42202, compatibility_level_invalid = 42203, - subject_version_operaton_not_permitted = 42205, + mode_invalid = 42204, + subject_version_operation_not_permitted = 42205, subject_version_has_references = 42206, subject_version_schema_id_already_exists = 42207, write_collision = 50301, diff --git a/src/v/pandaproxy/json/iobuf.h b/src/v/pandaproxy/json/iobuf.h index 79ec11e176b62..b20f31ce1df10 100644 --- a/src/v/pandaproxy/json/iobuf.h +++ b/src/v/pandaproxy/json/iobuf.h @@ -12,12 +12,10 @@ #pragma once #include "bytes/iobuf.h" -#include "bytes/iobuf_parser.h" +#include "json/iobuf_writer.h" #include "json/reader.h" -#include "json/stream.h" -#include "json/stringbuffer.h" #include "json/writer.h" -#include "pandaproxy/json/types.h" +#include "pandaproxy/json/rjson_util.h" #include "utils/base64.h" #include @@ -65,7 +63,8 @@ class rjson_serialize_impl { explicit rjson_serialize_impl(serialization_format fmt) : _fmt(fmt) {} - bool operator()(::json::Writer<::json::StringBuffer>& w, iobuf buf) { + template + bool operator()(::json::iobuf_writer& w, iobuf buf) { switch (_fmt) { case serialization_format::none: [[fallthrough]]; @@ -80,7 +79,8 @@ class rjson_serialize_impl { } } - bool encode_base64(::json::Writer<::json::StringBuffer>& w, iobuf buf) { + template + bool encode_base64(::json::iobuf_writer& w, iobuf buf) { if (buf.empty()) { return w.Null(); } @@ -88,15 +88,13 @@ class rjson_serialize_impl { return w.String(iobuf_to_base64(buf)); }; - bool encode_json(::json::Writer<::json::StringBuffer>& w, iobuf buf) { + template + bool encode_json(::json::Writer& w, iobuf buf) { if (buf.empty()) { return w.Null(); } - iobuf_parser p{std::move(buf)}; - auto str = p.read_string(p.bytes_left()); - static_assert(str.padding(), "StringStream requires null termination"); + ::json::chunked_input_stream ss{std::move(buf)}; ::json::Reader reader; - ::json::StringStream ss{str.c_str()}; return reader.Parse(ss, w); }; diff --git a/src/v/pandaproxy/json/requests/brokers.h b/src/v/pandaproxy/json/requests/brokers.h index 6a0e57ef55028..a9c715fdbfe60 100644 --- a/src/v/pandaproxy/json/requests/brokers.h +++ b/src/v/pandaproxy/json/requests/brokers.h @@ -11,9 +11,9 @@ #pragma once -#include "json/stringbuffer.h" +#include "json/json.h" #include "json/writer.h" -#include "model/metadata.h" +#include "model/fundamental.h" namespace pandaproxy::json { @@ -21,8 +21,9 @@ struct get_brokers_res { std::vector ids; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, const get_brokers_res& brokers) { +template +void rjson_serialize( + ::json::Writer& w, const get_brokers_res& brokers) { w.StartObject(); w.Key("brokers"); ::json::rjson_serialize(w, brokers.ids); diff --git a/src/v/pandaproxy/json/requests/create_consumer.h b/src/v/pandaproxy/json/requests/create_consumer.h index 4fd1ebf1c7e92..8cdf9538ff256 100644 --- a/src/v/pandaproxy/json/requests/create_consumer.h +++ b/src/v/pandaproxy/json/requests/create_consumer.h @@ -11,14 +11,9 @@ #pragma once -#include "bytes/iobuf.h" -#include "json/stringbuffer.h" #include "json/types.h" #include "json/writer.h" -#include "kafka/protocol/errors.h" -#include "kafka/protocol/produce.h" -#include "kafka/types.h" -#include "pandaproxy/json/iobuf.h" +#include "kafka/protocol/join_group.h" #include "pandaproxy/json/rjson_parse.h" #include "seastarx.h" #include "utils/string_switch.h" @@ -107,9 +102,9 @@ struct create_consumer_response { ss::sstring base_uri; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const create_consumer_response& res) { +template +void rjson_serialize( + ::json::Writer& w, const create_consumer_response& res) { w.StartObject(); w.Key("instance_id"); w.String(res.instance_id()); diff --git a/src/v/pandaproxy/json/requests/error_reply.h b/src/v/pandaproxy/json/requests/error_reply.h index c8770dcb500b7..7f82139204c22 100644 --- a/src/v/pandaproxy/json/requests/error_reply.h +++ b/src/v/pandaproxy/json/requests/error_reply.h @@ -12,7 +12,6 @@ #pragma once #include "json/json.h" -#include "json/stringbuffer.h" #include "json/writer.h" #include "seastarx.h" @@ -26,8 +25,8 @@ struct error_body { ss::sstring message; }; -inline void -rjson_serialize(::json::Writer<::json::StringBuffer>& w, const error_body& v) { +template +void rjson_serialize(::json::Writer& w, const error_body& v) { w.StartObject(); w.Key("error_code"); ::json::rjson_serialize(w, v.ec.value()); diff --git a/src/v/pandaproxy/json/requests/fetch.h b/src/v/pandaproxy/json/requests/fetch.h index 50680093feb09..33685dcbec4e7 100644 --- a/src/v/pandaproxy/json/requests/fetch.h +++ b/src/v/pandaproxy/json/requests/fetch.h @@ -11,18 +11,13 @@ #pragma once -#include "bytes/iobuf.h" -#include "bytes/iobuf_parser.h" -#include "json/stringbuffer.h" #include "json/writer.h" #include "kafka/protocol/errors.h" #include "kafka/protocol/fetch.h" #include "model/fundamental.h" #include "model/record.h" -#include "model/record_batch_reader.h" #include "pandaproxy/json/exceptions.h" #include "pandaproxy/json/iobuf.h" -#include "pandaproxy/json/requests/produce.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/json/types.h" #include "seastarx.h" @@ -43,8 +38,8 @@ class rjson_serialize_impl { , _tpv(tpv) , _base_offset(base_offset) {} - bool - operator()(::json::Writer<::json::StringBuffer>& w, model::record record) { + template + bool operator()(::json::iobuf_writer& w, model::record record) { auto offset = _base_offset() + record.offset_delta(); w.StartObject(); @@ -97,8 +92,9 @@ class rjson_serialize_impl { explicit rjson_serialize_impl(serialization_format fmt) : _fmt(fmt) {} - bool operator()( - ::json::Writer<::json::StringBuffer>& w, kafka::fetch_response&& res) { + template + bool + operator()(::json::iobuf_writer& w, kafka::fetch_response&& res) { // Eager check for errors for (auto& v : res) { if (v.partition_response->error_code != kafka::error_code::none) { diff --git a/src/v/pandaproxy/json/requests/offset_fetch.h b/src/v/pandaproxy/json/requests/offset_fetch.h index a804319e4e896..8967275e17797 100644 --- a/src/v/pandaproxy/json/requests/offset_fetch.h +++ b/src/v/pandaproxy/json/requests/offset_fetch.h @@ -11,9 +11,7 @@ #pragma once -#include "json/stringbuffer.h" #include "json/writer.h" -#include "kafka/protocol/errors.h" #include "kafka/protocol/offset_fetch.h" #include "seastarx.h" @@ -39,10 +37,13 @@ partitions_request_to_offset_request(std::vector tps) { } return res; } +} // namespace pandaproxy::json + +namespace kafka { -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const kafka::offset_fetch_response_topic& v) { +template +void rjson_serialize( + ::json::Writer& w, const kafka::offset_fetch_response_topic& v) { for (const auto& p : v.partitions) { w.StartObject(); w.Key("topic"); @@ -57,9 +58,9 @@ inline void rjson_serialize( } } -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const kafka::offset_fetch_response& v) { +template +void rjson_serialize( + ::json::Writer& w, const kafka::offset_fetch_response& v) { w.StartObject(); w.Key("offsets"); w.StartArray(); @@ -70,4 +71,4 @@ inline void rjson_serialize( w.EndObject(); } -} // namespace pandaproxy::json +} // namespace kafka diff --git a/src/v/pandaproxy/json/requests/produce.h b/src/v/pandaproxy/json/requests/produce.h index bfcc761ab95ce..02d8aef434d59 100644 --- a/src/v/pandaproxy/json/requests/produce.h +++ b/src/v/pandaproxy/json/requests/produce.h @@ -12,8 +12,7 @@ #pragma once #include "bytes/iobuf.h" -#include "json/json.h" -#include "json/stringbuffer.h" +#include "json/chunked_buffer.h" #include "json/types.h" #include "json/writer.h" #include "kafka/client/types.h" @@ -43,7 +42,7 @@ class produce_request_handler { serialization_format _fmt = serialization_format::none; state state = state::empty; - using json_writer = ::json::Writer<::json::StringBuffer>; + using json_writer = ::json::Writer<::json::chunked_buffer>; // If we're parsing json_v2, and the field is key or value (implied by // _json_writer being set), then forward calls to json_writer. @@ -56,8 +55,7 @@ class produce_request_handler { auto res = std::invoke( mem_func, *_json_writer, std::forward(args)...); if (_json_writer->IsComplete()) { - iobuf buf; - buf.append(_buf.GetString(), _buf.GetSize()); + iobuf buf = std::move(_buf).as_iobuf(); switch (state) { case state::key: result.back().key.emplace(std::move(buf)); @@ -261,28 +259,31 @@ class produce_request_handler { } private: - ::json::StringBuffer _buf; + ::json::chunked_buffer _buf; std::optional _json_writer; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const kafka::produce_response::partition& v) { +} // namespace pandaproxy::json + +namespace kafka { +template +void rjson_serialize( + ::json::Writer& w, const kafka::produce_response::partition& v) { w.StartObject(); w.Key("partition"); w.Int(v.partition_index); if (v.error_code != kafka::error_code::none) { w.Key("error_code"); - ::json::rjson_serialize(w, v.error_code); + rjson_serialize(w, v.error_code); } w.Key("offset"); w.Int64(v.base_offset); w.EndObject(); } -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const kafka::produce_response::topic& v) { +template +void rjson_serialize( + ::json::Writer& w, const kafka::produce_response::topic& v) { w.StartObject(); w.Key("offsets"); w.StartArray(); @@ -293,4 +294,4 @@ inline void rjson_serialize( w.EndObject(); } -} // namespace pandaproxy::json +} // namespace kafka diff --git a/src/v/pandaproxy/json/requests/subscribe_consumer.h b/src/v/pandaproxy/json/requests/subscribe_consumer.h index 9e4a2cb458b3b..14760e86d2a54 100644 --- a/src/v/pandaproxy/json/requests/subscribe_consumer.h +++ b/src/v/pandaproxy/json/requests/subscribe_consumer.h @@ -28,7 +28,7 @@ namespace pandaproxy::json { struct subscribe_consumer_request { - std::vector topics; + chunked_vector topics; }; template> diff --git a/src/v/pandaproxy/json/requests/test/fetch.cc b/src/v/pandaproxy/json/requests/test/fetch.cc index 3f7af00ef1542..558e469bd40a9 100644 --- a/src/v/pandaproxy/json/requests/test/fetch.cc +++ b/src/v/pandaproxy/json/requests/test/fetch.cc @@ -13,23 +13,19 @@ #include "json/writer.h" #include "kafka/client/test/utils.h" #include "kafka/protocol/errors.h" -#include "kafka/protocol/fetch.h" #include "kafka/protocol/wire.h" #include "model/fundamental.h" -#include "model/record.h" #include "model/timestamp.h" -#include "pandaproxy/json/requests/fetch.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/json/types.h" #include "seastarx.h" +#include "utils/fragmented_vector.h" #include #include #include -#include - namespace ppj = pandaproxy::json; std::optional @@ -45,7 +41,7 @@ make_record_set(model::offset offset, size_t count) { auto make_fetch_response( std::vector tps, model::offset offset, size_t count) { - std::vector parts; + chunked_vector parts; for (const auto& tp : tps) { kafka::fetch_response::partition res{tp.topic}; res.partitions.push_back(kafka::fetch_response::partition_response{ @@ -71,7 +67,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_fetch_empty) { auto fmt = ppj::serialization_format::binary_v2; ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> w(str_buf); + ::json::iobuf_writer<::json::StringBuffer> w(str_buf); ppj::rjson_serialize_fmt(fmt)(w, std::move(res)); auto expected = R"([])"; @@ -89,7 +85,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_fetch_one) { auto fmt = ppj::serialization_format::binary_v2; ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> w(str_buf); + ::json::iobuf_writer<::json::StringBuffer> w(str_buf); ppj::rjson_serialize_fmt(fmt)(w, std::move(res)); auto expected diff --git a/src/v/pandaproxy/json/requests/test/produce.cc b/src/v/pandaproxy/json/requests/test/produce.cc index 44342df7b16a0..3710f7fcdee8b 100644 --- a/src/v/pandaproxy/json/requests/test/produce.cc +++ b/src/v/pandaproxy/json/requests/test/produce.cc @@ -10,6 +10,7 @@ #include "pandaproxy/json/requests/produce.h" #include "kafka/protocol/produce.h" +#include "kafka/protocol/schemata/produce_response.h" #include "model/timestamp.h" #include "pandaproxy/json/exceptions.h" #include "pandaproxy/json/rjson_util.h" @@ -18,6 +19,8 @@ #include +#include + namespace ppj = pandaproxy::json; auto make_binary_v2_handler() { @@ -43,7 +46,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_binary_request) { ] })"; - auto records = ppj::rjson_parse(input, make_binary_v2_handler()); + auto records = ppj::impl::rjson_parse(input, make_binary_v2_handler()); BOOST_TEST(records.size() == 2); BOOST_TEST(!!records[0].value); @@ -74,7 +77,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_json_request) { ] })"; - auto records = ppj::rjson_parse(input, make_json_v2_handler()); + auto records = ppj::impl::rjson_parse(input, make_json_v2_handler()); BOOST_REQUIRE_EQUAL(records.size(), 2); BOOST_REQUIRE_EQUAL(records[0].partition_id, model::partition_id(0)); BOOST_REQUIRE(!records[0].key); @@ -108,7 +111,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_invalid_json_request) { })"; BOOST_CHECK_THROW( - ppj::rjson_parse(input, make_json_v2_handler()), + ppj::impl::rjson_parse(input, make_json_v2_handler()), pandaproxy::json::parse_error); } @@ -118,7 +121,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_empty) { "records": [] })"; - auto records = ppj::rjson_parse(input, make_binary_v2_handler()); + auto records = ppj::impl::rjson_parse(input, make_binary_v2_handler()); BOOST_TEST(records.size() == 0); } @@ -134,7 +137,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_records_name) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 25"); @@ -153,7 +156,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_partition_name) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 99"); @@ -172,7 +175,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_partition_type) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 112"); @@ -192,7 +195,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_before_records) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 28"); @@ -212,7 +215,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_after_records) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 152"); @@ -232,7 +235,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_between_records) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 144"); @@ -247,7 +250,7 @@ SEASTAR_THREAD_TEST_CASE(test_produce_request_error_no_records) { })"; BOOST_CHECK_EXCEPTION( - ppj::rjson_parse(input, make_binary_v2_handler()), + ppj::impl::rjson_parse(input, make_binary_v2_handler()), ppj::parse_error, [](ppj::parse_error const& e) { return e.what() == std::string_view("parse error at offset 24"); @@ -260,22 +263,22 @@ SEASTAR_THREAD_TEST_CASE(test_produce_response) { auto topic = kafka::produce_response::topic{ .name = model::topic{"topic0"}, - .partitions = { - kafka::produce_response::partition{ - .partition_index = model::partition_id{0}, - .error_code = kafka::error_code::none, - .base_offset = model::offset{42}, - .log_append_time_ms = model::timestamp{}, - .log_start_offset = model::offset{}}, - kafka::produce_response::partition{ - .partition_index = model::partition_id{1}, - .error_code = kafka::error_code::invalid_partitions, - .base_offset = model::offset{-1}, - .log_append_time_ms = model::timestamp{}, - .log_start_offset = model::offset{}}, - }}; - - auto output = ppj::rjson_serialize(topic); + }; + + topic.partitions.emplace_back(kafka::produce_response::partition{ + .partition_index = model::partition_id{0}, + .error_code = kafka::error_code::none, + .base_offset = model::offset{42}, + .log_append_time_ms = model::timestamp{}, + .log_start_offset = model::offset{}}); + topic.partitions.emplace_back(kafka::produce_response::partition{ + .partition_index = model::partition_id{1}, + .error_code = kafka::error_code::invalid_partitions, + .base_offset = model::offset{-1}, + .log_append_time_ms = model::timestamp{}, + .log_start_offset = model::offset{}}); + + auto output = ppj::rjson_serialize_str(topic); BOOST_TEST(output == expected); } diff --git a/src/v/pandaproxy/json/rjson_util.h b/src/v/pandaproxy/json/rjson_util.h index 2fe5d465c07f4..3ff8ff2c998d6 100644 --- a/src/v/pandaproxy/json/rjson_util.h +++ b/src/v/pandaproxy/json/rjson_util.h @@ -11,31 +11,73 @@ #pragma once +#include "bytes/iostream.h" +#include "json/chunked_buffer.h" +#include "json/chunked_input_stream.h" +#include "json/iobuf_writer.h" #include "json/json.h" -#include "json/prettywriter.h" #include "json/reader.h" #include "json/stream.h" #include "json/stringbuffer.h" -#include "json/writer.h" #include "pandaproxy/json/exceptions.h" #include "pandaproxy/json/types.h" +#include #include +#include +#include -#include +#include +#include namespace pandaproxy::json { template -ss::sstring rjson_serialize(const T& v) { - ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> wrt(str_buf); +class rjson_parse_impl; + +template +class rjson_serialize_impl; + +template +Buffer rjson_serialize_buf(T&& v) { + Buffer buf; + ::json::iobuf_writer wrt{buf}; using ::json::rjson_serialize; using ::pandaproxy::json::rjson_serialize; - rjson_serialize(wrt, v); + rjson_serialize(wrt, std::forward(v)); + + return buf; +} + +template +ss::sstring rjson_serialize_str(T&& v) { + auto str_buf = rjson_serialize_buf<::json::StringBuffer>( + std::forward(v)); + return {str_buf.GetString(), str_buf.GetSize()}; +} - return ss::sstring(str_buf.GetString(), str_buf.GetSize()); +template +iobuf rjson_serialize_iobuf(T&& v) { + return rjson_serialize_buf<::json::chunked_buffer>(std::forward(v)) + .as_iobuf(); +} + +inline ss::noncopyable_function(ss::output_stream&& os)> +as_body_writer(iobuf&& buf) { + return [buf{std::move(buf)}](ss::output_stream&& os) mutable { + return ss::do_with( + std::move(buf), std::move(os), [](auto& buf, auto& os) { + return write_iobuf_to_output_stream(std::move(buf), os) + .finally([&os] { return os.close(); }); + }); + }; +} + +template +ss::noncopyable_function(ss::output_stream&& os)> +rjson_serialize(T&& v) { + return as_body_writer(rjson_serialize_iobuf(std::forward(v))); } struct rjson_serialize_fmt_impl { @@ -48,8 +90,8 @@ struct rjson_serialize_fmt_impl { return rjson_serialize_impl>{fmt}( std::forward(t)); } - template - bool operator()(::json::Writer<::json::StringBuffer>& w, T&& t) { + template + bool operator()(::json::iobuf_writer& w, T&& t) { return rjson_serialize_impl>{fmt}( w, std::forward(t)); } @@ -59,18 +101,68 @@ inline rjson_serialize_fmt_impl rjson_serialize_fmt(serialization_format fmt) { return rjson_serialize_fmt_impl{fmt}; } +namespace impl { + template -requires std::is_same_v< - decltype(std::declval().result), - typename Handler::rjson_parse_result> +concept RjsonParseHandler = requires { + std::same_as< + decltype(std::declval().result), + typename Handler::rjson_parse_result>; +}; + +template typename Handler::rjson_parse_result -rjson_parse(const char* const s, Handler&& handler) { +rjson_parse_buf(Arg&& arg, Handler&& handler) { ::json::Reader reader; - ::json::StringStream ss(s); + IStream ss(std::forward(arg)); if (!reader.Parse(ss, handler)) { throw parse_error(reader.GetErrorOffset()); } - return std::move(handler.result); + return std::forward(handler).result; +} + +/// \brief Parse a payload using the handler. +/// +/// \warning rjson_parse is preferred, since it can be chunked. +template +typename Handler::rjson_parse_result +rjson_parse(const char* const s, Handler&& handler) { + return impl::rjson_parse_buf<::json::StringStream>( + s, std::forward(handler)); +} + +} // namespace impl + +///\brief Parse a payload using the handler. +template +typename Handler::rjson_parse_result rjson_parse(iobuf buf, Handler&& handler) { + return impl::rjson_parse_buf<::json::chunked_input_stream>( + std::move(buf), std::forward(handler)); +} + +///\brief Parse a request body using the handler. +template +typename ss::future +rjson_parse(std::unique_ptr req, Handler handler) { + if (!req->content.empty()) { + co_return impl::rjson_parse(req->content.data(), std::move(handler)); + } + + iobuf buf; + auto is = req->content_stream; + co_await ss::repeat([&buf, &is]() { + return is->read().then([&buf](ss::temporary_buffer tmp_buf) { + if (tmp_buf.empty()) { + return ss::make_ready_future( + ss::stop_iteration::yes); + } + buf.append(std::make_unique(std::move(tmp_buf))); + return ss::make_ready_future( + ss::stop_iteration::no); + }); + }); + + co_return rjson_parse(std::move(buf), std::move(handler)); } } // namespace pandaproxy::json diff --git a/src/v/pandaproxy/json/test/iobuf.cc b/src/v/pandaproxy/json/test/iobuf.cc index 8713238b33658..f0bf34d451555 100644 --- a/src/v/pandaproxy/json/test/iobuf.cc +++ b/src/v/pandaproxy/json/test/iobuf.cc @@ -10,8 +10,8 @@ #include "pandaproxy/json/iobuf.h" #include "bytes/iobuf_parser.h" +#include "json/iobuf_writer.h" #include "json/stringbuffer.h" -#include "json/writer.h" #include "pandaproxy/json/rjson_util.h" #include @@ -42,7 +42,7 @@ SEASTAR_THREAD_TEST_CASE(test_iobuf_serialize_binary) { in_buf.append(input.data(), input.size()); ::json::StringBuffer out_buf; - ::json::Writer<::json::StringBuffer> w(out_buf); + ::json::iobuf_writer<::json::StringBuffer> w(out_buf); ppj::rjson_serialize_fmt(ppj::serialization_format::binary_v2)( w, std::move(in_buf)); ss::sstring output{out_buf.GetString(), out_buf.GetSize()}; diff --git a/src/v/pandaproxy/json/test/parse.cc b/src/v/pandaproxy/json/test/parse.cc index cb75470644411..d7207474bf5d4 100644 --- a/src/v/pandaproxy/json/test/parse.cc +++ b/src/v/pandaproxy/json/test/parse.cc @@ -43,7 +43,8 @@ inline void parse_test(size_t data_size) { auto input = gen(data_size); perf_tests::start_measuring_time(); - auto records = ppj::rjson_parse(input.c_str(), make_binary_v2_handler()); + auto records = ppj::impl::rjson_parse( + input.c_str(), make_binary_v2_handler()); perf_tests::stop_measuring_time(); } diff --git a/src/v/pandaproxy/json/types.h b/src/v/pandaproxy/json/types.h index d698bcef65624..e4b9d910335b7 100644 --- a/src/v/pandaproxy/json/types.h +++ b/src/v/pandaproxy/json/types.h @@ -12,7 +12,7 @@ #pragma once #include -#include +#include namespace pandaproxy::json { @@ -52,10 +52,4 @@ inline std::string_view name(serialization_format fmt) { return "(unknown format)"; } -template -class rjson_parse_impl; - -template -class rjson_serialize_impl; - } // namespace pandaproxy::json diff --git a/src/v/pandaproxy/parsing/from_chars.h b/src/v/pandaproxy/parsing/from_chars.h index a7cac79112301..f2a7df05cdd4a 100644 --- a/src/v/pandaproxy/parsing/from_chars.h +++ b/src/v/pandaproxy/parsing/from_chars.h @@ -18,6 +18,8 @@ #include +#include + #include #include #include @@ -92,7 +94,7 @@ class from_chars { using value_type = typename type::rep; return wrap(from_chars{}(in)); } else if constexpr (is_ss_bool) { - return type(in == "true" || in == "TRUE" || in == "1"); + return type(boost::iequals(in, "true") || in == "1"); } else if constexpr (is_arithmetic) { return do_from_chars(in); } diff --git a/src/v/pandaproxy/reply.h b/src/v/pandaproxy/reply.h index 3e88a2e72bf59..18806e4003f40 100644 --- a/src/v/pandaproxy/reply.h +++ b/src/v/pandaproxy/reply.h @@ -71,8 +71,7 @@ errored_body(std::error_condition ec, ss::sstring msg) { pandaproxy::json::error_body body{.ec = ec, .message = std::move(msg)}; auto rep = std::make_unique(); rep->set_status(error_code_to_status(ec)); - auto b = json::rjson_serialize(body); - rep->write_body("json", b); + rep->write_body("json", json::rjson_serialize(body)); return rep; } diff --git a/src/v/pandaproxy/rest/handlers.cc b/src/v/pandaproxy/rest/handlers.cc index 9fc175e6e9314..56fe47a3a8baf 100644 --- a/src/v/pandaproxy/rest/handlers.cc +++ b/src/v/pandaproxy/rest/handlers.cc @@ -92,8 +92,7 @@ get_brokers(server::request_t rq, server::reply_t rp) { return b.node_id; }); - auto json_rslt = ppj::rjson_serialize(brokers); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(brokers)); rp.mime_type = res_fmt; return std::move(rp); @@ -123,8 +122,7 @@ get_topics_names(server::request_t rq, server::reply_t rp) { } } - auto json_rslt = ppj::rjson_serialize(names); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(names)); rp.mime_type = res_fmt; return std::move(rp); }); @@ -160,16 +158,16 @@ get_topics_records(server::request_t rq, server::reply_t rp) { return client .fetch_partition(std::move(tp), offset, max_bytes, timeout) .then([res_fmt](kafka::fetch_response res) { - ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> w(str_buf); + ::json::chunked_buffer buf; + ::json::iobuf_writer<::json::chunked_buffer> w(buf); ppj::rjson_serialize_fmt(res_fmt)(w, std::move(res)); - // TODO Ben: Prevent this linearization - return ss::make_ready_future(str_buf.GetString()); + return buf; }); }) - .then([res_fmt, rp = std::move(rp)](ss::sstring json_rslt) mutable { - rp.rep->write_body("json", json_rslt); + .then([res_fmt, rp = std::move(rp)](auto buf) mutable { + rp.rep->write_body( + "json", json::as_body_writer(std::move(buf).as_iobuf())); rp.mime_type = res_fmt; return std::move(rp); }); @@ -189,19 +187,16 @@ post_topics_name(server::request_t rq, server::reply_t rp) { vlog(plog.debug, "get_topics_name: topic: {}", topic); + auto records = co_await ppj::rjson_parse( + std::move(rq.req), ppj::produce_request_handler(req_fmt)); co_return co_await rq.dispatch( - [data{rq.req->content.data()}, - topic, - req_fmt, - res_fmt, - rp{std::move(rp)}](kafka::client::client& client) mutable { - auto records = ppj::rjson_parse( - data, ppj::produce_request_handler(req_fmt)); + [records{std::move(records)}, topic, res_fmt, rp{std::move(rp)}]( + kafka::client::client& client) mutable { return client.produce_records(topic, std::move(records)) .then([rp{std::move(rp)}, res_fmt](kafka::produce_response res) mutable { - auto json_rslt = ppj::rjson_serialize(res.data.responses[0]); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", ppj::rjson_serialize(res.data.responses[0])); rp.mime_type = res_fmt; return std::move(rp); }); @@ -229,8 +224,10 @@ create_consumer(server::request_t rq, server::reply_t rp) { auto group_id = parse::request_param( *rq.req, "group_name"); - auto req_data = ppj::rjson_parse( - rq.req->content.data(), ppj::create_consumer_request_handler()); + auto base_uri = make_consumer_uri_base(rq, group_id); + + auto req_data = co_await ppj::rjson_parse( + std::move(rq.req), ppj::create_consumer_request_handler()); validate_no_control( req_data.name(), parse::pp_parsing_error{req_data.name()}); @@ -249,8 +246,6 @@ create_consumer(server::request_t rq, server::reply_t rp) { parse::error_code::invalid_param, "auto.commit must be false"); } - auto base_uri = make_consumer_uri_base(rq, group_id); - co_return co_await rq.dispatch( group_id, [group_id, @@ -292,8 +287,7 @@ create_consumer(server::request_t rq, server::reply_t rp) { kafka::member_id name) mutable { json::create_consumer_response res{ .instance_id = name, .base_uri = base_uri + name}; - auto json_rslt = ppj::rjson_serialize(res); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(res)); rp.mime_type = res_fmt; return std::move(rp); }); @@ -343,8 +337,8 @@ subscribe_consumer(server::request_t rq, server::reply_t rp) { auto member_id = parse::request_param( *rq.req, "instance"); - auto req_data = ppj::rjson_parse( - rq.req->content.data(), ppj::subscribe_consumer_request_handler()); + auto req_data = co_await ppj::rjson_parse( + std::move(rq.req), ppj::subscribe_consumer_request_handler()); std::for_each( req_data.topics.begin(), req_data.topics.end(), @@ -410,14 +404,13 @@ consumer_fetch(server::request_t rq, server::reply_t rp) { return client.consumer_fetch(group_id, name, timeout, max_bytes) .then([res_fmt, rp{std::move(rp)}](auto res) mutable { - ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> w(str_buf); + ::json::chunked_buffer buf; + ::json::iobuf_writer<::json::chunked_buffer> w(buf); ppj::rjson_serialize_fmt(res_fmt)(w, std::move(res)); - // TODO Ben: Prevent this linearization - ss::sstring json_rslt = str_buf.GetString(); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", json::as_body_writer(std::move(buf).as_iobuf())); rp.mime_type = res_fmt; return std::move(rp); }); @@ -434,8 +427,9 @@ get_consumer_offsets(server::request_t rq, server::reply_t rp) { auto group_id{parse::request_param(*rq.req, "group_name")}; auto member_id{parse::request_param(*rq.req, "instance")}; - auto req_data = ppj::partitions_request_to_offset_request(ppj::rjson_parse( - rq.req->content.data(), ppj::partitions_request_handler())); + auto req_data = ppj::partitions_request_to_offset_request( + co_await ppj::rjson_parse( + std::move(rq.req), ppj::partitions_request_handler())); std::for_each(req_data.begin(), req_data.end(), [](const auto& r) { validate_no_control(r.name(), parse::pp_parsing_error{r.name()}); @@ -458,11 +452,7 @@ get_consumer_offsets(server::request_t rq, server::reply_t rp) { return client .consumer_offset_fetch(group_id, member_id, std::move(req_data)) .then([rp{std::move(rp)}, res_fmt](auto res) mutable { - ::json::StringBuffer str_buf; - ::json::Writer<::json::StringBuffer> w(str_buf); - ppj::rjson_serialize(w, res); - ss::sstring json_rslt = str_buf.GetString(); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(res)); rp.mime_type = res_fmt; return std::move(rp); }); @@ -480,11 +470,11 @@ post_consumer_offsets(server::request_t rq, server::reply_t rp) { auto member_id{parse::request_param(*rq.req, "instance")}; // If the request is empty, commit all offsets - auto req_data = rq.req->content.length() == 0 + auto req_data = rq.req->content_length == 0 ? std::vector() : ppj::partition_offsets_request_to_offset_commit_request( - ppj::rjson_parse( - rq.req->content.data(), + co_await ppj::rjson_parse( + std::move(rq.req), ppj::partition_offsets_request_handler())); std::for_each(req_data.begin(), req_data.end(), [](const auto& r) { diff --git a/src/v/pandaproxy/rest/test/consumer_group.cc b/src/v/pandaproxy/rest/test/consumer_group.cc index 0c0ea3ae13bb9..655d9384cfeb4 100644 --- a/src/v/pandaproxy/rest/test/consumer_group.cc +++ b/src/v/pandaproxy/rest/test/consumer_group.cc @@ -84,7 +84,7 @@ FIXTURE_TEST(pandaproxy_consumer_group, pandaproxy_test_fixture) { BOOST_REQUIRE_EQUAL( res.headers.result(), boost::beast::http::status::ok); - auto res_data = ppj::rjson_parse( + auto res_data = ppj::impl::rjson_parse( res.body.data(), ppj::create_consumer_response_handler()); BOOST_REQUIRE_EQUAL(res_data.instance_id, "test_consumer"); member_id = res_data.instance_id; @@ -315,7 +315,7 @@ FIXTURE_TEST( BOOST_REQUIRE_EQUAL( res.headers.result(), boost::beast::http::status::ok); - auto res_data = ppj::rjson_parse( + auto res_data = ppj::impl::rjson_parse( res.body.data(), ppj::create_consumer_response_handler()); BOOST_REQUIRE_EQUAL(res_data.instance_id, "test_consumer"); member_id = res_data.instance_id; diff --git a/src/v/pandaproxy/schema_registry/CMakeLists.txt b/src/v/pandaproxy/schema_registry/CMakeLists.txt index 0464814dfed06..cf27d6b99a0da 100644 --- a/src/v/pandaproxy/schema_registry/CMakeLists.txt +++ b/src/v/pandaproxy/schema_registry/CMakeLists.txt @@ -13,6 +13,7 @@ v_cc_library( SRCS api.cc configuration.cc + compatibility.cc handlers.cc error.cc service.cc diff --git a/src/v/pandaproxy/schema_registry/api.cc b/src/v/pandaproxy/schema_registry/api.cc index 31d59201ab59a..6eba740bed1d6 100644 --- a/src/v/pandaproxy/schema_registry/api.cc +++ b/src/v/pandaproxy/schema_registry/api.cc @@ -47,7 +47,7 @@ api::~api() noexcept = default; ss::future<> api::start() { _store = std::make_unique(); - co_await _store->start(_sg); + co_await _store->start(is_mutable(_cfg.mode_mutability), _sg); co_await _schema_id_validation_probe.start(); co_await _schema_id_validation_probe.invoke_on_all( &schema_id_validation_probe::setup_metrics); diff --git a/src/v/pandaproxy/schema_registry/avro.cc b/src/v/pandaproxy/schema_registry/avro.cc index 8aaa798fb6a9e..6fac38cc0ea2d 100644 --- a/src/v/pandaproxy/schema_registry/avro.cc +++ b/src/v/pandaproxy/schema_registry/avro.cc @@ -11,16 +11,17 @@ #include "pandaproxy/schema_registry/avro.h" +#include "bytes/streambuf.h" #include "json/allocator.h" +#include "json/chunked_input_stream.h" #include "json/document.h" -#include "json/encodings.h" -#include "json/stringbuffer.h" +#include "json/json.h" #include "json/types.h" -#include "json/writer.h" +#include "pandaproxy/schema_registry/compatibility.h" #include "pandaproxy/schema_registry/error.h" #include "pandaproxy/schema_registry/errors.h" #include "pandaproxy/schema_registry/sharded_store.h" -#include "utils/string_switch.h" +#include "pandaproxy/schema_registry/types.h" #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -43,28 +45,75 @@ #include #include +namespace pandaproxy::json { +using namespace ::json; +} + namespace pandaproxy::schema_registry { namespace { -bool check_compatible(avro::Node& reader, avro::Node& writer) { +using avro_compatibility_result = raw_compatibility_result; + +avro_compatibility_result check_compatible( + avro::Node& reader, avro::Node& writer, std::filesystem::path p = {}) { + auto type_to_upper = [](avro::Type t) { + auto s = toString(t); + std::transform(s.begin(), s.end(), s.begin(), ::toupper); + return s; + }; + avro_compatibility_result compat_result; if (reader.type() == writer.type()) { - // Do a quick check first - if (!writer.resolve(reader)) { - return false; + // Do some quick checks first + // These are detectable by the blunt `resolve` check below, but we want + // to extract as much error info as possible. + if (reader.type() == avro::Type::AVRO_ARRAY) { + compat_result.merge(check_compatible( + *reader.leafAt(0), *writer.leafAt(0), p / "items")); + } else if (reader.hasName() && reader.name() != writer.name()) { + // The Avro library doesn't fully support handling schema resolution + // with name aliases yet. While `equalOrAliasedBy` is available, + // `writer.resolve(reader)` doesn't take into account the aliases. + // Once Avro supports name alias resolution, we should only return a + // name_mismatch when + // `!writer.name().equalOrAliasedBy(reader.name())`, however, in the + // meantime it is best to return a more specific error. + auto suffix = writer.name().equalOrAliasedBy(reader.name()) + ? " (alias resolution is not yet fully supported)" + : ""; + compat_result.emplace( + p / "name", + avro_incompatibility::Type::name_mismatch, + fmt::format("expected: {}{}", writer.name(), suffix)); + } else if ( + reader.type() == avro::Type::AVRO_FIXED + && reader.fixedSize() != writer.fixedSize()) { + compat_result.emplace( + p / "size", + avro_incompatibility::Type::fixed_size_mismatch, + fmt::format( + "expected: {}, found: {}", + writer.fixedSize(), + reader.fixedSize())); + } else if (!writer.resolve(reader)) { + // Everything else is an UNKNOWN error with the current path + compat_result.emplace( + std::move(p), avro_incompatibility::Type::unknown); + return compat_result; } + if (reader.type() == avro::Type::AVRO_RECORD) { // Recursively check fields + auto fields_p = p / "fields"; for (size_t r_idx = 0; r_idx < reader.names(); ++r_idx) { size_t w_idx{0}; if (writer.nameIndex(reader.nameAt(int(r_idx)), w_idx)) { - // schemas for fields with the same name in both records are - // resolved recursively. - if (!check_compatible( - *reader.leafAt(int(r_idx)), - *writer.leafAt(int(w_idx)))) { - return false; - } + // schemas for fields with the same name in both records + // are resolved recursively. + compat_result.merge(check_compatible( + *reader.leafAt(int(r_idx)), + *writer.leafAt(int(w_idx)), + fields_p / std::to_string(r_idx) / "type")); } else if ( reader.defaultValueAt(int(r_idx)).type() == avro::AVRO_NULL) { // if the reader's record schema has a field with no default @@ -77,22 +126,32 @@ bool check_compatible(avro::Node& reader, avro::Node& writer) { if ( r_leaf->type() != avro::Type::AVRO_UNION || r_leaf->leafAt(0)->type() != avro::Type::AVRO_NULL) { - return false; + compat_result.emplace( + fields_p / std::to_string(r_idx), + avro_incompatibility::Type:: + reader_field_missing_default_value, + reader.nameAt(r_idx)); } } } - return true; } else if (reader.type() == avro::AVRO_ENUM) { // if the writer's symbol is not present in the reader's enum and // the reader has a default value, then that value is used, // otherwise an error is signalled. - if (reader.defaultValueAt(0).type() != avro::AVRO_NULL) { - return true; - } - for (size_t w_idx = 0; w_idx < writer.names(); ++w_idx) { - size_t r_idx{0}; - if (!reader.nameIndex(writer.nameAt(int(w_idx)), r_idx)) { - return false; + if (reader.defaultValueAt(0).type() == avro::AVRO_NULL) { + std::vector missing; + for (size_t w_idx = 0; w_idx < writer.names(); ++w_idx) { + size_t r_idx{0}; + if (const auto& n = writer.nameAt(int(w_idx)); + !reader.nameIndex(n, r_idx)) { + missing.emplace_back(n); + } + } + if (!missing.empty()) { + compat_result.emplace( + p / "symbols", + avro_incompatibility::Type::missing_enum_symbols, + fmt::format("[{}]", fmt::join(missing, ", "))); } } } else if (reader.type() == avro::AVRO_UNION) { @@ -104,17 +163,22 @@ bool check_compatible(avro::Node& reader, avro::Node& writer) { for (size_t w_idx = 0; w_idx < writer.leaves(); ++w_idx) { bool is_compat = false; for (size_t r_idx = 0; r_idx < reader.leaves(); ++r_idx) { - if (check_compatible( - *reader.leafAt(int(r_idx)), - *writer.leafAt(int(w_idx)))) { + if (!check_compatible( + *reader.leafAt(int(r_idx)), + *writer.leafAt(int(w_idx))) + .has_error()) { is_compat = true; } } if (!is_compat) { - return false; + compat_result.emplace( + p / std::to_string(w_idx), + avro_incompatibility::Type::missing_union_branch, + fmt::format( + "reader union lacking writer type: {}", + type_to_upper(writer.leafAt(w_idx)->type()))); } } - return true; } } else if (reader.type() == avro::AVRO_UNION) { // The first schema in the reader's union that matches the writer's @@ -122,12 +186,21 @@ bool check_compatible(avro::Node& reader, avro::Node& writer) { // signalled. // // Alternatively, any schema in the reader union must match writer. + bool is_compat = false; for (size_t r_idx = 0; r_idx < reader.leaves(); ++r_idx) { - if (check_compatible(*reader.leafAt(int(r_idx)), writer)) { - return true; + if (!check_compatible(*reader.leafAt(int(r_idx)), writer) + .has_error()) { + is_compat = true; } } - return false; + if (!is_compat) { + compat_result.emplace( + std::move(p), + avro_incompatibility::Type::missing_union_branch, + fmt::format( + "reader union lacking writer type: {}", + type_to_upper(writer.type()))); + } } else if (writer.type() == avro::AVRO_UNION) { // If the reader's schema matches the selected writer's schema, it is // recursively resolved against it. If they do not match, an error is @@ -135,13 +208,20 @@ bool check_compatible(avro::Node& reader, avro::Node& writer) { // // Alternatively, reader must match all schema in writer union. for (size_t w_idx = 0; w_idx < writer.leaves(); ++w_idx) { - if (!check_compatible(reader, *writer.leafAt(int(w_idx)))) { - return false; - } + compat_result.merge( + check_compatible(reader, *writer.leafAt(int(w_idx)), p)); } - return true; + } else if (writer.resolve(reader) == avro::RESOLVE_NO_MATCH) { + compat_result.emplace( + std::move(p), + avro_incompatibility::Type::type_mismatch, + fmt::format( + "reader type: {} not compatible with writer type: {}", + type_to_upper(reader.type()), + type_to_upper(writer.type()))); } - return writer.resolve(reader) != avro::RESOLVE_NO_MATCH; + + return compat_result; } enum class object_type { complex, field }; @@ -239,6 +319,11 @@ result sanitize_avro_type( case avro::AVRO_FIXED: case avro::AVRO_MAP: std::sort(o.begin(), o.end(), member_sorter{}); + for (auto& i : o) { + if (auto res = sanitize(i.value, ctx); !res.has_value()) { + return res; + } + } break; case avro::AVRO_RECORD: { auto res = sanitize_record(o, ctx); @@ -421,7 +506,10 @@ std::ostream& operator<<(std::ostream& os, const avro_schema_definition& def) { } canonical_schema_definition::raw_string avro_schema_definition::raw() const { - return canonical_schema_definition::raw_string{_impl.toJson(false)}; + iobuf_ostream os; + _impl.toJson(os.ostream()); + return canonical_schema_definition::raw_string{ + json::minify(std::move(os).buf())}; } ss::sstring avro_schema_definition::name() const { @@ -436,17 +524,22 @@ class collected_schema { bool insert(ss::sstring name, canonical_schema_definition def) { bool inserted = _names.insert(std::move(name)).second; if (inserted) { - _schemas.push_back(std::move(def).raw()()); + _schemas.push_back(std::move(def).raw()); } return inserted; } - ss::sstring flatten() { - return fmt::format("{}", fmt::join(_schemas, "\n")); + canonical_schema_definition::raw_string flatten() && { + iobuf out; + for (auto& s : _schemas) { + out.append(std::move(s)); + out.append("\n", 1); + } + return canonical_schema_definition::raw_string{std::move(out)}; } private: absl::flat_hash_set _names; - std::vector _schemas; + std::vector _schemas; }; ss::future collect_schema( @@ -454,18 +547,14 @@ ss::future collect_schema( collected_schema collected, ss::sstring name, canonical_schema schema) { - for (auto& ref : schema.def().refs()) { + for (auto const& ref : schema.def().refs()) { if (!collected.contains(ref.name)) { auto ss = co_await store.get_subject_schema( - std::move(ref.sub), ref.version, include_deleted::no); + ref.sub, ref.version, include_deleted::no); collected = co_await collect_schema( - store, - std::move(collected), - std::move(ref.name), - std::move(ss.schema)); + store, std::move(collected), ref.name, std::move(ss.schema)); } } - // NOLINTNEXTLINE(bugprone-use-after-move) collected.insert(std::move(name), std::move(schema).def()); co_return std::move(collected); } @@ -477,11 +566,10 @@ make_avro_schema_definition(sharded_store& store, canonical_schema schema) { auto name = schema.sub()(); auto schema_refs = schema.def().refs(); auto refs = co_await collect_schema(store, {}, name, std::move(schema)); - auto def = refs.flatten(); + iobuf_istream sis{std::move(refs).flatten()()}; + auto is = avro::istreamInputStream(sis.istream()); co_return avro_schema_definition{ - avro::compileJsonSchemaFromMemory( - reinterpret_cast(def.data()), def.length()), - std::move(schema_refs)}; + avro::compileJsonSchemaFromStream(*is), std::move(schema_refs)}; } catch (const avro::Exception& e) { ex = e; } @@ -496,12 +584,12 @@ sanitize_avro_schema_definition(unparsed_schema_definition def) { json::Document doc; constexpr auto flags = rapidjson::kParseDefaultFlags | rapidjson::kParseStopWhenDoneFlag; - const auto& raw = def.raw()(); - if (raw.empty()) { + if (def.raw()().empty()) { auto ec = error_code::schema_empty; return error_info{ec, make_error_code(ec).message()}; } - doc.Parse(raw.data(), raw.size()); + json::chunked_input_stream is{def.shared_raw()()}; + doc.ParseStream(is); if (doc.HasParseError()) { return error_info{ error_code::schema_invalid, @@ -513,28 +601,35 @@ sanitize_avro_schema_definition(unparsed_schema_definition def) { sanitize_context ctx{.alloc = doc.GetAllocator()}; auto res = sanitize(doc, ctx); if (res.has_error()) { + // TODO BP: Prevent this linearizaton + iobuf_parser p(std::move(def).raw()()); return error_info{ res.assume_error().code(), - fmt::format("{} {}", res.assume_error().message(), raw)}; + fmt::format( + "{} {}", + res.assume_error().message(), + p.read_string(p.bytes_left()))}; } - json::StringBuffer str_buf; - str_buf.Reserve(raw.size()); - json::Writer w{str_buf}; + json::chunked_buffer buf; + json::Writer w{buf}; if (!doc.Accept(w)) { return error_info{error_code::schema_invalid, "Invalid schema"}; } return canonical_schema_definition{ - std::string_view{str_buf.GetString(), str_buf.GetSize()}, + canonical_schema_definition::raw_string{std::move(buf).as_iobuf()}, schema_type::avro, def.refs()}; } -bool check_compatible( - const avro_schema_definition& reader, const avro_schema_definition& writer) { - return check_compatible(*reader().root(), *writer().root()); +compatibility_result check_compatible( + const avro_schema_definition& reader, + const avro_schema_definition& writer, + verbose is_verbose) { + return check_compatible( + *reader().root(), *writer().root(), "/")(is_verbose); } } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/avro.h b/src/v/pandaproxy/schema_registry/avro.h index 80c38d1a47a9e..9d13a67c490ee 100644 --- a/src/v/pandaproxy/schema_registry/avro.h +++ b/src/v/pandaproxy/schema_registry/avro.h @@ -23,7 +23,9 @@ make_avro_schema_definition(sharded_store& store, canonical_schema schema); result sanitize_avro_schema_definition(unparsed_schema_definition def); -bool check_compatible( - const avro_schema_definition& reader, const avro_schema_definition& writer); +compatibility_result check_compatible( + const avro_schema_definition& reader, + const avro_schema_definition& writer, + verbose is_verbose = verbose::no); } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/compatibility.cc b/src/v/pandaproxy/schema_registry/compatibility.cc new file mode 100644 index 0000000000000..0622c4e331352 --- /dev/null +++ b/src/v/pandaproxy/schema_registry/compatibility.cc @@ -0,0 +1,174 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "pandaproxy/schema_registry/compatibility.h" + +namespace pandaproxy::schema_registry { + +std::ostream& operator<<(std::ostream& os, const avro_incompatibility_type& t) { + switch (t) { + case avro_incompatibility_type::name_mismatch: + return os << "NAME_MISMATCH"; + case avro_incompatibility_type::fixed_size_mismatch: + return os << "FIXED_SIZE_MISMATCH"; + case avro_incompatibility_type::missing_enum_symbols: + return os << "MISSING_ENUM_SYMBOLS"; + case avro_incompatibility_type::reader_field_missing_default_value: + return os << "READER_FIELD_MISSING_DEFAULT_VALUE"; + case avro_incompatibility_type::type_mismatch: + return os << "TYPE_MISMATCH"; + case avro_incompatibility_type::missing_union_branch: + return os << "MISSING_UNION_BRANCH"; + case avro_incompatibility_type::unknown: + return os << "UNKNOWN"; + }; + __builtin_unreachable(); +} + +std::string_view description_for_type(avro_incompatibility_type t) { + switch (t) { + case avro_incompatibility_type::name_mismatch: + return "The name of the schema has changed (path '{path}')"; + case avro_incompatibility_type::fixed_size_mismatch: + return "The size of FIXED type field at path '{path}' in the " + "{{reader}} schema does not match with the {{writer}} schema"; + case avro_incompatibility_type::missing_enum_symbols: + return "The {{reader}} schema is missing enum symbols '{additional}' " + "at path '{path}' in the {{writer}} schema"; + case avro_incompatibility_type::reader_field_missing_default_value: + return "The field '{additional}' at path '{path}' in the {{reader}} " + "schema has " + "no default value and is missing in the {{writer}} schema"; + case avro_incompatibility_type::type_mismatch: + return "The type (path '{path}') of a field in the {{reader}} schema " + "does not match with the {{writer}} schema"; + case avro_incompatibility_type::missing_union_branch: + return "The {{reader}} schema is missing a type inside a union field " + "at path '{path}' in the {{writer}} schema"; + case avro_incompatibility_type::unknown: + return "{{reader}} schema is not compatible with {{writer}} schema: " + "check '{path}'"; + }; + __builtin_unreachable(); +} + +std::ostream& operator<<(std::ostream& os, const avro_incompatibility& v) { + fmt::print( + os, + "{{errorType:'{}', description:'{}', additionalInfo:'{}'}}", + v._type, + v.describe(), + v._additional_info); + return os; +} + +ss::sstring avro_incompatibility::describe() const { + return fmt::format( + fmt::runtime(description_for_type(_type)), + fmt::arg("path", _path.string()), + fmt::arg("additional", _additional_info)); +} + +std::ostream& +operator<<(std::ostream& os, const proto_incompatibility_type& t) { + switch (t) { + case proto_incompatibility_type::message_removed: + return os << "MESSAGE_REMOVED"; + case proto_incompatibility_type::field_kind_changed: + return os << "FIELD_KIND_CHANGED"; + case proto_incompatibility_type::field_scalar_kind_changed: + return os << "FIELD_SCALAR_KIND_CHANGED"; + case proto_incompatibility_type::field_named_type_changed: + return os << "FIELD_NAMED_TYPE_CHANGED"; + case proto_incompatibility_type::required_field_added: + return os << "REQUIRED_FIELD_ADDED"; + case proto_incompatibility_type::required_field_removed: + return os << "REQUIRED_FIELD_REMOVED"; + case proto_incompatibility_type::oneof_field_removed: + return os << "ONEOF_FIELD_REMOVED"; + case proto_incompatibility_type::multiple_fields_moved_to_oneof: + return os << "MULTIPLE_FIELDS_MOVED_TO_ONEOF"; + case proto_incompatibility_type::unknown: + return os << "UNKNOWN"; + } + __builtin_unreachable(); +} + +std::string_view description_for_type(proto_incompatibility_type t) { + switch (t) { + case proto_incompatibility_type::message_removed: + return "The {{reader}} schema is missing a field of type MESSAGE at " + "path '{path}' in the {{writer}} schema"; + case proto_incompatibility_type::field_kind_changed: + return "The type of a field at path '{path}' in the {{reader}} " + "schema does not match the {{writer}} schema"; + case proto_incompatibility_type::field_scalar_kind_changed: + return "The kind of a SCALAR field at path '{path}' in the {{reader}} " + "schema does not match its kind in the {{writer}} schema"; + case proto_incompatibility_type::field_named_type_changed: + return "The type of a MESSAGE field at path '{path}' in the {{reader}} " + "schema does not match its type in the {{writer}} schema "; + case proto_incompatibility_type::required_field_added: + return "A required field at path '{path}' in the {{reader}} schema " + "is missing in the {{writer}} schema"; + case proto_incompatibility_type::required_field_removed: + return "The {{reader}} schema is missing a required field at path: " + "'{path}' in the {{writer}} schema"; + case proto_incompatibility_type::oneof_field_removed: + return "The {{reader}} schema is missing a oneof field at path " + "'{path}' in the {{writer}} schema"; + case proto_incompatibility_type::multiple_fields_moved_to_oneof: + return "Multiple fields in the oneof at path '{path}' in the " + "{{reader}} schema are outside a oneof in the {{writer}} " + "schema "; + case proto_incompatibility_type::unknown: + return "{{reader}} schema is not compatible with {{writer}} schema: " + "check '{path}'"; + } + __builtin_unreachable(); +} + +std::ostream& operator<<(std::ostream& os, const proto_incompatibility& v) { + fmt::print( + os, R"({{errorType:"{}", description:"{}"}})", v._type, v.describe()); + return os; +} + +ss::sstring proto_incompatibility::describe() const { + return fmt::format( + fmt::runtime(description_for_type(_type)), + fmt::arg("path", _path.string())); +} + +compatibility_result +raw_compatibility_result::operator()(verbose is_verbose) && { + compatibility_result result = {.is_compat = !has_error()}; + if (is_verbose) { + result.messages.reserve(_errors.size()); + std::transform( + std::make_move_iterator(_errors.begin()), + std::make_move_iterator(_errors.end()), + std::back_inserter(result.messages), + [](auto e) { + return std::visit( + [](auto&& e) { return fmt::format("{{{}}}", e); }, e); + }); + } + return result; +} + +void raw_compatibility_result::merge(raw_compatibility_result&& other) { + _errors.reserve(_errors.size() + other._errors.size()); + std::move( + other._errors.begin(), other._errors.end(), std::back_inserter(_errors)); +} + +} // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/compatibility.h b/src/v/pandaproxy/schema_registry/compatibility.h new file mode 100644 index 0000000000000..1651e342b86f7 --- /dev/null +++ b/src/v/pandaproxy/schema_registry/compatibility.h @@ -0,0 +1,174 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "pandaproxy/schema_registry/types.h" +#include "vassert.h" + +#include + +#include +#include +#include + +/** + * compatibility.h + * + * Support classes for tracking, accumulating, and emitting formatted error + * messages while checking compatibility of avro & protobuf schemas. + */ + +namespace pandaproxy::schema_registry { + +enum class avro_incompatibility_type { + name_mismatch = 0, + fixed_size_mismatch, + missing_enum_symbols, + reader_field_missing_default_value, + type_mismatch, + missing_union_branch, + unknown, +}; + +/** + * avro_incompatibility - A single incompatibility between Avro schemas. + * + * Encapsulates: + * - the path to the location of the incompatibility in the _writer_ schema + * - the type of incompatibility + * - any additional context for the incompatibility (e.g. a field name) + * + * Primary interface is `describe`, which combines the contained info into + * a format string which can then be interpolated with identifying info for + * the reader and writer schema in the request handler. + */ +class avro_incompatibility { +public: + using Type = avro_incompatibility_type; + avro_incompatibility( + std::filesystem::path path, Type type, std::string_view info) + : _path(std::move(path)) + , _type(type) + , _additional_info(info) {} + + avro_incompatibility(std::filesystem::path path, Type type) + : avro_incompatibility(std::move(path), type, "") {} + + ss::sstring describe() const; + +private: + friend std::ostream& + operator<<(std::ostream& os, const avro_incompatibility& v); + + friend bool + operator==(const avro_incompatibility&, const avro_incompatibility&) + = default; + + // Useful for unit testing + template + friend H AbslHashValue(H h, const avro_incompatibility& e) { + return H::combine( + std::move(h), e._path.string(), e._type, e._additional_info); + } + + std::filesystem::path _path; + Type _type; + ss::sstring _additional_info; +}; + +enum class proto_incompatibility_type { + message_removed = 0, + field_kind_changed, + field_scalar_kind_changed, + field_named_type_changed, + required_field_added, + required_field_removed, + oneof_field_removed, + multiple_fields_moved_to_oneof, + unknown, +}; + +/** + * proto_incompatibility - A single incompatibility between Protobuf schemas. + * + * Encapsulates: + * - the path to the location of the incompatibility in the _writer_ schema + * - the type of incompatibility + * + * Primary interface is `describe`, which combines the contained info into + * a format string which can then be interpolated with identifying info for + * the reader and writer schemas in the request handler. + */ +class proto_incompatibility { +public: + using Type = proto_incompatibility_type; + proto_incompatibility(std::filesystem::path path, Type type) + : _path(std::move(path)) + , _type(type) {} + + ss::sstring describe() const; + Type type() const { return _type; } + +private: + friend std::ostream& + operator<<(std::ostream& os, const proto_incompatibility& v); + + friend bool + operator==(const proto_incompatibility&, const proto_incompatibility&) + = default; + + // Helpful for unit testing + template + friend H AbslHashValue(H h, const proto_incompatibility& e) { + return H::combine(std::move(h), e._path.string(), e._type); + } + + std::filesystem::path _path; + Type _type; +}; + +/** + * raw_compatibility_result - A collection of unformatted proto or avro + * incompatibilities. Its purpose is twofold: + * - Provide an abstracted way to accumulate incompatibilities across + * a recursive call chain. The `merge` function makes this simple + * and seeks to avoid excessive copying. + * - Provide a (type-constrained) generic means to process raw + * incompatibilities into formatted error messages. + */ +class raw_compatibility_result { + using schema_incompatibility + = std::variant; + +public: + raw_compatibility_result() = default; + + template + requires std::constructible_from + && std::convertible_to + auto emplace(Args&&... args) { + return _errors.emplace_back( + std::in_place_type, std::forward(args)...); + } + + compatibility_result operator()(verbose is_verbose) &&; + + // Move the contents of other into the errors vec of this + void merge(raw_compatibility_result&& other); + + bool has_error() const { return !_errors.empty(); } + +private: + std::vector _errors{}; +}; + +} // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/configuration.cc b/src/v/pandaproxy/schema_registry/configuration.cc index 62bf5729b23ec..fdef8eb124ed9 100644 --- a/src/v/pandaproxy/schema_registry/configuration.cc +++ b/src/v/pandaproxy/schema_registry/configuration.cc @@ -32,6 +32,7 @@ configuration::configuration() {}, {}, config::endpoint_tls_config::validate_many) + , mode_mutability(*this, "mode_mutability", "Allow modifying mode", {}, true) , schema_registry_replication_factor( *this, "schema_registry_replication_factor", diff --git a/src/v/pandaproxy/schema_registry/configuration.h b/src/v/pandaproxy/schema_registry/configuration.h index 72c2931b53905..dc13ff2fbb361 100644 --- a/src/v/pandaproxy/schema_registry/configuration.h +++ b/src/v/pandaproxy/schema_registry/configuration.h @@ -28,6 +28,7 @@ struct configuration final : public config::config_store { schema_registry_api; config::one_or_many_property schema_registry_api_tls; + config::property mode_mutability; config::property> schema_registry_replication_factor; config::property api_doc_dir; }; diff --git a/src/v/pandaproxy/schema_registry/error.cc b/src/v/pandaproxy/schema_registry/error.cc index 9fed38cd0985e..edab1d5c32674 100644 --- a/src/v/pandaproxy/schema_registry/error.cc +++ b/src/v/pandaproxy/schema_registry/error.cc @@ -53,7 +53,9 @@ struct error_category final : std::error_category { case error_code::compatibility_not_found: return "Subject does not have subject-level compatibility " "configured"; - case error_code::subject_version_operaton_not_permitted: + case error_code::mode_not_found: + return "Subject does not have subject-level mode configured"; + case error_code::subject_version_operation_not_permitted: return "Overwrite new schema is not permitted."; case error_code::subject_version_has_references: return "One or more references exist to the schema"; @@ -69,6 +71,8 @@ struct error_category final : std::error_category { return "Invalid compatibility level. Valid values are NONE, " "BACKWARD, FORWARD, FULL, BACKWARD_TRANSITIVE, " "FORWARD_TRANSITIVE, and FULL_TRANSITIVE"; + case error_code::mode_invalid: + return "Invalid mode. Valid values are READWRITE, READONLY"; } return "(unrecognized error)"; } @@ -93,6 +97,8 @@ struct error_category final : std::error_category { return reply_error_code::subject_version_not_deleted; // 40407 case error_code::compatibility_not_found: return reply_error_code::compatibility_not_found; // 40408 + case error_code::mode_not_found: + return reply_error_code::mode_not_found; // 40409 case error_code::subject_schema_invalid: return reply_error_code::internal_server_error; // 500 case error_code::write_collision: @@ -103,9 +109,9 @@ struct error_category final : std::error_category { return reply_error_code::schema_empty; // 42201 case error_code::schema_version_invalid: return reply_error_code::schema_version_invalid; // 42202 - case error_code::subject_version_operaton_not_permitted: + case error_code::subject_version_operation_not_permitted: return reply_error_code:: - subject_version_operaton_not_permitted; // 42205 + subject_version_operation_not_permitted; // 42205 case error_code::subject_version_has_references: return reply_error_code::subject_version_has_references; // 42206 case error_code::subject_version_schema_id_already_exists: @@ -117,6 +123,8 @@ struct error_category final : std::error_category { return reply_error_code::zookeeper_error; // 50001 case error_code::compatibility_level_invalid: return reply_error_code::compatibility_level_invalid; // 42203 + case error_code::mode_invalid: + return reply_error_code::mode_invalid; // 42204 } return {}; } diff --git a/src/v/pandaproxy/schema_registry/error.h b/src/v/pandaproxy/schema_registry/error.h index 33afa3cefcda7..46b65ff0de262 100644 --- a/src/v/pandaproxy/schema_registry/error.h +++ b/src/v/pandaproxy/schema_registry/error.h @@ -29,13 +29,15 @@ enum class error_code { subject_version_soft_deleted, subject_version_not_deleted, compatibility_not_found, - subject_version_operaton_not_permitted, + mode_not_found, + subject_version_operation_not_permitted, subject_version_has_references, subject_version_schema_id_already_exists, subject_schema_invalid, write_collision, topic_parse_error, compatibility_level_invalid, + mode_invalid, }; std::error_code make_error_code(error_code); diff --git a/src/v/pandaproxy/schema_registry/errors.h b/src/v/pandaproxy/schema_registry/errors.h index 6fe070ad069cc..a5ca2a2cd741d 100644 --- a/src/v/pandaproxy/schema_registry/errors.h +++ b/src/v/pandaproxy/schema_registry/errors.h @@ -22,6 +22,11 @@ namespace pandaproxy::schema_registry { +/// \brief error_info stores an error_code and custom message. +/// +/// This class is useful for transporting via an outcome::result +/// and automatic conversion to an `exception`. +/// See `outcome_throw_as_system_error_with_payload`. class error_info { public: error_info() = default; @@ -71,6 +76,13 @@ inline error_info not_found(const subject& sub) { fmt::format("Subject '{}' not found.", sub())}; } +inline error_info not_found(const subject& sub, mode) { + return error_info{ + error_code::mode_not_found, + fmt::format( + "Subject '{}' does not have subject-level mode configured.", sub())}; +} + inline error_info not_found(const subject&, schema_version id) { return error_info{ error_code::subject_version_not_found, @@ -158,4 +170,24 @@ inline error_info compatibility_not_found(const subject& sub) { sub())}; } +inline error_info mode_not_found(const subject& sub) { + return error_info{ + error_code::mode_not_found, + fmt::format( + "Subject '{}' does not have subject-level mode configured", sub())}; +} + +inline error_info mode_not_readwrite(const subject& sub) { + return error_info{ + error_code::subject_version_operation_not_permitted, + fmt::format("Subject {} is not in read-write mode", sub())}; +} + +inline error_info mode_is_readonly(const std::optional& sub) { + return error_info{ + error_code::subject_version_operation_not_permitted, + fmt::format( + "Subject {} is in read-only mode", sub.value_or(subject{"null"})())}; +} + } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/handlers.cc b/src/v/pandaproxy/schema_registry/handlers.cc index 709f7468bcf0f..a3c1bc44242af 100644 --- a/src/v/pandaproxy/schema_registry/handlers.cc +++ b/src/v/pandaproxy/schema_registry/handlers.cc @@ -9,9 +9,6 @@ #include "handlers.h" -#include "kafka/protocol/exceptions.h" -#include "kafka/protocol/kafka_batch_adapter.h" -#include "model/fundamental.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/json/types.h" #include "pandaproxy/parsing/httpd.h" @@ -23,11 +20,13 @@ #include "pandaproxy/schema_registry/requests/get_schemas_ids_id.h" #include "pandaproxy/schema_registry/requests/get_schemas_ids_id_versions.h" #include "pandaproxy/schema_registry/requests/get_subject_versions_version.h" +#include "pandaproxy/schema_registry/requests/mode.h" #include "pandaproxy/schema_registry/requests/post_subject_versions.h" #include "pandaproxy/schema_registry/storage.h" #include "pandaproxy/schema_registry/types.h" #include "pandaproxy/server.h" #include "storage/record_batch_builder.h" +#include "utils/json.h" #include #include @@ -73,8 +72,9 @@ result parse_numerical_schema_version(const ss::sstring& ver) { result> parse_schema_version(const ss::sstring& ver) { - return ver == "latest" ? std::optional{} - : parse_numerical_schema_version(ver).value(); + return (ver == "latest" || ver == "-1") + ? std::optional{} + : parse_numerical_schema_version(ver).value(); } ss::future @@ -87,8 +87,8 @@ get_config(server::request_t rq, server::reply_t rp) { auto res = co_await rq.service().schema_store().get_compatibility(); - auto json_rslt = ppj::rjson_serialize(get_config_req_rep{.compat = res}); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", ppj::rjson_serialize(get_config_req_rep{.compat = res})); co_return rp; } @@ -96,14 +96,12 @@ ss::future put_config(server::request_t rq, server::reply_t rp) { parse_content_type_header(rq); parse_accept_header(rq, rp); - auto config = ppj::rjson_parse( - rq.req->content.data(), put_config_handler<>{}); - rq.req.reset(); + auto config = co_await ppj::rjson_parse( + std::move(rq.req), put_config_handler<>{}); co_await rq.service().writer().write_config(std::nullopt, config.compat); - auto json_rslt = ppj::rjson_serialize(config); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(config)); co_return rp; } @@ -122,8 +120,8 @@ get_config_subject(server::request_t rq, server::reply_t rp) { auto res = co_await rq.service().schema_store().get_compatibility( sub, fallback); - auto json_rslt = ppj::rjson_serialize(get_config_req_rep{.compat = res}); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", ppj::rjson_serialize(get_config_req_rep{.compat = res})); co_return rp; } @@ -163,22 +161,19 @@ put_config_subject(server::request_t rq, server::reply_t rp) { parse_content_type_header(rq); parse_accept_header(rq, rp); auto sub = parse::request_param(*rq.req, "subject"); - auto config = ppj::rjson_parse( - rq.req->content.data(), put_config_handler<>{}); - rq.req.reset(); + auto config = co_await ppj::rjson_parse( + std::move(rq.req), put_config_handler<>{}); // Ensure we see latest writes co_await rq.service().writer().read_sync(); co_await rq.service().writer().write_config(sub, config.compat); - auto json_rslt = ppj::rjson_serialize(config); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(config)); co_return rp; } ss::future delete_config_subject(server::request_t rq, server::reply_t rp) { - parse_content_type_header(rq); parse_accept_header(rq, rp); auto sub = parse::request_param(*rq.req, "subject"); @@ -186,6 +181,7 @@ delete_config_subject(server::request_t rq, server::reply_t rp) { // ensure we see latest writes co_await rq.service().writer().read_sync(); + co_await rq.service().writer().check_mutable(sub); compatibility_level lvl{}; try { @@ -201,8 +197,8 @@ delete_config_subject(server::request_t rq, server::reply_t rp) { co_await rq.service().writer().delete_config(sub); - auto json_rslt = ppj::rjson_serialize(get_config_req_rep{.compat = lvl}); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", ppj::rjson_serialize(get_config_req_rep{.compat = lvl})); co_return rp; } @@ -210,9 +206,88 @@ ss::future get_mode(server::request_t rq, server::reply_t rp) { parse_accept_header(rq, rp); rq.req.reset(); - rp.rep->write_body("json", ss::sstring{R"({ - "mode": "READWRITE" -})"}); + // Ensure we see latest writes + co_await rq.service().writer().read_sync(); + + auto res = co_await rq.service().schema_store().get_mode(); + + rp.rep->write_body("json", ppj::rjson_serialize(mode_req_rep{.mode = res})); + co_return rp; +} + +ss::future put_mode(server::request_t rq, server::reply_t rp) { + parse_content_type_header(rq); + parse_accept_header(rq, rp); + auto frc = parse::query_param>(*rq.req, "force") + .value_or(force::no); + auto res = co_await ppj::rjson_parse(std::move(rq.req), mode_handler<>{}); + + co_await rq.service().writer().write_mode(std::nullopt, res.mode, frc); + + rp.rep->write_body("json", ppj::rjson_serialize(res)); + co_return rp; +} + +ss::future +get_mode_subject(server::request_t rq, server::reply_t rp) { + parse_accept_header(rq, rp); + auto sub = parse::request_param(*rq.req, "subject"); + auto fallback = parse::query_param>( + *rq.req, "defaultToGlobal") + .value_or(default_to_global::no); + rq.req.reset(); + + // Ensure we see latest writes + co_await rq.service().writer().read_sync(); + + auto res = co_await rq.service().schema_store().get_mode(sub, fallback); + + rp.rep->write_body("json", ppj::rjson_serialize(mode_req_rep{.mode = res})); + co_return rp; +} + +ss::future +put_mode_subject(server::request_t rq, server::reply_t rp) { + parse_content_type_header(rq); + parse_accept_header(rq, rp); + auto frc = parse::query_param>(*rq.req, "force") + .value_or(force::no); + auto sub = parse::request_param(*rq.req, "subject"); + auto res = co_await ppj::rjson_parse(std::move(rq.req), mode_handler<>{}); + + // Ensure we see latest writes + co_await rq.service().writer().read_sync(); + co_await rq.service().writer().write_mode(sub, res.mode, frc); + + rp.rep->write_body("json", ppj::rjson_serialize(res)); + co_return rp; +} + +ss::future +delete_mode_subject(server::request_t rq, server::reply_t rp) { + parse_accept_header(rq, rp); + auto sub = parse::request_param(*rq.req, "subject"); + + rq.req.reset(); + + // ensure we see latest writes + co_await rq.service().writer().read_sync(); + + mode m{}; + try { + m = co_await rq.service().schema_store().get_mode( + sub, default_to_global::no); + } catch (const exception& e) { + if (e.code() == error_code::mode_not_found) { + // Upstream compatibility: return 40401 instead of 40409 + throw as_exception(not_found(sub)); + } + throw; + } + + co_await rq.service().writer().delete_mode(sub); + + rp.rep->write_body("json", ppj::rjson_serialize(mode_req_rep{.mode = m})); co_return rp; } @@ -223,8 +298,7 @@ get_schemas_types(server::request_t rq, server::reply_t rp) { static const std::vector schemas_types{ "PROTOBUF", "AVRO"}; - auto json_rslt = ppj::rjson_serialize(schemas_types); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(schemas_types)); return ss::make_ready_future(std::move(rp)); } @@ -238,9 +312,10 @@ get_schemas_ids_id(server::request_t rq, server::reply_t rp) { return rq.service().schema_store().get_schema_definition(id); }); - auto json_rslt = ppj::rjson_serialize( - get_schemas_ids_id_response{.definition{std::move(def)}}); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize( + get_schemas_ids_id_response{.definition{std::move(def)}})); co_return rp; } @@ -259,9 +334,10 @@ get_schemas_ids_id_versions(server::request_t rq, server::reply_t rp) { auto svs = co_await rq.service().schema_store().get_schema_subject_versions( id); - auto json_rslt = ppj::rjson_serialize( - get_schemas_ids_id_versions_response{.subject_versions{std::move(svs)}}); - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize(get_schemas_ids_id_versions_response{ + .subject_versions{std::move(svs)}})); co_return rp; } @@ -280,10 +356,11 @@ ss::future::reply_t> get_schemas_ids_id_subjects( // Force early 40403 if the schema id isn't found co_await rq.service().schema_store().get_schema_definition(id); - auto subjects = co_await rq.service().schema_store().get_schema_subjects( - id, incl_del); - auto json_rslt{json::rjson_serialize(subjects)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize( + co_await rq.service().schema_store().get_schema_subjects( + id, incl_del))); co_return rp; } @@ -293,14 +370,17 @@ get_subjects(server::request_t rq, server::reply_t rp) { auto inc_del{ parse::query_param>(*rq.req, "deleted") .value_or(include_deleted::no)}; + auto subject_prefix{ + parse::query_param>(*rq.req, "subjectPrefix")}; rq.req.reset(); // List-type request: must ensure we see latest writes co_await rq.service().writer().read_sync(); - auto subjects = co_await rq.service().schema_store().get_subjects(inc_del); - auto json_rslt{json::rjson_serialize(subjects)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + json::rjson_serialize(co_await rq.service().schema_store().get_subjects( + inc_del, subject_prefix))); co_return rp; } @@ -319,8 +399,7 @@ get_subject_versions(server::request_t rq, server::reply_t rp) { auto versions = co_await rq.service().schema_store().get_versions( sub, inc_del); - auto json_rslt{json::rjson_serialize(versions)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(versions)); co_return rp; } @@ -329,19 +408,29 @@ post_subject(server::request_t rq, server::reply_t rp) { parse_content_type_header(rq); parse_accept_header(rq, rp); auto sub = parse::request_param(*rq.req, "subject"); - vlog(plog.debug, "post_subject subject='{}'", sub); + auto inc_del{ + parse::query_param>(*rq.req, "deleted") + .value_or(include_deleted::no)}; + auto norm{parse::query_param>(*rq.req, "normalize") + .value_or(normalize::no)}; + vlog( + plog.debug, + "post_subject subject='{}', normalize='{}', deleted='{}'", + sub, + norm, + inc_del); // We must sync co_await rq.service().writer().read_sync(); // Force 40401 if no subject - co_await rq.service().schema_store().get_versions(sub, include_deleted::no); + co_await rq.service().schema_store().get_versions(sub, inc_del); canonical_schema schema; try { - auto unparsed = ppj::rjson_parse( - rq.req->content.data(), post_subject_versions_request_handler<>{sub}); + auto unparsed = co_await ppj::rjson_parse( + std::move(rq.req), post_subject_versions_request_handler<>{sub}); schema = co_await rq.service().schema_store().make_canonical_schema( - std::move(unparsed.def)); + std::move(unparsed.def), norm); } catch (const exception& e) { if (e.code() == error_code::schema_empty) { throw as_exception(invalid_subject_schema(sub)); @@ -351,15 +440,15 @@ post_subject(server::request_t rq, server::reply_t rp) { throw as_exception(invalid_subject_schema(sub)); } - rq.req.reset(); - - auto sub_schema = co_await rq.service().schema_store().has_schema(schema); + auto sub_schema = co_await rq.service().schema_store().has_schema( + std::move(schema), inc_del); - auto json_rslt{json::rjson_serialize(post_subject_versions_version_response{ - .schema{std::move(sub_schema.schema)}, - .id{sub_schema.id}, - .version{sub_schema.version}})}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize(post_subject_versions_version_response{ + .schema{std::move(sub_schema.schema)}, + .id{sub_schema.id}, + .version{sub_schema.version}})); co_return rp; } @@ -368,22 +457,28 @@ post_subject_versions(server::request_t rq, server::reply_t rp) { parse_content_type_header(rq); parse_accept_header(rq, rp); auto sub = parse::request_param(*rq.req, "subject"); - vlog(plog.debug, "post_subject_versions subject='{}'", sub); + auto norm{parse::query_param>(*rq.req, "normalize") + .value_or(normalize::no)}; + vlog( + plog.debug, + "post_subject_versions subject='{}', normalize='{}'", + sub, + norm); co_await rq.service().writer().read_sync(); - auto unparsed = ppj::rjson_parse( - rq.req->content.data(), post_subject_versions_request_handler<>{sub}); - rq.req.reset(); + auto unparsed = co_await ppj::rjson_parse( + std::move(rq.req), post_subject_versions_request_handler<>{sub}); subject_schema schema{ co_await rq.service().schema_store().make_canonical_schema( - std::move(unparsed.def)), + std::move(unparsed.def), norm), unparsed.version.value_or(invalid_schema_version), unparsed.id.value_or(invalid_schema_id), is_deleted::no}; - auto ids = co_await rq.service().schema_store().get_schema_version(schema); + auto ids = co_await rq.service().schema_store().get_schema_version( + schema.share()); schema_id schema_id{ids.id.value_or(invalid_schema_id)}; if (!ids.version.has_value()) { @@ -395,9 +490,9 @@ post_subject_versions(server::request_t rq, server::reply_t rp) { std::move(schema)); } - auto json_rslt{ - json::rjson_serialize(post_subject_versions_response{.id{schema_id}})}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize(post_subject_versions_response{.id{schema_id}})); co_return rp; } @@ -420,11 +515,12 @@ ss::future::reply_t> get_subject_versions_version( sub, version, inc_del); }); - auto json_rslt{json::rjson_serialize(post_subject_versions_version_response{ - .schema = std::move(get_res.schema), - .id = get_res.id, - .version = get_res.version})}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body( + "json", + ppj::rjson_serialize(post_subject_versions_version_response{ + .schema = std::move(get_res.schema), + .id = get_res.id, + .version = get_res.version})); co_return rp; } @@ -445,7 +541,8 @@ ss::future::reply_t> get_subject_versions_version_schema( auto get_res = co_await rq.service().schema_store().get_subject_schema( sub, version, inc_del); - rp.rep->write_body("json", get_res.schema.def().raw()()); + rp.rep->write_body( + "json", ppj::as_body_writer(std::move(get_res.schema).def().raw()())); co_return rp; } @@ -464,8 +561,7 @@ get_subject_versions_version_referenced_by( auto references = co_await rq.service().schema_store().referenced_by( sub, version); - auto json_rslt{json::rjson_serialize(references)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(std::move(references))); co_return rp; } @@ -488,8 +584,7 @@ delete_subject(server::request_t rq, server::reply_t rp) { sub, std::nullopt) : co_await rq.service().writer().delete_subject_impermanent(sub); - auto json_rslt{json::rjson_serialize(versions)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(versions)); co_return rp; } @@ -536,19 +631,21 @@ delete_subject_version(server::request_t rq, server::reply_t rp) { co_await rq.service().writer().delete_subject_version(sub, version); } - auto json_rslt{json::rjson_serialize(version)}; - rp.rep->write_body("json", json_rslt); + rp.rep->write_body("json", ppj::rjson_serialize(version)); co_return rp; } ss::future compatibility_subject_version(server::request_t rq, server::reply_t rp) { + parse_content_type_header(rq); parse_accept_header(rq, rp); auto ver = parse::request_param(*rq.req, "version"); auto sub = parse::request_param(*rq.req, "subject"); - auto unparsed = ppj::rjson_parse( - rq.req->content.data(), post_subject_versions_request_handler<>{sub}); - rq.req.reset(); + auto is_verbose{ + parse::query_param>(*rq.req, "verbose") + .value_or(verbose::no)}; + auto unparsed = co_await ppj::rjson_parse( + std::move(rq.req), post_subject_versions_request_handler<>{sub}); // Must read, in case we have the subject in cache with an outdated config co_await rq.service().writer().read_sync(); @@ -570,15 +667,43 @@ compatibility_subject_version(server::request_t rq, server::reply_t rp) { version = parse_numerical_schema_version(ver).value(); } - auto schema = co_await rq.service().schema_store().make_canonical_schema( - std::move(unparsed.def)); - auto get_res = co_await get_or_load(rq, [&rq, &schema, version]() { - return rq.service().schema_store().is_compatible(version, schema); - }); + canonical_schema schema; + try { + schema = co_await rq.service().schema_store().make_canonical_schema( + std::move(unparsed.def)); + } catch (exception& e) { + constexpr auto reportable = [](std::error_code ec) { + constexpr std::array errors{ + error_code::schema_invalid, error_code::schema_empty}; + return absl::c_any_of( + errors, [ec](error_code e) { return ec == e; }); + }; + if (is_verbose && reportable(e.code())) { + rp.rep->write_body( + "json", + json::rjson_serialize(post_compatibility_res{ + .is_compat = false, + .messages = {e.message()}, + .is_verbose = is_verbose, + })); + co_return rp; + } + throw; + } - auto json_rslt{ - json::rjson_serialize(post_compatibility_res{.is_compat = get_res})}; - rp.rep->write_body("json", json_rslt); + auto get_res = co_await get_or_load( + rq, [&rq, schema{std::move(schema)}, version, is_verbose]() { + return rq.service().schema_store().is_compatible( + version, schema.share(), is_verbose); + }); + + rp.rep->write_body( + "json", + json::rjson_serialize(post_compatibility_res{ + .is_compat = get_res.is_compat, + .messages = std::move(get_res.messages), + .is_verbose = is_verbose, + })); co_return rp; } diff --git a/src/v/pandaproxy/schema_registry/handlers.h b/src/v/pandaproxy/schema_registry/handlers.h index 85b323d6cd788..8df1be9fabf3d 100644 --- a/src/v/pandaproxy/schema_registry/handlers.h +++ b/src/v/pandaproxy/schema_registry/handlers.h @@ -37,6 +37,18 @@ ss::future::reply_t> delete_config_subject( ss::future::reply_t> get_mode(ctx_server::request_t rq, ctx_server::reply_t rp); +ss::future::reply_t> +put_mode(ctx_server::request_t rq, ctx_server::reply_t rp); + +ss::future::reply_t> get_mode_subject( + ctx_server::request_t rq, ctx_server::reply_t rp); + +ss::future::reply_t> put_mode_subject( + ctx_server::request_t rq, ctx_server::reply_t rp); + +ss::future::reply_t> delete_mode_subject( + ctx_server::request_t rq, ctx_server::reply_t rp); + ss::future::reply_t> get_schemas_types( ctx_server::request_t rq, ctx_server::reply_t rp); diff --git a/src/v/pandaproxy/schema_registry/protobuf.cc b/src/v/pandaproxy/schema_registry/protobuf.cc index 123833c8857d2..fd722c33a891b 100644 --- a/src/v/pandaproxy/schema_registry/protobuf.cc +++ b/src/v/pandaproxy/schema_registry/protobuf.cc @@ -11,8 +11,10 @@ #include "pandaproxy/schema_registry/protobuf.h" +#include "bytes/streambuf.h" #include "kafka/protocol/errors.h" #include "pandaproxy/logger.h" +#include "pandaproxy/schema_registry/compatibility.h" #include "pandaproxy/schema_registry/errors.h" #include "pandaproxy/schema_registry/sharded_store.h" #include "ssx/sformat.h" @@ -201,8 +203,8 @@ class dp_error_collector final : public pb::DescriptorPool::ErrorCollector { class schema_def_input_stream : public pb::io::ZeroCopyInputStream { public: explicit schema_def_input_stream(const canonical_schema_definition& def) - : _str(def.raw()) - , _impl{_str().data(), static_cast(_str().size())} {} + : _is{def.shared_raw()} + , _impl{&_is.istream()} {} bool Next(const void** data, int* size) override { return _impl.Next(data, size); @@ -212,8 +214,8 @@ class schema_def_input_stream : public pb::io::ZeroCopyInputStream { int64_t ByteCount() const override { return _impl.ByteCount(); } private: - canonical_schema_definition::raw_string _str; - pb::io::ArrayInputStream _impl; + iobuf_istream _is; + pb::io::IstreamInputStream _impl; }; class parser { @@ -230,14 +232,14 @@ class parser { // Attempt parse a .proto file if (!_parser.Parse(&t, &_fdp)) { - // base64 decode the schema - std::string_view b64_def{ - schema.def().raw()().data(), schema.def().raw()().size()}; - auto bytes_def = base64_to_bytes(b64_def); - - // Attempt parse as an encoded FileDescriptorProto.pb - if (!_fdp.ParseFromArray( - bytes_def.data(), static_cast(bytes_def.size()))) { + try { + // base64 decode the schema + iobuf_istream is{base64_to_iobuf(schema.def().raw()())}; + // Attempt parse as an encoded FileDescriptorProto.pb + if (!_fdp.ParseFromIstream(&is.istream())) { + throw as_exception(error_collector.error()); + } + } catch (const base64_decoder_exception&) { throw as_exception(error_collector.error()); } } @@ -297,7 +299,7 @@ ss::future build_file_with_refs( ss::future import_schema( pb::DescriptorPool& dp, sharded_store& store, canonical_schema schema) { try { - co_return co_await build_file_with_refs(dp, store, schema); + co_return co_await build_file_with_refs(dp, store, schema.share()); } catch (const exception& e) { vlog(plog.warn, "Failed to decode schema: {}", e.what()); throw as_exception(invalid_schema(schema)); @@ -326,6 +328,7 @@ struct protobuf_schema_definition::impl { * messages */ ss::sstring debug_string() const { + // TODO BP: Prevent this linearization auto s = fd->DebugString(); // reordering not required if no package or no dependencies @@ -353,6 +356,7 @@ struct protobuf_schema_definition::impl { auto imports = trim(sv.substr(imports_pos, imports_len)); auto footer = trim(sv.substr(package_pos + package.length())); + // TODO BP: Prevent this linearization return ssx::sformat( "{}\n{}\n\n{}\n\n{}\n", header, package, imports, footer); } @@ -409,16 +413,17 @@ validate_protobuf_schema(sharded_store& store, canonical_schema schema) { ss::future make_canonical_protobuf_schema(sharded_store& store, unparsed_schema schema) { - // NOLINTBEGIN(bugprone-use-after-move) + auto [sub, unparsed] = std::move(schema).destructure(); + auto [def, type, refs] = std::move(unparsed).destructure(); canonical_schema temp{ - std::move(schema).sub(), - {canonical_schema_definition::raw_string{schema.def().raw()()}, - schema.def().type(), - schema.def().refs()}}; - - auto validated = co_await validate_protobuf_schema(store, temp); - co_return canonical_schema{std::move(temp).sub(), std::move(validated)}; - // NOLINTEND(bugprone-use-after-move) + sub, + {canonical_schema_definition::raw_string{std::move(def)()}, + type, + std::move(refs)}}; + + co_return canonical_schema{ + std::move(sub), + co_await validate_protobuf_schema(store, std::move(temp))}; } namespace { @@ -466,42 +471,148 @@ encoding get_encoding(pb::FieldDescriptor::Type type) { __builtin_unreachable(); } +using proto_compatibility_result = raw_compatibility_result; + struct compatibility_checker { - bool check_compatible() { return check_compatible(_writer.fd); } + proto_compatibility_result check_compatible(std::filesystem::path p) { + return check_compatible(_writer.fd, std::move(p)); + } - bool check_compatible(const pb::FileDescriptor* writer) { + proto_compatibility_result check_compatible( + const pb::FileDescriptor* writer, std::filesystem::path p) { // There must be a compatible reader message for every writer message + proto_compatibility_result compat_result; for (int i = 0; i < writer->message_type_count(); ++i) { auto w = writer->message_type(i); auto r = _reader._dp.FindMessageTypeByName(w->full_name()); - if (!r || !check_compatible(r, w)) { - return false; + + if (!r) { + compat_result.emplace( + p / w->name(), proto_incompatibility::Type::message_removed); + } else { + compat_result.merge(check_compatible(r, w, p / w->name())); } } - return true; + return compat_result; } - bool check_compatible( - const pb::Descriptor* reader, const pb::Descriptor* writer) { + proto_compatibility_result check_compatible( + const pb::Descriptor* reader, + const pb::Descriptor* writer, + std::filesystem::path p) { + proto_compatibility_result compat_result; if (!_seen_descriptors.insert(reader).second) { - return true; + return compat_result; } - for (int i = 0; i < writer->field_count(); ++i) { - if (reader->IsReservedNumber(i) || writer->IsReservedNumber(i)) { - continue; + + for (int i = 0; i < writer->nested_type_count(); ++i) { + auto w = writer->nested_type(i); + auto r = reader->FindNestedTypeByName(w->name()); + if (!r) { + compat_result.emplace( + p / w->name(), proto_incompatibility::Type::message_removed); + } else { + compat_result.merge(check_compatible(r, w, p / w->name())); } - int number = writer->field(i)->number(); + } + + for (int i = 0; i < writer->real_oneof_decl_count(); ++i) { + auto w = writer->oneof_decl(i); + compat_result.merge(check_compatible(reader, w, p / w->name())); + } + + for (int i = 0; i < reader->real_oneof_decl_count(); ++i) { + auto r = reader->oneof_decl(i); + compat_result.merge(check_compatible(r, writer, p / r->name())); + } + + // check writer fields + for (int i = 0; i < writer->field_count(); ++i) { + auto w = writer->field(i); + int number = w->number(); auto r = reader->FindFieldByNumber(number); - // A reader may ignore a writer field - if (r && !check_compatible(r, writer->field(i))) { - return false; + // A reader may ignore a writer field iff it is not `required` + if (!r && w->is_required()) { + compat_result.emplace( + p / std::to_string(w->number()), + proto_incompatibility::Type::required_field_removed); + } else if (r) { + auto oneof = r->containing_oneof(); + compat_result.merge(check_compatible( + r, + w, + p / (oneof ? oneof->name() : "") + / std::to_string(w->number()))); } } - return true; + + // check reader required fields + for (int i = 0; i < reader->field_count(); ++i) { + auto r = reader->field(i); + int number = r->number(); + auto w = writer->FindFieldByNumber(number); + // A writer may ignore a reader field iff it is not `required` + if ((!w || !w->is_required()) && r->is_required()) { + compat_result.emplace( + p / std::to_string(number), + proto_incompatibility::Type::required_field_added); + } + } + return compat_result; } - bool check_compatible( - const pb::FieldDescriptor* reader, const pb::FieldDescriptor* writer) { + proto_compatibility_result check_compatible( + const pb::Descriptor* reader, + const pb::OneofDescriptor* writer, + std::filesystem::path p) { + proto_compatibility_result compat_result; + + // If the oneof in question doesn't appear in the reader descriptor, + // then we don't need to account for any difference in fields. + if (!reader->FindOneofByName(writer->name())) { + return compat_result; + } + + for (int i = 0; i < writer->field_count(); ++i) { + auto w = writer->field(i); + auto r = reader->FindFieldByNumber(w->number()); + + if (!r || !r->real_containing_oneof()) { + compat_result.emplace( + p / std::to_string(w->number()), + proto_incompatibility::Type::oneof_field_removed); + } + } + return compat_result; + } + + proto_compatibility_result check_compatible( + const pb::OneofDescriptor* reader, + const pb::Descriptor* writer, + std::filesystem::path p) { + proto_compatibility_result compat_result; + + size_t count = 0; + for (int i = 0; i < reader->field_count(); ++i) { + auto r = reader->field(i); + auto w = writer->FindFieldByNumber(r->number()); + if (w && !w->real_containing_oneof()) { + ++count; + } + } + if (count > 1) { + compat_result.emplace( + std::move(p), + proto_incompatibility::Type::multiple_fields_moved_to_oneof); + } + return compat_result; + } + + proto_compatibility_result check_compatible( + const pb::FieldDescriptor* reader, + const pb::FieldDescriptor* writer, + std::filesystem::path p) { + proto_compatibility_result compat_result; switch (writer->type()) { case pb::FieldDescriptor::Type::TYPE_MESSAGE: case pb::FieldDescriptor::Type::TYPE_GROUP: { @@ -509,9 +620,23 @@ struct compatibility_checker { == pb::FieldDescriptor::Type::TYPE_MESSAGE || reader->type() == pb::FieldDescriptor::Type::TYPE_GROUP; - return type_is_compat - && check_compatible( - reader->message_type(), writer->message_type()); + if (!type_is_compat) { + compat_result.emplace( + std::move(p), + proto_incompatibility::Type::field_kind_changed); + } else if ( + reader->message_type()->name() + != writer->message_type()->name()) { + compat_result.emplace( + std::move(p), + proto_incompatibility::Type::field_named_type_changed); + } else { + compat_result.merge(check_compatible( + reader->message_type(), + writer->message_type(), + std::move(p))); + } + break; } case pb::FieldDescriptor::Type::TYPE_FLOAT: case pb::FieldDescriptor::Type::TYPE_DOUBLE: @@ -529,14 +654,27 @@ struct compatibility_checker { case pb::FieldDescriptor::Type::TYPE_SFIXED32: case pb::FieldDescriptor::Type::TYPE_FIXED64: case pb::FieldDescriptor::Type::TYPE_SFIXED64: - return check_compatible( - get_encoding(reader->type()), get_encoding(writer->type())); + compat_result.merge(check_compatible( + get_encoding(reader->type()), + get_encoding(writer->type()), + std::move(p))); } - __builtin_unreachable(); + return compat_result; } - bool check_compatible(encoding reader, encoding writer) { - return reader == writer && reader != encoding::struct_; + proto_compatibility_result check_compatible( + encoding reader, encoding writer, std::filesystem::path p) { + proto_compatibility_result compat_result; + // we know writer has scalar encoding because of the switch stmt above + if (reader == encoding::struct_) { + compat_result.emplace( + std::move(p), proto_incompatibility::Type::field_kind_changed); + } else if (reader != writer) { + compat_result.emplace( + std::move(p), + proto_incompatibility::Type::field_scalar_kind_changed); + } + return compat_result; } const protobuf_schema_definition::impl& _reader; @@ -546,11 +684,12 @@ struct compatibility_checker { } // namespace -bool check_compatible( +compatibility_result check_compatible( const protobuf_schema_definition& reader, - const protobuf_schema_definition& writer) { + const protobuf_schema_definition& writer, + verbose is_verbose) { compatibility_checker checker{reader(), writer()}; - return checker.check_compatible(); + return checker.check_compatible("#/")(is_verbose); } } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/protobuf.h b/src/v/pandaproxy/schema_registry/protobuf.h index edbc6aaf65c33..58c325064a47a 100644 --- a/src/v/pandaproxy/schema_registry/protobuf.h +++ b/src/v/pandaproxy/schema_registry/protobuf.h @@ -25,8 +25,9 @@ validate_protobuf_schema(sharded_store& store, canonical_schema schema); ss::future make_canonical_protobuf_schema(sharded_store& store, unparsed_schema schema); -bool check_compatible( +compatibility_result check_compatible( const protobuf_schema_definition& reader, - const protobuf_schema_definition& writer); + const protobuf_schema_definition& writer, + verbose is_verbose = verbose::no); } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/requests/compatibility.h b/src/v/pandaproxy/schema_registry/requests/compatibility.h index db9759f515225..8e81ded6b662f 100644 --- a/src/v/pandaproxy/schema_registry/requests/compatibility.h +++ b/src/v/pandaproxy/schema_registry/requests/compatibility.h @@ -18,14 +18,24 @@ namespace pandaproxy::schema_registry { struct post_compatibility_res { bool is_compat{false}; + std::vector messages; + + // `is_verbose` is not rendered into the response but `messages` are + // conditionally rendered based on `is_verbose` + verbose is_verbose; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::post_compatibility_res& res) { w.StartObject(); w.Key("is_compatible"); ::json::rjson_serialize(w, res.is_compat); + if (res.is_verbose) { + w.Key("messages"); + ::json::rjson_serialize(w, res.messages); + } w.EndObject(); } diff --git a/src/v/pandaproxy/schema_registry/requests/config.h b/src/v/pandaproxy/schema_registry/requests/config.h index d219ad6cfa02c..688e66f2e67fb 100644 --- a/src/v/pandaproxy/schema_registry/requests/config.h +++ b/src/v/pandaproxy/schema_registry/requests/config.h @@ -82,18 +82,18 @@ class put_config_handler : public json::base_handler { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const schema_registry::get_config_req_rep& res) { +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::get_config_req_rep& res) { w.StartObject(); w.Key(get_config_req_rep::field_name.data()); ::json::rjson_serialize(w, to_string_view(res.compat)); w.EndObject(); } -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const schema_registry::put_config_req_rep& res) { +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::put_config_req_rep& res) { w.StartObject(); w.Key(put_config_req_rep::field_name.data()); ::json::rjson_serialize(w, to_string_view(res.compat)); diff --git a/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id.h b/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id.h index 6eec85d6a497f..36ee3f8b6d45a 100644 --- a/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id.h +++ b/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id.h @@ -11,6 +11,7 @@ #pragma once +#include "json/iobuf_writer.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/schema_registry/types.h" @@ -20,9 +21,9 @@ struct get_schemas_ids_id_response { canonical_schema_definition definition; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const get_schemas_ids_id_response& res) { +template +void rjson_serialize( + ::json::iobuf_writer& w, const get_schemas_ids_id_response& res) { w.StartObject(); if (res.definition.type() != schema_type::avro) { w.Key("schemaType"); diff --git a/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id_versions.h b/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id_versions.h index a58ac6b0be1fd..3d6b9ce903187 100644 --- a/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id_versions.h +++ b/src/v/pandaproxy/schema_registry/requests/get_schemas_ids_id_versions.h @@ -13,16 +13,17 @@ #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/schema_registry/types.h" +#include "utils/fragmented_vector.h" namespace pandaproxy::schema_registry { struct get_schemas_ids_id_versions_response { - std::vector subject_versions; + chunked_vector subject_versions; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const get_schemas_ids_id_versions_response& res) { +template +void rjson_serialize( + ::json::Writer& w, const get_schemas_ids_id_versions_response& res) { w.StartArray(); for (const auto& sv : res.subject_versions) { w.StartObject(); diff --git a/src/v/pandaproxy/schema_registry/requests/get_subject_versions_version.h b/src/v/pandaproxy/schema_registry/requests/get_subject_versions_version.h index 23da030df1766..852ecc5ba91d0 100644 --- a/src/v/pandaproxy/schema_registry/requests/get_subject_versions_version.h +++ b/src/v/pandaproxy/schema_registry/requests/get_subject_versions_version.h @@ -11,6 +11,7 @@ #pragma once +#include "json/iobuf_writer.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/schema_registry/types.h" @@ -22,8 +23,9 @@ struct post_subject_versions_version_response { schema_version version; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, +template +void rjson_serialize( + ::json::iobuf_writer& w, const post_subject_versions_version_response& res) { w.StartObject(); w.Key("subject"); diff --git a/src/v/pandaproxy/schema_registry/requests/mode.h b/src/v/pandaproxy/schema_registry/requests/mode.h new file mode 100644 index 0000000000000..b94eae35fdb5c --- /dev/null +++ b/src/v/pandaproxy/schema_registry/requests/mode.h @@ -0,0 +1,89 @@ +/* + * Copyright 2021 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "json/types.h" +#include "pandaproxy/json/rjson_parse.h" +#include "pandaproxy/json/rjson_util.h" +#include "pandaproxy/schema_registry/errors.h" +#include "pandaproxy/schema_registry/types.h" + +namespace pandaproxy::schema_registry { + +struct mode_req_rep { + static constexpr std::string_view field_name = "mode"; + mode mode{mode::read_write}; +}; + +template> +class mode_handler : public json::base_handler { + enum class state { + empty = 0, + object, + mode, + }; + state _state = state::empty; + +public: + using Ch = typename json::base_handler::Ch; + using rjson_parse_result = mode_req_rep; + rjson_parse_result result; + + explicit mode_handler() + : json::base_handler{json::serialization_format::none} + , result() {} + + bool Key(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + if (_state == state::object && sv == mode_req_rep::field_name) { + _state = state::mode; + return true; + } + return false; + } + + bool String(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + if (_state == state::mode) { + auto s = from_string_view(sv); + if (s.has_value() && s.value() != mode::import) { + result.mode = *s; + _state = state::object; + } else { + auto code = error_code::mode_invalid; + throw as_exception( + error_info{code, make_error_code(code).message()}); + } + return s.has_value(); + } + return false; + } + + bool StartObject() { + return std::exchange(_state, state::object) == state::empty; + } + + bool EndObject(::json::SizeType) { + return std::exchange(_state, state::empty) == state::object; + } +}; + +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::mode_req_rep& res) { + w.StartObject(); + w.Key(mode_req_rep::field_name.data()); + ::json::rjson_serialize(w, to_string_view(res.mode)); + w.EndObject(); +} + +} // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/requests/post_subject_versions.h b/src/v/pandaproxy/schema_registry/requests/post_subject_versions.h index ff99f0419aa7c..f944bb273da6e 100644 --- a/src/v/pandaproxy/schema_registry/requests/post_subject_versions.h +++ b/src/v/pandaproxy/schema_registry/requests/post_subject_versions.h @@ -36,6 +36,8 @@ class post_subject_versions_request_handler schema, id, version, + metadata, + ruleset, schema_type, references, reference, @@ -74,6 +76,8 @@ class post_subject_versions_request_handler .match("schema", state::schema) .match("id", state::id) .match("version", state::version) + .match("metadata", state::metadata) + .match("ruleSet", state::ruleset) .match("schemaType", state::schema_type) .match("references", state::references) .default_match(std::nullopt)}; @@ -97,6 +101,8 @@ class post_subject_versions_request_handler case state::schema: case state::id: case state::version: + case state::metadata: + case state::ruleset: case state::schema_type: case state::references: case state::reference_name: @@ -107,6 +113,28 @@ class post_subject_versions_request_handler return false; } + bool Null() { + switch (_state) { + case state::metadata: + case state::ruleset: + _state = state::record; + return true; + case state::empty: + case state::record: + case state::schema: + case state::id: + case state::version: + case state::schema_type: + case state::references: + case state::reference: + case state::reference_name: + case state::reference_subject: + case state::reference_version: + break; + } + return false; + } + bool Uint(int i) { switch (_state) { case state::id: { @@ -127,6 +155,8 @@ class post_subject_versions_request_handler case state::empty: case state::record: case state::schema: + case state::metadata: + case state::ruleset: case state::schema_type: case state::references: case state::reference: @@ -141,8 +171,10 @@ class post_subject_versions_request_handler auto sv = std::string_view{str, len}; switch (_state) { case state::schema: { + iobuf buf; + buf.append(sv.data(), sv.size()); _schema.def = unparsed_schema_definition::raw_string{ - ss::sstring{sv}}; + std::move(buf)}; _state = state::record; return true; } @@ -168,6 +200,8 @@ class post_subject_versions_request_handler case state::record: case state::id: case state::version: + case state::metadata: + case state::ruleset: case state::references: case state::reference: case state::reference_version: @@ -191,6 +225,8 @@ class post_subject_versions_request_handler case state::schema: case state::id: case state::version: + case state::metadata: + case state::ruleset: case state::schema_type: case state::reference: case state::reference_name: @@ -220,6 +256,8 @@ class post_subject_versions_request_handler case state::schema: case state::id: case state::version: + case state::metadata: + case state::ruleset: case state::schema_type: case state::references: case state::reference_name: @@ -241,8 +279,9 @@ struct post_subject_versions_response { schema_id id; }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::post_subject_versions_response& res) { w.StartObject(); w.Key("id"); diff --git a/src/v/pandaproxy/schema_registry/requests/test/get_subject_versions_version.cc b/src/v/pandaproxy/schema_registry/requests/test/get_subject_versions_version.cc index 72dc69bd8f7c9..792d41711cac0 100644 --- a/src/v/pandaproxy/schema_registry/requests/test/get_subject_versions_version.cc +++ b/src/v/pandaproxy/schema_registry/requests/test/get_subject_versions_version.cc @@ -9,12 +9,11 @@ #include "pandaproxy/schema_registry/requests/get_subject_versions_version.h" +#include "pandaproxy/json/rjson_util.h" #include "seastarx.h" #include -#include - namespace ppj = pandaproxy::json; namespace pps = pandaproxy::schema_registry; @@ -30,7 +29,9 @@ SEASTAR_THREAD_TEST_CASE(test_post_subject_versions_version_response) { const pps::subject sub{"imported-ref"}; pps::post_subject_versions_version_response response{ - .schema{pps::subject{"imported-ref"}, schema_def}, .id{12}, .version{2}}; + .schema{pps::subject{"imported-ref"}, schema_def.copy()}, + .id{12}, + .version{2}}; const ss::sstring expected{ R"( @@ -48,7 +49,7 @@ SEASTAR_THREAD_TEST_CASE(test_post_subject_versions_version_response) { "schema": ")" + escaped_schema_def + R"("})"}; - auto result{ppj::rjson_serialize(response)}; + auto result = ppj::rjson_serialize_str(response); BOOST_REQUIRE_EQUAL(::json::minify(expected), result); } diff --git a/src/v/pandaproxy/schema_registry/requests/test/post_subject_versions.cc b/src/v/pandaproxy/schema_registry/requests/test/post_subject_versions.cc index ec926cc6387f7..665a988f6b589 100644 --- a/src/v/pandaproxy/schema_registry/requests/test/post_subject_versions.cc +++ b/src/v/pandaproxy/schema_registry/requests/test/post_subject_versions.cc @@ -50,20 +50,22 @@ SEASTAR_THREAD_TEST_CASE(test_post_subject_versions_parser) { })"}; const pps::subject sub{"test_subject"}; const parse_result expected{ - {sub, expected_schema_def}, std::nullopt, std::nullopt}; + {sub, expected_schema_def.share()}, std::nullopt, std::nullopt}; - auto result{ppj::rjson_parse( + auto result{ppj::impl::rjson_parse( payload.data(), pps::post_subject_versions_request_handler{sub})}; // canonicalisation now requires a sharded_store, for now, minify. - // NOLINTBEGIN(bugprone-use-after-move) + auto [rsub, unparsed] = std::move(result.def).destructure(); + auto [def, type, refs] = std::move(unparsed).destructure(); + result.def = { - std::move(result.def).sub(), + std::move(rsub), pps::unparsed_schema_definition{ - ::json::minify(result.def.def().raw()()), + pps::unparsed_schema_definition::raw_string{ + ::json::minify(std::move(def)())}, pps::schema_type::avro, - std::move(result.def).def().refs()}}; - // NOLINTEND(bugprone-use-after-move) + std::move(refs)}}; BOOST_REQUIRE_EQUAL(expected.def, result.def); BOOST_REQUIRE_EQUAL(expected.id.has_value(), result.id.has_value()); diff --git a/src/v/pandaproxy/schema_registry/seq_writer.cc b/src/v/pandaproxy/schema_registry/seq_writer.cc index 17e65700a1ae3..0da4bfe05e86f 100644 --- a/src/v/pandaproxy/schema_registry/seq_writer.cc +++ b/src/v/pandaproxy/schema_registry/seq_writer.cc @@ -12,17 +12,19 @@ #include "kafka/client/client_fetch_batch_reader.h" #include "pandaproxy/error.h" #include "pandaproxy/logger.h" +#include "pandaproxy/schema_registry/error.h" #include "pandaproxy/schema_registry/errors.h" #include "pandaproxy/schema_registry/exceptions.h" #include "pandaproxy/schema_registry/sharded_store.h" #include "pandaproxy/schema_registry/storage.h" -#include "random/simple_time_jitter.h" #include "ssx/future-util.h" +#include "storage/record_batch_builder.h" #include "vassert.h" #include "vlog.h" #include #include +#include #include @@ -30,6 +32,78 @@ using namespace std::chrono_literals; namespace pandaproxy::schema_registry { +namespace { + +struct batch_builder : public storage::record_batch_builder { + explicit batch_builder( + model::offset base_offset, std::optional sub) + : record_batch_builder{model::record_batch_type::raft_data, model::offset{base_offset}} + , sub{std::move(sub)} {} + + using record_batch_builder::add_raw_kv; + using record_batch_builder::build; + + void operator()(std::optional&& key, std::optional&& value) { + add_raw_kw(std::move(key), std::move(value), {}); + } + + template + requires requires(K k, V v) { + to_json_iobuf(k); + to_json_iobuf(v); + } + void operator()(K&& key, V&& value) { + add_raw_kv( + to_json_iobuf(std::forward(key)), + to_json_iobuf(std::forward(value))); + } + + void operator()(const seq_marker& s) { + vlog( + plog.debug, + "Delete {} tombstoning sub={} at {}", + to_string_view(s.key_type), + sub, + s); + + // Assumption: magic is the same as it was when key was + // originally read. + switch (s.key_type) { + case seq_marker_key_type::schema: { + auto key = schema_key{ + .seq{s.seq}, .node{s.node}, .sub{*sub}, .version{s.version}}; + add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); + } break; + case seq_marker_key_type::delete_subject: { + auto key = delete_subject_key{ + .seq{s.seq}, .node{s.node}, .sub{*sub}}; + add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); + } break; + case seq_marker_key_type::config: { + auto key = config_key{.seq{s.seq}, .node{s.node}, .sub{sub}}; + add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); + } break; + case seq_marker_key_type::mode: { + auto key = mode_key{.seq{s.seq}, .node{s.node}, .sub{sub}}; + add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); + } break; + case seq_marker_key_type::invalid: + vassert(false, "Unknown key type"); + break; + } + } + + void operator()(const std::vector& sequences) { + for (const seq_marker& s : sequences) { + (*this)(s); + } + } + + std::optional sub; +}; + +} // namespace + /// Call this before reading from the store, if servicing /// a REST API endpoint that requires global knowledge of latest /// data (i.e. any listings) @@ -41,6 +115,15 @@ ss::future<> seq_writer::read_sync() { co_await wait_for(max_offset - model::offset{1}); } +ss::future<> seq_writer::check_mutable(std::optional const& sub) { + auto mode = sub ? co_await _store.get_mode(*sub, default_to_global::yes) + : co_await _store.get_mode(); + if (mode == mode::read_only) { + throw as_exception(mode_is_readonly(sub)); + } + co_return; +} + ss::future<> seq_writer::wait_for(model::offset offset) { return container().invoke_on(0, _smp_opts, [offset](seq_writer& seq) { if (auto waiters = seq._wait_for_sem.waiters(); waiters != 0) { @@ -72,37 +155,36 @@ ss::future<> seq_writer::wait_for(model::offset offset) { /// Helper for write methods that need to check + retry if their /// write landed where they expected it to. /// -/// \param write_at Offset at which caller expects their write to land +/// \param write_at Offset at which caller expects their write to land. If +/// std::nullopt, the offset is not checked. /// \param batch Message to write /// \return true if the write landed at `write_at`, else false -ss::future seq_writer::produce_and_check( - model::offset write_at, model::record_batch batch) { - // Because we rely on checking exactly where our message (singular) landed, - // only use this function with batches of a single message. - vassert(batch.record_count() == 1, "Only single-message batches allowed"); +ss::future seq_writer::produce_and_apply( + std::optional write_at, model::record_batch batch) { + vassert( + write_at.value_or(batch.base_offset()) == batch.base_offset(), + "Set the base_offset to the expected write_at"); kafka::partition_produce_response res = co_await _client.local().produce_record_batch( - model::schema_registry_internal_tp, std::move(batch)); + model::schema_registry_internal_tp, batch.copy()); - // TODO(Ben): Check the error reporting here if (res.error_code != kafka::error_code::none) { - throw kafka::exception(res.error_code, *res.error_message); + throw kafka::exception(res.error_code, res.error_message.value_or("")); } - auto wrote_at = res.base_offset; - if (wrote_at == write_at) { - vlog(plog.debug, "seq_writer: Successful write at {}", wrote_at); - - co_return true; + auto success = write_at.value_or(res.base_offset) == res.base_offset; + if (success) { + vlog(plog.debug, "seq_writer: Successful write at {}", res.base_offset); + co_await consume_to_store(_store, *this)(std::move(batch)); } else { vlog( plog.debug, "seq_writer: Failed write at {} (wrote at {})", write_at, - wrote_at); - co_return false; + res.base_offset); } + co_return success; }; ss::future<> seq_writer::advance_offset(model::offset offset) { @@ -129,19 +211,25 @@ void seq_writer::advance_offset_inner(model::offset offset) { } ss::future> seq_writer::do_write_subject_version( - subject_schema schema, model::offset write_at, seq_writer& seq) { + subject_schema schema, model::offset write_at) { + co_await check_mutable(schema.schema.sub()); + // Check if store already contains this data: if // so, we do no I/O and return the schema ID. - auto projected = co_await seq._store.project_ids(schema).handle_exception( - [](std::exception_ptr e) { - vlog(plog.debug, "write_subject_version: project_ids failed: {}", e); - return ss::make_exception_future(e); - }); + auto projected + = co_await _store.project_ids(schema.share()) + .handle_exception([](std::exception_ptr e) { + vlog( + plog.debug, "write_subject_version: project_ids failed: {}", e); + return ss::make_exception_future(e); + }); if (!projected.inserted) { vlog(plog.debug, "write_subject_version: no-op"); co_return projected.id; } else { + auto canonical = std::move(schema.schema); + auto sub = canonical.sub(); vlog( plog.debug, "seq_writer::write_subject_version project offset={} " @@ -149,49 +237,44 @@ ss::future> seq_writer::do_write_subject_version( "schema={} " "version={}", write_at, - schema.schema.sub(), + sub, projected.id, projected.version); auto key = schema_key{ .seq{write_at}, - .node{seq._node_id}, - .sub{schema.schema.sub()}, + .node{_node_id}, + .sub{sub}, .version{projected.version}}; auto value = canonical_schema_value{ - .schema{schema.schema}, + .schema{std::move(canonical)}, .version{projected.version}, .id{projected.id}, .deleted = is_deleted::no}; - auto batch = as_record_batch(key, value); + batch_builder rb(write_at, sub); + rb(std::move(key), std::move(value)); - auto success = co_await seq.produce_and_check( - write_at, std::move(batch)); - if (success) { - auto applier = consume_to_store(seq._store, seq); - using Tag = decltype(value.schema)::tag; - co_await applier.apply(write_at, key, value); - seq.advance_offset_inner(write_at); + if (co_await produce_and_apply(write_at, std::move(rb).build())) { co_return projected.id; } else { + // Pass up a None, our caller's cue to retry co_return std::nullopt; } } } ss::future seq_writer::write_subject_version(subject_schema schema) { - return sequenced_write([this, schema{std::move(schema)}]( - model::offset write_at, seq_writer& seq) { - return do_write_subject_version(schema, write_at, seq); - }); + co_return co_await sequenced_write( + [&schema](model::offset write_at, seq_writer& seq) { + return seq.do_write_subject_version(schema.share(), write_at); + }); } ss::future> seq_writer::do_write_config( std::optional sub, compatibility_level compat, - model::offset write_at, - seq_writer& seq) { + model::offset write_at) { vlog( plog.debug, "write_config sub={} compat={} offset={}", @@ -199,14 +282,16 @@ ss::future> seq_writer::do_write_config( to_string_view(compat), write_at); + co_await check_mutable(sub); + try { // Check for no-op case compatibility_level existing; if (sub.has_value()) { - existing = co_await seq._store.get_compatibility( + existing = co_await _store.get_compatibility( sub.value(), default_to_global::no); } else { - existing = co_await seq._store.get_compatibility(); + existing = co_await _store.get_compatibility(); } if (existing == compat) { co_return false; @@ -215,15 +300,12 @@ ss::future> seq_writer::do_write_config( // ignore } - auto key = config_key{.seq{write_at}, .node{seq._node_id}, .sub{sub}}; - auto value = config_value{.compat = compat}; - auto batch = as_record_batch(key, value); + batch_builder rb(write_at, sub); + rb( + config_key{.seq{write_at}, .node{_node_id}, .sub{sub}}, + config_value{.compat = compat}); - auto success = co_await seq.produce_and_check(write_at, std::move(batch)); - if (success) { - auto applier = consume_to_store(seq._store, seq); - co_await applier.apply(write_at, key, value); - seq.advance_offset_inner(write_at); + if (co_await produce_and_apply(write_at, std::move(rb).build())) { co_return true; } else { // Pass up a None, our caller's cue to retry @@ -233,95 +315,129 @@ ss::future> seq_writer::do_write_config( ss::future seq_writer::write_config( std::optional sub, compatibility_level compat) { - return sequenced_write([this, sub{std::move(sub)}, compat]( - model::offset write_at, seq_writer& seq) { - return do_write_config(sub, compat, write_at, seq); - }); + return sequenced_write( + [sub{std::move(sub)}, compat](model::offset write_at, seq_writer& seq) { + return seq.do_write_config(sub, compat, write_at); + }); } -ss::future> seq_writer::do_delete_config( - subject sub, model::offset write_at, seq_writer& seq) { - vlog(plog.debug, "delete config sub={} offset={}", sub, write_at); +ss::future> seq_writer::do_delete_config(subject sub) { + vlog(plog.debug, "delete config sub={}", sub); + + co_await check_mutable(sub); try { - co_await seq._store.get_compatibility(sub, default_to_global::no); + co_await _store.get_compatibility(sub, default_to_global::no); } catch (const exception&) { // subject config already blank co_return false; } - std::vector sequences{ - co_await _store.get_subject_config_written_at(sub)}; + batch_builder rb{model::offset{0}, sub}; + rb(co_await _store.get_subject_config_written_at(sub)); + + if (co_await produce_and_apply(std::nullopt, std::move(rb).build())) { + co_return true; + } else { + // Pass up a None, our caller's cue to retry + co_return std::nullopt; + } +} - storage::record_batch_builder rb{ - model::record_batch_type::raft_data, model::offset{0}}; +ss::future seq_writer::delete_config(subject sub) { + return sequenced_write( + [sub{std::move(sub)}](model::offset, seq_writer& seq) { + return seq.do_delete_config(sub); + }); +} - std::vector keys; - for (const auto& s : sequences) { - vlog( - plog.debug, - "Deleting config: tombstoning config_key for sub={} at {}", - sub, - s); +ss::future> seq_writer::do_write_mode( + std::optional sub, mode m, force f, model::offset write_at) { + vlog( + plog.debug, + "write_mode sub={} mode={} force={} offset={}", + sub, + to_string_view(m), + f, + write_at); - vassert( - s.key_type == seq_marker_key_type::config, - "Unexpected key type: {}", - s.key_type); + _store.check_mode_mutability(force::no); - auto key = config_key{.seq{s.seq}, .node{s.node}, .sub{sub}}; - keys.push_back(key); - rb.add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); + try { + // Check for no-op case + mode existing = sub ? co_await _store.get_mode( + sub.value(), default_to_global::no) + : co_await _store.get_mode(); + if (existing == m) { + co_return false; + } + } catch (const exception& e) { + if (e.code() != error_code::mode_not_found) { + throw; + } } - auto ts_batch = std::move(rb).build(); - kafka::partition_produce_response res - = co_await _client.local().produce_record_batch( - model::schema_registry_internal_tp, std::move(ts_batch)); + batch_builder rb(write_at, sub); + rb( + mode_key{.seq{write_at}, .node{_node_id}, .sub{sub}}, + mode_value{.mode = m}); - if (res.error_code != kafka::error_code::none) { - vlog( - plog.error, - "Error writing to subject topic: {} {}", - res.error_code, - res.error_message); - throw kafka::exception(res.error_code, *res.error_message); + if (co_await produce_and_apply(write_at, std::move(rb).build())) { + co_return true; + } else { + // Pass up a None, our caller's cue to retry + co_return std::nullopt; } +} - auto applier = consume_to_store(seq._store, seq); - auto offset = res.base_offset; - for (const auto& k : keys) { - co_await applier.apply(offset, k, std::nullopt); - seq.advance_offset_inner(offset); - ++offset; - } +ss::future +seq_writer::write_mode(std::optional sub, mode mode, force f) { + return sequenced_write( + [sub{std::move(sub)}, mode, f](model::offset write_at, seq_writer& seq) { + return seq.do_write_mode(sub, mode, f, write_at); + }); +} + +ss::future> +seq_writer::do_delete_mode(subject sub, model::offset write_at) { + vlog(plog.debug, "delete mode sub={} offset={}", sub, write_at); - co_return true; + // Report an error if the mode isn't registered + co_await _store.get_mode(sub, default_to_global::no); + _store.check_mode_mutability(force::no); + + batch_builder rb{write_at, sub}; + rb(co_await _store.get_subject_mode_written_at(sub)); + if (co_await produce_and_apply(std::nullopt, std::move(rb).build())) { + co_return true; + } else { + // Pass up a None, our caller's cue to retry + co_return std::nullopt; + } } -ss::future seq_writer::delete_config(subject sub) { +ss::future seq_writer::delete_mode(subject sub) { return sequenced_write( - [this, sub{std::move(sub)}](model::offset write_at, seq_writer& seq) { - return do_delete_config(sub, write_at, seq); + [sub{std::move(sub)}](model::offset write_at, seq_writer& seq) { + return seq.do_delete_mode(sub, write_at); }); } /// Impermanent delete: update a version with is_deleted=true ss::future> seq_writer::do_delete_subject_version( - subject sub, - schema_version version, - model::offset write_at, - seq_writer& seq) { - if (co_await seq._store.is_referenced(sub, version)) { + subject sub, schema_version version, model::offset write_at) { + co_await check_mutable(sub); + + if (co_await _store.is_referenced(sub, version)) { throw as_exception(has_references(sub, version)); } - auto s_res = co_await seq._store.get_subject_schema( + auto s_res = co_await _store.get_subject_schema( sub, version, include_deleted::yes); subject_schema ss = std::move(s_res); auto key = schema_key{ - .seq{write_at}, .node{seq._node_id}, .sub{sub}, .version{version}}; + .seq{write_at}, .node{_node_id}, .sub{sub}, .version{version}}; vlog(plog.debug, "seq_writer::delete_subject_version {}", key); auto value = canonical_schema_value{ .schema{std::move(ss.schema)}, @@ -329,14 +445,17 @@ ss::future> seq_writer::do_delete_subject_version( .id{ss.id}, .deleted{is_deleted::yes}}; - auto batch = as_record_batch(key, value); + batch_builder rb(write_at, sub); + rb(std::move(key), std::move(value)); - auto success = co_await seq.produce_and_check(write_at, std::move(batch)); - if (success) { - auto applier = consume_to_store(seq._store, seq); - using Tag = decltype(value.schema)::tag; - co_await applier.apply(write_at, key, value); - seq.advance_offset_inner(write_at); + { + // Clear config if this is a delete of the last version + auto vec = co_await _store.get_versions(sub, include_deleted::no); + if (vec.size() == 1 && vec.front() == version) { + rb(co_await _store.get_subject_config_written_at(sub)); + } + } + if (co_await produce_and_apply(write_at, std::move(rb).build())) { co_return true; } else { // Pass up a None, our caller's cue to retry @@ -346,26 +465,27 @@ ss::future> seq_writer::do_delete_subject_version( ss::future seq_writer::delete_subject_version(subject sub, schema_version version) { - return sequenced_write([this, sub{std::move(sub)}, version]( - model::offset write_at, seq_writer& seq) { - return do_delete_subject_version(sub, version, write_at, seq); - }); + return sequenced_write( + [sub{std::move(sub)}, version](model::offset write_at, seq_writer& seq) { + return seq.do_delete_subject_version(sub, version, write_at); + }); } ss::future>> -seq_writer::do_delete_subject_impermanent( - subject sub, model::offset write_at, seq_writer& seq) { +seq_writer::do_delete_subject_impermanent(subject sub, model::offset write_at) { + co_await check_mutable(sub); + // Grab the versions before they're gone. - auto versions = co_await seq._store.get_versions(sub, include_deleted::no); + auto versions = co_await _store.get_versions(sub, include_deleted::no); // Inspect the subject to see if its already deleted - if (co_await seq._store.is_subject_deleted(sub)) { + if (co_await _store.is_subject_deleted(sub)) { co_return std::make_optional(versions); } auto is_referenced = co_await ssx::parallel_transform( - versions.begin(), versions.end(), [&seq, &sub](auto const& ver) { - return seq._store.is_referenced(sub, ver); + versions.begin(), versions.end(), [this, &sub](auto const& ver) { + return _store.is_referenced(sub, ver); }); if (std::any_of(is_referenced.begin(), is_referenced.end(), [](auto v) { return v; @@ -374,16 +494,28 @@ seq_writer::do_delete_subject_impermanent( } // Proceed to write - auto key = delete_subject_key{ - .seq{write_at}, .node{seq._node_id}, .sub{sub}}; - auto value = delete_subject_value{.sub{sub}}; - auto batch = as_record_batch(key, value); + batch_builder rb{write_at, sub}; + rb( + delete_subject_key{.seq{write_at}, .node{_node_id}, .sub{sub}}, + delete_subject_value{.sub{sub}}); - auto success = co_await seq.produce_and_check(write_at, std::move(batch)); - if (success) { - auto applier = consume_to_store(seq._store, seq); - co_await applier.apply(write_at, key, value); - seq.advance_offset_inner(write_at); + try { + rb(co_await _store.get_subject_mode_written_at(sub)); + } catch (exception const& e) { + if (e.code() != error_code::subject_not_found) { + throw; + } + } + + try { + rb(co_await _store.get_subject_config_written_at(sub)); + } catch (exception const& e) { + if (e.code() != error_code::subject_not_found) { + throw; + } + } + + if (co_await produce_and_apply(write_at, std::move(rb).build())) { co_return versions; } else { // Pass up a None, our caller's cue to retry @@ -395,8 +527,8 @@ ss::future> seq_writer::delete_subject_impermanent(subject sub) { vlog(plog.debug, "delete_subject_impermanent sub={}", sub); return sequenced_write( - [this, sub{std::move(sub)}](model::offset write_at, seq_writer& seq) { - return do_delete_subject_impermanent(sub, write_at, seq); + [sub{std::move(sub)}](model::offset write_at, seq_writer& seq) { + return seq.do_delete_subject_impermanent(sub, write_at); }); } @@ -406,20 +538,24 @@ seq_writer::delete_subject_impermanent(subject sub) { /// will hard-delete the whole subject. ss::future> seq_writer::delete_subject_permanent( subject sub, std::optional version) { - return container().invoke_on(0, _smp_opts, [sub, version](seq_writer& seq) { - return ss::with_semaphore(seq._write_sem, 1, [sub, version, &seq]() { - return seq.delete_subject_permanent_inner(sub, version); - }); - }); + return sequenced_write( + [sub{std::move(sub)}, version](model::offset, seq_writer& seq) { + return seq.delete_subject_permanent_inner(sub, version); + }); } -ss::future> +ss::future>> seq_writer::delete_subject_permanent_inner( subject sub, std::optional version) { std::vector sequences; + batch_builder rb{model::offset{0}, sub}; + /// Check for whether our victim is already soft-deleted happens /// within these store functions (will throw a 404-equivalent if so) vlog(plog.debug, "delete_subject_permanent sub={}", sub); + + co_await check_mutable(sub); + if (version.has_value()) { // Check version first to see if the version exists sequences = co_await _store.get_subject_version_written_at( @@ -431,81 +567,16 @@ seq_writer::delete_subject_permanent_inner( // Deleting the subject, or the last version, deletes the subject if (!version.has_value() || versions.size() == 1) { - sequences = co_await _store.get_subject_written_at(sub); - } - - storage::record_batch_builder rb{ - model::record_batch_type::raft_data, model::offset{0}}; - - std::vector> keys; - for (auto s : sequences) { - vlog( - plog.debug, - "Delete subject_permanent: tombstoning sub={} at {}", - sub, - s); - - // Assumption: magic is the same as it was when key was - // originally read. - switch (s.key_type) { - case seq_marker_key_type::schema: { - auto key = schema_key{ - .seq{s.seq}, .node{s.node}, .sub{sub}, .version{s.version}}; - keys.push_back(key); - rb.add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); - } break; - case seq_marker_key_type::delete_subject: { - auto key = delete_subject_key{ - .seq{s.seq}, .node{s.node}, .sub{sub}}; - keys.push_back(key); - rb.add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); - } break; - case seq_marker_key_type::config: { - auto key = config_key{.seq{s.seq}, .node{s.node}, .sub{sub}}; - keys.push_back(key); - rb.add_raw_kv(to_json_iobuf(std::move(key)), std::nullopt); - } break; - default: - vassert(false, "Unknown key type"); - } - } - - // Produce tombstones. We do not need to check where they landed, - // because these can arrive in any order and be safely repeated. - auto batch = std::move(rb).build(); - - kafka::partition_produce_response res - = co_await _client.local().produce_record_batch( - model::schema_registry_internal_tp, std::move(batch)); - if (res.error_code != kafka::error_code::none) { - vlog( - plog.error, - "Error writing to schema topic: {} {}", - res.error_code, - res.error_message); - throw kafka::exception(res.error_code, *res.error_message); + rb(co_await _store.get_subject_written_at(sub)); } + rb(sequences); - // Replay the persisted deletions into our store - auto applier = consume_to_store(_store, *this); - auto offset = res.base_offset; - for (auto k : keys) { - co_await ss::visit( - k, - [&applier, &offset](const schema_key& skey) { - using Tag = canonical_schema_definition_tag; - return applier.apply(offset, skey, std::nullopt); - }, - [&applier, &offset](const delete_subject_key& dkey) { - return applier.apply(offset, dkey, std::nullopt); - }, - [&applier, &offset](const config_key& ckey) { - return applier.apply(offset, ckey, std::nullopt); - }); - advance_offset_inner(offset); - offset++; + if (co_await produce_and_apply(std::nullopt, std::move(rb).build())) { + co_return versions; + } else { + // Pass up a None, our caller's cue to retry + co_return std::nullopt; } - co_return versions; } } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/seq_writer.h b/src/v/pandaproxy/schema_registry/seq_writer.h index fd538b8006446..faca6040d8d9a 100644 --- a/src/v/pandaproxy/schema_registry/seq_writer.h +++ b/src/v/pandaproxy/schema_registry/seq_writer.h @@ -40,6 +40,9 @@ class seq_writer final : public ss::peering_sharded_service { ss::future<> read_sync(); + // Throws 42205 if the subject cannot be modified + ss::future<> check_mutable(std::optional const& sub); + // API for readers: notify us when they have read and applied an offset ss::future<> advance_offset(model::offset offset); @@ -50,6 +53,10 @@ class seq_writer final : public ss::peering_sharded_service { ss::future delete_config(subject sub); + ss::future write_mode(std::optional sub, mode m, force f); + + ss::future delete_mode(subject sub); + ss::future delete_subject_version(subject sub, schema_version version); @@ -69,29 +76,30 @@ class seq_writer final : public ss::peering_sharded_service { void advance_offset_inner(model::offset offset); - ss::future> do_write_subject_version( - subject_schema schema, model::offset write_at, seq_writer& seq); + ss::future> + do_write_subject_version(subject_schema schema, model::offset write_at); ss::future> do_write_config( std::optional sub, compatibility_level compat, - model::offset write_at, - seq_writer& seq); + model::offset write_at); + + ss::future> do_delete_config(subject sub); + + ss::future> do_write_mode( + std::optional sub, mode m, force f, model::offset write_at); ss::future> - do_delete_config(subject sub, model::offset write_at, seq_writer& seq); + do_delete_mode(subject sub, model::offset write_at); ss::future> do_delete_subject_version( - subject sub, - schema_version version, - model::offset write_at, - seq_writer& seq); + subject sub, schema_version version, model::offset write_at); ss::future>> - do_delete_subject_impermanent( - subject sub, model::offset write_at, seq_writer& seq); + do_delete_subject_impermanent(subject sub, model::offset write_at); - ss::future> delete_subject_permanent_inner( + ss::future>> + delete_subject_permanent_inner( subject sub, std::optional version); simple_time_jitter _jitter{std::chrono::milliseconds{50}}; @@ -165,8 +173,8 @@ class seq_writer final : public ss::peering_sharded_service { } } - ss::future - produce_and_check(model::offset write_at, model::record_batch batch); + ss::future produce_and_apply( + std::optional write_at, model::record_batch batch); /// Block until this offset is available, fetching if necessary ss::future<> wait_for(model::offset offset); diff --git a/src/v/pandaproxy/schema_registry/service.cc b/src/v/pandaproxy/schema_registry/service.cc index da85d2d008617..1361fb2301d65 100644 --- a/src/v/pandaproxy/schema_registry/service.cc +++ b/src/v/pandaproxy/schema_registry/service.cc @@ -57,9 +57,10 @@ const security::acl_principal principal{ class wrap { public: - wrap(ss::gate& g, one_shot& os, server::function_handler h) + wrap(ss::gate& g, one_shot& os, auth_level lvl, server::function_handler h) : _g{g} , _os{os} + , _auth_level(lvl) , _h{std::move(h)} {} ss::future @@ -68,8 +69,11 @@ class wrap { rq.service().config().schema_registry_api.value(), rq.req->get_listener_idx()); try { - rq.user = maybe_authenticate_request( - rq.authn_method, rq.service().authenticator(), *rq.req); + rq.user = maybe_authorize_request( + rq.authn_method, + _auth_level, + rq.service().authenticator(), + *rq.req); } catch (unauthorized_user_exception& e) { audit_authn_failure(rq, e.get_username(), e.what()); throw; @@ -192,6 +196,7 @@ class wrap { private: ss::gate& _g; one_shot& _os; + auth_level _auth_level; server::function_handler _h; }; @@ -200,91 +205,118 @@ server::routes_t get_schema_registry_routes(ss::gate& gate, one_shot& es) { routes.api = ss::httpd::schema_registry_json::name; routes.routes.emplace_back(server::route_t{ - ss::httpd::schema_registry_json::get_config, wrap(gate, es, get_config)}); + ss::httpd::schema_registry_json::get_config, + wrap(gate, es, auth_level::user, get_config)}); routes.routes.emplace_back(server::route_t{ - ss::httpd::schema_registry_json::put_config, wrap(gate, es, put_config)}); + ss::httpd::schema_registry_json::put_config, + wrap(gate, es, auth_level::user, put_config)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_config_subject, - wrap(gate, es, get_config_subject)}); + wrap(gate, es, auth_level::user, get_config_subject)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::put_config_subject, - wrap(gate, es, put_config_subject)}); + wrap(gate, es, auth_level::user, put_config_subject)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::delete_config_subject, - wrap(gate, es, delete_config_subject)}); + wrap(gate, es, auth_level::user, delete_config_subject)}); routes.routes.emplace_back(server::route_t{ - ss::httpd::schema_registry_json::get_mode, wrap(gate, es, get_mode)}); + ss::httpd::schema_registry_json::get_mode, + wrap(gate, es, auth_level::user, get_mode)}); + + routes.routes.emplace_back(server::route_t{ + ss::httpd::schema_registry_json::put_mode, + wrap(gate, es, auth_level::superuser, put_mode)}); + + routes.routes.emplace_back(server::route_t{ + ss::httpd::schema_registry_json::get_mode_subject, + wrap(gate, es, auth_level::user, get_mode_subject)}); + + routes.routes.emplace_back(server::route_t{ + ss::httpd::schema_registry_json::put_mode_subject, + wrap(gate, es, auth_level::superuser, put_mode_subject)}); + + routes.routes.emplace_back(server::route_t{ + ss::httpd::schema_registry_json::delete_mode_subject, + wrap(gate, es, auth_level::superuser, delete_mode_subject)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_schemas_types, - wrap(gate, es, get_schemas_types)}); + wrap(gate, es, auth_level::publik, get_schemas_types)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_schemas_ids_id, - wrap(gate, es, get_schemas_ids_id)}); + wrap(gate, es, auth_level::user, get_schemas_ids_id)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_schemas_ids_id_versions, - wrap(gate, es, get_schemas_ids_id_versions)}); + wrap(gate, es, auth_level::user, get_schemas_ids_id_versions)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_schemas_ids_id_subjects, - wrap(gate, es, get_schemas_ids_id_subjects)}); + wrap(gate, es, auth_level::user, get_schemas_ids_id_subjects)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_subjects, - wrap(gate, es, get_subjects)}); + wrap(gate, es, auth_level::user, get_subjects)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_subject_versions, - wrap(gate, es, get_subject_versions)}); + wrap(gate, es, auth_level::user, get_subject_versions)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::post_subject, - wrap(gate, es, post_subject)}); + wrap(gate, es, auth_level::user, post_subject)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::post_subject_versions, - wrap(gate, es, post_subject_versions)}); + wrap(gate, es, auth_level::user, post_subject_versions)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_subject_versions_version, - wrap(gate, es, get_subject_versions_version)}); + wrap(gate, es, auth_level::user, get_subject_versions_version)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::get_subject_versions_version_schema, - wrap(gate, es, get_subject_versions_version_schema)}); + wrap(gate, es, auth_level::user, get_subject_versions_version_schema)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json:: get_subject_versions_version_referenced_by, - wrap(gate, es, get_subject_versions_version_referenced_by)}); + wrap( + gate, + es, + auth_level::user, + get_subject_versions_version_referenced_by)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json:: get_subject_versions_version_referenced_by_deprecated, - wrap(gate, es, get_subject_versions_version_referenced_by)}); + wrap( + gate, + es, + auth_level::user, + get_subject_versions_version_referenced_by)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::delete_subject, - wrap(gate, es, delete_subject)}); + wrap(gate, es, auth_level::user, delete_subject)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::delete_subject_version, - wrap(gate, es, delete_subject_version)}); + wrap(gate, es, auth_level::user, delete_subject_version)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::compatibility_subject_version, - wrap(gate, es, compatibility_subject_version)}); + wrap(gate, es, auth_level::user, compatibility_subject_version)}); routes.routes.emplace_back(server::route_t{ ss::httpd::schema_registry_json::schema_registry_status_ready, - wrap(gate, es, status_ready)}); + wrap(gate, es, auth_level::publik, status_ready)}); return routes; } diff --git a/src/v/pandaproxy/schema_registry/sharded_store.cc b/src/v/pandaproxy/schema_registry/sharded_store.cc index cc1bdc9f73284..b26de08aac576 100644 --- a/src/v/pandaproxy/schema_registry/sharded_store.cc +++ b/src/v/pandaproxy/schema_registry/sharded_store.cc @@ -11,6 +11,7 @@ #include "pandaproxy/schema_registry/sharded_store.h" +#include "config/configuration.h" #include "hashing/jump_consistent_hash.h" #include "hashing/xx.h" #include "kafka/protocol/errors.h" @@ -22,6 +23,7 @@ #include "pandaproxy/schema_registry/protobuf.h" #include "pandaproxy/schema_registry/store.h" #include "pandaproxy/schema_registry/types.h" +#include "pandaproxy/schema_registry/util.h" #include "vlog.h" #include @@ -48,13 +50,14 @@ ss::shard_id shard_for(schema_id id) { return jump_consistent_hash(id(), ss::smp::count); } -bool check_compatible(const valid_schema& reader, const valid_schema& writer) { - return reader.visit([&](const auto& reader) { - return writer.visit([&](const auto& writer) { +compatibility_result check_compatible( + const valid_schema& reader, const valid_schema& writer, verbose is_verbose) { + return reader.visit([&](const auto& reader) -> compatibility_result { + return writer.visit([&](const auto& writer) -> compatibility_result { if constexpr (std::is_same_v) { - return check_compatible(reader, writer); + return check_compatible(reader, writer, is_verbose); } - return false; + return {.is_compat = false}; }); }); } @@ -67,20 +70,21 @@ constexpr auto set_accumulator = } // namespace -ss::future<> sharded_store::start(ss::smp_service_group sg) { +ss::future<> sharded_store::start(is_mutable mut, ss::smp_service_group sg) { _smp_opts = ss::smp_submit_to_options{sg}; - return _store.start(); + return _store.start(mut); } ss::future<> sharded_store::stop() { return _store.stop(); } ss::future -sharded_store::make_canonical_schema(unparsed_schema schema) { +sharded_store::make_canonical_schema(unparsed_schema schema, normalize norm) { switch (schema.type()) { case schema_type::avro: { + auto [sub, unparsed] = std::move(schema).destructure(); co_return canonical_schema{ - std::move(schema.sub()), - sanitize_avro_schema_definition(schema.def()).value()}; + std::move(sub), + sanitize_avro_schema_definition(std::move(unparsed)).value()}; } case schema_type::protobuf: co_return co_await make_canonical_protobuf_schema( @@ -94,7 +98,7 @@ sharded_store::make_canonical_schema(unparsed_schema schema) { ss::future<> sharded_store::validate_schema(canonical_schema schema) { switch (schema.type()) { case schema_type::avro: { - co_await make_avro_schema_definition(*this, schema); + co_await make_avro_schema_definition(*this, std::move(schema)); co_return; } case schema_type::protobuf: @@ -112,10 +116,12 @@ sharded_store::make_valid_schema(canonical_schema schema) { // See #3596 for details, especially if modifying it. switch (schema.type()) { case schema_type::avro: { - co_return co_await make_avro_schema_definition(*this, schema); + co_return co_await make_avro_schema_definition( + *this, std::move(schema)); } case schema_type::protobuf: { - co_return co_await make_protobuf_schema_definition(*this, schema); + co_return co_await make_protobuf_schema_definition( + *this, std::move(schema)); } case schema_type::json: break; @@ -126,7 +132,7 @@ sharded_store::make_valid_schema(canonical_schema schema) { ss::future sharded_store::get_schema_version(subject_schema schema) { // Validate the schema (may throw) - co_await validate_schema(schema.schema); + co_await validate_schema(schema.schema.share()); // Determine if the definition already exists auto map = [&schema](store& s) { @@ -192,14 +198,15 @@ sharded_store::get_schema_version(subject_schema schema) { // Check compatibility of the schema if (!v_id.has_value() && !versions.empty()) { auto compat = co_await is_compatible( - versions.back().version, schema.schema); - if (!compat) { + versions.back().version, schema.schema.share(), verbose::yes); + if (!compat.is_compat) { throw exception( error_code::schema_incompatible, fmt::format( "Schema being registered is incompatible with an earlier " - "schema for subject \"{}\"", - sub)); + "schema for subject \"{}\", details: [{}]", + sub, + fmt::join(compat.messages, ", "))); } } co_return has_schema_result{s_id, v_id}; @@ -236,8 +243,14 @@ ss::future sharded_store::upsert( schema_id id, schema_version version, is_deleted deleted) { - auto canonical = co_await make_canonical_schema(schema); - co_return co_await upsert(marker, canonical, id, version, deleted); + auto norm = normalize{ + config::shard_local_cfg().schema_registry_normalize_on_startup()}; + co_return co_await upsert( + marker, + co_await make_canonical_schema(std::move(schema), norm), + id, + version, + deleted); } ss::future sharded_store::upsert( @@ -246,15 +259,10 @@ ss::future sharded_store::upsert( schema_id id, schema_version version, is_deleted deleted) { - // NOLINTNEXTLINE(bugprone-use-after-move) - co_await upsert_schema(id, std::move(schema).def()); + auto [sub, def] = std::move(schema).destructure(); + co_await upsert_schema(id, std::move(def)); co_return co_await upsert_subject( - marker, - // NOLINTNEXTLINE(bugprone-use-after-move) - std::move(schema).sub(), - version, - id, - deleted); + marker, std::move(sub), version, id, deleted); } ss::future sharded_store::has_schema(schema_id id) { @@ -264,11 +272,17 @@ ss::future sharded_store::has_schema(schema_id id) { }); } -ss::future sharded_store::has_schema(canonical_schema schema) { - auto versions = co_await get_versions(schema.sub(), include_deleted::no); +ss::future<> sharded_store::delete_schema(schema_id id) { + return _store.invoke_on( + shard_for(id), _smp_opts, [id](store& s) { s.delete_schema(id); }); +} + +ss::future +sharded_store::has_schema(canonical_schema schema, include_deleted inc_del) { + auto versions = co_await get_versions(schema.sub(), inc_del); try { - co_await validate_schema(schema); + co_await validate_schema(schema.share()); } catch (const exception& e) { throw as_exception(invalid_subject_schema(schema.sub())); } @@ -276,8 +290,7 @@ ss::future sharded_store::has_schema(canonical_schema schema) { std::optional sub_schema; for (auto ver : versions) { try { - auto res = co_await get_subject_schema( - schema.sub(), ver, include_deleted::no); + auto res = co_await get_subject_schema(schema.sub(), ver, inc_del); if (schema.def() == res.schema.def()) { sub_schema.emplace(std::move(res)); break; @@ -305,32 +318,30 @@ sharded_store::get_schema_definition(schema_id id) { }); } -ss::future> +ss::future> sharded_store::get_schema_subject_versions(schema_id id) { + using subject_versions = chunked_vector; auto map = [id](store& s) { return s.get_schema_subject_versions(id); }; - auto reduce = - [](std::vector acc, std::vector svs) { - acc.insert(acc.end(), svs.begin(), svs.end()); - return acc; - }; - co_return co_await _store.map_reduce0( - map, std::vector{}, reduce); + auto reduce = [](subject_versions acc, subject_versions svs) { + acc.reserve(acc.size() + svs.size()); + std::move(svs.begin(), svs.end(), std::back_inserter(acc)); + return acc; + }; + co_return co_await _store.map_reduce0(map, subject_versions{}, reduce); } -ss::future> +ss::future> sharded_store::get_schema_subjects(schema_id id, include_deleted inc_del) { + using subjects = chunked_vector; auto map = [id, inc_del](store& s) { return s.get_schema_subjects(id, inc_del); }; - auto reduce = [](std::vector acc, std::vector subs) { - acc.insert( - acc.end(), - std::make_move_iterator(subs.begin()), - std::make_move_iterator(subs.end())); + auto reduce = [](subjects acc, subjects subs) { + acc.reserve(acc.size() + subs.size()); + std::move(subs.begin(), subs.end(), std::back_inserter(acc)); return acc; }; - auto subs = co_await _store.map_reduce0( - map, std::vector{}, reduce); + auto subs = co_await _store.map_reduce0(map, subjects{}, reduce); absl::c_sort(subs); co_return subs; } @@ -355,17 +366,23 @@ ss::future sharded_store::get_subject_schema( .deleted = v_id.deleted}; } -ss::future> -sharded_store::get_subjects(include_deleted inc_del) { - auto map = [inc_del](store& s) { return s.get_subjects(inc_del); }; - auto reduce = [](std::vector acc, std::vector subs) { - acc.insert( - acc.end(), - std::make_move_iterator(subs.begin()), - std::make_move_iterator(subs.end())); +ss::future> sharded_store::get_subjects( + include_deleted inc_del, std::optional subject_prefix) { + using subjects = chunked_vector; + auto map = [inc_del, &subject_prefix](store& s) { + return s.get_subjects(inc_del, subject_prefix); + }; + auto reduce = [](subjects acc, subjects subs) { + acc.reserve(acc.size() + subs.size()); + std::move(subs.begin(), subs.end(), std::back_inserter(acc)); return acc; }; - co_return co_await _store.map_reduce0(map, std::vector{}, reduce); + co_return co_await _store.map_reduce0(map, subjects{}, reduce); +} + +ss::future sharded_store::has_subjects(include_deleted inc_del) { + auto map = [inc_del](store& s) { return s.has_subjects(inc_del); }; + return _store.map_reduce0(map, false, std::logical_or<>{}); } ss::future> @@ -389,7 +406,7 @@ ss::future sharded_store::is_referenced(subject sub, schema_version ver) { // Find whether any subject version reference any of the schema co_return co_await _store.map_reduce0( [refs{std::move(references)}](store& s) { - return s.subject_versions_has_any_of(refs); + return s.subject_versions_has_any_of(refs, include_deleted::no); }, false, std::logical_or<>{}); @@ -478,6 +495,15 @@ sharded_store::get_subject_config_written_at(subject sub) { }); } +ss::future> +sharded_store::get_subject_mode_written_at(subject sub) { + auto sub_shard{shard_for(sub)}; + co_return co_await _store.invoke_on( + sub_shard, _smp_opts, [sub{std::move(sub)}](store& s) { + return s.store::get_subject_mode_written_at(sub).value(); + }); +} + ss::future> sharded_store::get_subject_version_written_at(subject sub, schema_version ver) { auto sub_shard{shard_for(sub)}; @@ -489,10 +515,66 @@ sharded_store::get_subject_version_written_at(subject sub, schema_version ver) { ss::future sharded_store::delete_subject_version( subject sub, schema_version ver, force force) { + auto sub_shard = shard_for(sub); + auto [schema_id, result] = co_await _store.invoke_on( + sub_shard, _smp_opts, [sub{std::move(sub)}, ver, force](store& s) { + auto schema_id = s.get_subject_version_id( + sub, ver, include_deleted::yes) + .value() + .id; + auto result = s.delete_subject_version(sub, ver, force).value(); + return std::make_pair(schema_id, result); + }); + + auto remaining_subjects_exist = co_await _store.map_reduce0( + [schema_id](store& s) { + return s.subject_versions_has_any_of( + {schema_id}, include_deleted::yes); + }, + false, + std::logical_or{}); + + if (!remaining_subjects_exist) { + co_await delete_schema(schema_id); + } + + co_return result; +} + +ss::future sharded_store::get_mode() { + co_return _store.local().get_mode().value(); +} + +ss::future +sharded_store::get_mode(subject sub, default_to_global fallback) { auto sub_shard{shard_for(sub)}; co_return co_await _store.invoke_on( - sub_shard, _smp_opts, [sub{std::move(sub)}, ver, force](store& s) { - return s.delete_subject_version(sub, ver, force).value(); + sub_shard, [sub{std::move(sub)}, fallback](store& s) { + return s.get_mode(sub, fallback).value(); + }); +} + +ss::future sharded_store::set_mode(mode m, force f) { + auto map = [m, f](store& s) { return s.set_mode(m, f).value(); }; + auto reduce = std::logical_and<>{}; + co_return co_await _store.map_reduce0(map, true, reduce); +} + +ss::future +sharded_store::set_mode(seq_marker marker, subject sub, mode m, force f) { + auto sub_shard{shard_for(sub)}; + co_return co_await _store.invoke_on( + sub_shard, _smp_opts, [marker, sub{std::move(sub)}, m, f](store& s) { + return s.set_mode(marker, sub, m, f).value(); + }); +} + +ss::future +sharded_store::clear_mode(seq_marker marker, subject sub, force f) { + auto sub_shard{shard_for(sub)}; + co_return co_await _store.invoke_on( + sub_shard, _smp_opts, [marker, sub{std::move(sub)}, f](store& s) { + return s.clear_mode(marker, sub, f).value(); }); } @@ -600,8 +682,20 @@ ss::future<> sharded_store::maybe_update_max_schema_id(schema_id id) { ss::future sharded_store::is_compatible( schema_version version, canonical_schema new_schema) { + auto rslt = co_await do_is_compatible( + version, std::move(new_schema), verbose::no); + co_return rslt.is_compat; +} + +ss::future sharded_store::is_compatible( + schema_version version, canonical_schema new_schema, verbose is_verbose) { + return do_is_compatible(version, std::move(new_schema), is_verbose); +} + +ss::future sharded_store::do_is_compatible( + schema_version version, canonical_schema new_schema, verbose is_verbose) { // Lookup the version_ids - const auto& sub = new_schema.sub(); + const auto sub = new_schema.sub(); const auto versions = co_await _store.invoke_on( shard_for(sub), _smp_opts, [sub](auto& s) { return s.get_version_ids(sub, include_deleted::no).value(); @@ -625,16 +719,22 @@ ss::future sharded_store::is_compatible( auto old_schema = co_await get_subject_schema( sub, version, include_deleted::no); + // Lookup the compatibility level + auto compat = co_await get_compatibility(sub, default_to_global::yes); + // Types must always match if (old_schema.schema.type() != new_schema.type()) { - co_return false; + compatibility_result result{.is_compat = false}; + if (is_verbose) { + result.messages = { + "Incompatible because of different schema type", + fmt::format("{{compatibility: {}}}", compat)}; + } + co_return result; } - // Lookup the compatibility level - auto compat = co_await get_compatibility(sub, default_to_global::yes); - if (compat == compatibility_level::none) { - co_return true; + co_return compatibility_result{.is_compat = true}; } // Currently support PROTOBUF, AVRO @@ -644,7 +744,8 @@ ss::future sharded_store::is_compatible( throw as_exception(invalid_schema_type(new_schema.type())); } - // if transitive, search all, otherwise seach forwards from version + // search backwards + // if transitive, search all, seach until version if ( compat == compatibility_level::backward_transitive || compat == compatibility_level::forward_transitive @@ -652,34 +753,86 @@ ss::future sharded_store::is_compatible( ver_it = versions.begin(); } - auto new_valid = co_await make_valid_schema(new_schema); + auto it = std::reverse_iterator(versions.end()); + auto it_end = std::reverse_iterator(ver_it); + + auto new_valid = co_await make_valid_schema(std::move(new_schema)); - auto is_compat = true; - for (; is_compat && ver_it != versions.end(); ++ver_it) { - if (ver_it->deleted) { + compatibility_result result{.is_compat = true}; + + auto formatter = [](std::string_view rdr, std::string_view wrtr) { + return [rdr, wrtr](std::string_view msg) { + return fmt::format( + fmt::runtime(msg), + fmt::arg("reader", rdr), + fmt::arg("writer", wrtr)); + }; + }; + + for (; result.is_compat && it != it_end; ++it) { + if (it->deleted) { continue; } auto old_schema = co_await get_subject_schema( - sub, ver_it->version, include_deleted::no); - auto old_valid = co_await make_valid_schema(old_schema.schema); + sub, it->version, include_deleted::no); + auto old_valid = co_await make_valid_schema( + std::move(old_schema.schema)); + + std::vector version_messages; if ( compat == compatibility_level::backward || compat == compatibility_level::backward_transitive || compat == compatibility_level::full || compat == compatibility_level::full_transitive) { - is_compat = is_compat && check_compatible(new_valid, old_valid); + auto r = check_compatible(new_valid, old_valid, is_verbose); + result.is_compat = result.is_compat && r.is_compat; + version_messages.reserve( + version_messages.size() + r.messages.size()); + std::transform( + std::make_move_iterator(r.messages.begin()), + std::make_move_iterator(r.messages.end()), + std::back_inserter(version_messages), + formatter("new", "old")); } if ( compat == compatibility_level::forward || compat == compatibility_level::forward_transitive || compat == compatibility_level::full || compat == compatibility_level::full_transitive) { - is_compat = is_compat && check_compatible(old_valid, new_valid); + auto r = check_compatible(old_valid, new_valid, is_verbose); + result.is_compat = result.is_compat && r.is_compat; + version_messages.reserve( + version_messages.size() + r.messages.size()); + std::transform( + std::make_move_iterator(r.messages.begin()), + std::make_move_iterator(r.messages.end()), + std::back_inserter(version_messages), + formatter("old", "new")); } + + if (is_verbose && !result.is_compat) { + version_messages.emplace_back( + fmt::format("{{oldSchemaVersion: {}}}", old_schema.version)); + version_messages.emplace_back( + fmt::format("{{oldSchema: '{}'}}", to_string(old_valid.raw()))); + version_messages.emplace_back( + fmt::format("{{compatibility: '{}'}}", compat)); + } + + result.messages.reserve( + result.messages.size() + version_messages.size()); + std::move( + version_messages.begin(), + version_messages.end(), + std::back_inserter(result.messages)); } - co_return is_compat; + co_return result; +} + +void sharded_store::check_mode_mutability(force f) const { + _store.local().check_mode_mutability(f).value(); } ss::future sharded_store::has_version( diff --git a/src/v/pandaproxy/schema_registry/sharded_store.h b/src/v/pandaproxy/schema_registry/sharded_store.h index d8f387aa32a1a..838f4993c9259 100644 --- a/src/v/pandaproxy/schema_registry/sharded_store.h +++ b/src/v/pandaproxy/schema_registry/sharded_store.h @@ -12,6 +12,7 @@ #pragma once #include "pandaproxy/schema_registry/types.h" +#include "utils/fragmented_vector.h" #include @@ -23,11 +24,12 @@ class store; /// subject or schema_id class sharded_store { public: - ss::future<> start(ss::smp_service_group sg); + ss::future<> start(is_mutable mut, ss::smp_service_group sg); ss::future<> stop(); ///\brief Make the canonical form of the schema - ss::future make_canonical_schema(unparsed_schema schema); + ss::future make_canonical_schema( + unparsed_schema schema, normalize norm = normalize::no); ///\brief Check the schema parses with the native format ss::future validate_schema(canonical_schema schema); @@ -63,17 +65,18 @@ class sharded_store { is_deleted deleted); ss::future has_schema(schema_id id); - ss::future has_schema(canonical_schema schema); + ss::future has_schema( + canonical_schema schema, include_deleted inc_del = include_deleted::no); ///\brief Return a schema definition by id. ss::future get_schema_definition(schema_id id); ///\brief Return a list of subject-versions for the shema id. - ss::future> + ss::future> get_schema_subject_versions(schema_id id); ///\brief Return a list of subjects for the schema id. - ss::future> + ss::future> get_schema_subjects(schema_id id, include_deleted inc_del); ///\brief Return a schema by subject and version (or latest). @@ -83,7 +86,12 @@ class sharded_store { include_deleted inc_dec); ///\brief Return a list of subjects. - ss::future> get_subjects(include_deleted inc_del); + ss::future> get_subjects( + include_deleted inc_del, + std::optional subject_prefix = std::nullopt); + + ///\brief Return whether there are any subjects. + ss::future has_subjects(include_deleted inc_del); ///\brief Return a list of versions and associated schema_id. ss::future> @@ -113,6 +121,11 @@ class sharded_store { ss::future> get_subject_config_written_at(subject sub); + ///\brief Get sequence number history of subject mode. Subject need + /// not be soft-deleted first + ss::future> + get_subject_mode_written_at(subject sub); + ///\brief Get sequence number history (errors out if not soft-deleted) ss::future> get_subject_version_written_at(subject sub, schema_version version); @@ -122,6 +135,24 @@ class sharded_store { ss::future delete_subject_version( subject sub, schema_version version, force f = force::no); + ///\brief Get the global mode. + ss::future get_mode(); + + ///\brief Get the mode for a subject, or fallback to global. + ss::future get_mode(subject sub, default_to_global fallback); + + ///\brief Set the global mode. + /// \param force Override checks, always apply action + ss::future set_mode(mode m, force f); + + ///\brief Set the mode for a subject. + /// \param force Override checks, always apply action + ss::future set_mode(seq_marker marker, subject sub, mode m, force f); + + ///\brief Clear the mode for a subject. + /// \param force Override checks, always apply action + ss::future clear_mode(seq_marker marker, subject sub, force f); + ///\brief Get the global compatibility level. ss::future get_compatibility(); @@ -147,11 +178,27 @@ class sharded_store { ss::future is_compatible(schema_version version, canonical_schema new_schema); + ///\brief Check if the provided schema is compatible with the subject and + /// version, according the the current compatibility, with the result + /// optionally accompanied by a vector of detailed error messages. + /// + /// If the compatibility level is transitive, then all versions are checked, + /// otherwise checks are against the version provided and newer. + ss::future is_compatible( + schema_version version, canonical_schema new_schema, verbose is_verbose); + ss::future has_version(const subject&, schema_id, include_deleted); + //// \brief Throw if the store is not mutable + void check_mode_mutability(force f) const; + private: + ss::future do_is_compatible( + schema_version version, canonical_schema new_schema, verbose is_verbose); + ss::future upsert_schema(schema_id id, canonical_schema_definition def); + ss::future<> delete_schema(schema_id id); struct insert_subject_result { schema_version version; diff --git a/src/v/pandaproxy/schema_registry/storage.h b/src/v/pandaproxy/schema_registry/storage.h index b39ab8a1898a2..f3c940fc930a4 100644 --- a/src/v/pandaproxy/schema_registry/storage.h +++ b/src/v/pandaproxy/schema_registry/storage.h @@ -11,9 +11,8 @@ #pragma once -#include "bytes/iobuf_parser.h" +#include "json/iobuf_writer.h" #include "json/json.h" -#include "json/stringbuffer.h" #include "json/types.h" #include "json/writer.h" #include "model/metadata.h" @@ -37,7 +36,8 @@ namespace pandaproxy::schema_registry { using topic_key_magic = named_type; -enum class topic_key_type { noop = 0, schema, config, delete_subject }; +enum class topic_key_type { noop = 0, schema, config, mode, delete_subject }; + constexpr std::string_view to_string_view(topic_key_type kt) { switch (kt) { case topic_key_type::noop: @@ -46,6 +46,8 @@ constexpr std::string_view to_string_view(topic_key_type kt) { return "SCHEMA"; case topic_key_type::config: return "CONFIG"; + case topic_key_type::mode: + return "MODE"; case topic_key_type::delete_subject: return "DELETE_SUBJECT"; } @@ -58,6 +60,7 @@ from_string_view(std::string_view sv) { .match(to_string_view(topic_key_type::noop), topic_key_type::noop) .match(to_string_view(topic_key_type::schema), topic_key_type::schema) .match(to_string_view(topic_key_type::config), topic_key_type::config) + .match(to_string_view(topic_key_type::mode), topic_key_type::mode) .match( to_string_view(topic_key_type::delete_subject), topic_key_type::delete_subject) @@ -158,9 +161,9 @@ struct schema_key { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const schema_registry::schema_key& key) { +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::schema_key& key) { w.StartObject(); w.Key("keytype"); ::json::rjson_serialize(w, to_string_view(key.keytype)); @@ -321,9 +324,9 @@ struct schema_value { using unparsed_schema_value = schema_value; using canonical_schema_value = schema_value; -template -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, const schema_value& val) { +template +void rjson_serialize( + ::json::iobuf_writer& w, const schema_value& val) { w.StartObject(); w.Key("subject"); ::json::rjson_serialize(w, val.schema.sub()); @@ -642,9 +645,9 @@ struct config_key { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const schema_registry::config_key& key) { +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::config_key& key) { w.StartObject(); w.Key("keytype"); ::json::rjson_serialize(w, to_string_view(key.keytype)); @@ -780,9 +783,9 @@ struct config_value { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, - const schema_registry::config_value& val) { +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::config_value& val) { w.StartObject(); if (val.sub.has_value()) { w.Key("subject"); @@ -847,6 +850,241 @@ class config_value_handler : public json::base_handler { } }; +struct mode_key { + static constexpr topic_key_type keytype{topic_key_type::mode}; + std::optional seq; + std::optional node; + std::optional sub; + topic_key_magic magic{0}; + + friend bool operator==(const mode_key&, const mode_key&) = default; + + friend std::ostream& operator<<(std::ostream& os, const mode_key& v) { + if (v.seq.has_value() && v.node.has_value()) { + fmt::print( + os, + "seq: {} node: {} keytype: {}, subject: {}, magic: {}", + *v.seq, + *v.node, + to_string_view(v.keytype), + v.sub.value_or(invalid_subject), + v.magic); + } else { + fmt::print( + os, + "unsequenced keytype: {}, subject: {}, magic: {}", + to_string_view(v.keytype), + v.sub.value_or(invalid_subject), + v.magic); + } + return os; + } +}; + +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::mode_key& key) { + w.StartObject(); + w.Key("keytype"); + ::json::rjson_serialize(w, to_string_view(key.keytype)); + w.Key("subject"); + if (key.sub) { + ::json::rjson_serialize(w, key.sub.value()); + } else { + w.Null(); + } + w.Key("magic"); + ::json::rjson_serialize(w, key.magic); + if (key.seq.has_value()) { + w.Key("seq"); + ::json::rjson_serialize(w, *key.seq); + } + if (key.node.has_value()) { + w.Key("node"); + ::json::rjson_serialize(w, *key.node); + } + w.EndObject(); +} + +template> +class mode_key_handler : public json::base_handler { + enum class state { + empty = 0, + object, + keytype, + seq, + node, + subject, + magic, + }; + state _state = state::empty; + +public: + using Ch = typename json::base_handler::Ch; + using rjson_parse_result = mode_key; + rjson_parse_result result; + + mode_key_handler() + : json::base_handler{json::serialization_format::none} {} + + bool Key(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + std::optional s{string_switch>(sv) + .match("keytype", state::keytype) + .match("seq", state::seq) + .match("node", state::node) + .match("subject", state::subject) + .match("magic", state::magic) + .default_match(std::nullopt)}; + return s.has_value() && std::exchange(_state, *s) == state::object; + } + + bool Uint(int i) { + switch (_state) { + case state::magic: { + result.magic = topic_key_magic{i}; + _state = state::object; + return true; + } + case state::seq: { + result.seq = model::offset{i}; + _state = state::object; + return true; + } + case state::node: { + result.node = model::node_id{i}; + _state = state::object; + return true; + } + case state::empty: + case state::subject: + case state::keytype: + case state::object: + return false; + } + return false; + } + + bool String(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + switch (_state) { + case state::keytype: { + auto kt = from_string_view(sv); + _state = state::object; + return kt == result.keytype; + } + case state::subject: { + result.sub = subject{ss::sstring{sv}}; + _state = state::object; + return true; + } + case state::empty: + case state::seq: + case state::node: + case state::object: + case state::magic: + return false; + } + return false; + } + + bool Null() { + // The subject, and only the subject, is nullable. + return std::exchange(_state, state::object) == state::subject; + } + + bool StartObject() { + return std::exchange(_state, state::object) == state::empty; + } + + bool EndObject(::json::SizeType) { + return result.seq.has_value() == result.node.has_value() + && std::exchange(_state, state::empty) == state::object; + } +}; + +struct mode_value { + mode mode{mode::read_write}; + std::optional sub; + + friend bool operator==(const mode_value&, const mode_value&) = default; + + friend std::ostream& operator<<(std::ostream& os, const mode_value& v) { + if (v.sub.has_value()) { + fmt::print(os, "subject: {}, ", v.sub.value()); + } + fmt::print(os, "mode: {}", to_string_view(v.mode)); + + return os; + } +}; + +template +void rjson_serialize( + ::json::Writer& w, const schema_registry::mode_value& val) { + w.StartObject(); + if (val.sub.has_value()) { + w.Key("subject"); + ::json::rjson_serialize(w, val.sub.value()); + } + w.Key("mode"); + ::json::rjson_serialize(w, to_string_view(val.mode)); + w.EndObject(); +} + +template> +class mode_value_handler : public json::base_handler { + enum class state { + empty = 0, + object, + mode, + subject, + }; + state _state = state::empty; + +public: + using Ch = typename json::base_handler::Ch; + using rjson_parse_result = mode_value; + rjson_parse_result result; + + mode_value_handler() + : json::base_handler{json::serialization_format::none} {} + + bool Key(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + std::optional s{string_switch>(sv) + .match("mode", state::mode) + .match("subject", state::subject) + .default_match(std::nullopt)}; + return s.has_value() && std::exchange(_state, *s) == state::object; + } + + bool String(const Ch* str, ::json::SizeType len, bool) { + auto sv = std::string_view{str, len}; + if (_state == state::mode) { + auto s = from_string_view(sv); + if (s.has_value()) { + result.mode = *s; + _state = state::object; + } + return s.has_value(); + } else if (_state == state::subject) { + result.sub.emplace(sv); + _state = state::object; + return true; + } + return false; + } + + bool StartObject() { + return std::exchange(_state, state::object) == state::empty; + } + + bool EndObject(::json::SizeType) { + return std::exchange(_state, state::empty) == state::object; + } +}; + struct delete_subject_key { static constexpr topic_key_type keytype{topic_key_type::delete_subject}; std::optional seq; @@ -880,8 +1118,8 @@ struct delete_subject_key { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, const delete_subject_key& key) { +template +void rjson_serialize(::json::Writer& w, const delete_subject_key& key) { w.StartObject(); w.Key("keytype"); ::json::rjson_serialize(w, to_string_view(key.keytype)); @@ -1021,8 +1259,9 @@ struct delete_subject_value { } }; -inline void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, const delete_subject_value& val) { +template +void rjson_serialize( + ::json::Writer& w, const delete_subject_value& val) { w.StartObject(); w.Key("subject"); ::json::rjson_serialize(w, val.sub); @@ -1110,17 +1349,13 @@ class delete_subject_value_handler : public json::base_handler { template auto from_json_iobuf(iobuf&& iobuf, Args&&... args) { - auto p = iobuf_parser(std::move(iobuf)); - auto str = p.read_string(p.bytes_left()); - return json::rjson_parse(str.data(), Handler{std::forward(args)...}); + return json::rjson_parse( + std::move(iobuf), Handler{std::forward(args)...}); } template -auto to_json_iobuf(T t) { - auto val_js = json::rjson_serialize(t); - iobuf buf; - buf.append(val_js.data(), val_js.size()); - return buf; +auto to_json_iobuf(T&& t) { + return json::rjson_serialize_iobuf(std::forward(t)); } template @@ -1189,7 +1424,18 @@ struct consume_to_store { val); break; } - case topic_key_type::delete_subject: + case topic_key_type::mode: { + std::optional val; + if (!record.value().empty()) { + auto value = record.release_value(); + val.emplace( + from_json_iobuf>(std::move(value))); + } + co_await apply( + offset, from_json_iobuf>(std::move(key)), val); + break; + } + case topic_key_type::delete_subject: { std::optional val; if (!record.value().empty()) { val.emplace(from_json_iobuf>( @@ -1202,6 +1448,7 @@ struct consume_to_store { std::move(val)); break; } + } co_await _sequencer.advance_offset(offset); } @@ -1332,6 +1579,61 @@ struct consume_to_store { } } + ss::future<> + apply(model::offset offset, mode_key key, std::optional val) { + // Drop out-of-sequence messages + // + // Check seq if it was provided, otherwise assume 3rdparty + // compatibility, which can't collide. + if (val && key.seq.has_value() && offset != key.seq) { + vlog( + plog.debug, + "Ignoring out of order {} (at offset {})", + key, + offset); + co_return; + } + + if (key.magic != 0) { + throw exception( + error_code::topic_parse_error, + fmt::format("Unexpected magic: {}", key)); + } + try { + vlog(plog.debug, "Applying: {}", key); + if (key.sub.has_value()) { + if (!val.has_value()) { + co_await _store.clear_mode( + seq_marker{ + .seq = key.seq, + .node = key.node, + .version{invalid_schema_version}, // Not applicable + .key_type = seq_marker_key_type::mode}, + *key.sub, + force::yes); + } else { + co_await _store.set_mode( + seq_marker{ + .seq = key.seq, + .node = key.node, + .version{invalid_schema_version}, // Not applicable + .key_type = seq_marker_key_type::mode}, + *key.sub, + val->mode, + force::yes); + } + } else if (val.has_value()) { + co_await _store.set_mode(val->mode, force::yes); + } else { + vlog( + plog.warn, + "Tried to apply mode with neither subject nor value"); + } + } catch (const exception& e) { + vlog(plog.debug, "Error replaying: {}: {}", key, e); + } + } + ss::future<> apply( model::offset offset, delete_subject_key key, @@ -1380,6 +1682,7 @@ struct consume_to_store { vlog(plog.debug, "Error replaying: {}: {}", key, e); } } + void end_of_stream() {} sharded_store& _store; seq_writer& _sequencer; diff --git a/src/v/pandaproxy/schema_registry/store.h b/src/v/pandaproxy/schema_registry/store.h index a785354922a87..b636612f501d5 100644 --- a/src/v/pandaproxy/schema_registry/store.h +++ b/src/v/pandaproxy/schema_registry/store.h @@ -15,6 +15,7 @@ #include "pandaproxy/schema_registry/error.h" #include "pandaproxy/schema_registry/errors.h" #include "pandaproxy/schema_registry/types.h" +#include "utils/fragmented_vector.h" #include #include @@ -59,6 +60,11 @@ class store { public: using schema_id_set = absl::btree_set; + explicit store() = default; + + explicit store(is_mutable mut) + : _mutable(mut) {} + struct insert_result { schema_version version; schema_id id; @@ -72,9 +78,9 @@ class store { /// /// return the schema_version and schema_id, and whether it's new. insert_result insert(canonical_schema schema) { - auto id = insert_schema(std::move(schema).def()).id; - // NOLINTNEXTLINE(bugprone-use-after-move) - auto [version, inserted] = insert_subject(std::move(schema).sub(), id); + auto [sub, def] = std::move(schema).destructure(); + auto id = insert_schema(std::move(def)).id; + auto [version, inserted] = insert_subject(std::move(sub), id); return {version, id, inserted}; } @@ -85,7 +91,7 @@ class store { if (it == _schemas.end()) { return not_found(id); } - return {it->second.definition}; + return {it->second.definition.copy()}; } ///\brief Return the id of the schema, if it already exists. @@ -101,8 +107,8 @@ class store { } ///\brief Return a list of subject-versions for the shema id. - std::vector get_schema_subject_versions(schema_id id) { - std::vector svs; + chunked_vector get_schema_subject_versions(schema_id id) { + chunked_vector svs; for (const auto& s : _subjects) { for (const auto& vs : s.second.versions) { if (vs.id == id && !vs.deleted) { @@ -114,9 +120,9 @@ class store { } ///\brief Return a list of subjects for the schema id. - std::vector + chunked_vector get_schema_subjects(schema_id id, include_deleted inc_del) { - std::vector subs; + chunked_vector subs; for (const auto& s : _subjects) { if (absl::c_any_of( s.second.versions, [id, inc_del](const auto& vs) { @@ -170,15 +176,19 @@ class store { } ///\brief Return a list of subjects. - std::vector get_subjects(include_deleted inc_del) const { - std::vector res; + chunked_vector get_subjects( + include_deleted inc_del, + const std::optional& subject_prefix = std::nullopt) const { + chunked_vector res; res.reserve(_subjects.size()); for (const auto& sub : _subjects) { if (inc_del || !sub.second.deleted) { auto has_version = absl::c_any_of( sub.second.versions, [inc_del](auto const& v) { return inc_del || !v.deleted; }); - if (has_version) { + if ( + has_version + && sub.first().starts_with(subject_prefix.value_or(""))) { res.push_back(sub.first); } } @@ -186,6 +196,15 @@ class store { return res; } + ///\brief Return if there are subjects. + bool has_subjects(include_deleted inc_del) const { + return absl::c_any_of(_subjects, [inc_del](auto const& sub) { + return absl::c_any_of( + sub.second.versions, + [inc_del](auto const& v) { return inc_del || !v.deleted; }); + }); + } + ///\brief Return a list of versions and associated schema_id. result> get_versions(const subject& sub, include_deleted inc_del) const { @@ -274,6 +293,34 @@ class store { return result; } + /// \brief Return the seq_marker write history of a subject, but only + /// mode_keys + /// + /// \return A vector (possibly empty) + result> + get_subject_mode_written_at(const subject& sub) const { + auto sub_it = BOOST_OUTCOME_TRYX( + get_subject_iter(sub, include_deleted::yes)); + + // This should never happen (how can a record get into the + // store without an originating sequenced record?), but return + // an error instead of vasserting out. + if (sub_it->second.written_at.empty()) { + return not_found(sub); + } + + std::vector result; + std::copy_if( + sub_it->second.written_at.begin(), + sub_it->second.written_at.end(), + std::back_inserter(result), + [](const auto& sm) { + return sm.key_type == seq_marker_key_type::mode; + }); + + return result; + } + /// \brief Return the seq_marker write history of a version. /// /// \return A vector with at least one element @@ -341,7 +388,14 @@ class store { result> get_version_ids(const subject& sub, include_deleted inc_del) const { auto sub_it = BOOST_OUTCOME_TRYX(get_subject_iter(sub, inc_del)); - return sub_it->second.versions; + std::vector res; + absl::c_copy_if( + sub_it->second.versions, + std::back_inserter(res), + [inc_del](const subject_version_entry& e) { + return inc_del || !e.deleted; + }); + return {std::move(res)}; } ///\brief Return whether this subject has a version that references the @@ -350,8 +404,8 @@ class store { const subject& sub, schema_id id, include_deleted inc_del) const { auto sub_it = BOOST_OUTCOME_TRYX(get_subject_iter(sub, inc_del)); const auto& vs = sub_it->second.versions; - return std::any_of(vs.cbegin(), vs.cend(), [id](const auto& entry) { - return entry.id == id; + return absl::c_any_of(vs, [id, inc_del](const auto& entry) { + return entry.id == id && (inc_del || !entry.deleted); }); } @@ -379,11 +433,13 @@ class store { return has_ids; } - bool subject_versions_has_any_of(const schema_id_set& ids) { - return absl::c_any_of(_subjects, [&ids](const auto& s) { - return absl::c_any_of(s.second.versions, [&ids, &s](const auto& v) { - return !s.second.deleted && ids.contains(v.id); - }); + bool subject_versions_has_any_of( + const schema_id_set& ids, include_deleted inc_del) { + return absl::c_any_of(_subjects, [&ids, inc_del](const auto& s) { + return absl::c_any_of( + s.second.versions, [&ids, &s, inc_del](const auto& v) { + return (inc_del || !s.second.deleted) && ids.contains(v.id); + }); }); } @@ -464,6 +520,46 @@ class store { return true; } + ///\brief Get the global mode. + result get_mode() const { return _mode; } + + ///\brief Get the mode for a subject, or fallback to global. + result + get_mode(const subject& sub, default_to_global fallback) const { + auto sub_it = get_subject_iter(sub, include_deleted::yes); + if (sub_it && (sub_it.assume_value())->second.mode.has_value()) { + return (sub_it.assume_value())->second.mode.value(); + } else if (fallback) { + return _mode; + } + return mode_not_found(sub); + } + + ///\brief Set the global mode. + result set_mode(mode m, force f) { + BOOST_OUTCOME_TRYX(check_mode_mutability(f)); + return std::exchange(_mode, m) != m; + } + + ///\brief Set the mode for a subject. + result + set_mode(seq_marker marker, const subject& sub, mode m, force f) { + BOOST_OUTCOME_TRYX(check_mode_mutability(f)); + auto& sub_entry = _subjects[sub]; + sub_entry.written_at.push_back(marker); + return std::exchange(sub_entry.mode, m) != m; + } + + ///\brief Clear the mode for a subject. + result + clear_mode(const seq_marker& marker, const subject& sub, force f) { + BOOST_OUTCOME_TRYX(check_mode_mutability(f)); + auto sub_it = BOOST_OUTCOME_TRYX( + get_subject_iter(sub, include_deleted::yes)); + std::erase(sub_it->second.written_at, marker); + return std::exchange(sub_it->second.mode, std::nullopt) != std::nullopt; + } + ///\brief Get the global compatibility level. result get_compatibility() const { return _compatibility; @@ -536,6 +632,8 @@ class store { .second; } + void delete_schema(schema_id id) { _schemas.erase(id); } + struct insert_subject_result { schema_version version; bool inserted; @@ -599,6 +697,16 @@ class store { return !found; } + //// \brief Return error if the store is not mutable + result check_mode_mutability(force f) const { + if (!_mutable && !f) { + return error_info{ + error_code::subject_version_operation_not_permitted, + "Mode changes are not allowed"}; + } + return outcome::success(); + } + private: struct schema_entry { explicit schema_entry(canonical_schema_definition definition) @@ -609,6 +717,7 @@ class store { struct subject_entry { std::optional compatibility; + std::optional mode; std::vector versions; is_deleted deleted{false}; @@ -673,6 +782,8 @@ class store { schema_map _schemas; subject_map _subjects; compatibility_level _compatibility{compatibility_level::backward}; + mode _mode{mode::read_write}; + is_mutable _mutable{is_mutable::no}; }; } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/test/CMakeLists.txt b/src/v/pandaproxy/schema_registry/test/CMakeLists.txt index 77709c84537cb..52c9786ddc1ea 100644 --- a/src/v/pandaproxy/schema_registry/test/CMakeLists.txt +++ b/src/v/pandaproxy/schema_registry/test/CMakeLists.txt @@ -12,6 +12,17 @@ rp_test( LABELS pandaproxy ) +rp_test( + UNIT_TEST + BINARY_NAME pandaproxy_schema_registry_multi_thread + SOURCES + mt_sharded_store.cc + DEFINITIONS BOOST_TEST_DYN_LINK + LIBRARIES v::seastar_testing_main v_pandaproxy_schema_registry + ARGS "-- -c 4" + LABELS pandaproxy +) + rp_test( UNIT_TEST BINARY_NAME pandaproxy_schema_registry_single_thread diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_3rdparty.cc b/src/v/pandaproxy/schema_registry/test/compatibility_3rdparty.cc index 060882aa5e78c..e66acb390e980 100644 --- a/src/v/pandaproxy/schema_registry/test/compatibility_3rdparty.cc +++ b/src/v/pandaproxy/schema_registry/test/compatibility_3rdparty.cc @@ -22,21 +22,33 @@ #include #include -#include +#include #include namespace pps = pandaproxy::schema_registry; +namespace pandaproxy::schema_registry { + +std::ostream& operator<<(std::ostream& os, mode m) { + return os << to_string_view(m); +} + +} // namespace pandaproxy::schema_registry + inline model::record_batch make_record_batch( - std::string_view key, std::string_view val, model::offset base_offset) { + std::string_view key, + std::optional val, + model::offset base_offset) { storage::record_batch_builder rb{ model::record_batch_type::raft_data, base_offset}; iobuf key_buf; - iobuf val_buf; + std::optional val_buf; key_buf.append(key.data(), key.size()); - val_buf.append(val.data(), val.size()); - + if (val) { + val_buf = iobuf(); + val_buf->append(val->data(), val->size()); + } rb.add_raw_kv(std::move(key_buf), std::move(val_buf)); return std::move(rb).build(); } @@ -46,6 +58,12 @@ constexpr std::string_view config_key_0{ constexpr std::string_view config_value_0{ R"({"compatibilityLevel":"BACKWARD"})"}; +constexpr std::string_view mode_key_0{R"({"keytype":"MODE","magic":0})"}; +constexpr std::string_view mode_key_sub_0{ + R"({"keytype":"MODE","subject":"subject_0","magic":0})"}; +constexpr std::string_view mode_value_rw{R"({"mode":"READWRITE"})"}; +constexpr std::string_view mode_value_ro{R"({"mode":"READONLY"})"}; + constexpr std::string_view schema_key_0{ R"({"keytype":"SCHEMA","subject":"subject_0","version":1,"magic":1})"}; constexpr std::string_view schema_value_0{ @@ -58,7 +76,7 @@ constexpr std::string_view del_sub_value_0{ SEASTAR_THREAD_TEST_CASE(test_consume_to_store_3rdparty) { pps::sharded_store s; - s.start(ss::default_smp_service_group()).get(); + s.start(pps::is_mutable::yes, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&s]() { s.stop().get(); }); // This kafka client will not be used by the sequencer @@ -112,4 +130,75 @@ SEASTAR_THREAD_TEST_CASE(test_consume_to_store_3rdparty) { [](const pps::exception& e) { return e.code() == pps::error_code::subject_not_found; }); + + // perm delete sub version + BOOST_REQUIRE_NO_THROW( + c(make_record_batch(schema_key_0, std::nullopt, base_offset++)).get()); + + // Test mode default + BOOST_REQUIRE_EQUAL(c._store.get_mode().get(), pps::mode::read_write); + + // Test mode READONLY + BOOST_REQUIRE_NO_THROW( + c(make_record_batch(mode_key_0, mode_value_ro, base_offset++)).get()); + BOOST_REQUIRE_EQUAL(c._store.get_mode().get(), pps::mode::read_only); + + // Test mode no subject, no fallback + BOOST_REQUIRE_EXCEPTION( + c._store.get_mode(pps::subject{"subject_0"}, pps::default_to_global::no) + .get(), + pps::exception, + [](const pps::exception& e) { + return e.code() == pps::error_code::mode_not_found; + }); + + // Test mode no subject, with fallback + BOOST_REQUIRE_EQUAL( + c._store.get_mode(pps::subject{"subject_0"}, pps::default_to_global::yes) + .get(), + pps ::mode::read_only); + + // test mode READWRITE + BOOST_REQUIRE_NO_THROW( + c(make_record_batch(mode_key_0, mode_value_rw, base_offset++)).get()); + BOOST_REQUIRE_EQUAL(c._store.get_mode().get(), pps::mode::read_write); + + // test mode subject override + BOOST_REQUIRE_NO_THROW( + c(make_record_batch(mode_key_sub_0, mode_value_ro, base_offset++)).get()); + BOOST_REQUIRE_EQUAL( + c._store.get_mode(pps::subject{"subject_0"}, pps::default_to_global::no) + .get(), + pps::mode::read_only); + + // test subject is not found + BOOST_REQUIRE_EXCEPTION( + c._store.get_versions(pps::subject{"subject_0"}, pps::include_deleted::no) + .get(), + pps::exception, + [](const pps::exception& e) { + return e.code() == pps::error_code::subject_not_found; + }); + + // test subject is not found + BOOST_REQUIRE_EXCEPTION( + c._store + .get_versions(pps::subject{"subject_0"}, pps::include_deleted::yes) + .get(), + pps::exception, + [](const pps::exception& e) { + return e.code() == pps::error_code::subject_not_found; + }); + + // clear mode subject override + c(make_record_batch(mode_key_sub_0, std::nullopt, base_offset++)).get(); + BOOST_REQUIRE_EXCEPTION( + c._store.get_mode(pps::subject{"subject_0"}, pps::default_to_global::no) + .get(), + pps::exception, + [](const pps::exception& e) { + return e.code() == pps::error_code::mode_not_found; + }); + + // Add a subject version } diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_avro.cc b/src/v/pandaproxy/schema_registry/test/compatibility_avro.cc index 79decc2cc31c0..2c92bd5d1ec3a 100644 --- a/src/v/pandaproxy/schema_registry/test/compatibility_avro.cc +++ b/src/v/pandaproxy/schema_registry/test/compatibility_avro.cc @@ -11,26 +11,52 @@ #include "pandaproxy/schema_registry/avro.h" #include "pandaproxy/schema_registry/sharded_store.h" +#include "pandaproxy/schema_registry/test/compatibility_common.h" #include "pandaproxy/schema_registry/types.h" #include +#include +#include +#include + +#include + namespace pp = pandaproxy; namespace pps = pp::schema_registry; +namespace { + bool check_compatible( + const pps::canonical_schema_definition& r, + const pps::canonical_schema_definition& w) { + pps::sharded_store s; + return check_compatible( + pps::make_avro_schema_definition( + s, {pps::subject("r"), {r.shared_raw(), pps::schema_type::avro}}) + .get(), + pps::make_avro_schema_definition( + s, {pps::subject("w"), {w.shared_raw(), pps::schema_type::avro}}) + .get()) + .is_compat; +} + +pps::compatibility_result check_compatible_verbose( const pps::canonical_schema_definition& r, const pps::canonical_schema_definition& w) { pps::sharded_store s; return check_compatible( pps::make_avro_schema_definition( - s, {pps::subject("r"), {r.raw(), pps::schema_type::avro}}) + s, {pps::subject("r"), {r.shared_raw(), pps::schema_type::avro}}) .get(), pps::make_avro_schema_definition( - s, {pps::subject("w"), {w.raw(), pps::schema_type::avro}}) - .get()); + s, {pps::subject("w"), {w.shared_raw(), pps::schema_type::avro}}) + .get(), + pps::verbose::yes); } +} // namespace + SEASTAR_THREAD_TEST_CASE(test_avro_type_promotion) { BOOST_REQUIRE(check_compatible(schema_long, schema_int)); BOOST_REQUIRE(check_compatible(schema_float, schema_int)); @@ -239,10 +265,11 @@ SEASTAR_THREAD_TEST_CASE(test_avro_schema_definition) { R"({"type":"record","name":"myrecord","fields":[{"name":"f1","type":"string"},{"name":"f2","type":"string","default":"foo"}]})", pps::schema_type::avro}; pps::sharded_store s; - auto valid - = pps::make_avro_schema_definition( - s, {pps::subject("s2"), {schema2.raw(), pps::schema_type::avro}}) - .get(); + auto valid = pps::make_avro_schema_definition( + s, + {pps::subject("s2"), + {schema2.shared_raw(), pps::schema_type::avro}}) + .get(); static_assert( std:: is_same_v, pps::avro_schema_definition>, @@ -267,7 +294,8 @@ SEASTAR_THREAD_TEST_CASE(test_avro_schema_definition_custom_attributes) { auto valid = pps::make_avro_schema_definition( s, {pps::subject("s2"), - {avro_metadata_schema.raw(), pps::schema_type::avro}}) + {avro_metadata_schema.shared_raw(), + pps::schema_type::avro}}) .get(); static_assert( std:: @@ -276,3 +304,267 @@ SEASTAR_THREAD_TEST_CASE(test_avro_schema_definition_custom_attributes) { pps::canonical_schema_definition avro_conversion{valid}; BOOST_CHECK_EQUAL(expected, avro_conversion); } + +SEASTAR_THREAD_TEST_CASE(test_avro_alias_resolution_stopgap) { + auto writer = avro::compileJsonSchemaFromString( + R"({"type":"record","fields":[{"name":"bar","type":"float"}],"name":"foo"})"); + + auto reader = avro::compileJsonSchemaFromString( + R"({"type":"record","fields":[{"name":"bar","type":"float"}],"name":"foo_renamed","aliases":["foo"]})"); + + auto& writer_root = *writer.root(); + auto& reader_root = *reader.root(); + + // This should resolve to true but it currently resolves to false because + // the Avro library doesn't fully support handling schema resolution with + // aliases yet. When the avro library supports alias resolution this test + // will fail and we should then update the compat check to don't report an + // incompatibility when the record names are properly aliased. + BOOST_CHECK(!writer_root.resolve(reader_root)); +} + +namespace { + +const auto schema_old = pps::sanitize_avro_schema_definition( + { + R"({ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "f1", + "type": "string" + }, + { + "name": "f2", + "type": { + "name": "nestedRec", + "type": "record", + "fields": [ + { + "name": "nestedF", + "type": "string" + } + ] + } + }, + { + "name": "uF", + "type": ["int", "string"] + }, + { + "name": "enumF", + "type": { + "name": "ABorC", + "type": "enum", + "symbols": ["a", "b", "c"] + } + }, + { + "name": "fixedF", + "type": { + "type": "fixed", + "name": "fixedT", + "size": 1 + } + }, + { + "name": "oldUnion", + "type": ["int", "string"] + }, + { + "name": "newUnion", + "type": "int" + }, + { + "name": "someList", + "type": { + "type": "array", + "items": "int" + } + }, + { + "name": "otherEnumF", + "type": { + "type": "enum", + "name": "someEnum1", + "symbols" : ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"] + } + } + ] +})", + pps::schema_type::avro}) + .value(); + +const auto schema_new = pps::sanitize_avro_schema_definition( + { + R"({ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "f1", + "type": "int" + }, + { + "name": "f2", + "type": { + "name": "nestedRec2", + "type": "record", + "fields": [ + { + "name": "broken", + "type": "string" + } + ] + } + }, + { + "name": "uF", + "type": ["string"] + }, + { + "name": "enumF", + "type": { + "name": "ABorC", + "type": "enum", + "symbols": ["a"] + } + }, + { + "name": "fixedF", + "type": { + "type": "fixed", + "name": "fixedT", + "size": 2 + } + }, + { + "name": "oldUnion", + "type": "boolean" + }, + { + "name": "newUnion", + "type": ["boolean", "string"] + }, + { + "name": "someList", + "type": { + "type": "array", + "items": "long" + } + }, + { + "name": "otherEnumF", + "type": { + "type": "enum", + "name": "someEnum2", + "aliases": ["someEnum1"], + "symbols" : ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"] + } + } + ] +})", + pps::schema_type::avro}) + .value(); + +using incompatibility = pps::avro_incompatibility; + +const absl::flat_hash_set forward_expected{ + {"/fields/0/type", + incompatibility::Type::type_mismatch, + "reader type: STRING not compatible with writer type: INT"}, + {"/fields/1/type/name", + incompatibility::Type::name_mismatch, + "expected: nestedRec2"}, + {"/fields/1/type/fields/0", + incompatibility::Type::reader_field_missing_default_value, + "nestedF"}, + {"/fields/4/type/size", + incompatibility::Type::fixed_size_mismatch, + "expected: 2, found: 1"}, + {"/fields/5/type", + incompatibility::Type::missing_union_branch, + "reader union lacking writer type: BOOLEAN"}, + {"/fields/6/type", /* NOTE: this is more path info than the reference impl */ + incompatibility::Type::type_mismatch, + "reader type: INT not compatible with writer type: BOOLEAN"}, + {"/fields/6/type", /* NOTE: this is more path info than the reference impl */ + incompatibility::Type::type_mismatch, + "reader type: INT not compatible with writer type: STRING"}, + {"/fields/7/type/items", + incompatibility::Type::type_mismatch, + "reader type: INT not compatible with writer type: LONG"}, + {"/fields/8/type/name", + incompatibility::Type::name_mismatch, + "expected: someEnum2"}, +}; +const absl::flat_hash_set backward_expected{ + {"/fields/0/type", + incompatibility::Type::type_mismatch, + "reader type: INT not compatible with writer type: STRING"}, + {"/fields/1/type/name", + incompatibility::Type::name_mismatch, + "expected: nestedRec"}, + {"/fields/1/type/fields/0", + incompatibility::Type::reader_field_missing_default_value, + "broken"}, + {"/fields/2/type/0", + incompatibility::Type::missing_union_branch, + "reader union lacking writer type: INT"}, + {"/fields/3/type/symbols", + incompatibility::Type::missing_enum_symbols, + "[b, c]"}, + {"/fields/4/type/size", + incompatibility::Type::fixed_size_mismatch, + "expected: 1, found: 2"}, + {"/fields/5/type", /* NOTE: this is more path info than the reference impl */ + incompatibility::Type::type_mismatch, + "reader type: BOOLEAN not compatible with writer type: INT"}, + {"/fields/5/type", /* NOTE: this is more path info than the reference impl */ + incompatibility::Type::type_mismatch, + "reader type: BOOLEAN not compatible with writer type: STRING"}, + {"/fields/6/type", + incompatibility::Type::missing_union_branch, + "reader union lacking writer type: INT"}, + // Note: once Avro supports schema resolution with name aliases, the + // incompatibility below should go away + {"/fields/8/type/name", + incompatibility::Type::name_mismatch, + "expected: someEnum1 (alias resolution is not yet fully supported)"}, +}; + +const auto compat_data = std::to_array>({ + { + schema_old.share(), + schema_new.share(), + forward_expected, + }, + { + schema_new.share(), + schema_old.share(), + backward_expected, + }, +}); + +std::string format_set(const absl::flat_hash_set& d) { + return fmt::format("{}", fmt::join(d, "\n")); +} + +} // namespace + +SEASTAR_THREAD_TEST_CASE(test_avro_compat_messages) { + for (const auto& cd : compat_data) { + auto compat = check_compatible_verbose(cd.reader, cd.writer); + absl::flat_hash_set errs{ + compat.messages.begin(), compat.messages.end()}; + absl::flat_hash_set expected{ + cd.expected.messages.begin(), cd.expected.messages.end()}; + + BOOST_CHECK(!compat.is_compat); + BOOST_CHECK_EQUAL(errs.size(), expected.size()); + BOOST_REQUIRE_MESSAGE( + errs == expected, + fmt::format("{} != {}", format_set(errs), format_set(expected))); + } +} diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_common.h b/src/v/pandaproxy/schema_registry/test/compatibility_common.h new file mode 100644 index 0000000000000..ad07d2fac25b6 --- /dev/null +++ b/src/v/pandaproxy/schema_registry/test/compatibility_common.h @@ -0,0 +1,36 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "pandaproxy/schema_registry/compatibility.h" +#include "pandaproxy/schema_registry/types.h" + +#include + +namespace pps = pandaproxy::schema_registry; + +template +struct compat_test_data { + compat_test_data( + pps::canonical_schema_definition reader, + pps::canonical_schema_definition writer, + absl::flat_hash_set exp) + : reader(std::move(reader)) + , writer(std::move(writer)) + , expected([&exp]() { + pps::raw_compatibility_result raw; + absl::c_for_each(std::move(exp), [&raw](auto e) { + raw.emplace(std::move(e)); + }); + return std::move(raw)(pps::verbose::yes); + }()) {} + + pps::canonical_schema_definition reader; + pps::canonical_schema_definition writer; + pps::compatibility_result expected; +}; diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.cc b/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.cc index 13166cfc53153..338cf1be0115b 100644 --- a/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.cc +++ b/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.cc @@ -13,20 +13,27 @@ #include "pandaproxy/schema_registry/exceptions.h" #include "pandaproxy/schema_registry/protobuf.h" #include "pandaproxy/schema_registry/sharded_store.h" +#include "pandaproxy/schema_registry/test/compatibility_common.h" #include "pandaproxy/schema_registry/types.h" #include +#include #include +#include +#include #include namespace pp = pandaproxy; namespace pps = pp::schema_registry; +namespace { + struct simple_sharded_store { simple_sharded_store() { - store.start(ss::default_smp_service_group()).get(); + store.start(pps::is_mutable::yes, ss::default_smp_service_group()) + .get(); } ~simple_sharded_store() { store.stop().get(); } simple_sharded_store(const simple_sharded_store&) = delete; @@ -44,7 +51,7 @@ struct simple_sharded_store { std::nullopt, version, pps::seq_marker_key_type::schema}, - schema, + schema.share(), id, version, pps::is_deleted::no) @@ -76,23 +83,43 @@ bool check_compatible( .get(); } +pps::compatibility_result check_compatible_verbose( + const pps::canonical_schema_definition& r, + const pps::canonical_schema_definition& w) { + pps::sharded_store s; + return check_compatible( + pps::make_protobuf_schema_definition( + s, {pps::subject("r"), {r.shared_raw(), pps::schema_type::protobuf}}) + .get(), + pps::make_protobuf_schema_definition( + s, {pps::subject("w"), {w.shared_raw(), pps::schema_type::protobuf}}) + .get(), + pps::verbose::yes); +} + +} // namespace + SEASTAR_THREAD_TEST_CASE(test_protobuf_simple) { simple_sharded_store store; - auto schema1 = pps::canonical_schema{pps::subject{"simple"}, simple}; + auto schema1 = pps::canonical_schema{ + pps::subject{"simple"}, simple.share()}; store.insert(schema1, pps::schema_version{1}); - auto valid_simple - = pps::make_protobuf_schema_definition(store.store, schema1).get(); + auto valid_simple = pps::make_protobuf_schema_definition( + store.store, schema1.share()) + .get(); BOOST_REQUIRE_EQUAL(valid_simple.name({0}).value(), "Simple"); } SEASTAR_THREAD_TEST_CASE(test_protobuf_nested) { simple_sharded_store store; - auto schema1 = pps::canonical_schema{pps::subject{"nested"}, nested}; + auto schema1 = pps::canonical_schema{ + pps::subject{"nested"}, nested.share()}; store.insert(schema1, pps::schema_version{1}); - auto valid_nested - = pps::make_protobuf_schema_definition(store.store, schema1).get(); + auto valid_nested = pps::make_protobuf_schema_definition( + store.store, schema1.share()) + .get(); BOOST_REQUIRE_EQUAL(valid_nested.name({0}).value(), "A0"); BOOST_REQUIRE_EQUAL(valid_nested.name({1, 0, 2}).value(), "A1.B0.C2"); BOOST_REQUIRE_EQUAL(valid_nested.name({1, 0, 4}).value(), "A1.B0.C4"); @@ -102,10 +129,11 @@ SEASTAR_THREAD_TEST_CASE(test_protobuf_imported_failure) { simple_sharded_store store; // imported depends on simple, which han't been inserted - auto schema1 = pps::canonical_schema{pps::subject{"imported"}, imported}; + auto schema1 = pps::canonical_schema{ + pps::subject{"imported"}, imported.share()}; store.insert(schema1, pps::schema_version{1}); BOOST_REQUIRE_EXCEPTION( - pps::make_protobuf_schema_definition(store.store, schema1).get(), + pps::make_protobuf_schema_definition(store.store, schema1.share()).get(), pps::exception, [](const pps::exception& ex) { return ex.code() == pps::error_code::schema_invalid; @@ -115,16 +143,18 @@ SEASTAR_THREAD_TEST_CASE(test_protobuf_imported_failure) { SEASTAR_THREAD_TEST_CASE(test_protobuf_imported_not_referenced) { simple_sharded_store store; - auto schema1 = pps::canonical_schema{pps::subject{"simple"}, simple}; + auto schema1 = pps::canonical_schema{ + pps::subject{"simple"}, simple.share()}; auto schema2 = pps::canonical_schema{ - pps::subject{"imported"}, imported_no_ref}; + pps::subject{"imported"}, imported_no_ref.share()}; store.insert(schema1, pps::schema_version{1}); - auto valid_simple - = pps::make_protobuf_schema_definition(store.store, schema1).get(); + auto valid_simple = pps::make_protobuf_schema_definition( + store.store, schema1.share()) + .get(); BOOST_REQUIRE_EXCEPTION( - pps::make_protobuf_schema_definition(store.store, schema2).get(), + pps::make_protobuf_schema_definition(store.store, schema2.share()).get(), pps::exception, [](const pps::exception& ex) { return ex.code() == pps::error_code::schema_invalid; @@ -134,43 +164,86 @@ SEASTAR_THREAD_TEST_CASE(test_protobuf_imported_not_referenced) { SEASTAR_THREAD_TEST_CASE(test_protobuf_referenced) { simple_sharded_store store; - auto schema1 = pps::canonical_schema{pps::subject{"simple.proto"}, simple}; + auto schema1 = pps::canonical_schema{ + pps::subject{"simple.proto"}, simple.share()}; auto schema2 = pps::canonical_schema{ - pps::subject{"imported.proto"}, imported}; + pps::subject{"imported.proto"}, imported.share()}; auto schema3 = pps::canonical_schema{ - pps::subject{"imported-again.proto"}, imported_again}; + pps::subject{"imported-again.proto"}, imported_again.share()}; store.insert(schema1, pps::schema_version{1}); store.insert(schema2, pps::schema_version{1}); store.insert(schema3, pps::schema_version{1}); - auto valid_simple - = pps::make_protobuf_schema_definition(store.store, schema1).get(); - auto valid_imported - = pps::make_protobuf_schema_definition(store.store, schema2).get(); - auto valid_imported_again - = pps::make_protobuf_schema_definition(store.store, schema3).get(); + auto valid_simple = pps::make_protobuf_schema_definition( + store.store, schema1.share()) + .get(); + auto valid_imported = pps::make_protobuf_schema_definition( + store.store, schema2.share()) + .get(); + auto valid_imported_again = pps::make_protobuf_schema_definition( + store.store, schema3.share()) + .get(); } SEASTAR_THREAD_TEST_CASE(test_protobuf_recursive_reference) { simple_sharded_store store; - auto schema1 = pps::canonical_schema{pps::subject{"simple.proto"}, simple}; + auto schema1 = pps::canonical_schema{ + pps::subject{"simple.proto"}, simple.share()}; auto schema2 = pps::canonical_schema{ - pps::subject{"imported.proto"}, imported}; + pps::subject{"imported.proto"}, imported.share()}; auto schema3 = pps::canonical_schema{ - pps::subject{"imported-twice.proto"}, imported_twice}; + pps::subject{"imported-twice.proto"}, imported_twice.share()}; store.insert(schema1, pps::schema_version{1}); store.insert(schema2, pps::schema_version{1}); store.insert(schema3, pps::schema_version{1}); - auto valid_simple - = pps::make_protobuf_schema_definition(store.store, schema1).get(); - auto valid_imported - = pps::make_protobuf_schema_definition(store.store, schema2).get(); - auto valid_imported_again - = pps::make_protobuf_schema_definition(store.store, schema3).get(); + auto valid_simple = pps::make_protobuf_schema_definition( + store.store, schema1.share()) + .get(); + auto valid_imported = pps::make_protobuf_schema_definition( + store.store, schema2.share()) + .get(); + auto valid_imported_again = pps::make_protobuf_schema_definition( + store.store, schema3.share()) + .get(); +} + +SEASTAR_THREAD_TEST_CASE(test_binary_protobuf) { + simple_sharded_store store; + + BOOST_REQUIRE_NO_THROW(store.store + .make_valid_schema(pps::canonical_schema{ + pps::subject{"com.redpanda.Payload.proto"}, + pps::canonical_schema_definition{ + base64_raw_proto, pps::schema_type::protobuf}}) + .get()); +} + +SEASTAR_THREAD_TEST_CASE(test_invalid_binary_protobuf) { + simple_sharded_store store; + + auto broken_base64_raw_proto = base64_raw_proto.substr(1); + + auto schema = pps::canonical_schema{ + pps::subject{"com.redpanda.Payload.proto"}, + pps::canonical_schema_definition{ + broken_base64_raw_proto, pps::schema_type::protobuf}}; + + BOOST_REQUIRE_EXCEPTION( + store.store + .make_valid_schema(pps::canonical_schema{ + pps::subject{"com.redpanda.Payload.proto"}, + pps::canonical_schema_definition{ + broken_base64_raw_proto, pps::schema_type::protobuf}}) + .get(), + pps::exception, + [](const pps::exception& e) { + std::cout << e.what(); + return e.code() == pps::error_code::schema_invalid; + }); } SEASTAR_THREAD_TEST_CASE(test_protobuf_well_known) { @@ -265,7 +338,7 @@ message well_known_types { store.insert(schema, pps::schema_version{1}); auto valid_empty - = pps::make_protobuf_schema_definition(store.store, schema).get(); + = pps::make_protobuf_schema_definition(store.store, schema.share()).get(); } SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_empty) { @@ -619,3 +692,310 @@ message Bar { } )"); } + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_message_removed) { + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Outer { message Inner { int32 id = 1;}; Inner x = 1; })", + R"(syntax = "proto3"; message Outer { message Inner { int32 id = 1;}; message Inner2 { int32 id = 1;}; Inner x = 1; })")); +} + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_field_name_type_changed) { + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Outer { message Inner { int32 id = 1;}; message Inner2 { int32 id = 1;}; Inner2 x = 1; })", + R"(syntax = "proto3"; message Outer { message Inner { int32 id = 1;}; message Inner2 { int32 id = 1;}; Inner x = 1; })")); +} + +SEASTAR_THREAD_TEST_CASE( + test_protobuf_compatibility_required_field_added_removed) { + // field added + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; required int32 new_id = 2; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; })")); + // field removed + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; required int32 new_id = 2; })")); +} + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_field_made_reserved) { + // required + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; reserved 2; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; required int32 new_id = 2; })")); + // not required + BOOST_REQUIRE(check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; reserved 2; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; optional int32 new_id = 2; })")); +} + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_field_unmade_reserved) { + // required + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; required int32 new_id = 2; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; reserved 2; })")); + // not required + BOOST_REQUIRE(check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto2"; message Simple { optional int32 id = 1; optional int32 new_id = 2; })", + R"(syntax = "proto2"; message Simple { optional int32 id = 1; reserved 2; })")); +} + +SEASTAR_THREAD_TEST_CASE( + test_protobuf_compatibility_multiple_fields_moved_to_oneof) { + BOOST_REQUIRE(check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; } })", + R"(syntax = "proto3"; message Simple { int32 id = 1; })")); + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; int32 new_id = 2; } })", + R"(syntax = "proto3"; message Simple { int32 id = 1; int32 new_id = 2; })")); +} + +SEASTAR_THREAD_TEST_CASE( + test_protobuf_compatibility_fields_moved_out_of_oneof) { + BOOST_REQUIRE(check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Simple { int32 id = 1; int32 new_id = 2; })", + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; int32 new_id = 2; } })")); +} + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_oneof_field_removed) { + BOOST_REQUIRE(!check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; } })", + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; int32 new_id = 2; } })")); +} + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compatibility_oneof_fully_removed) { + BOOST_REQUIRE(check_compatible( + pps::compatibility_level::backward, + R"(syntax = "proto3"; message Simple { int32 other = 3; })", + R"(syntax = "proto3"; message Simple { oneof wrapper { int32 id = 1; int32 new_id = 2; } int32 other = 3; })")); +} + +namespace { + +const pps::canonical_schema_definition proto2_old{ + R"(syntax = "proto2"; + +message someMessage { + required int32 a = 1; +} + +message myrecord { + message Msg1 { + required int32 f1 = 1; + } + message Msg2 { + required int32 f1 = 1; + } + required Msg1 m1 = 1; + required Msg1 m2 = 2; + required int32 i1 = 3; + + required int32 i2 = 4; + + oneof union { + int32 u1 = 5; + string u2 = 6; + bool u3 = 23; + bool u4 = 40; + } + + required int32 notu1 = 7; + required string notu2 = 8; + +})", + pps::schema_type::protobuf}; + +const pps::canonical_schema_definition proto2_new{ + R"(syntax = "proto2"; + +message myrecord { + message Msg1d { + required int32 f1 = 1; + } + message Msg2 { + required string f1 = 1; + } + required Msg1d m1 = 1; + required int32 m2 = 2; + required string i1 = 3; + // required int32 i2 = 4; + + oneof union { + int32 u1 = 5; + string u2 = 16; + string u3 = 23; + } + required bool u4 = 40; + + oneof union2 { + int32 notu1 = 7; + string notu2 = 8; + } + + required string whoops = 12; +})", + pps::schema_type::protobuf}; + +const pps::canonical_schema_definition proto3_old{ + R"(syntax = "proto3"; + +message someMessage { + int32 a = 1; +} + +message myrecord { + message Msg1 { + int32 f1 = 1; + } + message Msg2 { + int32 f1 = 1; + } + Msg1 m1 = 1; + Msg1 m2 = 2; + int32 i1 = 3; + + int32 i2 = 4; + + oneof union { + int32 u1 = 5; + string u2 = 6; + bool u3 = 23; + bool u4 = 40; + } + + int32 notu1 = 7; + string notu2 = 8; + +} +)", + pps::schema_type::protobuf}; + +const pps::canonical_schema_definition proto3_new{ + R"(syntax = "proto3"; + +message myrecord { + message Msg1d { + int32 f1 = 1; + } + message Msg2 { + string f1 = 1; + } + Msg1d m1 = 1; + int32 m2 = 2; + string i1 = 3; + + oneof union { + int32 u1 = 5; + string u2 = 16; + string u3 = 23; + } + + bool u4 = 40; + + oneof union2 { + int32 notu1 = 7; + string notu2 = 8; + } + + string whoops = 12; +})", + pps::schema_type::protobuf}; + +using incompatibility = pps::proto_incompatibility; + +const absl::flat_hash_set forward_expected{ + {"#/myrecord/union/16", incompatibility::Type::oneof_field_removed}, + {"#/myrecord/union/23", incompatibility::Type::field_scalar_kind_changed}, + {"#/myrecord/1", incompatibility::Type::field_named_type_changed}, + {"#/myrecord/2", incompatibility::Type::field_kind_changed}, + {"#/myrecord/3", incompatibility::Type::field_scalar_kind_changed}, + {"#/myrecord/Msg1d", incompatibility::Type::message_removed}, + {"#/myrecord/Msg2/1", incompatibility::Type::field_scalar_kind_changed}, + // These are ignored for proto3 schemas + {"#/myrecord/4", incompatibility::Type::required_field_added}, + {"#/myrecord/7", incompatibility::Type::required_field_added}, + {"#/myrecord/8", incompatibility::Type::required_field_added}, + {"#/myrecord/12", incompatibility::Type::required_field_removed}, +}; + +const absl::flat_hash_set backward_expected{ + {"#/someMessage", incompatibility::Type::message_removed}, + {"#/myrecord/union2", incompatibility::Type::multiple_fields_moved_to_oneof}, + {"#/myrecord/union/6", incompatibility::Type::oneof_field_removed}, + {"#/myrecord/union/23", incompatibility::Type::field_scalar_kind_changed}, + {"#/myrecord/union/40", incompatibility::Type::oneof_field_removed}, + {"#/myrecord/1", incompatibility::Type::field_named_type_changed}, + {"#/myrecord/2", incompatibility::Type::field_kind_changed}, + {"#/myrecord/3", incompatibility::Type::field_scalar_kind_changed}, + {"#/myrecord/Msg1", incompatibility::Type::message_removed}, + {"#/myrecord/Msg2/1", incompatibility::Type::field_scalar_kind_changed}, + // These are ignored for proto3 schemas + {"#/myrecord/4", incompatibility::Type::required_field_removed}, + {"#/myrecord/40", incompatibility::Type::required_field_added}, + {"#/myrecord/12", incompatibility::Type::required_field_added}, +}; + +absl::flat_hash_set +remove_proto2_incompatibilites(absl::flat_hash_set exp) { + absl::erase_if(exp, [](const auto& e) { + return ( + e.type() == incompatibility::Type::required_field_removed + || e.type() == incompatibility::Type::required_field_added); + }); + return exp; +} + +const auto compat_data = std::to_array>({ + { + proto2_old.copy(), + proto2_new.copy(), + forward_expected, + }, + { + proto2_new.copy(), + proto2_old.copy(), + backward_expected, + }, + { + proto3_old.copy(), + proto3_new.copy(), + remove_proto2_incompatibilites(forward_expected), + }, + { + proto3_new.copy(), + proto3_old.copy(), + remove_proto2_incompatibilites(backward_expected), + }, +}); + +std::string format_set(const absl::flat_hash_set& d) { + return fmt::format("{}", fmt::join(d, "\n")); +} + +} // namespace + +SEASTAR_THREAD_TEST_CASE(test_protobuf_compat_messages) { + for (const auto& cd : compat_data) { + auto compat = check_compatible_verbose(cd.reader, cd.writer); + absl::flat_hash_set errs{ + compat.messages.begin(), compat.messages.end()}; + absl::flat_hash_set expected{ + cd.expected.messages.begin(), cd.expected.messages.end()}; + BOOST_CHECK(!compat.is_compat); + BOOST_CHECK_EQUAL(errs.size(), expected.size()); + BOOST_REQUIRE_MESSAGE( + errs == expected, + fmt::format("{} != {}", format_set(errs), format_set(expected))); + } +} diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.h b/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.h index 8dc490e622bfd..881e97c1b635d 100644 --- a/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.h +++ b/src/v/pandaproxy/schema_registry/test/compatibility_protobuf.h @@ -88,3 +88,70 @@ message A1 { } })", pps::schema_type::protobuf}; + +// Binary encoded protobuf descriptor: +// +// syntax = "proto3"; +// +// package com.redpanda; +// +// option go_package = "./;main"; +// option java_multiple_files = true; +// +// import "google/protobuf/timestamp.proto"; +// +// message Payload { +// int32 val = 1; +// google.protobuf.Timestamp timestamp = 2; +// } +// +// message A { +// message B { +// message C { +// message D { +// message M00 {} +// message M01 {} +// message M02 {} +// message M03 {} +// message M04 {} +// message M05 {} +// message M06 {} +// message M07 {} +// message M08 {} +// message M09 {} +// message M10 {} +// message M11 {} +// message M12 {} +// message M13 {} +// message M14 {} +// message M15 {} +// message M16 {} +// message M17 {} +// message NestedPayload { +// int32 val = 1; +// google.protobuf.Timestamp timestamp = 2; +// } +// } +// } +// } +// } +// +// message CompressiblePayload { +// int32 val = 1; +// google.protobuf.Timestamp timestamp = 2; +// string message = 3; +// } +constexpr std::string_view base64_raw_proto{ + "Cg1wYXlsb2FkLnByb3RvEgxjb20ucmVkcGFuZGEaH2dvb2dsZS9wcm90b2J1Zi90aW1lc3RhbXAu" + "cHJvdG8iRQoHUGF5bG9hZBILCgN2YWwYASABKAUSLQoJdGltZXN0YW1wGAIgASgLMhouZ29vZ2xl" + "LnByb3RvYnVmLlRpbWVzdGFtcCLgAQoBQRraAQoBQhrUAQoBQxrOAQoBRBoFCgNNMDAaBQoDTTAx" + "GgUKA00wMhoFCgNNMDMaBQoDTTA0GgUKA00wNRoFCgNNMDYaBQoDTTA3GgUKA00wOBoFCgNNMDka" + "BQoDTTEwGgUKA00xMRoFCgNNMTIaBQoDTTEzGgUKA00xNBoFCgNNMTUaBQoDTTE2GgUKA00xNxpL" + "Cg1OZXN0ZWRQYXlsb2FkEgsKA3ZhbBgBIAEoBRItCgl0aW1lc3RhbXAYAiABKAsyGi5nb29nbGUu" + "cHJvdG9idWYuVGltZXN0YW1wImIKE0NvbXByZXNzaWJsZVBheWxvYWQSCwoDdmFsGAEgASgFEi0K" + "CXRpbWVzdGFtcBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDwoHbWVzc2FnZRgD" + "IAEoCUILUAFaBy4vO21haW5iBnByb3RvMw=="}; + +const pps::canonical_schema_definition base64_proto{ + pps::canonical_schema_definition{ + base64_raw_proto, pps::schema_type::protobuf}}; diff --git a/src/v/pandaproxy/schema_registry/test/compatibility_store.cc b/src/v/pandaproxy/schema_registry/test/compatibility_store.cc index f62f911cde3d9..e5a881d758b88 100644 --- a/src/v/pandaproxy/schema_registry/test/compatibility_store.cc +++ b/src/v/pandaproxy/schema_registry/test/compatibility_store.cc @@ -24,7 +24,7 @@ SEASTAR_THREAD_TEST_CASE(test_avro_basic_backwards_store_compat) { // used to read the data written in the previous schema. pps::sharded_store s; - s.start(ss::default_smp_service_group()).get(); + s.start(pps::is_mutable::yes, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&s]() { s.stop().get(); }); pps::seq_marker dummy_marker; @@ -33,56 +33,46 @@ SEASTAR_THREAD_TEST_CASE(test_avro_basic_backwards_store_compat) { auto sub = pps::subject{"sub"}; s.upsert( dummy_marker, - {sub, pps::canonical_schema_definition{schema1}}, + {sub, schema1.share()}, pps::schema_id{1}, pps::schema_version{1}, pps::is_deleted::no) .get(); // add a defaulted field - BOOST_REQUIRE(s.is_compatible( - pps::schema_version{1}, - {sub, pps::canonical_schema_definition{schema2}}) - .get()); + BOOST_REQUIRE( + s.is_compatible(pps::schema_version{1}, {sub, schema2.share()}).get()); s.upsert( dummy_marker, - {sub, pps::canonical_schema_definition{schema2}}, + {sub, schema2.share()}, pps::schema_id{2}, pps::schema_version{2}, pps::is_deleted::no) .get(); // Test non-defaulted field - BOOST_REQUIRE(!s.is_compatible( - pps::schema_version{1}, - {sub, pps::canonical_schema_definition{schema3}}) - .get()); + BOOST_REQUIRE( + !s.is_compatible(pps::schema_version{1}, {sub, schema3.share()}).get()); // Insert schema with non-defaulted field s.upsert( dummy_marker, - {sub, pps::canonical_schema_definition{schema2}}, + {sub, schema2.share()}, pps::schema_id{2}, pps::schema_version{2}, pps::is_deleted::no) .get(); // Test Remove defaulted field to previous - BOOST_REQUIRE(s.is_compatible( - pps::schema_version{2}, - {sub, pps::canonical_schema_definition{schema3}}) - .get()); + BOOST_REQUIRE( + s.is_compatible(pps::schema_version{2}, {sub, schema3.share()}).get()); // Test Remove defaulted field to first - should fail - BOOST_REQUIRE(!s.is_compatible( - pps::schema_version{1}, - {sub, pps::canonical_schema_definition{schema3}}) - .get()); + BOOST_REQUIRE( + !s.is_compatible(pps::schema_version{1}, {sub, schema3.share()}).get()); s.set_compatibility(pps::compatibility_level::backward_transitive).get(); // Test transitive defaulted field to previous - should fail - BOOST_REQUIRE(!s.is_compatible( - pps::schema_version{2}, - {sub, pps::canonical_schema_definition{schema3}}) - .get()); + BOOST_REQUIRE( + !s.is_compatible(pps::schema_version{2}, {sub, schema3.share()}).get()); } diff --git a/src/v/pandaproxy/schema_registry/test/consume_to_store.cc b/src/v/pandaproxy/schema_registry/test/consume_to_store.cc index 3c5e3e4ffa3ff..f750703af741d 100644 --- a/src/v/pandaproxy/schema_registry/test/consume_to_store.cc +++ b/src/v/pandaproxy/schema_registry/test/consume_to_store.cc @@ -83,7 +83,7 @@ inline model::record_batch make_delete_subject_permanently_batch( SEASTAR_THREAD_TEST_CASE(test_consume_to_store) { pps::sharded_store s; - s.start(ss::default_smp_service_group()).get(); + s.start(pps::is_mutable::yes, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&s]() { s.stop().get(); }); // This kafka client will not be used by the sequencer @@ -114,7 +114,8 @@ SEASTAR_THREAD_TEST_CASE(test_consume_to_store) { auto good_schema_1 = pps::as_record_batch( pps::schema_key{sequence, node_id, subject0, version0, magic1}, - pps::canonical_schema_value{{subject0, string_def0}, version0, id0}); + pps::canonical_schema_value{ + {subject0, string_def0.share()}, version0, id0}); BOOST_REQUIRE_NO_THROW(c(good_schema_1.copy()).get()); auto s_res = s.get_subject_schema( @@ -124,7 +125,8 @@ SEASTAR_THREAD_TEST_CASE(test_consume_to_store) { auto good_schema_ref_1 = pps::as_record_batch( pps::schema_key{sequence, node_id, subject0, version1, magic1}, - pps::canonical_schema_value{{subject0, string_def0}, version1, id1}); + pps::canonical_schema_value{ + {subject0, string_def0.share()}, version1, id1}); BOOST_REQUIRE_NO_THROW(c(good_schema_ref_1.copy()).get()); auto s_ref_res = s.get_subject_schema( @@ -141,7 +143,8 @@ SEASTAR_THREAD_TEST_CASE(test_consume_to_store) { auto bad_schema_magic = pps::as_record_batch( pps::schema_key{sequence, node_id, subject0, version0, magic2}, - pps::canonical_schema_value{{subject0, string_def0}, version0, id0}); + pps::canonical_schema_value{ + {subject0, string_def0.share()}, version0, id0}); BOOST_REQUIRE_THROW(c(bad_schema_magic.copy()).get(), pps::exception); BOOST_REQUIRE( @@ -202,7 +205,7 @@ model::record_batch as_record_batch(Key key) { SEASTAR_THREAD_TEST_CASE(test_consume_to_store_after_compaction) { pps::sharded_store s; - s.start(ss::default_smp_service_group()).get(); + s.start(pps::is_mutable::no, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&s]() { s.stop().get(); }); // This kafka client will not be used by the sequencer @@ -234,7 +237,8 @@ SEASTAR_THREAD_TEST_CASE(test_consume_to_store_after_compaction) { // Insert the schema at seq 0 auto good_schema_1 = pps::as_record_batch( pps::schema_key{sequence, node_id, subject0, version0, magic1}, - pps::canonical_schema_value{{subject0, string_def0}, version0, id0}); + pps::canonical_schema_value{ + {subject0, string_def0.share()}, version0, id0}); BOOST_REQUIRE_NO_THROW(c(good_schema_1.copy()).get()); // Roll the segment // Soft delete the version (at seq 1) diff --git a/src/v/pandaproxy/schema_registry/test/mt_sharded_store.cc b/src/v/pandaproxy/schema_registry/test/mt_sharded_store.cc new file mode 100644 index 0000000000000..c03cb77dbf697 --- /dev/null +++ b/src/v/pandaproxy/schema_registry/test/mt_sharded_store.cc @@ -0,0 +1,76 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "pandaproxy/schema_registry/error.h" +#include "pandaproxy/schema_registry/exceptions.h" +#include "pandaproxy/schema_registry/protobuf.h" +#include "pandaproxy/schema_registry/sharded_store.h" +#include "pandaproxy/schema_registry/test/compatibility_protobuf.h" +#include "pandaproxy/schema_registry/types.h" + +#include +#include +#include + +#include +#include + +namespace pp = pandaproxy; +namespace pps = pp::schema_registry; + +SEASTAR_THREAD_TEST_CASE(test_sharded_store_cross_shard_def) { + constexpr size_t id_n = 1000; + pps::sharded_store store; + store.start(pps::is_mutable::yes, ss::default_smp_service_group()).get(); + auto stop_store = ss::defer([&store] { store.stop().get(); }); + + const pps::schema_version ver1{1}; + + // Upsert a large(ish) number of schemas to the store, all with different + // subject names and IDs, so they should hash to different shards. + for (int i = 1; i <= id_n; ++i) { + auto referenced_schema = pps::canonical_schema{ + pps::subject{fmt::format("simple_{}.proto", i)}, simple.share()}; + store + .upsert( + pps::seq_marker{ + std::nullopt, + std::nullopt, + ver1, + pps::seq_marker_key_type::schema}, + referenced_schema.copy(), + pps::schema_id{i}, + ver1, + pps::is_deleted::no) + .get(); + } + + BOOST_REQUIRE(store.has_schema(pps::schema_id{id_n}).get()); + + // for each upserted schema, submit a number of concurrent + // get_schema_definition requests, spreading the requests across cores. the + // goal here is to have copies of a particular schema live on shards other + // than the "home" shard of that schema. previously, when + // 'store::get_schema_definition' was implemented with an iobuf share at the + // interface, this loop would reliably lead to a UAF segfault in + // '~ss::deleter()', the refcounted, non-thread-safe deleter for the + // temporary buffers underlying the shared iobuf. + for (int i = 1; i <= id_n; ++i) { + constexpr int num_parallel_requests = 20; + ss::parallel_for_each( + boost::irange(0, num_parallel_requests), + [&store, i](auto shrd) { + return ss::smp::submit_to(shrd % ss::smp::count, [&store, i]() { + return store.get_schema_definition(pps::schema_id{i}) + .discard_result(); + }); + }) + .get(); + } +} diff --git a/src/v/pandaproxy/schema_registry/test/post_subjects_subject_version.cc b/src/v/pandaproxy/schema_registry/test/post_subjects_subject_version.cc index 77a8094d07b6c..5ecd5cd430f75 100644 --- a/src/v/pandaproxy/schema_registry/test/post_subjects_subject_version.cc +++ b/src/v/pandaproxy/schema_registry/test/post_subjects_subject_version.cc @@ -8,7 +8,6 @@ // by the Apache License, Version 2.0 #include "pandaproxy/error.h" -#include "pandaproxy/json/error.h" #include "pandaproxy/json/rjson_util.h" #include "pandaproxy/schema_registry/test/avro_payloads.h" #include "pandaproxy/schema_registry/test/client_utils.h" @@ -16,8 +15,6 @@ #include "pandaproxy/test/pandaproxy_fixture.h" #include "pandaproxy/test/utils.h" -#include - namespace pp = pandaproxy; namespace ppj = pp::json; namespace pps = pp::schema_registry; @@ -26,8 +23,8 @@ struct request { pps::canonical_schema schema; }; -void rjson_serialize( - ::json::Writer<::json::StringBuffer>& w, const request& r) { +template +void rjson_serialize(::json::iobuf_writer& w, const request& r) { w.StartObject(); w.Key("schema"); ::json::rjson_serialize(w, r.schema.def().raw()); @@ -569,7 +566,7 @@ FIXTURE_TEST(schema_registry_post_avro_references, pandaproxy_test_fixture) { info("Post company schema (expect schema_id=1)"); auto res = post_schema( - client, company_req.schema.sub(), ppj::rjson_serialize(company_req)); + client, company_req.schema.sub(), ppj::rjson_serialize_str(company_req)); BOOST_REQUIRE_EQUAL(res.body, R"({"id":1})"); BOOST_REQUIRE_EQUAL( res.headers.at(boost::beast::http::field::content_type), @@ -577,7 +574,9 @@ FIXTURE_TEST(schema_registry_post_avro_references, pandaproxy_test_fixture) { info("Post employee schema (expect schema_id=2)"); res = post_schema( - client, employee_req.schema.sub(), ppj::rjson_serialize(employee_req)); + client, + employee_req.schema.sub(), + ppj::rjson_serialize_str(employee_req)); BOOST_REQUIRE_EQUAL(res.body, R"({"id":2})"); BOOST_REQUIRE_EQUAL( res.headers.at(boost::beast::http::field::content_type), diff --git a/src/v/pandaproxy/schema_registry/test/sanitize_avro.cc b/src/v/pandaproxy/schema_registry/test/sanitize_avro.cc index 9a1fb9cfc563d..37a31b7d12325 100644 --- a/src/v/pandaproxy/schema_registry/test/sanitize_avro.cc +++ b/src/v/pandaproxy/schema_registry/test/sanitize_avro.cc @@ -17,7 +17,7 @@ namespace pp = pandaproxy; namespace pps = pp::schema_registry; -pps::unparsed_schema_definition not_minimal{ +const pps::unparsed_schema_definition not_minimal{ R"({ "type": "record", "name": "myrecord", @@ -25,73 +25,73 @@ pps::unparsed_schema_definition not_minimal{ })", pps::schema_type::avro}; -pps::canonical_schema_definition not_minimal_sanitized{ +const pps::canonical_schema_definition not_minimal_sanitized{ R"({"type":"record","name":"myrecord","fields":[{"name":"f1","type":"string"}]})", pps::schema_type::avro}; -pps::unparsed_schema_definition leading_dot{ +const pps::unparsed_schema_definition leading_dot{ R"({"type":"record","name":"record","fields":[{"name":"one","type":["null",{"fields":[{"name":"f1","type":["null","string"]}],"name":".r1","type":"record"}]},{"name":"two","type":["null",".r1"]}]})", pps::schema_type::avro}; -pps::canonical_schema_definition leading_dot_sanitized{ +const pps::canonical_schema_definition leading_dot_sanitized{ R"({"type":"record","name":"record","fields":[{"name":"one","type":["null",{"type":"record","name":"r1","fields":[{"name":"f1","type":["null","string"]}]}]},{"name":"two","type":["null","r1"]}]})", pps::schema_type::avro}; -pps::unparsed_schema_definition leading_dot_ns{ +const pps::unparsed_schema_definition leading_dot_ns{ R"({"type":"record","name":"record","fields":[{"name":"one","type":["null",{"fields":[{"name":"f1","type":["null","string"]}],"name":".ns.r1","type":"record"}]},{"name":"two","type":["null",".ns.r1"]}]})", pps::schema_type::avro}; -pps::canonical_schema_definition leading_dot_ns_sanitized{ +const pps::canonical_schema_definition leading_dot_ns_sanitized{ R"({"type":"record","name":"record","fields":[{"name":"one","type":["null",{"type":"record","name":"r1","namespace":".ns","fields":[{"name":"f1","type":["null","string"]}]}]},{"name":"two","type":["null",".ns.r1"]}]})", pps::schema_type::avro}; -pps::unparsed_schema_definition record_not_sorted{ +const pps::unparsed_schema_definition record_not_sorted{ R"({"name":"sort_record","type":"record","aliases":["alias"],"fields":[{"type":"string","name":"one"}],"namespace":"ns","doc":"doc"})", pps::schema_type::avro}; -pps::canonical_schema_definition record_sorted_sanitized{ +const pps::canonical_schema_definition record_sorted_sanitized{ R"({"type":"record","name":"sort_record","namespace":"ns","doc":"doc","fields":[{"name":"one","type":"string"}],"aliases":["alias"]})", pps::schema_type::avro}; -pps::unparsed_schema_definition enum_not_sorted{ +const pps::unparsed_schema_definition enum_not_sorted{ R"({"name":"ns.sort_enum","type":"enum","aliases":["alias"],"symbols":["one", "two", "three"],"default":"two","doc":"doc"})", pps::schema_type::avro}; -pps::canonical_schema_definition enum_sorted_sanitized{ +const pps::canonical_schema_definition enum_sorted_sanitized{ R"({"type":"enum","name":"sort_enum","namespace":"ns","doc":"doc","symbols":["one","two","three"],"default":"two","aliases":["alias"]})", pps::schema_type::avro}; -pps::unparsed_schema_definition array_not_sorted{ +const pps::unparsed_schema_definition array_not_sorted{ R"({"type": "array", "default": [], "items" : "string"})", pps::schema_type::avro}; -pps::canonical_schema_definition array_sorted_sanitized{ +const pps::canonical_schema_definition array_sorted_sanitized{ R"({"type":"array","items":"string","default":[]})", pps::schema_type::avro}; -pps::unparsed_schema_definition map_not_sorted{ +const pps::unparsed_schema_definition map_not_sorted{ R"({"type": "map", "default": {}, "values" : "string"})", pps::schema_type::avro}; -pps::canonical_schema_definition map_sorted_sanitized{ +const pps::canonical_schema_definition map_sorted_sanitized{ R"({"type":"map","values":"string","default":{}})", pps::schema_type::avro}; -pps::unparsed_schema_definition fixed_not_sorted{ +const pps::unparsed_schema_definition fixed_not_sorted{ R"({"size":16, "type": "fixed", "aliases":["fixed"], "name":"ns.sorted_fixed"})", pps::schema_type::avro}; -pps::canonical_schema_definition fixed_sorted_sanitized{ +const pps::canonical_schema_definition fixed_sorted_sanitized{ R"({"type":"fixed","name":"sorted_fixed","namespace":"ns","size":16,"aliases":["fixed"]})", pps::schema_type::avro}; -pps::unparsed_schema_definition record_of_obj_unsanitized{ +const pps::unparsed_schema_definition record_of_obj_unsanitized{ R"({"name":"sort_record_of_obj","type":"record","fields":[{"type":{"type":"string","connect.parameters":{"tidb_type":"TEXT"}},"default":"","name":"field"}]})", pps::schema_type::avro}; -pps::canonical_schema_definition record_of_obj_sanitized{ +const pps::canonical_schema_definition record_of_obj_sanitized{ R"({"type":"record","name":"sort_record_of_obj","fields":[{"name":"field","type":{"type":"string","connect.parameters":{"tidb_type":"TEXT"}},"default":""}]})", pps::schema_type::avro}; -pps::unparsed_schema_definition namespace_nested_same_unsanitized{ +const pps::unparsed_schema_definition namespace_nested_same_unsanitized{ R"({ "type": "record", "name": "Example", @@ -184,7 +184,7 @@ pps::unparsed_schema_definition namespace_nested_same_unsanitized{ })", pps::schema_type::avro}; -pps::canonical_schema_definition namespace_nested_same_sanitized{ +const pps::canonical_schema_definition namespace_nested_same_sanitized{ ::json::minify( R"({ "type": "record", @@ -280,61 +280,63 @@ pps::canonical_schema_definition namespace_nested_same_sanitized{ BOOST_AUTO_TEST_CASE(test_sanitize_avro_minify) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(not_minimal).value(), + pps::sanitize_avro_schema_definition(not_minimal.share()).value(), not_minimal_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_name) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(leading_dot).value(), + pps::sanitize_avro_schema_definition(leading_dot.share()).value(), leading_dot_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_name_ns) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(leading_dot_ns).value(), + pps::sanitize_avro_schema_definition(leading_dot_ns.share()).value(), leading_dot_ns_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_record_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(record_not_sorted).value(), + pps::sanitize_avro_schema_definition(record_not_sorted.share()).value(), record_sorted_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_enum_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(enum_not_sorted).value(), + pps::sanitize_avro_schema_definition(enum_not_sorted.share()).value(), enum_sorted_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_array_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(array_not_sorted).value(), + pps::sanitize_avro_schema_definition(array_not_sorted.share()).value(), array_sorted_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_map_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(map_not_sorted).value(), + pps::sanitize_avro_schema_definition(map_not_sorted.share()).value(), map_sorted_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_avro_fixed_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(fixed_not_sorted).value(), + pps::sanitize_avro_schema_definition(fixed_not_sorted.share()).value(), fixed_sorted_sanitized); } BOOST_AUTO_TEST_CASE(test_sanitize_record_of_obj_sorting) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(record_of_obj_unsanitized).value(), + pps::sanitize_avro_schema_definition(record_of_obj_unsanitized.share()) + .value(), record_of_obj_sanitized); } BOOST_AUTO_TEST_CASE(test_namespace_nested_same) { BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(namespace_nested_same_unsanitized) + pps::sanitize_avro_schema_definition( + namespace_nested_same_unsanitized.share()) .value(), namespace_nested_same_sanitized); } @@ -344,9 +346,12 @@ pps::canonical_schema_definition debezium_schema{ pps::schema_type::avro}; BOOST_AUTO_TEST_CASE(test_sanitize_avro_debzium) { - auto unparsed = pandaproxy::schema_registry::unparsed_schema_definition{ - debezium_schema.raw()(), debezium_schema.type()}; + auto unparsed = pps::unparsed_schema_definition{ + pps::unparsed_schema_definition::raw_string{ + debezium_schema.shared_raw()()}, + debezium_schema.type()}; BOOST_REQUIRE_EQUAL( - pps::sanitize_avro_schema_definition(unparsed).value(), debezium_schema); + pps::sanitize_avro_schema_definition(unparsed.share()).value(), + debezium_schema); } diff --git a/src/v/pandaproxy/schema_registry/test/sharded_store.cc b/src/v/pandaproxy/schema_registry/test/sharded_store.cc index 2c2dbc7a67c2f..5ec2d277da914 100644 --- a/src/v/pandaproxy/schema_registry/test/sharded_store.cc +++ b/src/v/pandaproxy/schema_registry/test/sharded_store.cc @@ -25,19 +25,19 @@ namespace pps = pp::schema_registry; SEASTAR_THREAD_TEST_CASE(test_sharded_store_referenced_by) { pps::sharded_store store; - store.start(ss::default_smp_service_group()).get(); + store.start(pps::is_mutable::yes, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&store]() { store.stop().get(); }); const pps::schema_version ver1{1}; // Insert simple auto referenced_schema = pps::canonical_schema{ - pps::subject{"simple.proto"}, simple}; + pps::subject{"simple.proto"}, simple.share()}; store .upsert( pps::seq_marker{ std::nullopt, std::nullopt, ver1, pps::seq_marker_key_type::schema}, - referenced_schema, + referenced_schema.share(), pps::schema_id{1}, ver1, pps::is_deleted::no) @@ -45,13 +45,13 @@ SEASTAR_THREAD_TEST_CASE(test_sharded_store_referenced_by) { // Insert referenced auto importing_schema = pps::canonical_schema{ - pps::subject{"imported.proto"}, imported}; + pps::subject{"imported.proto"}, imported.share()}; store .upsert( pps::seq_marker{ std::nullopt, std::nullopt, ver1, pps::seq_marker_key_type::schema}, - importing_schema, + importing_schema.share(), pps::schema_id{2}, ver1, pps::is_deleted::no) @@ -79,7 +79,7 @@ SEASTAR_THREAD_TEST_CASE(test_sharded_store_referenced_by) { .upsert( pps::seq_marker{ std::nullopt, std::nullopt, ver1, pps::seq_marker_key_type::schema}, - importing_schema, + importing_schema.share(), pps::schema_id{2}, ver1, pps::is_deleted::yes) @@ -97,7 +97,7 @@ SEASTAR_THREAD_TEST_CASE(test_sharded_store_referenced_by) { SEASTAR_THREAD_TEST_CASE(test_sharded_store_find_unordered) { pps::sharded_store store; - store.start(ss::default_smp_service_group()).get(); + store.start(pps::is_mutable::no, ss::default_smp_service_group()).get(); auto stop_store = ss::defer([&store]() { store.stop().get(); }); pps::unparsed_schema array_unsanitized{ @@ -116,18 +116,18 @@ SEASTAR_THREAD_TEST_CASE(test_sharded_store_find_unordered) { // Insert an unsorted schema "onto the topic". auto referenced_schema = pps::canonical_schema{ - pps::subject{"simple.proto"}, simple}; + pps::subject{"simple.proto"}, simple.share()}; store .upsert( pps::seq_marker{ std::nullopt, std::nullopt, ver1, pps::seq_marker_key_type::schema}, - array_unsanitized, + array_unsanitized.share(), pps::schema_id{1}, ver1, pps::is_deleted::no) .get(); - auto res = store.has_schema(array_sanitized).get(); + auto res = store.has_schema(array_sanitized.share()).get(); BOOST_REQUIRE_EQUAL(res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(res.version, ver1); } diff --git a/src/v/pandaproxy/schema_registry/test/storage.cc b/src/v/pandaproxy/schema_registry/test/storage.cc index d79b14fc60663..aef243d10295e 100644 --- a/src/v/pandaproxy/schema_registry/test/storage.cc +++ b/src/v/pandaproxy/schema_registry/test/storage.cc @@ -127,75 +127,75 @@ const pps::delete_subject_value delete_subject_value{ BOOST_AUTO_TEST_CASE(test_storage_serde) { { - auto key = ppj::rjson_parse( + auto key = ppj::impl::rjson_parse( avro_schema_key_sv.data(), pps::schema_key_handler<>{}); BOOST_CHECK_EQUAL(avro_schema_key, key); - auto str = ppj::rjson_serialize(avro_schema_key); + auto str = ppj::rjson_serialize_str(avro_schema_key); BOOST_CHECK_EQUAL(str, ::json::minify(avro_schema_key_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( avro_schema_value_sv.data(), pps::canonical_schema_value_handler<>{}); BOOST_CHECK_EQUAL(avro_schema_value, val); - auto str = ppj::rjson_serialize(avro_schema_value); + auto str = ppj::rjson_serialize_str(avro_schema_value); BOOST_CHECK_EQUAL(str, ::json::minify(avro_schema_value_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( config_key_sv.data(), pps::config_key_handler<>{}); BOOST_CHECK_EQUAL(config_key, val); - auto str = ppj::rjson_serialize(config_key); + auto str = ppj::rjson_serialize_str(config_key); BOOST_CHECK_EQUAL(str, ::json::minify(config_key_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( config_key_sub_sv.data(), pps::config_key_handler<>{}); BOOST_CHECK_EQUAL(config_key_sub, val); - auto str = ppj::rjson_serialize(config_key_sub); + auto str = ppj::rjson_serialize_str(config_key_sub); BOOST_CHECK_EQUAL(str, ::json::minify(config_key_sub_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( config_value_sv.data(), pps::config_value_handler<>{}); BOOST_CHECK_EQUAL(config_value, val); - auto str = ppj::rjson_serialize(config_value); + auto str = ppj::rjson_serialize_str(config_value); BOOST_CHECK_EQUAL(str, ::json::minify(config_value_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( config_value_sub_sv.data(), pps::config_value_handler<>{}); BOOST_CHECK_EQUAL(config_value_sub, val); - auto str = ppj::rjson_serialize(config_value_sub); + auto str = ppj::rjson_serialize_str(config_value_sub); BOOST_CHECK_EQUAL(str, ::json::minify(config_value_sub_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( delete_subject_key_sv.data(), pps::delete_subject_key_handler<>{}); BOOST_CHECK_EQUAL(delete_subject_key, val); - auto str = ppj::rjson_serialize(delete_subject_key); + auto str = ppj::rjson_serialize_str(delete_subject_key); BOOST_CHECK_EQUAL(str, ::json::minify(delete_subject_key_sv)); } { - auto val = ppj::rjson_parse( + auto val = ppj::impl::rjson_parse( delete_subject_value_sv.data(), pps::delete_subject_value_handler<>{}); BOOST_CHECK_EQUAL(delete_subject_value, val); - auto str = ppj::rjson_serialize(delete_subject_value); + auto str = ppj::rjson_serialize_str(delete_subject_value); BOOST_CHECK_EQUAL(str, ::json::minify(delete_subject_value_sv)); } } diff --git a/src/v/pandaproxy/schema_registry/test/store.cc b/src/v/pandaproxy/schema_registry/test/store.cc index 74f424cfc2670..b551d773940a8 100644 --- a/src/v/pandaproxy/schema_registry/test/store.cc +++ b/src/v/pandaproxy/schema_registry/test/store.cc @@ -38,31 +38,31 @@ BOOST_AUTO_TEST_CASE(test_store_insert) { pps::store s; // First insert, expect id{1}, version{1} - auto ins_res = s.insert({subject0, string_def0}); + auto ins_res = s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); // Insert duplicate, expect id{1}, versions{1} - ins_res = s.insert({subject0, string_def0}); + ins_res = s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(!ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); // Insert duplicate, with spaces, expect id{1}, versions{1} - ins_res = s.insert({subject0, string_def1}); + ins_res = s.insert({subject0, string_def1.share()}); BOOST_REQUIRE(!ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); // Insert on different subject, expect id{1}, version{1} - ins_res = s.insert({subject1, string_def0}); + ins_res = s.insert({subject1, string_def0.share()}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); // Insert different schema, expect id{2}, version{2} - ins_res = s.insert({subject0, int_def0}); + ins_res = s.insert({subject0, int_def0.share()}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{2}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{2}); @@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_in_order) { BOOST_REQUIRE(upsert( s, subject0, - string_def0, + string_def0.share(), pps::schema_type::avro, pps::schema_id{0}, pps::schema_version{0}, @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_in_order) { BOOST_REQUIRE(upsert( s, subject0, - string_def0, + string_def0.share(), pps::schema_type::avro, pps::schema_id{1}, pps::schema_version{1}, @@ -121,7 +121,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_reverse_order) { BOOST_REQUIRE(upsert( s, subject0, - string_def0, + string_def0.share(), pps::schema_type::avro, pps::schema_id{1}, pps::schema_version{1}, @@ -129,7 +129,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_reverse_order) { BOOST_REQUIRE(upsert( s, subject0, - string_def0, + string_def0.share(), pps::schema_type::avro, pps::schema_id{0}, pps::schema_version{0}, @@ -152,7 +152,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_override) { BOOST_REQUIRE(upsert( s, subject0, - string_def0, + string_def0.share(), pps::schema_type::avro, pps::schema_id{0}, pps::schema_version{0}, @@ -161,7 +161,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_override) { BOOST_REQUIRE(!upsert( s, subject0, - int_def0, + int_def0.share(), pps::schema_type::avro, pps::schema_id{0}, pps::schema_version{0}, @@ -177,7 +177,7 @@ BOOST_AUTO_TEST_CASE(test_store_upsert_override) { auto s_res = s.get_subject_schema( subject0, pps::schema_version{0}, pps::include_deleted::no); BOOST_REQUIRE(s_res.has_value()); - BOOST_REQUIRE(s_res.value().schema.def() == int_def0); + BOOST_REQUIRE(s_res.value().schema.def() == int_def0.share()); } BOOST_AUTO_TEST_CASE(test_store_get_schema) { @@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema) { BOOST_REQUIRE(err.code() == pps::error_code::schema_id_not_found); // First insert, expect id{1} - auto ins_res = s.insert({subject0, string_def0}); + auto ins_res = s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); @@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema) { BOOST_REQUIRE(res.has_value()); auto def = std::move(res).assume_value(); - BOOST_REQUIRE_EQUAL(def, string_def0); + BOOST_REQUIRE_EQUAL(def, string_def0.share()); } BOOST_AUTO_TEST_CASE(test_store_get_schema_subject_versions) { @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema_subject_versions) { // First insert, expect id{1} auto ins_res = s.insert( - {subject0, pps::canonical_schema_definition(schema1)}); + {subject0, pps::canonical_schema_definition(schema1.share())}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); @@ -222,7 +222,8 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema_subject_versions) { BOOST_REQUIRE(versions.empty()); // Second insert, expect id{2} - ins_res = s.insert({subject0, pps::canonical_schema_definition(schema2)}); + ins_res = s.insert( + {subject0, pps::canonical_schema_definition(schema2.share())}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{2}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{2}); @@ -259,7 +260,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema_subjects) { // First insert, expect id{1} auto ins_res = s.insert( - {subject0, pps::canonical_schema_definition(schema1)}); + {subject0, pps::canonical_schema_definition(schema1.share())}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); @@ -270,13 +271,15 @@ BOOST_AUTO_TEST_CASE(test_store_get_schema_subjects) { BOOST_REQUIRE_EQUAL(absl::c_count_if(subjects, is_equal(subject0)), 1); // Second insert, same schema, expect id{1} - ins_res = s.insert({subject1, pps::canonical_schema_definition(schema1)}); + ins_res = s.insert( + {subject1, pps::canonical_schema_definition(schema1.share())}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); // Insert yet another schema associated with a different subject - ins_res = s.insert({subject2, pps::canonical_schema_definition(schema2)}); + ins_res = s.insert( + {subject2, pps::canonical_schema_definition(schema2.share())}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{2}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); @@ -326,7 +329,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_subject_schema) { BOOST_REQUIRE(err.code() == pps::error_code::subject_not_found); // First insert, expect id{1}, version{1} - auto ins_res = s.insert({subject0, string_def0}); + auto ins_res = s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(ins_res.version, pps::schema_version{1}); @@ -339,10 +342,10 @@ BOOST_AUTO_TEST_CASE(test_store_get_subject_schema) { BOOST_REQUIRE_EQUAL(val.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(val.version, pps::schema_version{1}); BOOST_REQUIRE_EQUAL(val.deleted, pps::is_deleted::no); - BOOST_REQUIRE_EQUAL(val.schema.def(), string_def0); + BOOST_REQUIRE_EQUAL(val.schema.def(), string_def0.share()); // Second insert, expect id{1}, version{1} - ins_res = s.insert({subject0, string_def0}); + ins_res = s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(!ins_res.inserted); BOOST_REQUIRE_EQUAL(ins_res.id, pps::schema_id{1}); @@ -354,7 +357,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_subject_schema) { BOOST_REQUIRE_EQUAL(val.id, pps::schema_id{1}); BOOST_REQUIRE_EQUAL(val.version, pps::schema_version{1}); BOOST_REQUIRE_EQUAL(val.deleted, pps::is_deleted::no); - BOOST_REQUIRE_EQUAL(val.schema.def(), string_def0); + BOOST_REQUIRE_EQUAL(val.schema.def(), string_def0.share()); // Request bad version res = s.get_subject_schema( @@ -368,7 +371,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_versions) { pps::store s; // First insert, expect id{1}, version{1} - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); auto versions = s.get_versions(subject0, pps::include_deleted::no); BOOST_REQUIRE(versions.has_value()); @@ -376,7 +379,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_versions) { BOOST_REQUIRE_EQUAL(versions.value().front(), pps::schema_version{1}); // Insert duplicate, expect id{1}, versions{1} - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); versions = s.get_versions(subject0, pps::include_deleted::no); BOOST_REQUIRE(versions.has_value()); @@ -384,7 +387,7 @@ BOOST_AUTO_TEST_CASE(test_store_get_versions) { BOOST_REQUIRE_EQUAL(versions.value().front(), pps::schema_version{1}); // Insert different schema, expect id{2}, version{2} - s.insert({subject0, int_def0}); + s.insert({subject0, int_def0.share()}); versions = s.get_versions(subject0, pps::include_deleted::no); BOOST_REQUIRE(versions.has_value()); @@ -404,13 +407,13 @@ BOOST_AUTO_TEST_CASE(test_store_get_subjects) { BOOST_REQUIRE(subjects.empty()); // First insert - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); subjects = s.get_subjects(pps::include_deleted::no); BOOST_REQUIRE_EQUAL(subjects.size(), 1); BOOST_REQUIRE_EQUAL(absl::c_count_if(subjects, is_equal(subject0)), 1); // second insert - s.insert({subject1, string_def0}); + s.insert({subject1, string_def0.share()}); subjects = s.get_subjects(pps::include_deleted::no); BOOST_REQUIRE(subjects.size() == 2); BOOST_REQUIRE_EQUAL(absl::c_count_if(subjects, is_equal(subject0)), 1); @@ -476,7 +479,7 @@ BOOST_AUTO_TEST_CASE(test_store_subject_compat) { pps::compatibility_level::backward}; pps::store s; BOOST_REQUIRE(s.get_compatibility().value() == global_expected); - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); auto sub_expected = pps::compatibility_level::backward; BOOST_REQUIRE( @@ -514,7 +517,7 @@ BOOST_AUTO_TEST_CASE(test_store_subject_compat_fallback) { pps::compatibility_level expected{pps::compatibility_level::backward}; pps::store s; - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); BOOST_REQUIRE(s.get_compatibility(subject0, fallback).value() == expected); expected = pps::compatibility_level::forward; @@ -562,8 +565,8 @@ BOOST_AUTO_TEST_CASE(test_store_delete_subject) { pps::error_code::subject_not_found); // First insert, expect id{1}, version{1} - s.insert({subject0, string_def0}); - s.insert({subject0, int_def0}); + s.insert({subject0, string_def0.share()}); + s.insert({subject0, int_def0.share()}); auto v_res = s.get_versions(subject0, pps::include_deleted::no); BOOST_REQUIRE(v_res.has_value()); @@ -672,8 +675,8 @@ BOOST_AUTO_TEST_CASE(test_store_delete_subject_version) { pps::error_code::subject_not_found); // First insert, expect id{1}, version{1} - s.insert({subject0, string_def0}); - s.insert({subject0, int_def0}); + s.insert({subject0, string_def0.share()}); + s.insert({subject0, int_def0.share()}); auto v_res = s.get_versions(subject0, pps::include_deleted::no); BOOST_REQUIRE(v_res.has_value()); @@ -743,9 +746,9 @@ BOOST_AUTO_TEST_CASE(test_store_subject_version_latest) { s.set_compatibility(pps::compatibility_level::none).value(); // First insert, expect id{1}, version{1} - s.insert({subject0, string_def0}); + s.insert({subject0, string_def0.share()}); // First insert, expect id{2}, version{2} - s.insert({subject0, int_def0}); + s.insert({subject0, int_def0.share()}); // Test latest auto latest = s.get_subject_version_id( @@ -791,8 +794,8 @@ BOOST_AUTO_TEST_CASE(test_store_delete_subject_after_delete_version) { pps::seq_marker dummy_marker; // First insert, expect id{1}, version{1} - s.insert({subject0, string_def0}); - s.insert({subject0, int_def0}); + s.insert({subject0, string_def0.share()}); + s.insert({subject0, int_def0.share()}); // delete version 1 s.upsert_subject( diff --git a/src/v/pandaproxy/schema_registry/test/util.cc b/src/v/pandaproxy/schema_registry/test/util.cc index d5bbc282833bc..f50297e631ba5 100644 --- a/src/v/pandaproxy/schema_registry/test/util.cc +++ b/src/v/pandaproxy/schema_registry/test/util.cc @@ -44,7 +44,8 @@ BOOST_AUTO_TEST_CASE(test_make_schema_definition) { auto res = pps::make_schema_definition>(example_avro_schema); BOOST_REQUIRE(res); - BOOST_REQUIRE_EQUAL(res.value()(), minified_avro_schema); + auto str = to_string(std::move(res).value()); + BOOST_REQUIRE_EQUAL(str, minified_avro_schema); } BOOST_AUTO_TEST_CASE(test_make_schema_definition_failure) { diff --git a/src/v/pandaproxy/schema_registry/types.cc b/src/v/pandaproxy/schema_registry/types.cc index a53d52b2bf3d6..62f9100831214 100644 --- a/src/v/pandaproxy/schema_registry/types.cc +++ b/src/v/pandaproxy/schema_registry/types.cc @@ -11,6 +11,8 @@ #include "types.h" +#include "util.h" + #include #include #include @@ -47,7 +49,8 @@ std::ostream& operator<<( os, "type: {}, definition: {}, references: {}", to_string_view(def.type()), - def.raw(), + // TODO BP: Prevent this linearization + to_string(def.shared_raw()), def.refs()); return os; } @@ -59,7 +62,8 @@ std::ostream& operator<<( os, "type: {}, definition: {}, references: {}", to_string_view(def.type()), - def.raw(), + // TODO BP: Prevent this linearization + to_string(def.shared_raw()), def.refs()); return os; } @@ -70,6 +74,11 @@ std::ostream& operator<<(std::ostream& os, const schema_reference& ref) { return os; } +bool operator<(const schema_reference& lhs, const schema_reference& rhs) { + return std::tie(lhs.name, lhs.sub, lhs.version) + < std::tie(rhs.name, rhs.sub, rhs.version); +} + std::ostream& operator<<(std::ostream& os, const unparsed_schema& ref) { fmt::print(os, "subject: {}, {}", ref.sub(), ref.def()); return os; diff --git a/src/v/pandaproxy/schema_registry/types.h b/src/v/pandaproxy/schema_registry/types.h index 2a594f72795e9..7609d7e779e23 100644 --- a/src/v/pandaproxy/schema_registry/types.h +++ b/src/v/pandaproxy/schema_registry/types.h @@ -11,6 +11,7 @@ #pragma once +#include "json/iobuf_writer.h" #include "kafka/protocol/errors.h" #include "model/metadata.h" #include "outcome.h" @@ -28,16 +29,41 @@ namespace pandaproxy::schema_registry { +using is_mutable = ss::bool_class; using permanent_delete = ss::bool_class; using include_deleted = ss::bool_class; using is_deleted = ss::bool_class; using default_to_global = ss::bool_class; using force = ss::bool_class; +using normalize = ss::bool_class; +using verbose = ss::bool_class; template std::enable_if_t, std::optional> from_string_view(std::string_view); +enum class mode { import = 0, read_only, read_write }; + +constexpr std::string_view to_string_view(mode e) { + switch (e) { + case mode::import: + return "IMPORT"; + case mode::read_only: + return "READONLY"; + case mode::read_write: + return "READWRITE"; + } + return "{invalid}"; +} +template<> +constexpr std::optional from_string_view(std::string_view sv) { + return string_switch>(sv) + .match(to_string_view(mode::import), mode::import) + .match(to_string_view(mode::read_only), mode::read_only) + .match(to_string_view(mode::read_write), mode::read_write) + .default_match(std::nullopt); +} + enum class schema_type { avro = 0, json, protobuf }; constexpr std::string_view to_string_view(schema_type e) { @@ -84,6 +110,9 @@ struct schema_reference { friend std::ostream& operator<<(std::ostream& os, const schema_reference& ref); + friend bool + operator<(const schema_reference& lhs, const schema_reference& rhs); + ss::sstring name; subject sub{invalid_subject}; schema_version version{invalid_schema_version}; @@ -94,18 +123,33 @@ template class typed_schema_definition { public: using tag = Tag; - using raw_string = named_type; + struct raw_string : named_type { + raw_string() = default; + explicit raw_string(iobuf&& buf) noexcept + : named_type{std::move(buf)} {} + explicit raw_string(std::string_view sv) + : named_type{iobuf::from(sv)} {} + }; using references = std::vector; + typed_schema_definition() = default; + typed_schema_definition(typed_schema_definition&&) noexcept = default; + typed_schema_definition(const typed_schema_definition&) = delete; + typed_schema_definition& operator=(typed_schema_definition&&) noexcept + = default; + typed_schema_definition& operator=(const typed_schema_definition& other) + = delete; + ~typed_schema_definition() noexcept = default; + template typed_schema_definition(T&& def, schema_type type) - : _def{ss::sstring{std::forward(def)}} + : _def{std::forward(def)} , _type{type} , _refs{} {} template typed_schema_definition(T&& def, schema_type type, references refs) - : _def{ss::sstring{std::forward(def)}} + : _def{std::forward(def)} , _type{type} , _refs{std::move(refs)} {} @@ -120,10 +164,26 @@ class typed_schema_definition { const raw_string& raw() const& { return _def; } raw_string raw() && { return std::move(_def); } + raw_string shared_raw() const { + auto& buf = const_cast(_def()); + return raw_string{buf.share(0, buf.size_bytes())}; + } const references& refs() const& { return _refs; } references refs() && { return std::move(_refs); } + typed_schema_definition share() const { + return {shared_raw(), type(), refs()}; + } + + typed_schema_definition copy() const { + return {raw_string{_def().copy()}, type(), refs()}; + } + + auto destructure() && { + return make_tuple(std::move(_def), _type, std::move(_refs)); + } + private: raw_string _def; schema_type _type{schema_type::avro}; @@ -289,22 +349,28 @@ struct subject_version { }; // Very similar to topic_key_type, separate to avoid intermingling storage code -enum class seq_marker_key_type { invalid = 0, schema, delete_subject, config }; +enum class seq_marker_key_type { + invalid = 0, + schema, + delete_subject, + config, + mode +}; constexpr std::string_view to_string_view(seq_marker_key_type v) { switch (v) { case seq_marker_key_type::schema: return "schema"; - break; case seq_marker_key_type::delete_subject: return "delete_subject"; - break; case seq_marker_key_type::config: return "config"; + case seq_marker_key_type::mode: + return "mode"; + case seq_marker_key_type::invalid: break; - default: - return "invalid"; } + return "invalid"; } // Record the sequence+node where updates were made to a subject, @@ -350,6 +416,13 @@ class typed_schema { const schema_definition& def() const& { return _def; } schema_definition def() && { return std::move(_def); } + typed_schema share() const { return {sub(), def().share()}; } + typed_schema copy() const { return {sub(), def().copy()}; } + + auto destructure() && { + return make_tuple(std::move(_sub), std::move(_def)); + } + private: subject _sub{invalid_subject}; schema_definition _def{"", schema_type::avro}; @@ -364,6 +437,9 @@ struct subject_schema { schema_version version{invalid_schema_version}; schema_id id{invalid_schema_id}; is_deleted deleted{false}; + subject_schema share() const { + return {schema.share(), version, id, deleted}; + } }; enum class compatibility_level { @@ -421,4 +497,21 @@ from_string_view(std::string_view sv) { .default_match(std::nullopt); } +struct compatibility_result { + bool is_compat; + std::vector messages; +}; + } // namespace pandaproxy::schema_registry + +namespace json { + +template +void rjson_serialize( + json::iobuf_writer& w, + const pandaproxy::schema_registry::canonical_schema_definition::raw_string& + def) { + w.String(def()); +} + +} // namespace json diff --git a/src/v/pandaproxy/schema_registry/util.h b/src/v/pandaproxy/schema_registry/util.h index bdd50017721b0..ad24dd28d0d1d 100644 --- a/src/v/pandaproxy/schema_registry/util.h +++ b/src/v/pandaproxy/schema_registry/util.h @@ -11,9 +11,9 @@ #pragma once +#include "bytes/iobuf_parser.h" +#include "json/chunked_buffer.h" #include "json/document.h" -#include "json/json.h" -#include "json/stringbuffer.h" #include "json/writer.h" #include "likely.h" #include "pandaproxy/schema_registry/errors.h" @@ -37,7 +37,7 @@ namespace pandaproxy::schema_registry { /// /// Returns error_code::schema_invalid on failure template -result +result make_schema_definition(std::string_view sv) { // Validate and minify // TODO (Ben): Minify. e.g.: @@ -52,12 +52,16 @@ make_schema_definition(std::string_view sv) { rapidjson::GetParseError_En(doc.GetParseError()), doc.GetErrorOffset())}; } - ::json::GenericStringBuffer str_buf; - str_buf.Reserve(sv.size()); - ::json::Writer<::json::GenericStringBuffer> w{str_buf}; + ::json::chunked_buffer buf; + ::json::Writer<::json::chunked_buffer> w{buf}; doc.Accept(w); - return unparsed_schema_definition::raw_string{ - ss::sstring{str_buf.GetString(), str_buf.GetSize()}}; + return canonical_schema_definition::raw_string{std::move(buf).as_iobuf()}; +} + +template +ss::sstring to_string(named_type def) { + iobuf_parser p{std::move(def)}; + return p.read_string(p.bytes_left()); } } // namespace pandaproxy::schema_registry diff --git a/src/v/pandaproxy/schema_registry/validation.cc b/src/v/pandaproxy/schema_registry/validation.cc index cd37343622c74..ad9a39cc6c000 100644 --- a/src/v/pandaproxy/schema_registry/validation.cc +++ b/src/v/pandaproxy/schema_registry/validation.cc @@ -108,19 +108,19 @@ std::vector get_proto_offsets(iobuf_parser& p) { ss::future> get_record_name( pandaproxy::schema_registry::sharded_store& store, field field, - const model::topic& topic, subject_name_strategy sns, - const canonical_schema_definition& schema, + canonical_schema_definition schema, std::optional>& offsets) { if (sns == subject_name_strategy::topic_name) { // Result is succesfully nothing co_return ""; } - switch (schema.type()) { + auto schema_type = schema.type(); + switch (schema_type) { case schema_type::avro: { auto s = co_await make_avro_schema_definition( - store, {subject("r"), {schema.raw(), schema.type()}}); + store, {subject("r"), {std::move(schema).raw(), schema_type}}); co_return s().root()->name().fullname(); } break; case schema_type::protobuf: { @@ -128,7 +128,7 @@ ss::future> get_record_name( co_return std::nullopt; } auto s = co_await make_protobuf_schema_definition( - store, {subject("r"), {schema.raw(), schema.type()}}); + store, {subject("r"), {std::move(schema).raw(), schema_type}}); auto r = s.name(*offsets); if (!r) { co_return std::nullopt; @@ -275,7 +275,7 @@ class schema_id_validator::impl { } auto record_name = co_await get_record_name( - *_api->_store, field, topic, sns, *schema, proto_offsets); + *_api->_store, field, sns, *std::move(schema), proto_offsets); if (!record_name) { vlog( plog.debug, diff --git a/src/v/pandaproxy/server.cc b/src/v/pandaproxy/server.cc index be9066edb2b20..da882572d46b3 100644 --- a/src/v/pandaproxy/server.cc +++ b/src/v/pandaproxy/server.cc @@ -143,6 +143,7 @@ server::server( _api20.set_api_doc(_server._routes); _api20.register_api_file(_server._routes, header); _api20.add_definitions_file(_server._routes, definitions); + _server.set_content_streaming(true); } /* diff --git a/src/v/raft/configuration_manager.cc b/src/v/raft/configuration_manager.cc index 7326be3f0b2bd..0adfe10a76d93 100644 --- a/src/v/raft/configuration_manager.cc +++ b/src/v/raft/configuration_manager.cc @@ -177,6 +177,7 @@ configuration_manager::add(std::vector configurations) { // handling backward compatibility i.e. revisionless configurations co.cfg.maybe_set_initial_revision(_initial_revision); + reset_override(co.cfg.revision_id()); add_configuration(co.offset, std::move(co.cfg)); _highest_known_offset = std::max(_highest_known_offset, co.offset); } @@ -211,9 +212,30 @@ const group_configuration& configuration_manager::get_latest() const { vassert( !_configurations.empty(), "Configuration manager should always have at least one configuration"); + if (_configuration_force_override) [[unlikely]] { + return *_configuration_force_override; + } + return _configurations.rbegin()->second.cfg; } +void configuration_manager::set_override(group_configuration cfg) { + vlog(_ctxlog.info, "Setting configuration override to {}", cfg); + _configuration_force_override = std::make_unique( + std::move(cfg)); +} + +void configuration_manager::reset_override( + model::revision_id added_configuration_revision) { + if ( + _configuration_force_override + && _configuration_force_override->revision_id() + <= added_configuration_revision) [[unlikely]] { + vlog(_ctxlog.info, "Resetting configuration override"); + _configuration_force_override.reset(); + } +}; + model::offset configuration_manager::get_latest_offset() const { vassert( !_configurations.empty(), diff --git a/src/v/raft/configuration_manager.h b/src/v/raft/configuration_manager.h index 117e5fe0a088a..c202f3bb29b8a 100644 --- a/src/v/raft/configuration_manager.h +++ b/src/v/raft/configuration_manager.h @@ -167,10 +167,27 @@ class configuration_manager { ss::future<> adjust_configuration_idx(configuration_idx); + /** + * Sets a forced override for current configuration. The override is active + * and returned as latest configuration until it is cleared by adding a new + * configuration to group configuration manage. + * + * @param cfg the configuration to override with + */ + void set_override(group_configuration); + + /** + * Checks if configuration override is active + */ + bool has_configuration_override() const { + return _configuration_force_override != nullptr; + } + friend std::ostream& operator<<(std::ostream&, const configuration_manager&); private: + void reset_override(model::revision_id); ss::future<> store_configurations(); ss::future<> store_highest_known_offset(); bytes configurations_map_key() const { @@ -219,5 +236,7 @@ class configuration_manager { model::revision_id _initial_revision{}; ctx_log& _ctxlog; configuration_idx _next_index{0}; + std::unique_ptr _configuration_force_override + = nullptr; }; } // namespace raft diff --git a/src/v/raft/consensus.cc b/src/v/raft/consensus.cc index fbd8249ac0937..79a2f071124d7 100644 --- a/src/v/raft/consensus.cc +++ b/src/v/raft/consensus.cc @@ -276,6 +276,7 @@ ss::future<> consensus::stop() { co_await _append_requests_buffer.stop(); co_await _batcher.stop(); + _election_lock.broken(); _op_lock.broken(); co_await _bg.close(); @@ -354,7 +355,7 @@ consensus::success_reply consensus::update_follower_index( "Received append entries response node_id doesn't match expected " "node_id (received: {}, expected: {})", reply.node_id.id(), - node); + physical_node); return success_reply::no; } @@ -515,19 +516,34 @@ void consensus::maybe_promote_to_voter(vnode id) { return ss::now(); } - vlog(_ctxlog.trace, "promoting node {} to voter", id); - return _op_lock.get_units() - .then([this, id](ssx::semaphore_units u) mutable { - auto latest_cfg = _configuration_manager.get_latest(); - latest_cfg.promote_to_voter(id); + // do not promote if the previous configuration is still uncommitted, + // otherwise we may add several new voters in quick succession, that the + // old voters will not know of, resulting in a possibility of + // non-intersecting quorums. + if (_configuration_manager.get_latest_offset() > _commit_index) { + return ss::now(); + } - return replicate_configuration( - std::move(u), std::move(latest_cfg)); - }) - .then([this, id](std::error_code ec) { - vlog( - _ctxlog.trace, "node {} promotion result {}", id, ec.message()); - }); + return _op_lock.get_units().then([this, + id](ssx::semaphore_units u) mutable { + // check once more under _op_lock to protect against races with + // concurrent voter promotions. + if (_configuration_manager.get_latest_offset() > _commit_index) { + return ss::now(); + } + + vlog(_ctxlog.trace, "promoting node {} to voter", id); + auto latest_cfg = _configuration_manager.get_latest(); + latest_cfg.promote_to_voter(id); + return replicate_configuration(std::move(u), std::move(latest_cfg)) + .then([this, id](std::error_code ec) { + vlog( + _ctxlog.trace, + "node {} promotion result {}", + id, + ec.message()); + }); + }); }); } @@ -884,11 +900,26 @@ bool consensus::should_skip_vote(bool ignore_heartbeat) { } ss::future consensus::dispatch_prevote(bool leadership_transfer) { - auto pvstm_p = std::make_unique(this); - auto pvstm = pvstm_p.get(); + vlog( + _ctxlog.info, + "starting pre-vote leader election, current term: {}, leadership " + "transfer: {}", + _term, + leadership_transfer); + if (leadership_transfer) { - return ss::make_ready_future(true); + return _op_lock.with([this]() { + _vstate = vote_state::candidate; + if (_leader_id) { + _leader_id = std::nullopt; + trigger_leadership_notification(); + } + return true; + }); } + auto pvstm_p = std::make_unique(this); + auto pvstm = pvstm_p.get(); + return pvstm->prevote(leadership_transfer) .then_wrapped([this, pvstm_p = std::move(pvstm_p), pvstm]( ss::future prevote_f) mutable { @@ -967,7 +998,16 @@ void consensus::dispatch_vote(bool leadership_transfer) { = ssx::spawn_with_gate_then(_bg, [this, leadership_transfer] { return dispatch_prevote(leadership_transfer) .then([this, leadership_transfer](bool ready) mutable { - if (!ready) { + vlog( + _ctxlog.debug, + "pre-vote phase success: {}, current term: {}, " + "leadership transfer: {}", + ready, + _term, + leadership_transfer); + // if a current node is not longer candidate we should skip + // proceeding to actual vote phase + if (!ready || _vstate != vote_state::candidate) { return ss::make_ready_future<>(); } auto vstm = std::make_unique(this); @@ -1275,14 +1315,10 @@ consensus::abort_configuration_change(model::revision_id revision) { if (latest_cfg.revision_id() > revision) { co_return errc::invalid_configuration_update; } + auto new_cfg = latest_cfg; new_cfg.abort_configuration_change(revision); - auto batches = details::serialize_configuration_as_batches( - std::move(new_cfg)); - for (auto& b : batches) { - b.set_term(_term); - }; /** * Aborting configuration change is an operation that may lead to data loss. * It must be possible to abort configuration change even if there is no @@ -1290,21 +1326,10 @@ consensus::abort_configuration_change(model::revision_id revision) { * replicas log. If new leader will be elected using new configuration it * will eventually propagate valid configuration to all the followers. */ - auto append_result = co_await disk_append( - model::make_memory_record_batch_reader(std::move(batches)), - update_last_quorum_index::yes); - vlog( - _ctxlog.info, - "appended reconfiguration aborting configuration at offset {}", - append_result.base_offset); - // flush log as all configuration changes must eventually be committed. - co_await flush_log(); - // if current node is a leader make sure we will try to update committed - // index, it may be required for single participant raft groups - if (is_leader()) { - maybe_update_majority_replicated_index(); - maybe_update_leader_commit_idx(); - } + update_follower_stats(new_cfg); + _configuration_manager.set_override(std::move(new_cfg)); + do_step_down("reconfiguration-aborted"); + co_return errc::success; } @@ -1326,22 +1351,10 @@ ss::future consensus::force_replace_configuration_locally( new_cfg.set_version(group_configuration::v_6); } vlog(_ctxlog.info, "Force replacing configuration with: {}", new_cfg); - auto batches = details::serialize_configuration_as_batches( - std::move(new_cfg)); - for (auto& b : batches) { - b.set_term(_term); - }; - auto result = co_await disk_append( - model::make_memory_record_batch_reader(std::move(batches)), - update_last_quorum_index::yes); - vlog( - _ctxlog.debug, - "appended reconfiguration to force update replica " - "set at " - "offset {}", - result.base_offset); - co_await flush_log(); + update_follower_stats(new_cfg); + _configuration_manager.set_override(std::move(new_cfg)); + do_step_down("forced-reconfiguration"); } catch (const ss::broken_semaphore&) { co_return errc::shutting_down; @@ -1789,7 +1802,7 @@ ss::future consensus::do_vote(vote_request r) { _term = r.term; _voted_for = {}; term_changed = true; - do_step_down("voter_term_greater"); + do_step_down("candidate_term_greater"); if (_leader_id) { _leader_id = std::nullopt; trigger_leadership_notification(); @@ -1797,7 +1810,7 @@ ss::future consensus::do_vote(vote_request r) { // do not grant vote if log isn't ok if (!reply.log_ok) { - // even tough we step down we do not want to update the hbeat as it + // even though we step down we do not want to update the hbeat as it // would cause subsequent votes to fail (_hbeat is updated by the // leader) _hbeat = clock_type::time_point::min(); @@ -1864,7 +1877,7 @@ consensus::do_append_entries(append_entries_request&& r) { _probe->append_request(); if (unlikely(is_request_target_node_invalid("append_entries", r))) { - return ss::make_ready_future(reply); + co_return reply; } // no need to trigger timeout vlog(_ctxlog.trace, "Received append entries request: {}", r); @@ -1872,7 +1885,7 @@ consensus::do_append_entries(append_entries_request&& r) { // raft.pdf: Reply false if term < currentTerm (§5.1) if (request_metadata.term < _term) { reply.result = reply_result::failure; - return ss::make_ready_future(std::move(reply)); + co_return reply; } /** * When the current leader is alive, whenever a follower receives heartbeat, @@ -1891,7 +1904,7 @@ consensus::do_append_entries(append_entries_request&& r) { _voted_for = {}; maybe_update_leader(r.source_node()); - return do_append_entries(std::move(r)); + co_return co_await do_append_entries(std::move(r)); } // raft.pdf:If AppendEntries RPC received from new leader: convert to // follower (§5.2) @@ -1920,7 +1933,7 @@ consensus::do_append_entries(append_entries_request&& r) { request_metadata.dirty_offset > request_metadata.prev_log_index); reply.may_recover = _follower_recovery_state->is_active(); - return ss::make_ready_future(std::move(reply)); + co_return reply; } // section 2 @@ -1953,18 +1966,66 @@ consensus::do_append_entries(append_entries_request&& r) { request_metadata.dirty_offset > request_metadata.prev_log_index); reply.may_recover = _follower_recovery_state->is_active(); - return ss::make_ready_future(std::move(reply)); + co_return reply; } - // special case heartbeat case + model::offset adjusted_prev_log_index = request_metadata.prev_log_index; + if (adjusted_prev_log_index < last_log_offset) { + // The append point is before the end of our log. We need to skip + // over batches that we already have (they will have the matching + // term) to find the true truncation point. This is important for the + // case when we already have _all_ batches locally (possible if e.g. + // the request was delayed/duplicated). In this case we don't want to + // truncate, otherwise we might lose already committed data. + + struct find_mismatch_consumer { + const consensus& parent; + model::offset last_log_offset; + model::offset last_matched; + + ss::future + operator()(const model::record_batch& b) { + model::offset last_batch_offset + = last_matched + + model::offset(b.header().last_offset_delta + 1); + if ( + last_batch_offset > last_log_offset + || parent.get_term(last_batch_offset) != b.term()) { + co_return ss::stop_iteration::yes; + } + last_matched = last_batch_offset; + co_return ss::stop_iteration::no; + } + + model::offset end_of_stream() { return last_matched; } + }; + + model::offset last_matched = co_await r.batches().peek_each_ref( + find_mismatch_consumer{ + .parent = *this, + .last_log_offset = last_log_offset, + .last_matched = adjusted_prev_log_index}, + model::no_timeout); // no_timeout as the batches are already in memory + if (last_matched != adjusted_prev_log_index) { + vlog( + _ctxlog.info, + "skipped matching records in append_entries batch from {} to {}, " + "current state: {}", + adjusted_prev_log_index, + last_matched, + meta()); + adjusted_prev_log_index = last_matched; + } + } + + // special case for heartbeats and batches without new records. // we need to handle it early (before executing truncation) // as timeouts are asynchronous to append calls and can have stall data if (r.batches().is_end_of_stream()) { - if (request_metadata.prev_log_index < last_log_offset) { + if (adjusted_prev_log_index < last_log_offset) { // do not tuncate on heartbeat just response with false reply.result = reply_result::failure; - return ss::make_ready_future( - std::move(reply)); + co_return reply; } auto f = ss::now(); if (r.is_flush_required() && lstats.dirty_offset > _flushed_offset) { @@ -1987,18 +2048,15 @@ consensus::do_append_entries(append_entries_request&& r) { _follower_recovery_state.reset(); } - return f.then([this, reply, request_metadata] { - return maybe_update_follower_commit_idx( - model::offset(request_metadata.commit_index)) - .then([this, reply]() mutable { - reply.last_flushed_log_index = _flushed_offset; - reply.result = reply_result::success; - return reply; - }); - }); + co_await std::move(f); + co_await maybe_update_follower_commit_idx( + model::offset(request_metadata.commit_index)); + reply.last_flushed_log_index = _flushed_offset; + reply.result = reply_result::success; + co_return reply; } - if (request_metadata.prev_log_index < request_metadata.dirty_offset) { + if (adjusted_prev_log_index < request_metadata.dirty_offset) { // This is a valid recovery request. In case we haven't allowed it, // defer to the leader and force-enter the recovery state. upsert_recovery_state( @@ -2007,12 +2065,12 @@ consensus::do_append_entries(append_entries_request&& r) { } // section 3 - if (request_metadata.prev_log_index < last_log_offset) { - if (unlikely(request_metadata.prev_log_index < last_visible_index())) { + if (adjusted_prev_log_index < last_log_offset) { + if (unlikely(adjusted_prev_log_index < _commit_index)) { reply.result = reply_result::success; // clamp dirty offset to the current commit index not to allow // leader reasoning about follower log beyond that point - reply.last_dirty_log_index = last_visible_index(); + reply.last_dirty_log_index = _commit_index; reply.last_flushed_log_index = _commit_index; vlog( _ctxlog.info, @@ -2020,122 +2078,130 @@ consensus::do_append_entries(append_entries_request&& r) { "present, request: {}, current state: {}", request_metadata, meta()); - return ss::make_ready_future( - std::move(reply)); + co_return reply; } auto truncate_at = model::next_offset( - model::offset(request_metadata.prev_log_index)); + model::offset(adjusted_prev_log_index)); vlog( _ctxlog.info, "Truncating log in term: {}, Request previous log index: {} is " "earlier than log end offset: {}, last visible index: {}, leader " "last visible index: {}. Truncating to: {}", request_metadata.term, - request_metadata.prev_log_index, + adjusted_prev_log_index, lstats.dirty_offset, last_visible_index(), _last_leader_visible_offset, truncate_at); _probe->log_truncated(); + _last_quorum_replicated_index = std::min( + model::prev_offset(truncate_at), _last_quorum_replicated_index); + _majority_replicated_index = std::min( + model::prev_offset(truncate_at), _majority_replicated_index); + _flushed_offset = std::min( + model::prev_offset(truncate_at), _flushed_offset); - // We are truncating the offset translator before truncating the log - // because if saving offset translator state fails, we will retry and - // eventually log and offset translator will become consistent. OTOH if - // log truncation were first and saving offset translator state failed, - // we wouldn't retry and log and offset translator could diverge. - return _offset_translator.truncate(truncate_at) - .then([this, truncate_at] { - return _log->truncate(storage::truncate_config( - truncate_at, _scheduling.default_iopc)); - }) - .then([this, truncate_at] { - _last_quorum_replicated_index = std::min( - model::prev_offset(truncate_at), _last_quorum_replicated_index); - // update flushed offset since truncation may happen to already - // flushed entries - _flushed_offset = std::min( - model::prev_offset(truncate_at), _flushed_offset); - - return _configuration_manager.truncate(truncate_at).then([this] { - _probe->configuration_update(); - update_follower_stats(_configuration_manager.get_latest()); - }); - }) - .then([this, r = std::move(r), truncate_at]() mutable { - auto lstats = _log->offsets(); - if (unlikely( - lstats.dirty_offset != r.metadata().prev_log_index)) { - vlog( - _ctxlog.warn, - "Log truncation error, expected offset: {}, log " - "offsets: " - "{}, requested truncation at {}", - r.metadata().prev_log_index, - lstats, - truncate_at); - _flushed_offset = std::min( - model::prev_offset(lstats.dirty_offset), _flushed_offset); - } - return do_append_entries(std::move(r)); - }) - .handle_exception([this, reply](const std::exception_ptr& e) mutable { - vlog(_ctxlog.warn, "Error occurred while truncating log - {}", e); - reply.result = reply_result::failure; - return ss::make_ready_future(reply); - }); + try { + // We are truncating the offset translator before truncating the + // log because if saving offset translator state fails, we will + // retry and eventually log and offset translator will become + // consistent. OTOH if log truncation were first and saving + // offset translator state failed, we wouldn't retry and log and + // offset translator could diverge. + co_await _offset_translator.truncate(truncate_at); + + co_await _log->truncate( + storage::truncate_config(truncate_at, _scheduling.default_iopc)); + // update flushed offset once again after truncation as flush is + // executed concurrently to append entries and it may race with + // the truncation + _flushed_offset = std::min( + model::prev_offset(truncate_at), _flushed_offset); + + co_await _configuration_manager.truncate(truncate_at); + _probe->configuration_update(); + update_follower_stats(_configuration_manager.get_latest()); + + auto lstats = _log->offsets(); + if (unlikely(lstats.dirty_offset != adjusted_prev_log_index)) { + vlog( + _ctxlog.warn, + "Log truncation error, expected offset: {}, log offsets: {}, " + "requested truncation at {}", + adjusted_prev_log_index, + lstats, + truncate_at); + _flushed_offset = std::min( + model::prev_offset(lstats.dirty_offset), _flushed_offset); + } + } catch (...) { + vlog( + _ctxlog.warn, + "Error occurred while truncating log - {}", + std::current_exception()); + reply.result = reply_result::failure; + co_return reply; + } + + co_return co_await do_append_entries(std::move(r)); } // success. copy entries for each subsystem - using offsets_ret = storage::append_result; - return disk_append( - std::move(r).release_batches(), update_last_quorum_index::no) - .then([this, m = request_metadata, target = reply.target_node_id]( - offsets_ret ofs) { - auto last_visible = std::min(ofs.last_offset, m.last_visible_index); - maybe_update_last_visible_index(last_visible); - _last_leader_visible_offset = std::max( - m.last_visible_index, _last_leader_visible_offset); - _confirmed_term = _term; - return maybe_update_follower_commit_idx(model::offset(m.commit_index)) - .then([this, m, ofs, target] { - if (_follower_recovery_state) { - _follower_recovery_state->update_progress( - ofs.last_offset, - std::max(m.dirty_offset, ofs.last_offset)); - - if (m.dirty_offset == m.prev_log_index) { - // Normal (non-recovery, non-heartbeat) append_entries - // request means that recovery is over. - vlog( - _ctxlog.debug, - "exiting follower_recovery_state, leader meta: {} " - "(our offset: {})", - m, - ofs.last_offset); - _follower_recovery_state.reset(); - } - // m.dirty_offset can be bogus here if we are talking to - // a pre-23.3 redpanda. In this case we can't reliably - // distinguish between recovery and normal append_entries - // and will exit recovery only via heartbeats (which is okay - // but can inflate the number of recovering partitions - // statistic a bit). - } - return make_append_entries_reply(target, ofs); - }); - }) - .handle_exception([this, reply](const std::exception_ptr& e) mutable { - vlog( - _ctxlog.warn, "Error occurred while appending log entries - {}", e); - reply.result = reply_result::failure; - return ss::make_ready_future(reply); - }) - .finally([this] { - // we do not want to include our disk flush latency into - // the leader vote timeout - _hbeat = clock_type::now(); - }); + try { + auto deferred = ss::defer([this] { + // we do not want to include our disk flush latency into + // the leader vote timeout + _hbeat = clock_type::now(); + }); + + storage::append_result ofs = co_await disk_append( + std::move(r).release_batches(), update_last_quorum_index::no); + auto last_visible = std::min( + ofs.last_offset, request_metadata.last_visible_index); + maybe_update_last_visible_index(last_visible); + + _last_leader_visible_offset = std::max( + request_metadata.last_visible_index, _last_leader_visible_offset); + _confirmed_term = _term; + + co_await maybe_update_follower_commit_idx( + request_metadata.commit_index); + + if (_follower_recovery_state) { + _follower_recovery_state->update_progress( + ofs.last_offset, + std::max(request_metadata.dirty_offset, ofs.last_offset)); + + if ( + request_metadata.dirty_offset + == request_metadata.prev_log_index) { + // Normal (non-recovery, non-heartbeat) append_entries + // request means that recovery is over. + vlog( + _ctxlog.debug, + "exiting follower_recovery_state, leader meta: {} " + "(our offset: {})", + request_metadata, + ofs.last_offset); + _follower_recovery_state.reset(); + } + // m.dirty_offset can be bogus here if we are talking to + // a pre-23.3 redpanda. In this case we can't reliably + // distinguish between recovery and normal append_entries + // and will exit recovery only via heartbeats (which is okay + // but can inflate the number of recovering partitions + // statistic a bit). + } + co_return make_append_entries_reply(reply.target_node_id, ofs); + } catch (...) { + vlog( + _ctxlog.warn, + "Error occurred while appending log entries - {}", + std::current_exception()); + reply.result = reply_result::failure; + co_return reply; + } } void consensus::maybe_update_leader(vnode request_node) { @@ -2730,8 +2796,11 @@ ss::future consensus::disk_append( auto f = ss::now(); if (!configurations.empty()) { // we can use latest configuration to update follower stats - update_follower_stats(configurations.back().cfg); - f = _configuration_manager.add(std::move(configurations)); + f = _configuration_manager.add(std::move(configurations)) + .then([this] { + update_follower_stats( + _configuration_manager.get_latest()); + }); } return f.then([this, ret = ret] { @@ -3009,7 +3078,9 @@ void consensus::update_follower_stats(const group_configuration& cfg) { } void consensus::trigger_leadership_notification() { - _probe->leadership_changed(); + if (_leader_id == _self) { + _probe->leadership_changed(); + } vlog( _ctxlog.debug, "triggering leadership notification with term: {}, new leader: {}", @@ -3751,7 +3822,7 @@ reply_result consensus::lightweight_heartbeat( target_node, _self, source_node); - return reply_result::failure; + return reply_result::group_unavailable; } /** @@ -3806,7 +3877,7 @@ ss::future consensus::full_heartbeat( target_vnode, _self, source_vnode); - reply.result = reply_result::failure; + reply.result = reply_result::group_unavailable; co_return reply; } /** @@ -3911,4 +3982,18 @@ std::optional consensus::get_learner_start_offset() const { return std::nullopt; } +size_t consensus::bytes_to_deliver_to_learners() const { + if (!is_leader()) { + return 0; + } + + size_t total = 0; + for (auto& [f_id, f_meta] : _fstats) { + if (f_meta.is_learner) [[unlikely]] { + total += _log->size_bytes_after_offset(f_meta.match_index); + } + } + return total; +} + } // namespace raft diff --git a/src/v/raft/consensus.h b/src/v/raft/consensus.h index 0160c17022e96..7fe7f31df6e62 100644 --- a/src/v/raft/consensus.h +++ b/src/v/raft/consensus.h @@ -518,6 +518,15 @@ class consensus { inline void maybe_update_leader(vnode request_node); + bool has_configuration_override() const { + return _configuration_manager.has_configuration_override(); + } + /** + * Returns the number of bytes that are required to deliver to all + * learners that are being recovered. + */ + size_t bytes_to_deliver_to_learners() const; + private: friend replicate_entries_stm; friend vote_stm; @@ -806,7 +815,7 @@ class consensus { bool _transferring_leadership{false}; /// useful for when we are not the leader - clock_type::time_point _hbeat = clock_type::now(); + clock_type::time_point _hbeat = clock_type::now(); // is max() iff leader clock_type::time_point _became_leader_at = clock_type::now(); clock_type::time_point _instantiated_at = clock_type::now(); @@ -824,15 +833,22 @@ class consensus { /// used to wait for background ops before shutting down ss::gate _bg; + /** + * Locks listed in the order of nestedness, election being the outermost + * and snapshot the innermost. I.e. if any of these locks are used at the + * same time, they should be acquired in the listed order and released in + * reverse order. + */ + /// guards from concurrent election where this instance is a candidate + mutex _election_lock{"consensus::election_lock"}; /// all raft operations must happen exclusively since the common case /// is for the operation to touch the disk mutex _op_lock; /// since snapshot state is orthogonal to raft state when writing snapshot /// it is enough to grab the snapshot mutex, there is no need to keep - /// oplock, if the two locks are expected to be acquired at the same time - /// the snapshot lock should always be an internal (taken after the - /// _op_lock) - mutex _snapshot_lock; + /// oplock + mutex _snapshot_lock{"consensus::snapshot_lock"}; + /// used for notifying when commits happened to log event_manager _event_manager; std::unique_ptr _probe; diff --git a/src/v/raft/consensus_utils.cc b/src/v/raft/consensus_utils.cc index 0687b71de8b5c..2381ec343a7fc 100644 --- a/src/v/raft/consensus_utils.cc +++ b/src/v/raft/consensus_utils.cc @@ -315,84 +315,6 @@ group_configuration deserialize_nested_configuration(iobuf_parser& parser) { return reflection::adl{}.from(parser); } -model::record_batch_reader make_config_extracting_reader( - model::offset base_offset, - std::vector& target, - model::record_batch_reader&& source) { - class extracting_reader final : public model::record_batch_reader::impl { - private: - using storage_t = model::record_batch_reader::storage_t; - using data_t = model::record_batch_reader::data_t; - using foreign_t = model::record_batch_reader::foreign_data_t; - - public: - explicit extracting_reader( - model::offset o, - std::vector& target, - std::unique_ptr src) - : _next_offset( - o < model::offset(0) ? model::offset(0) : o + model::offset(1)) - , _configurations(target) - , _ptr(std::move(src)) {} - extracting_reader(const extracting_reader&) = delete; - extracting_reader& operator=(const extracting_reader&) = delete; - extracting_reader(extracting_reader&&) = delete; - extracting_reader& operator=(extracting_reader&&) = delete; - ~extracting_reader() override = default; - - bool is_end_of_stream() const final { - // ok to copy a bool - return _ptr->is_end_of_stream(); - } - - void print(std::ostream& os) final { - fmt::print(os, "configuration extracting reader, proxy for "); - _ptr->print(os); - } - - data_t& get_batches(storage_t& st) { - if (std::holds_alternative(st)) { - return std::get(st); - } else { - return *std::get(st).buffer; - } - } - - ss::future - do_load_slice(model::timeout_clock::time_point t) final { - return _ptr->do_load_slice(t).then([this](storage_t recs) { - for (auto& batch : get_batches(recs)) { - if ( - batch.header().type - == model::record_batch_type::raft_configuration) { - extract_configuration(batch); - } - // calculate next offset - _next_offset += model::offset( - batch.header().last_offset_delta) - + model::offset(1); - } - return recs; - }); - } - - void extract_configuration(model::record_batch& batch) { - iobuf_parser parser(batch.copy_records().begin()->release_value()); - _configurations.emplace_back( - _next_offset, deserialize_configuration(parser)); - } - - private: - model::offset _next_offset; - std::vector& _configurations; - std::unique_ptr _ptr; - }; - auto reader = std::make_unique( - base_offset, target, std::move(source).release()); - - return model::record_batch_reader(std::move(reader)); -} - bytes serialize_group_key(raft::group_id group, metadata_key key_type) { iobuf buf; reflection::serialize(buf, key_type, group); diff --git a/src/v/raft/consensus_utils.h b/src/v/raft/consensus_utils.h index 904917de229b0..e81a72cfa1b10 100644 --- a/src/v/raft/consensus_utils.h +++ b/src/v/raft/consensus_utils.h @@ -129,17 +129,6 @@ class do_for_each_batch_consumer { Func _f; }; -/** - * Extracts all configurations from underlying reader. Configuration are stored - * in a vector passed as a reference to reader. The reader can will - * automatically assing offsets to following batches using provided base offset - * as a staring point - */ -model::record_batch_reader make_config_extracting_reader( - model::offset, - std::vector&, - model::record_batch_reader&&); - /** * Function that allow consuming batches with given consumer while lazily * extracting raft::group_configuration from the reader. @@ -152,20 +141,43 @@ auto for_each_ref_extract_configuration( model::record_batch_reader&& rdr, ReferenceConsumer c, model::timeout_clock::time_point tm) { - using conf_t = std::vector; - - return ss::do_with( - conf_t{}, - [tm, c = std::move(c), base_offset, rdr = std::move(rdr)]( - conf_t& configurations) mutable { - return make_config_extracting_reader( - base_offset, configurations, std::move(rdr)) - .for_each_ref(std::move(c), tm) - .then([&configurations](auto res) { - return std::make_tuple( - std::move(res), std::move(configurations)); - }); - }); + struct extracting_consumer { + ss::future operator()(model::record_batch& batch) { + if ( + batch.header().type + == model::record_batch_type::raft_configuration) { + iobuf_parser parser( + batch.copy_records().begin()->release_value()); + configurations.emplace_back( + next_offset, deserialize_configuration(parser)); + } + + // we have to calculate offsets manually because the batch may not + // yet have the base offset assigned. + next_offset += model::offset(batch.header().last_offset_delta) + + model::offset(1); + + return wrapped(batch); + } + + auto end_of_stream() { + return ss::futurize_invoke( + [this] { return wrapped.end_of_stream(); }) + .then([confs = std::move(configurations)](auto ret) mutable { + return std::make_tuple(std::move(ret), std::move(confs)); + }); + } + + ReferenceConsumer wrapped; + model::offset next_offset; + std::vector configurations; + }; + + return std::move(rdr).for_each_ref( + extracting_consumer{ + .wrapped = std::move(c), + .next_offset = model::next_offset(base_offset)}, + tm); } bytes serialize_group_key(raft::group_id, metadata_key); diff --git a/src/v/raft/coordinated_recovery_throttle.cc b/src/v/raft/coordinated_recovery_throttle.cc index 40a184b666097..f058c68d266a7 100644 --- a/src/v/raft/coordinated_recovery_throttle.cc +++ b/src/v/raft/coordinated_recovery_throttle.cc @@ -91,11 +91,22 @@ void coordinated_recovery_throttle::setup_metrics() { namespace sm = ss::metrics; _public_metrics.add_group( prometheus_sanitize::metrics_name("raft:recovery"), - {sm::make_gauge( - "partition_movement_available_bandwidth", - [this] { return _throttler.available(); }, - sm::description( - "Bandwidth available for partition movement. bytes/sec"))}); + {// note: deprecate partition_movement_available_bandwidth + // in favor of partition_movement_consumed_bandwidth when + // possible. + sm::make_gauge( + "partition_movement_available_bandwidth", + [this] { return _throttler.available(); }, + sm::description( + "Bandwidth available for partition movement. bytes/sec")), + sm::make_gauge( + "partition_movement_consumed_bandwidth", + [this] { + return _throttler.last_reset_capacity() + - _throttler.available(); + }, + sm::description( + "Bandwidth consumed for partition movement. bytes/sec"))}); } } diff --git a/src/v/raft/group_manager.cc b/src/v/raft/group_manager.cc index 3d9daa2e7187b..5567d7d8b6771 100644 --- a/src/v/raft/group_manager.cc +++ b/src/v/raft/group_manager.cc @@ -52,6 +52,18 @@ group_manager::group_manager( _configuration.heartbeat_interval) , _feature_table(feature_table.local()) , _flush_timer_jitter(_configuration.flush_timer_interval_ms) + // we use a reasonable default not to bloat the configuration properties + , _metric_collection_interval(5s) + , _metrics_timer([this] { + try { + collect_learner_metrics(); + } catch (...) { + vlog( + raftlog.error, + "failed to collect learner metrics - {}", + std::current_exception()); + } + }) , _is_ready(false) { _flush_timer.set_callback([this] { ssx::spawn_with_gate(_gate, [this] { @@ -70,9 +82,13 @@ ss::future<> group_manager::start() { co_await _heartbeats.start(); co_await _recovery_scheduler.start(); _flush_timer.arm(_flush_timer_jitter()); + _metrics_timer.arm_periodic(_metric_collection_interval); } ss::future<> group_manager::stop() { + _metrics.clear(); + _public_metrics.clear(); + _metrics_timer.cancel(); auto f = _gate.close(); _flush_timer.cancel(); @@ -225,9 +241,22 @@ void group_manager::setup_metrics() { _metrics.add_group( prometheus_sanitize::metrics_name("raft"), {sm::make_gauge( - "group_count", - [this] { return _groups.size(); }, - sm::description("Number of raft groups"))}); + "group_count", + [this] { return _groups.size(); }, + sm::description("Number of raft groups")), + sm::make_gauge( + "learners_gap_bytes", + [this] { return _learners_gap_bytes; }, + sm::description( + "Total numbers of bytes that must be delivered to learners"))}); + + _public_metrics.add_group( + prometheus_sanitize::metrics_name("raft"), + {sm::make_gauge( + "learners_gap_bytes", + [this] { return _learners_gap_bytes; }, + sm::description( + "Total numbers of bytes that must be delivered to learners"))}); } ss::future<> group_manager::flush_groups() { @@ -248,4 +277,13 @@ ss::future<> group_manager::flush_groups() { return ss::now(); }); } +void group_manager::collect_learner_metrics() { + // we can use a synchronous loop here as the number of raft groups per core + // is limited. + _learners_gap_bytes = 0; + for (const auto& group : _groups) { + _learners_gap_bytes += group->bytes_to_deliver_to_learners(); + } +} + } // namespace raft diff --git a/src/v/raft/group_manager.h b/src/v/raft/group_manager.h index de5b194b03e78..66cf028c3f468 100644 --- a/src/v/raft/group_manager.h +++ b/src/v/raft/group_manager.h @@ -100,6 +100,7 @@ class group_manager { void setup_metrics(); ss::future<> flush_groups(); + void collect_learner_metrics(); raft::group_configuration create_initial_configuration( std::vector, model::revision_id) const; @@ -114,6 +115,7 @@ class group_manager { notification_list _notifications; metrics::internal_metric_groups _metrics; + metrics::public_metric_groups _public_metrics; storage::api& _storage; coordinated_recovery_throttle& _recovery_throttle; recovery_memory_quota _recovery_mem_quota; @@ -121,7 +123,9 @@ class group_manager { features::feature_table& _feature_table; ss::timer _flush_timer; timeout_jitter _flush_timer_jitter; - + std::chrono::milliseconds _metric_collection_interval; + ss::timer<> _metrics_timer; + size_t _learners_gap_bytes{0}; bool _is_ready; }; diff --git a/src/v/raft/heartbeat_manager.cc b/src/v/raft/heartbeat_manager.cc index 19693ebfb2800..0561fb3808662 100644 --- a/src/v/raft/heartbeat_manager.cc +++ b/src/v/raft/heartbeat_manager.cc @@ -439,6 +439,17 @@ void heartbeat_manager::process_reply( return; } auto& reply = r.value(); + + if (reply.source() != n) { + vlog( + raftlog.warn, + "got heartbeat reply from a different node id {} (expected {}), " + "ignoring", + reply.source(), + n); + return; + } + reply.for_each_lw_reply([this, n, target = reply.target(), &groups]( group_id group, reply_result result) { auto it = _consensus_groups.find(group); diff --git a/src/v/raft/prevote_stm.cc b/src/v/raft/prevote_stm.cc index 12904c031e544..d2b2a6268d228 100644 --- a/src/v/raft/prevote_stm.cc +++ b/src/v/raft/prevote_stm.cc @@ -21,6 +21,7 @@ #include "rpc/types.h" #include "ssx/semaphore.h" +#include #include #include @@ -81,6 +82,18 @@ prevote_stm::process_reply(vnode n, ss::future> f) { auto r = f.get0(); if (r.has_value()) { auto v = r.value(); + if (v.term != _req.term) { + vlog( + _ctxlog.trace, + "prevote ack: node {} has a higher term {}", + n, + v.term); + voter_reply->second._is_failed = true; + voter_reply->second._is_pending = false; + if (v.term > _req.term) { + _term_update = v.term; + } + } _ptr->maybe_update_node_reply_timestamp(n); if (v.log_ok) { vlog( @@ -182,20 +195,32 @@ ss::future prevote_stm::do_prevote() { [this](vnode id) { ssx::background = dispatch_prevote(id); }); // process results - return process_replies().then([this]() { - const auto only_voter = _config->unique_voter_count() == 1 - && _config->is_voter(_ptr->self()); - if ( - _success && !only_voter - && _ptr->_node_priority_override == zero_voter_priority) { - vlog( - _ctxlog.debug, - "Ignoring successful pre-vote. Node priority too low: {}", - _ptr->_node_priority_override.value()); - _success = false; - } - return _success; - }); + return process_replies() + .then([this]() { + const auto only_voter = _config->unique_voter_count() == 1 + && _config->is_voter(_ptr->self()); + if ( + _success && !only_voter + && _ptr->_node_priority_override == zero_voter_priority) { + vlog( + _ctxlog.debug, + "Ignoring successful pre-vote. Node priority too low: {}", + _ptr->_node_priority_override.value()); + _success = false; + } + }) + .then([this] { + if (_term_update) { + return update_term().then([this] { return _success; }); + } + return ss::make_ready_future(_success); + }); +} + +ss::future<> prevote_stm::update_term() { + auto u = co_await _ptr->_op_lock.get_units(); + _ptr->_term = std::max(_ptr->term(), _term_update.value()); + _ptr->_vstate = consensus::vote_state::candidate; } ss::future<> prevote_stm::process_replies() { diff --git a/src/v/raft/prevote_stm.h b/src/v/raft/prevote_stm.h index f9d319d54f312..e3b740a5a3576 100644 --- a/src/v/raft/prevote_stm.h +++ b/src/v/raft/prevote_stm.h @@ -11,6 +11,7 @@ #pragma once +#include "model/fundamental.h" #include "outcome.h" #include "raft/logger.h" #include "raft/types.h" @@ -51,11 +52,13 @@ class prevote_stm { ss::future> do_dispatch_prevote(vnode); ss::future<> process_reply(vnode n, ss::future> f); ss::future<> process_replies(); + ss::future<> update_term(); // args consensus* _ptr; // make sure to always make a copy; never move() this struct vote_request _req; bool _success = false; + std::optional _term_update; // for sequentiality/progress ssx::semaphore _sem; std::optional _config; diff --git a/src/v/raft/probe.cc b/src/v/raft/probe.cc index c7051c99f74ee..48e2a06bb8aee 100644 --- a/src/v/raft/probe.cc +++ b/src/v/raft/probe.cc @@ -49,8 +49,8 @@ void probe::setup_public_metrics(const model::ntp& ntp) { {sm::make_counter( "leadership_changes", [this] { return _leadership_changes; }, - sm::description("Number of leadership changes across all partitions " - "of a given topic"), + sm::description("Number of won leader elections across all partitions " + "in given topic"), labels) .aggregate(aggregate_labels)}); } @@ -113,7 +113,7 @@ void probe::setup_metrics(const model::ntp& ntp) { sm::make_counter( "leadership_changes", [this] { return _leadership_changes; }, - sm::description("Number of leadership changes"), + sm::description("Number of won leader elections"), labels), sm::make_counter( "replicate_request_errors", diff --git a/src/v/raft/recovery_stm.cc b/src/v/raft/recovery_stm.cc index 8f27c802bc2c3..59a048aae8238 100644 --- a/src/v/raft/recovery_stm.cc +++ b/src/v/raft/recovery_stm.cc @@ -20,6 +20,7 @@ #include "raft/raftgen_service.h" #include "ssx/sformat.h" #include "storage/snapshot.h" +#include "utils/human.h" #include #include @@ -455,9 +456,14 @@ ss::future<> recovery_stm::install_snapshot(required_snapshot_type s_type) { ss::future<> recovery_stm::take_on_demand_snapshot(model::offset last_included_offset) { vlog( - _ctxlog.debug, - "creating on demand snapshot with last included offset: {}", - last_included_offset); + _ctxlog.info, + "creating on demand snapshot with last included offset: {}, current " + "leader start offset: {}. Total partition size on leader {}, expected to " + "transfer to learner: {}", + last_included_offset, + _ptr->start_offset(), + human::bytes(_ptr->log()->size_bytes()), + human::bytes(_ptr->log()->size_bytes_after_offset(last_included_offset))); _inflight_snapshot_last_included_index = last_included_offset; // if there is no stm_manager available for the raft group use empty diff --git a/src/v/raft/replicate_batcher.cc b/src/v/raft/replicate_batcher.cc index ac33b0338646f..61587e3130739 100644 --- a/src/v/raft/replicate_batcher.cc +++ b/src/v/raft/replicate_batcher.cc @@ -13,6 +13,7 @@ #include "raft/replicate_entries_stm.h" #include "raft/types.h" #include "ssx/future-util.h" +#include "utils/fragmented_vector.h" #include #include @@ -118,7 +119,7 @@ ss::future replicate_batcher::do_cache( model::record_batch_reader r, consistency_level consistency_lvl, std::optional timeout) { - auto batches = co_await model::consume_reader_to_memory( + auto batches = co_await model::consume_reader_to_chunked_vector( std::move(r), timeout ? model::timeout_clock::now() + *timeout : model::no_timeout); @@ -136,7 +137,7 @@ ss::future replicate_batcher::do_cache( ss::future replicate_batcher::do_cache_with_backpressure( std::optional expected_term, - ss::circular_buffer batches, + chunked_vector batches, size_t bytes, consistency_level consistency_lvl, std::optional timeout) { @@ -164,7 +165,7 @@ replicate_batcher::do_cache_with_backpressure( } size_t record_count = 0; - std::vector data; + chunked_vector data; data.reserve(batches.size()); for (auto& b : batches) { record_count += b.record_count(); diff --git a/src/v/raft/replicate_batcher.h b/src/v/raft/replicate_batcher.h index e45361d503e70..61a4ca8a89b0f 100644 --- a/src/v/raft/replicate_batcher.h +++ b/src/v/raft/replicate_batcher.h @@ -16,6 +16,7 @@ #include "raft/types.h" #include "ssx/semaphore.h" #include "units.h" +#include "utils/fragmented_vector.h" #include "utils/mutex.h" #include @@ -30,7 +31,7 @@ class replicate_batcher { public: item( size_t record_count, - std::vector batches, + chunked_vector batches, ssx::semaphore_units u, std::optional expected_term, consistency_level c_lvl, @@ -97,7 +98,7 @@ class replicate_batcher { } } size_t _record_count; - std::vector _data; + chunked_vector _data; ssx::semaphore_units _units; std::optional _expected_term; // consistency level is stored to distinguish when an item promise @@ -146,7 +147,7 @@ class replicate_batcher { ss::future do_cache_with_backpressure( std::optional, - ss::circular_buffer, + chunked_vector, size_t, consistency_level, std::optional); diff --git a/src/v/raft/state_machine_base.h b/src/v/raft/state_machine_base.h index e1d01a081a429..56800f2ed6a2d 100644 --- a/src/v/raft/state_machine_base.h +++ b/src/v/raft/state_machine_base.h @@ -60,13 +60,6 @@ class state_machine_base { */ virtual ss::future<> apply_raft_snapshot(const iobuf&) = 0; - /** - * Returns a unique identifier of this state machine. Each stm built on top - * of the same Raft group must have different id. - * Id is going to be used when logging and to mark parts of the snapshots. - */ - virtual std::string_view get_name() const = 0; - /** * Returns a snapshot of an STM state with requested last included offset */ diff --git a/src/v/raft/state_machine_manager.cc b/src/v/raft/state_machine_manager.cc index c60aa4d5873b3..2058692752d3b 100644 --- a/src/v/raft/state_machine_manager.cc +++ b/src/v/raft/state_machine_manager.cc @@ -124,7 +124,7 @@ batch_applicator::apply_to_stm( _log.trace, "[{}][{}] applying batch with base {} and last {} offsets", _ctx, - state.stm_entry->stm->get_name(), + state.stm_entry->name, batch.header().base_offset, last_offset); @@ -145,7 +145,7 @@ batch_applicator::apply_to_stm( _log.warn, "[{}][{}] error applying batch with base_offset: {} - {}", _ctx, - state.stm_entry->stm->get_name(), + state.stm_entry->name, batch.base_offset(), std::current_exception()); state.error = true; @@ -153,16 +153,20 @@ batch_applicator::apply_to_stm( } } +state_machine_manager::named_stm::named_stm(ss::sstring name, stm_ptr stm) + : name(std::move(name)) + , stm(std::move(stm)) {} + state_machine_manager::state_machine_manager( - consensus* raft, std::vector stms, ss::scheduling_group apply_sg) + consensus* raft, std::vector stms, ss::scheduling_group apply_sg) : _raft(raft) , _log(ctx_log(_raft->group(), _raft->ntp())) , _apply_sg(apply_sg) { - for (auto& stm : stms) { - std::string_view name = stm->get_name(); + for (auto& n_stm : stms) { _machines.try_emplace( - ss::sstring(name), - ss::make_lw_shared(std::move(stm))); + n_stm.name, + ss::make_lw_shared( + n_stm.name, std::move(n_stm.stm))); } } @@ -211,7 +215,10 @@ ss::future<> state_machine_manager::apply_raft_snapshot() { } auto fut = co_await ss::coroutine::as_future( - do_apply_raft_snapshot(std::move(snapshot->metadata), snapshot->reader)); + acquire_background_apply_mutexes().then([&, this](auto units) mutable { + return do_apply_raft_snapshot( + std::move(snapshot->metadata), snapshot->reader, std::move(units)); + })); co_await snapshot->reader.close(); if (fut.failed()) { const auto e = fut.get_exception(); @@ -224,7 +231,9 @@ ss::future<> state_machine_manager::apply_raft_snapshot() { } ss::future<> state_machine_manager::do_apply_raft_snapshot( - snapshot_metadata metadata, storage::snapshot_reader& reader) { + snapshot_metadata metadata, + storage::snapshot_reader& reader, + std::vector background_apply_units) { const auto snapshot_file_sz = co_await reader.get_snapshot_size(); const auto last_offset = metadata.last_included_index; @@ -247,11 +256,11 @@ ss::future<> state_machine_manager::do_apply_raft_snapshot( _log.debug, "applying empty snapshot at offset: {} for backward " "compatibility", - metadata.last_included_index); + last_offset); co_await ss::coroutine::parallel_for_each( - _machines, [metadata, last_offset](auto& pair) { + _machines, [last_offset](auto& pair) { auto stm = pair.second->stm; - if (stm->last_applied_offset() >= metadata.last_included_index) { + if (stm->last_applied_offset() >= last_offset) { return ss::now(); } return stm->apply_raft_snapshot(iobuf{}).then([stm, last_offset] { @@ -265,28 +274,39 @@ ss::future<> state_machine_manager::do_apply_raft_snapshot( auto snap = co_await serde::read_async(parser); co_await ss::coroutine::parallel_for_each( - snap.snapshot_map, - [this, metadata, last_offset](auto& snapshot_pair) { - auto it = _machines.find(snapshot_pair.first); - if ( - it == _machines.end() - || it->second->stm->last_applied_offset() - >= metadata.last_included_index) { - return ss::now(); - } - - return it->second->stm - ->apply_raft_snapshot(std::move(snapshot_pair.second)) - .then([stm = it->second->stm, last_offset] { - stm->set_next( - std::max(model::next_offset(last_offset), stm->next())); - }); + _machines, + [this, snap = std::move(snap), last_offset]( + state_machines_t::value_type& stm_pair) mutable { + return apply_snapshot_to_stm(stm_pair.second, snap, last_offset); }); } _next = model::next_offset(metadata.last_included_index); + background_apply_units.clear(); } -ss::future<> state_machine_manager::apply() { +ss::future<> state_machine_manager::apply_snapshot_to_stm( + ss::lw_shared_ptr stm_entry, + const managed_snapshot& snapshot, + model::offset last_offset) { + auto it = snapshot.snapshot_map.find(stm_entry->name); + + if (stm_entry->stm->last_applied_offset() < last_offset) { + if (it != snapshot.snapshot_map.end()) { + co_await stm_entry->stm->apply_raft_snapshot(it->second); + } else { + /** + * In order to hold the stm contract we need to call the + * apply_raft_snapshot with empty data + */ + co_await stm_entry->stm->apply_raft_snapshot(iobuf{}); + } + } + + stm_entry->stm->set_next( + std::max(model::next_offset(last_offset), stm_entry->stm->next())); +} + +ss::future<> state_machine_manager::try_apply_in_foreground() { try { ss::coroutine::switch_to sg_sw(_apply_sg); // wait until consensus commit index is >= _next @@ -301,6 +321,30 @@ ss::future<> state_machine_manager::apply() { co_return co_await apply_raft_snapshot(); } + // collect STMs which has the same _next offset as the offset in + // manager and there is no background apply taking place + std::vector machines; + for (auto& [_, entry] : _machines) { + /** + * We can simply check if a mutex is ready here as calling + * maybe_start_background_apply() will make the mutex underlying + * semaphore immediately not ready as there are no scheduling points + * before calling `get_units` + */ + if ( + entry->stm->next() == _next + && entry->background_apply_mutex.ready()) { + machines.push_back(entry); + } + } + if (machines.empty()) { + vlog( + _log.debug, + "no machines were selected to apply in foreground, current next " + "offset: {}", + _next); + co_return; + } /** * Raft make_reader method allows callers reading up to * last_visible index. In order to make the STMs safe and working @@ -320,27 +364,24 @@ ss::future<> state_machine_manager::apply() { _next, _raft->committed_offset(), ss::default_priority_class()); model::record_batch_reader reader = co_await _raft->make_reader(config); - // collect STMs which has the same _next offset as the offset in - // manager and there is no background apply taking place - std::vector machines; - for (auto& [_, entry] : _machines) { - /** - * We can simply check if a mutex is ready here as calling - * maybe_start_background_apply() will make the mutex underlying - * semaphore immediately not ready as there are no scheduling points - * before calling `get_units` - */ - if ( - entry->stm->next() == _next - && entry->background_apply_mutex.ready()) { - machines.push_back(entry); - } - } - auto last_applied = co_await std::move(reader).consume( + + auto max_last_applied = co_await std::move(reader).consume( batch_applicator(default_ctx, machines, _as, _log), model::no_timeout); - _next = std::max(model::next_offset(last_applied), _next); + if (max_last_applied == model::offset{}) { + vlog( + _log.warn, + "no progress has been made during state machine apply. Current " + "next offset: {}", + _next); + /** + * If no progress has been made, yield to prevent busy looping + */ + co_await ss::sleep_abortable(100ms, _as); + co_return; + } + _next = std::max(model::next_offset(max_last_applied), _next); vlog(_log.trace, "updating _next offset with: {}", _next); } catch (const ss::timed_out_error&) { vlog(_log.debug, "state machine apply timeout"); @@ -351,6 +392,10 @@ ss::future<> state_machine_manager::apply() { vlog( _log.warn, "manager apply exception: {}", std::current_exception()); } +} + +ss::future<> state_machine_manager::apply() { + co_await try_apply_in_foreground(); /** * If any of the state machine is behind, dispatch background apply fibers */ @@ -377,7 +422,7 @@ void state_machine_manager::maybe_start_background_apply( vlog( _log.debug, "starting background apply fiber for '{}' state machine", - entry->stm->get_name()); + entry->name); ssx::spawn_with_gate(_gate, [this, entry] { return entry->background_apply_mutex.get_units().then( @@ -394,39 +439,44 @@ ss::future<> state_machine_manager::background_apply_fiber( entry_ptr entry, ssx::semaphore_units units) { while (!_as.abort_requested() && entry->stm->next() < _next) { storage::log_reader_config config( - entry->stm->next(), _next, ss::default_priority_class()); + entry->stm->next(), + model::prev_offset(_next), + ss::default_priority_class()); vlog( _log.debug, "reading batches in range [{}, {}] for '{}' stm background apply", entry->stm->next(), _next, - entry->stm->get_name()); + entry->name); bool error = false; try { model::record_batch_reader reader = co_await _raft->make_reader( config); - co_await std::move(reader).consume( + auto last_applied_before = entry->stm->last_applied_offset(); + auto last_applied_after = co_await std::move(reader).consume( batch_applicator(background_ctx, {entry}, _as, _log), model::no_timeout); - + if (last_applied_before >= last_applied_after) { + error = true; + } } catch (...) { error = true; vlog( _log.warn, "exception thrown from background apply fiber for {} - {}", - entry->stm->get_name(), + entry->name, std::current_exception()); } if (error) { - co_await ss::sleep_abortable(1s, _as); + co_await ss::sleep_abortable(100ms, _as); } } units.return_all(); vlog( _log.debug, "finished background apply for '{}' state machine", - entry->stm->get_name()); + entry->name); } ss::future diff --git a/src/v/raft/state_machine_manager.h b/src/v/raft/state_machine_manager.h index 62f4a4fbf03a1..034f0076c56ee 100644 --- a/src/v/raft/state_machine_manager.h +++ b/src/v/raft/state_machine_manager.h @@ -20,21 +20,27 @@ #include "serde/envelope.h" #include "storage/snapshot.h" #include "storage/types.h" +#include "utils/absl_sstring_hash.h" #include "utils/mutex.h" #include #include +#include #include #include +#include #include #include namespace raft { template -concept ManagableStateMachine = std::derived_from; +concept ManagableStateMachine = requires(T stm) { + std::derived_from; + { T::name } -> std::convertible_to; +}; template concept StateMachineIterateFunc = requires( Func f, const ss::sstring& name, const state_machine_base& stm) { @@ -76,6 +82,24 @@ class state_machine_manager final { ss::future<> stop(); model::offset last_applied() const { return model::prev_offset(_next); } + /** + * Returns a pointer to specific type of state machine. + * + * This API provides basic runtime validation verifying if requested STM + * type matches the name passed. It returns a nullptr if state machine with + * requested name is not registered in manager. + */ + template + ss::shared_ptr get() { + auto it = _machines.find(T::name); + if (it == _machines.end()) { + return nullptr; + } + auto ptr = ss::dynamic_pointer_cast(it->second->stm); + vassert( + ptr != nullptr, "Incorrect STM type requested for STM {}", T::name); + return ptr; + } template void for_each_stm(Func&& func) const { @@ -86,10 +110,15 @@ class state_machine_manager final { private: using stm_ptr = ss::shared_ptr; + struct named_stm { + named_stm(ss::sstring, stm_ptr); + ss::sstring name; + stm_ptr stm; + }; state_machine_manager( consensus* raft, - std::vector stms_to_manage, + std::vector stms_to_manage, ss::scheduling_group apply_sg); friend class batch_applicator; @@ -98,8 +127,10 @@ class state_machine_manager final { static constexpr const char* background_ctx = "background"; struct state_machine_entry { - explicit state_machine_entry(ss::shared_ptr stm) - : stm(std::move(stm)) {} + explicit state_machine_entry( + ss::sstring name, ss::shared_ptr stm) + : name(std::move(name)) + , stm(std::move(stm)) {} state_machine_entry(state_machine_entry&&) noexcept = default; state_machine_entry(const state_machine_entry&) noexcept = delete; state_machine_entry& operator=(state_machine_entry&&) noexcept = delete; @@ -107,19 +138,24 @@ class state_machine_manager final { = delete; ~state_machine_entry() = default; + ss::sstring name; ss::shared_ptr stm; mutex background_apply_mutex; }; using entry_ptr = ss::lw_shared_ptr; - using state_machines_t = absl::flat_hash_map; + using state_machines_t + = absl::flat_hash_map; void maybe_start_background_apply(const entry_ptr&); ss::future<> background_apply_fiber(entry_ptr, ssx::semaphore_units); ss::future<> apply_raft_snapshot(); ss::future<> do_apply_raft_snapshot( - raft::snapshot_metadata metadata, storage::snapshot_reader& reader); + raft::snapshot_metadata metadata, + storage::snapshot_reader& reader, + std::vector background_apply_units); ss::future<> apply(); + ss::future<> try_apply_in_foreground(); ss::future> acquire_background_apply_mutexes(); @@ -140,6 +176,11 @@ class state_machine_manager final { auto serde_fields() { return std::tie(snapshot_map); } }; + ss::future<> apply_snapshot_to_stm( + ss::lw_shared_ptr stm_entry, + const managed_snapshot& snapshot, + model::offset last_included_offset); + consensus* _raft; ctx_log _log; mutex _apply_mutex; @@ -158,7 +199,7 @@ class state_machine_manager_builder { template ss::shared_ptr create_stm(Args&&... args) { auto machine = ss::make_shared(std::forward(args)...); - _stms.push_back(machine); + _stms.emplace_back(ss::sstring(T::name), machine); return machine; } @@ -170,7 +211,7 @@ class state_machine_manager_builder { } private: - std::vector _stms; + std::vector _stms; ss::scheduling_group _sg = ss::default_scheduling_group(); }; diff --git a/src/v/raft/tests/basic_raft_fixture_test.cc b/src/v/raft/tests/basic_raft_fixture_test.cc index e41c6b58ac252..0351306ba6095 100644 --- a/src/v/raft/tests/basic_raft_fixture_test.cc +++ b/src/v/raft/tests/basic_raft_fixture_test.cc @@ -217,7 +217,7 @@ TEST_F_CORO( // wait for leader co_await wait_for_leader(10s); for (auto& [_, node] : nodes()) { - node->on_dispatch([](raft::msg_type t) { + node->on_dispatch([](model::node_id, raft::msg_type t) { if ( t == raft::msg_type::append_entries && random_generators::get_int(1000) > 800) { @@ -294,15 +294,9 @@ TEST_F_CORO( [this, &last_visible] { for (auto& [id, node] : nodes()) { auto o = node->raft()->last_visible_index(); + auto dirty_offset = node->raft()->dirty_offset(); vassert( - last_visible[id] <= o, - "Visible offset moved back on node {}, current visible offset: " - "{}, last visible offset: {}", - id, - o, - last_visible[id]); - vassert( - o <= node->raft()->dirty_offset(), + o <= dirty_offset, "last visible offset can not be larger than log end offset"); last_visible[id] = o; } diff --git a/src/v/raft/tests/foreign_entry_test.cc b/src/v/raft/tests/foreign_entry_test.cc index aefbbaf94f4db..5532df4856927 100644 --- a/src/v/raft/tests/foreign_entry_test.cc +++ b/src/v/raft/tests/foreign_entry_test.cc @@ -155,18 +155,17 @@ struct foreign_entry_fixture { ss::future extract_configuration(model::record_batch_reader&& rdr) { - using cfgs_t = std::vector; - return ss::do_with(cfgs_t{}, [rdr = std::move(rdr)](cfgs_t& cfgs) mutable { - auto wrapping_rdr = raft::details::make_config_extracting_reader( - model::offset(0), cfgs, std::move(rdr)); - - return model::consume_reader_to_memory( - std::move(wrapping_rdr), model::no_timeout) - .then([&cfgs](ss::circular_buffer) { - BOOST_REQUIRE(!cfgs.empty()); - return cfgs.begin()->cfg; - }); - }); + struct noop_consumer { + ss::future operator()(model::record_batch&) { + co_return ss::stop_iteration::no; + } + int end_of_stream() { return 0; } + }; + + auto [_, cfgs] = co_await raft::details::for_each_ref_extract_configuration( + model::offset(0), std::move(rdr), noop_consumer{}, model::no_timeout); + BOOST_REQUIRE(!cfgs.empty()); + co_return cfgs.begin()->cfg; } FIXTURE_TEST(sharing_one_reader, foreign_entry_fixture) { diff --git a/src/v/raft/tests/persisted_stm_test.cc b/src/v/raft/tests/persisted_stm_test.cc index 1806da64befc1..2bf6c34216fb5 100644 --- a/src/v/raft/tests/persisted_stm_test.cc +++ b/src/v/raft/tests/persisted_stm_test.cc @@ -123,12 +123,11 @@ struct kv_state class persisted_kv : public persisted_stm<> { public: + static constexpr std::string_view name = "persited_kv_stm"; explicit persisted_kv(raft_node_instance& rn) : persisted_stm<>("simple-kv", logger, rn.raft().get()) , raft_node(rn) {} - std::string_view get_name() const final { return "persisted_kv"; }; - ss::future<> start() override { return persisted_stm<>::start(); } ss::future<> stop() override { return persisted_stm<>::stop(); } @@ -172,12 +171,13 @@ class persisted_kv : public persisted_stm<> { 0, last_applied_offset(), serde::to_iobuf(state)); }; - static void + static std::optional apply_to_state(const model::record_batch& batch, kv_state& state) { if (batch.header().type != model::record_batch_type::raft_data) { - return; + return std::nullopt; } - batch.for_each_record([&state](model::record r) { + kv_operation last_op; + batch.for_each_record([&state, &last_op](model::record r) { auto op = serde::from_iobuf(r.value().copy()); /** * Here we check if validation pre replication is correct. @@ -207,11 +207,16 @@ class persisted_kv : public persisted_stm<> { state.remove(op.key); } } + last_op = op; }); + return last_op; } ss::future<> apply(const model::record_batch& batch) override { - apply_to_state(batch, state); + auto last_op = apply_to_state(batch, state); + if (last_op) { + last_operation = std::move(*last_op); + } co_return; } @@ -266,9 +271,38 @@ class persisted_kv : public persisted_stm<> { } kv_state state; + kv_operation last_operation; raft_node_instance& raft_node; }; +class other_persisted_kv : public persisted_kv { +public: + static constexpr std::string_view name = "other_persited_kv_stm"; + explicit other_persisted_kv(raft_node_instance& rn) + : persisted_kv(rn) {} + ss::future<> apply_raft_snapshot(const iobuf& buffer) override { + if (buffer.empty()) { + co_return; + } + state = serde::from_iobuf(buffer.copy()); + co_return; + }; + /** + * This STM doesn't execute the full apply logic from the base persisted_kv + * as it is going to be started without the full data in the snapshot, hence + * the validation would fail. + */ + ss::future<> apply(const model::record_batch& batch) override { + if (batch.header().type != model::record_batch_type::raft_data) { + co_return; + } + batch.for_each_record([this](model::record r) { + last_operation = serde::from_iobuf(r.value().copy()); + }); + co_return; + } +}; + struct persisted_stm_test_fixture : state_machine_fixture { ss::future<> initialize_state_machines() { create_nodes(); @@ -550,3 +584,70 @@ TEST_F_CORO(persisted_stm_test_fixture, test_raft_and_local_snapshot) { ASSERT_EQ_CORO(stm->state, expected); } } +/** + * Tests the scenario in which an STM is added to the partition after it was + * already alive and Raft snapshot was taken on the partition. + * + * The snapshot doesn't contain data for the newly created stm, however the stm + * next offset should still be updated to make it possible for the STM to catch + * up. + */ +TEST_F_CORO(persisted_stm_test_fixture, test_adding_state_machine) { + co_await initialize_state_machines(); + kv_state expected; + auto ops = random_operations(2000); + for (auto batch : ops) { + co_await apply_operations(expected, std::move(batch)); + } + co_await wait_for_apply(); + for (const auto& [_, stm] : node_stms) { + ASSERT_EQ_CORO(stm->state, expected); + } + + // take local snapshot on every node + co_await take_local_snapshot_on_every_node(); + // update state + auto ops_phase_two = random_operations(50); + for (auto batch : ops_phase_two) { + co_await apply_operations(expected, std::move(batch)); + } + + co_await wait_for_apply(); + for (const auto& [_, stm] : node_stms) { + ASSERT_EQ_CORO(stm->state, expected); + } + + // take Raft snapshot on every node, there are two possibilities here either + // a snapshot will be taken at offset preceding current local snapshot or + // the one following local snapshot. + co_await take_raft_snapshot_all_nodes(); + + auto committed = node(model::node_id(0)).raft()->committed_offset(); + + absl::flat_hash_map data_directories; + for (auto& [id, node] : nodes()) { + data_directories[id] = node->raft()->log()->config().base_directory(); + } + + for (auto& [id, data_dir] : data_directories) { + co_await stop_node(id); + add_node(id, model::revision_id(0), std::move(data_dir)); + } + ss::shared_ptr other_stm; + for (auto& [_, node] : nodes()) { + co_await node->initialise(all_vnodes()); + raft::state_machine_manager_builder builder; + auto stm = builder.create_stm(*node); + other_stm = builder.create_stm(*node); + co_await node->start(std::move(builder)); + node_stms.emplace(node->get_vnode(), std::move(stm)); + } + + co_await wait_for_committed_offset(committed, 30s); + co_await wait_for_apply(); + + for (const auto& [_, stm] : node_stms) { + ASSERT_EQ_CORO(stm->state, expected); + ASSERT_EQ_CORO(stm->last_operation, other_stm->last_operation); + } +} diff --git a/src/v/raft/tests/raft_fixture.cc b/src/v/raft/tests/raft_fixture.cc index dbba5ca8e2302..28fb7417ac90d 100644 --- a/src/v/raft/tests/raft_fixture.cc +++ b/src/v/raft/tests/raft_fixture.cc @@ -79,11 +79,12 @@ ss::future<> channel::stop() { _as.request_abort(); _new_messages.broken(); - co_await _gate.close(); + auto f = _gate.close(); for (auto& m : _messages) { m.resp_data.set_exception(ss::abort_requested_exception()); } + co_await std::move(f); } } @@ -239,16 +240,16 @@ channel& in_memory_test_protocol::get_channel(model::node_id id) { return *it->second; } -void in_memory_test_protocol::on_dispatch( - ss::noncopyable_function(msg_type)> f) { +void in_memory_test_protocol::on_dispatch(dispatch_callback_t f) { _on_dispatch_handlers.push_back(std::move(f)); } ss::future<> in_memory_test_protocol::stop() { - co_await _gate.close(); + auto f = _gate.close(); for (auto& [_, ch] : _channels) { co_await ch->stop(); } + co_await std::move(f); } template @@ -299,7 +300,7 @@ in_memory_test_protocol::dispatch(model::node_id id, ReqT req) { const auto msg_type = map_msg_type(); for (const auto& f : _on_dispatch_handlers) { - co_await f(msg_type); + co_await f(id, msg_type); } try { @@ -555,8 +556,7 @@ raft_node_instance::random_batch_base_offset(model::offset max) { co_return batches.front().base_offset(); } -void raft_node_instance::on_dispatch( - ss::noncopyable_function(msg_type)> f) { +void raft_node_instance::on_dispatch(dispatch_callback_t f) { _protocol->on_dispatch(std::move(f)); } @@ -564,6 +564,9 @@ seastar::future<> raft_fixture::TearDownAsync() { co_await seastar::coroutine::parallel_for_each( _nodes, [](auto& pair) { return pair.second->stop(); }); + co_await seastar::coroutine::parallel_for_each( + _nodes, [](auto& pair) { return pair.second->remove_data(); }); + co_await _features.stop(); } seastar::future<> raft_fixture::SetUpAsync() { @@ -615,7 +618,7 @@ raft_fixture::stop_node(model::node_id id, remove_data_dir remove) { } raft_node_instance& raft_fixture::node(model::node_id id) { - return *_nodes.find(id)->second; + return *_nodes.at(id); } ss::future diff --git a/src/v/raft/tests/raft_fixture.h b/src/v/raft/tests/raft_fixture.h index d3486ec9b4fee..741d13959ce5f 100644 --- a/src/v/raft/tests/raft_fixture.h +++ b/src/v/raft/tests/raft_fixture.h @@ -91,6 +91,9 @@ struct raft_node_map { node_for(model::node_id) = 0; }; +using dispatch_callback_t + = ss::noncopyable_function(model::node_id, msg_type)>; + class in_memory_test_protocol : public consensus_client_protocol::impl { public: explicit in_memory_test_protocol(raft_node_map&, prefix_logger&); @@ -128,7 +131,7 @@ class in_memory_test_protocol : public consensus_client_protocol::impl { channel& get_channel(model::node_id id); - void on_dispatch(ss::noncopyable_function(msg_type)> f); + void on_dispatch(dispatch_callback_t f); ss::future<> stop(); @@ -137,8 +140,7 @@ class in_memory_test_protocol : public consensus_client_protocol::impl { ss::future> dispatch(model::node_id, ReqT req); ss::gate _gate; absl::flat_hash_map> _channels; - std::vector(msg_type)>> - _on_dispatch_handlers; + std::vector _on_dispatch_handlers; raft_node_map& _nodes; prefix_logger& _logger; }; @@ -222,7 +224,7 @@ class raft_node_instance : public ss::weakly_referencable { /// //// \param f The callback function to be invoked when a message is /// dispatched. - void on_dispatch(ss::noncopyable_function(msg_type)> f); + void on_dispatch(dispatch_callback_t); private: model::node_id _id; diff --git a/src/v/raft/tests/raft_reconfiguration_test.cc b/src/v/raft/tests/raft_reconfiguration_test.cc index 2b2fce3824fe6..1616208e42526 100644 --- a/src/v/raft/tests/raft_reconfiguration_test.cc +++ b/src/v/raft/tests/raft_reconfiguration_test.cc @@ -25,6 +25,7 @@ #include "serde/serde.h" #include "storage/record_batch_builder.h" #include "test_utils/async.h" +#include "test_utils/randoms.h" #include "test_utils/test.h" #include @@ -35,6 +36,7 @@ #include #include +#include #include #include @@ -49,16 +51,30 @@ static ss::logger test_log("reconfiguration-test"); */ using use_snapshot = ss::bool_class; -using use_initial_learner_offset = ss::bool_class; -struct test_params { - use_snapshot snapshot; - int initial_size; - int nodes_to_add; - int nodes_to_remove; - use_initial_learner_offset learner_start_offset; - consistency_level consistency_level = raft::consistency_level::quorum_ack; +using use_initial_learner_offset + = ss::bool_class; + +enum class isolated_t { + none, + old_leader, + old_followers, + random, }; +std::ostream& operator<<(std::ostream& o, isolated_t pt) { + switch (pt) { + case isolated_t::none: + return o << "isolated::none"; + case isolated_t::old_leader: + return o << "isolated::old_leader"; + case isolated_t::old_followers: + return o << "isolated::old_followers"; + case isolated_t::random: + return o << "isolated::random"; + } + __builtin_unreachable(); +} + struct reconfiguration_test : testing::WithParamInterface> + consistency_level, + isolated_t>> , raft_fixture { - ss::future<> - wait_for_reconfiguration_to_finish(std::chrono::milliseconds timeout) { - RPTEST_REQUIRE_EVENTUALLY_CORO(timeout, [this] { - for (auto& [_, n] : nodes()) { + ss::future<> wait_for_reconfiguration_to_finish( + const absl::flat_hash_set& target_ids, + std::chrono::milliseconds timeout) { + RPTEST_REQUIRE_EVENTUALLY_CORO(timeout, [this, target_ids] { + for (auto id : target_ids) { + auto& n = node(id); if ( - n->raft()->config().get_state() + n.raft()->config().get_state() != raft::configuration_state::simple) { return false; } @@ -130,16 +149,16 @@ wait_for_offset(model::offset expected, raft_fixture::raft_nodes_t& nodes) { co_return result(raft::errc::timeout); } -void assert_offset_translator_state_is_consistent( - raft_fixture::raft_nodes_t& nodes) { - if (nodes.size() == 1) { +static void assert_offset_translator_state_is_consistent( + const std::vector& nodes) { + if (nodes.size() <= 1) { return; } model::offset start_offset{}; - auto first_raft = nodes.begin()->second->raft(); + auto first_raft = nodes.front()->raft(); model::offset dirty_offset = first_raft->dirty_offset(); // get the max start offset - for (auto& [id, n] : nodes) { + for (auto* n : nodes) { start_offset = std::max(n->raft()->start_offset(), start_offset); } std::vector deltas; @@ -153,7 +172,7 @@ void assert_offset_translator_state_is_consistent( for (model::offset o : boost::irange(start_offset, dirty_offset)) { ASSERT_EQ( - it->second->raft()->get_offset_translator_state()->delta(o), + (*it)->raft()->get_offset_translator_state()->delta(o), deltas[idx++]); } } @@ -161,38 +180,43 @@ void assert_offset_translator_state_is_consistent( TEST_P_CORO(reconfiguration_test, configuration_replace_test) { const auto param = GetParam(); - use_snapshot snapshot = std::get<0>(param); + auto snapshot = std::get(param); int initial_size = std::get<1>(param); int nodes_to_add = std::get<2>(param); int nodes_to_remove = std::get<3>(param); - use_initial_learner_offset use_learner_start_offset = std::get<4>(param); - consistency_level consistency_level = std::get<5>(param); + auto use_learner_start_offset = std::get(param); + auto consistency_lvl = std::get(param); + auto isolated = std::get(param); // skip test cases that makes no sense if ( nodes_to_add + initial_size - nodes_to_remove <= 0 || initial_size < nodes_to_remove) { co_return; } + if (initial_size == 1 && isolated == isolated_t::old_followers) { + co_return; + } fmt::print( "test parameters: {{snapshot: {}, initial_size: {}, " "nodes_to_add: {}, nodes_to_remove: {}, use_learner_start_offset: {}, " - "consistency_lvl: {}}}\n", + "consistency_lvl: {}, isolated: {}}}\n", snapshot, initial_size, nodes_to_add, nodes_to_remove, use_learner_start_offset, - consistency_level); + consistency_lvl, + isolated); // create group with initial configuration co_await create_simple_group(initial_size); // replicate batches auto result = co_await retry_with_leader( model::timeout_clock::now() + 30s, - [this, consistency_level](raft_node_instance& leader_node) { + [this, consistency_lvl](raft_node_instance& leader_node) { return leader_node.raft() ->replicate( - make_random_batches(), replicate_options(consistency_level)) + make_random_batches(), replicate_options(consistency_lvl)) .then([this](::result r) { if (!r) { return ss::make_ready_future<::result>( @@ -208,7 +232,7 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { auto& leader_node = node(leader); model::offset start_offset = leader_node.raft()->start_offset(); if (snapshot) { - if (consistency_level == consistency_level::leader_ack) { + if (consistency_lvl == consistency_level::leader_ack) { for (auto& [_, n] : nodes()) { co_await n->raft()->refresh_commit_index(); } @@ -250,9 +274,13 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { } } + auto old_node_ids = all_ids(); + + auto current_node_ids = old_node_ids; auto current_nodes = all_vnodes(); for (int i = 0; i < nodes_to_remove; ++i) { + current_node_ids.erase(current_nodes.back().id()); current_nodes.pop_back(); } absl::flat_hash_set added_nodes; @@ -261,6 +289,7 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { auto& n = add_node( model::node_id(initial_size + i), model::revision_id(0)); current_nodes.push_back(n.get_vnode()); + current_node_ids.insert(n.get_vnode().id()); added_nodes.emplace(n.get_vnode()); return n.init_and_start({}); }); @@ -268,6 +297,12 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { ASSERT_EQ_CORO( current_nodes.size(), initial_size + nodes_to_add - nodes_to_remove); + vlog( + test_log.info, + "dispatching reconfiguration: {} -> {}", + old_node_ids, + current_node_ids); + // update group configuration auto success = co_await retry_with_leader( model::timeout_clock::now() + 30s, @@ -283,10 +318,53 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { }); }); ASSERT_TRUE_CORO(success); + + auto isolated_nodes + = ss::make_lw_shared>(); + switch (isolated) { + case isolated_t::none: + break; + case isolated_t::old_leader: + isolated_nodes->insert(leader); + break; + case isolated_t::old_followers: + *isolated_nodes = old_node_ids; + isolated_nodes->erase(leader); + break; + case isolated_t::random: + for (auto n : all_ids()) { + if (tests::random_bool()) { + isolated_nodes->insert(n); + } + } + break; + } + + if (!isolated_nodes->empty()) { + vlog(test_log.info, "isolating nodes: {}", *isolated_nodes); + + for (const auto& [source_id, node] : nodes()) { + node->on_dispatch([=](model::node_id dest_id, raft::msg_type) { + if ( + isolated_nodes->contains(source_id) + != isolated_nodes->contains(dest_id)) { + return ss::sleep(5s); + } + return ss::now(); + }); + } + + // heal the partition 5s later + (void)ss::sleep(5s).then([isolated_nodes] { + vlog(test_log.info, "healing the network partition"); + isolated_nodes->clear(); + }); + } + co_await with_leader( - 30s, [this, consistency_level](raft_node_instance& leader_node) { + 30s, [this, consistency_lvl](raft_node_instance& leader_node) { // wait for committed offset to propagate - if (consistency_level == raft::consistency_level::quorum_ack) { + if (consistency_lvl == raft::consistency_level::quorum_ack) { return wait_for_committed_offset( leader_node.raft()->committed_offset(), 30s); } else { @@ -295,7 +373,7 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { } }); - co_await wait_for_reconfiguration_to_finish(30s); + co_await wait_for_reconfiguration_to_finish(current_node_ids, 30s); co_await assert_logs_equal(start_offset); @@ -303,8 +381,9 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { current_nodes.begin(), current_nodes.end()); // validate configuration - for (const auto& [_, n] : nodes()) { - auto cfg = n->raft()->config(); + for (auto id : current_node_ids) { + auto& n = node(id); + auto cfg = n.raft()->config(); auto cfg_vnodes = cfg.all_nodes(); ASSERT_EQ_CORO( current_nodes_set, @@ -313,11 +392,16 @@ TEST_P_CORO(reconfiguration_test, configuration_replace_test) { ASSERT_FALSE_CORO(cfg.old_config().has_value()); ASSERT_TRUE_CORO(cfg.current_config().learners.empty()); - if (learner_start_offset && added_nodes.contains(n->get_vnode())) { - ASSERT_EQ_CORO(n->raft()->start_offset(), learner_start_offset); + if (learner_start_offset && added_nodes.contains(n.get_vnode())) { + ASSERT_EQ_CORO(n.raft()->start_offset(), learner_start_offset); } } - assert_offset_translator_state_is_consistent(nodes()); + + std::vector current_node_ptrs; + for (auto id : current_node_ids) { + current_node_ptrs.push_back(&node(id)); + } + assert_offset_translator_state_is_consistent(current_node_ptrs); } INSTANTIATE_TEST_SUITE_P( @@ -330,4 +414,19 @@ INSTANTIATE_TEST_SUITE_P( testing::Values(0, 1, 3), // to remove testing::Values(use_initial_learner_offset::yes), testing::Values( - consistency_level::quorum_ack, consistency_level::leader_ack))); + consistency_level::quorum_ack, consistency_level::leader_ack), + testing::Values(isolated_t::none))); + +INSTANTIATE_TEST_SUITE_P( + reconfiguration_with_isolated_nodes, + reconfiguration_test, + testing::Combine( + testing::Values(use_snapshot::no), + testing::Values(3), // initial size + testing::Values(2), // to add + testing::Values(0, 2), // to remove + testing::Values(use_initial_learner_offset::yes), + testing::Values( + consistency_level::quorum_ack, consistency_level::leader_ack), + testing::Values( + isolated_t::old_followers, isolated_t::old_leader, isolated_t::random))); diff --git a/src/v/raft/tests/simple_raft_fixture.h b/src/v/raft/tests/simple_raft_fixture.h index 146fcce3f183f..13bc29c116f92 100644 --- a/src/v/raft/tests/simple_raft_fixture.h +++ b/src/v/raft/tests/simple_raft_fixture.h @@ -70,7 +70,10 @@ struct simple_raft_fixture { .get(); _producer_state_manager .start( - config::mock_binding(std::numeric_limits::max()), + ss::sharded_parameter([] { + return config::shard_local_cfg() + .max_concurrent_producer_ids.bind(); + }), std::chrono::milliseconds::max()) .get(); _producer_state_manager diff --git a/src/v/raft/tests/stm_manager_test.cc b/src/v/raft/tests/stm_manager_test.cc index c835ca4096493..4044a864bac3f 100644 --- a/src/v/raft/tests/stm_manager_test.cc +++ b/src/v/raft/tests/stm_manager_test.cc @@ -12,18 +12,15 @@ using namespace raft; inline ss::logger logger("stm-test-logger"); -struct other_simple_kv : public simple_kv { - explicit other_simple_kv(raft_node_instance& rn) - : simple_kv(rn) {} - std::string_view get_name() const override { return "other_simple_kv"; }; +struct other_kv : simple_kv { + using simple_kv::simple_kv; + static constexpr std::string_view name = "other_simple_kv"; }; - struct throwing_kv : public simple_kv { + static constexpr std::string_view name = "throwing_kv"; explicit throwing_kv(raft_node_instance& rn) : simple_kv(rn) {} - std::string_view get_name() const override { return "throwing_kv"; }; - ss::future<> apply(const model::record_batch& batch) override { if (tests::random_bool()) { throw std::runtime_error("runtime error from throwing stm"); @@ -53,11 +50,10 @@ struct throwing_kv : public simple_kv { * Local snapshot stm manages its own local snapshot. */ struct local_snapshot_stm : public simple_kv { + static constexpr std::string_view name = "local_snapshot_kv"; explicit local_snapshot_stm(raft_node_instance& rn) : simple_kv(rn) {} - std::string_view get_name() const override { return "local_snapshot_stm"; }; - ss::future<> apply(const model::record_batch& batch) override { vassert( batch.base_offset() == next(), @@ -79,6 +75,47 @@ struct local_snapshot_stm : public simple_kv { }; }; +// State machine that induces lag from the tip of +// of the log +class slow_kv : public simple_kv { +public: + static constexpr std::string_view name = "slow_kv"; + + explicit slow_kv(raft_node_instance& rn) + : simple_kv(rn) {} + + ss::future<> apply(const model::record_batch& batch) override { + co_await ss::sleep(5ms); + co_return co_await simple_kv::apply(batch); + } + + ss::future<> apply_raft_snapshot(const iobuf&) override { + return ss::now(); + } +}; + +// Fails the first apply, starts a background fiber and not lets the +// background apply fiber finish relative to slow_kv +class bg_only_kv : public slow_kv { +public: + static constexpr std::string_view name = "bg_only_stm"; + + explicit bg_only_kv(raft_node_instance& rn) + : slow_kv(rn) {} + + ss::future<> apply(const model::record_batch& batch) override { + if (_first_apply) { + _first_apply = false; + throw std::runtime_error("induced failure"); + } + co_await ss::sleep(5ms); + co_return co_await slow_kv::apply(batch); + } + +private: + bool _first_apply = true; +}; + TEST_F_CORO(state_machine_fixture, test_basic_apply) { /** * Create 3 replicas group with simple_kv STM @@ -89,7 +126,7 @@ TEST_F_CORO(state_machine_fixture, test_basic_apply) { for (auto& [id, node] : nodes()) { raft::state_machine_manager_builder builder; auto kv_stm = builder.create_stm(*node); - auto other_kv_stm = builder.create_stm(*node); + auto other_kv_stm = builder.create_stm(*node); co_await node->init_and_start(all_vnodes(), std::move(builder)); stms.push_back(kv_stm); stms.push_back(ss::dynamic_pointer_cast(other_kv_stm)); @@ -104,6 +141,40 @@ TEST_F_CORO(state_machine_fixture, test_basic_apply) { } } +TEST_F_CORO(state_machine_fixture, test_snapshot_with_bg_fibers) { + create_nodes(); + std::vector> stms; + for (auto& [id, node] : nodes()) { + raft::state_machine_manager_builder builder; + auto slow_kv_stm = builder.create_stm(*node); + auto bg_kv_stm = builder.create_stm(*node); + co_await node->init_and_start(all_vnodes(), std::move(builder)); + stms.push_back(ss::dynamic_pointer_cast(slow_kv_stm)); + stms.push_back(ss::dynamic_pointer_cast(bg_kv_stm)); + } + auto& leader_node = node(co_await wait_for_leader(10s)); + bool stop = false; + auto write_sleep_f = ss::do_until( + [&stop] { return stop; }, + [&] { + return build_random_state(1000).discard_result().then( + [] { return ss::sleep(3ms); }); + }); + + auto truncate_sleep_f = ss::do_until( + [&stop] { return stop; }, + [&] { + return leader_node.raft() + ->write_snapshot({leader_node.raft()->committed_offset(), iobuf{}}) + .then([] { return ss::sleep(3ms); }); + }); + + co_await ss::sleep(10s); + stop = true; + co_await std::move(write_sleep_f); + co_await std::move(truncate_sleep_f); +} + TEST_F_CORO(state_machine_fixture, test_apply_throwing_exception) { /** * Create 3 replicas group with simple_kv STM @@ -355,3 +426,132 @@ TEST_F_CORO( ASSERT_EQ_CORO(new_stm->state, partial_expected_state); } + +struct controllable_throwing_kv : public simple_kv { + static constexpr std::string_view name = "controllable_throwing_kv_1"; + explicit controllable_throwing_kv(raft_node_instance& rn) + : simple_kv(rn) {} + + ss::future<> apply(const model::record_batch& batch) override { + if (batch.last_offset() > _allow_apply) { + throw std::runtime_error(fmt::format( + "not allowed to apply batches with last offset greater than {}. " + "Current batch last offset: {}", + _allow_apply, + batch.last_offset())); + } + vassert( + batch.base_offset() == next(), + "batch {} base offset is not the next to apply, expected base " + "offset: {}", + batch.header(), + next()); + co_await simple_kv::apply(batch); + co_return; + } + + void allow_apply_to(model::offset o) { _allow_apply = o; } + + model::offset _allow_apply; +}; + +struct controllable_throwing_kv_2 : public controllable_throwing_kv { + using controllable_throwing_kv::controllable_throwing_kv; + + static constexpr std::string_view name = "controllable_throwing_kv_2"; +}; + +struct controllable_throwing_kv_3 : public controllable_throwing_kv { + using controllable_throwing_kv::controllable_throwing_kv; + + static constexpr std::string_view name = "controllable_throwing_kv_3"; +}; +TEST_F_CORO(state_machine_fixture, test_all_machines_throw) { + /** + * This test covers the scenario in which all state machines thrown an + * exception during apply, and then one of the state machines makes some + * progress. + */ + create_nodes(); + std::vector> stms; + for (auto& [id, node] : nodes()) { + raft::state_machine_manager_builder builder; + auto kv_1 = builder.create_stm(*node); + auto kv_2 = builder.create_stm(*node); + auto kv_3 = builder.create_stm(*node); + + stms.push_back(ss::dynamic_pointer_cast(kv_1)); + stms.push_back(ss::dynamic_pointer_cast(kv_2)); + stms.push_back(ss::dynamic_pointer_cast(kv_3)); + + co_await node->init_and_start(all_vnodes(), std::move(builder)); + } + for (auto& [id, node] : nodes()) { + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(100)); + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(100)); + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(150)); + } + vlog(logger().info, "Generating state for test"); + auto expected = co_await build_random_state( + 500, wait_for_each_batch::no, 1); + vlog(logger().info, "Waiting for state machines"); + RPTEST_REQUIRE_EVENTUALLY_CORO(15s, [&] { + return std::ranges::all_of( + nodes() | std::views::values, + [&](std::unique_ptr& node) { + auto la = node->raft() + ->stm_manager() + ->get() + ->last_applied_offset(); + return la >= model::offset(150); + }); + }); + + for (auto& [id, node] : nodes()) { + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(160)); + } + RPTEST_REQUIRE_EVENTUALLY_CORO(15s, [&] { + return std::ranges::all_of( + nodes() | std::views::values, + [&](std::unique_ptr& node) { + auto la = node->raft() + ->stm_manager() + ->get() + ->last_applied_offset(); + return la >= model::offset(160); + }); + }); + + for (auto& [id, node] : nodes()) { + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(1000)); + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(1000)); + node->raft() + ->stm_manager() + ->get() + ->allow_apply_to(model::offset(1000)); + } + + co_await wait_for_apply(); + + for (auto& stm : stms) { + ASSERT_EQ_CORO(stm->state, expected); + } +} diff --git a/src/v/raft/tests/stm_test_fixture.h b/src/v/raft/tests/stm_test_fixture.h index 32b74d6762d88..aae6a56752739 100644 --- a/src/v/raft/tests/stm_test_fixture.h +++ b/src/v/raft/tests/stm_test_fixture.h @@ -41,7 +41,6 @@ using namespace raft; namespace { - /** * We use value entry struct to make kv_store apply operations not * idempotent @@ -71,6 +70,7 @@ struct value_entry */ struct simple_kv : public raft::state_machine_base { using state_t = absl::flat_hash_map; + static constexpr std::string_view name = "simple_kv"; explicit simple_kv(raft_node_instance& rn) : raft_node(rn) {} @@ -108,8 +108,6 @@ struct simple_kv : public raft::state_machine_base { co_return; }; - std::string_view get_name() const override { return "simple_kv"; }; - ss::future take_snapshot(model::offset last_included_offset) override { state_t inc_state; @@ -174,11 +172,14 @@ struct state_machine_fixture : raft_fixture { ss::future> build_random_state( - int op_cnt, wait_for_each_batch wait_for_each = wait_for_each_batch::no) { + int op_cnt, + wait_for_each_batch wait_for_each = wait_for_each_batch::no, + size_t max_batch_size = 50) { absl::flat_hash_map state; for (int i = 0; i < op_cnt;) { - const auto batch_sz = random_generators::get_int(1, 50); + const auto batch_sz = random_generators::get_int( + 1, max_batch_size); std::vector>> ops; for (auto n = 0; n < batch_sz; ++n) { auto k = random_generators::gen_alphanum_string(10); diff --git a/src/v/raft/types.h b/src/v/raft/types.h index c6b3aaab60175..77b9d519d6084 100644 --- a/src/v/raft/types.h +++ b/src/v/raft/types.h @@ -264,6 +264,7 @@ struct append_entries_request return std::move(_batches); } + model::record_batch_reader& batches() { return _batches; } const model::record_batch_reader& batches() const { return _batches; } static append_entries_request make_foreign(append_entries_request&& req); diff --git a/src/v/raft/vote_stm.cc b/src/v/raft/vote_stm.cc index 4acffdd6af89d..acaa6ba13cb52 100644 --- a/src/v/raft/vote_stm.cc +++ b/src/v/raft/vote_stm.cc @@ -93,53 +93,56 @@ ss::future<> vote_stm::dispatch_one(vnode n) { ss::future<> vote_stm::vote(bool leadership_transfer) { using skip_vote = ss::bool_class; - return _ptr->_op_lock - .with([this, leadership_transfer] { - _config = _ptr->config(); - // check again while under op_sem - if (_ptr->should_skip_vote(leadership_transfer)) { - return ss::make_ready_future(skip_vote::yes); - } - // 5.2.1 mark node as candidate, and update leader id - _ptr->_vstate = consensus::vote_state::candidate; - // only trigger notification when we had a leader previously - if (_ptr->_leader_id) { - _ptr->_leader_id = std::nullopt; - _ptr->trigger_leadership_notification(); - } - - // 5.2.1.2 - _ptr->_term += model::term_id(1); - _ptr->_voted_for = {}; - - // special case, it may happen that node requesting votes is not a - // voter, it may happen if it is a learner in previous configuration - _replies.emplace(_ptr->_self, vmeta{}); - - // vote is the only method under _op_sem - _config->for_each_voter( - [this](vnode id) { _replies.emplace(id, vmeta{}); }); - auto lstats = _ptr->_log->offsets(); - auto last_entry_term = _ptr->get_last_entry_term(lstats); - - _req = vote_request{ - .node_id = _ptr->_self, - .group = _ptr->group(), - .term = _ptr->term(), - .prev_log_index = lstats.dirty_offset, - .prev_log_term = last_entry_term, - .leadership_transfer = leadership_transfer}; - // we have to self vote before dispatching vote request to - // other nodes, this vote has to be done under op semaphore as - // it changes voted_for state - return self_vote().then([] { return skip_vote::no; }); - }) - .then([this](skip_vote skip) { - if (skip) { - return ss::make_ready_future<>(); - } - return do_vote(); - }); + return _ptr->_election_lock.with([this, leadership_transfer] { + return _ptr->_op_lock + .with([this, leadership_transfer] { + _config = _ptr->config(); + // check again while under op_sem + if (_ptr->should_skip_vote(leadership_transfer)) { + return ss::make_ready_future(skip_vote::yes); + } + // 5.2.1 mark node as candidate, and update leader id + _ptr->_vstate = consensus::vote_state::candidate; + // only trigger notification when we had a leader previously + if (_ptr->_leader_id) { + _ptr->_leader_id = std::nullopt; + _ptr->trigger_leadership_notification(); + } + + // 5.2.1.2 + _ptr->_term += model::term_id(1); + _ptr->_voted_for = {}; + + // special case, it may happen that node requesting votes is not a + // voter, it may happen if it is a learner in previous + // configuration + _replies.emplace(_ptr->_self, vmeta{}); + + // vote is the only method under _op_sem + _config->for_each_voter( + [this](vnode id) { _replies.emplace(id, vmeta{}); }); + auto lstats = _ptr->_log->offsets(); + auto last_entry_term = _ptr->get_last_entry_term(lstats); + + _req = vote_request{ + .node_id = _ptr->_self, + .group = _ptr->group(), + .term = _ptr->term(), + .prev_log_index = lstats.dirty_offset, + .prev_log_term = last_entry_term, + .leadership_transfer = leadership_transfer}; + // we have to self vote before dispatching vote request to + // other nodes, this vote has to be done under op semaphore as + // it changes voted_for state + return self_vote().then([] { return skip_vote::no; }); + }) + .then([this](skip_vote skip) { + if (skip) { + return ss::make_ready_future<>(); + } + return do_vote(); + }); + }); } ss::future<> vote_stm::do_vote() { @@ -203,7 +206,7 @@ ss::future<> vote_stm::process_replies() { ss::future<> vote_stm::wait() { return _vote_bg.close(); } ss::future<> vote_stm::update_vote_state(ssx::semaphore_units u) { - // use reply term to update voter term + // use reply term to update our term for (auto& [_, r] : _replies) { if (r.value && r.value->has_value()) { auto term = r.value->value().term; @@ -212,7 +215,7 @@ ss::future<> vote_stm::update_vote_state(ssx::semaphore_units u) { _ctxlog.info, "Vote failed - received larger term: {}", term); _ptr->_term = term; _ptr->_voted_for = {}; - _ptr->_vstate = consensus::vote_state::follower; + fail_election(); co_return; } } @@ -242,7 +245,7 @@ ss::future<> vote_stm::update_vote_state(ssx::semaphore_units u) { _ctxlog.debug, "Ignoring successful vote. Node priority too low: {}", _ptr->_node_priority_override.value()); - _ptr->_vstate = consensus::vote_state::follower; + fail_election(); co_return; } @@ -268,7 +271,7 @@ ss::future<> vote_stm::update_vote_state(ssx::semaphore_units u) { auto ec = co_await replicate_config_as_new_leader(std::move(u)); - // if we didn't replicated configuration, step down + // even if failed to replicate, don't step down: followers may be behind if (ec) { vlog( _ctxlog.info, @@ -315,4 +318,13 @@ ss::future<> vote_stm::self_vote() { m->second.set_value(reply); }); } + +void vote_stm::fail_election() { + vassert( + _ptr->_vstate != consensus::vote_state::leader + && _ptr->_hbeat != clock_type::time_point::max(), + "Became a leader outside current election"); + _ptr->_vstate = consensus::vote_state::follower; +} + } // namespace raft diff --git a/src/v/raft/vote_stm.h b/src/v/raft/vote_stm.h index 37c4979b3c9b2..6e5e16bece274 100644 --- a/src/v/raft/vote_stm.h +++ b/src/v/raft/vote_stm.h @@ -78,6 +78,8 @@ class vote_stm { std::unique_ptr> value; }; + void fail_election(); + friend std::ostream& operator<<(std::ostream&, const vmeta&); ss::future<> do_vote(); diff --git a/src/v/redpanda/admin/api-doc/broker.json b/src/v/redpanda/admin/api-doc/broker.json index 1763638c50878..9e31db0b3e87b 100644 --- a/src/v/redpanda/admin/api-doc/broker.json +++ b/src/v/redpanda/admin/api-doc/broker.json @@ -40,6 +40,24 @@ } ] }, + { + "path": "/v1/broker_uuids", + "operations": [ + { + "method": "GET", + "summary": "Get a list of node id to UUID mappings", + "type": "array", + "items": { + "type": "broker_uuid_mapping" + }, + "nickname": "get_broker_uuids", + "produces": [ + "application/json" + ], + "parameters": [] + } + ] + }, { "path": "/v1/brokers/{id}", "operations": [ @@ -434,6 +452,20 @@ "description": "id of a core on a given node" } } + }, + "broker_uuid_mapping": { + "id": "broker_uuid_mapping", + "description": "Mapping between UUID and node id", + "properties": { + "node_id": { + "type": "int", + "description": "Broker assigned id" + }, + "uuid": { + "type": "string", + "description": "Broker unique identifier. UUID is generated when Redpanda starts with empty data folder" + } + } } } } \ No newline at end of file diff --git a/src/v/redpanda/admin/api-doc/debug.json b/src/v/redpanda/admin/api-doc/debug.json index 2dbce3df86709..47e91e49096d2 100644 --- a/src/v/redpanda/admin/api-doc/debug.json +++ b/src/v/redpanda/admin/api-doc/debug.json @@ -124,12 +124,11 @@ }, "503": { "description": "Test failed to start", - "schema":{ + "schema": { "type": "json" } } } - } ] }, @@ -159,7 +158,6 @@ ], "parameters": [] } - ] }, { @@ -325,10 +323,10 @@ ], "parameters": [ { - "name": "service", - "in": "query", - "required": true, - "type": "string" + "name": "service", + "in": "query", + "required": true, + "type": "string" } ], "responses": { @@ -395,10 +393,10 @@ ], "parameters": [ { - "name": "value", - "in": "query", - "required": true, - "type": "boolean" + "name": "value", + "in": "query", + "required": true, + "type": "boolean" } ] } @@ -453,91 +451,91 @@ ] }, { - "path": "/v1/debug/unsafe_reset_metadata/{topic}/{partition}", - "operations": [ - { - "method": "POST", - "summary": "Resets the manifest, updating all replicas with the given manifest. This is very unsafe, so be sure the provided manifest actually has valid contents. Formatting aside, very little validation will be done on the requested manifest.", - "operationId": "unsafe_reset_metadata", - "nickname": "unsafe_reset_metadata", - "parameters": [ - { - "name": "topic", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "partition", - "in": "path", - "required": true, - "type": "integer" - }, - { - "name": "body", - "required": true, - "paramType": "body" - } - ], - "responseMessages": [ + "path": "/v1/debug/unsafe_reset_metadata/{topic}/{partition}", + "operations": [ { - "code": 200, - "message": "Partition metadata is reset" + "method": "POST", + "summary": "Resets the manifest, updating all replicas with the given manifest. This is very unsafe, so be sure the provided manifest actually has valid contents. Formatting aside, very little validation will be done on the requested manifest.", + "operationId": "unsafe_reset_metadata", + "nickname": "unsafe_reset_metadata", + "parameters": [ + { + "name": "topic", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "partition", + "in": "path", + "required": true, + "type": "integer" + }, + { + "name": "body", + "required": true, + "paramType": "body" + } + ], + "responseMessages": [ + { + "code": 200, + "message": "Partition metadata is reset" + } + ] } - ] - } - ] + ] }, { - "path": "/v1/debug/storage/disk_stat/{type}", - "operations": [ - { - "method": "GET", - "summary": "Return disk space statistics.", - "operationId": "get_disk_stat", - "nickname": "get_disk_stat", - "type": "disk_stat", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "type", - "in": "path", - "required": true, - "type": "string" - } - ], - "responseMessages": [ - { - "code": 200 - } - ] - }, - { - "method": "PUT", - "summary": "Override disk space statistics.", - "operationId": "put_disk_stat", - "nickname": "put_disk_stat", - "type": "disk_stat_overrides", - "produces": [ - "application/json" - ], - "parameters": [ + "path": "/v1/debug/storage/disk_stat/{type}", + "operations": [ { - "name": "type", - "in": "path", - "required": true, - "type": "string" - } - ], - "responseMessages": [ + "method": "GET", + "summary": "Return disk space statistics.", + "operationId": "get_disk_stat", + "nickname": "get_disk_stat", + "type": "disk_stat", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "type", + "in": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 200 + } + ] + }, { - "code": 200 + "method": "PUT", + "summary": "Override disk space statistics.", + "operationId": "put_disk_stat", + "nickname": "put_disk_stat", + "type": "disk_stat_overrides", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "type", + "in": "path", + "required": true, + "type": "string" + } + ], + "responseMessages": [ + { + "code": 200 + } + ] } - ] - } - ] + ] }, { "path": "/v1/debug/storage/offset_translator/{namespace}/{topic}/{partition}", @@ -547,7 +545,7 @@ "summary": "Return list of all local offsets translated", "nickname": "get_local_offsets_translated", "produces": [ - "application/json" + "application/json" ], "type": "array", "parameters": [ @@ -605,6 +603,34 @@ ] } ] + }, + { + "path": "/v1/debug/broker_uuid", + "operations": [ + { + "method": "GET", + "summary": "Return current broker id and UUID", + "nickname": "get_broker_uuid", + "produces": [ + "application/json" + ] + }, + { + "method": "PUT", + "summary": "Overrides stored broker UUID and ID values", + "nickname": "override_broker_uuid", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "required": true, + "paramType": "broker_id_override" + } + ] + } + ] } ], "models": { @@ -896,7 +922,7 @@ } }, "stm_state": { - "id" : "stm_state", + "id": "stm_state", "description": "Stm related state for a given replica.", "properties": { "name": { @@ -940,11 +966,11 @@ "description": "Current raft term" }, "offset_translator_state": { - "type" : "string", + "type": "string", "description": "State of the offset translator" }, "group_configuration": { - "type" : "string", + "type": "string", "description": "Local raft replica configuration" }, "confirmed_term": { @@ -999,10 +1025,10 @@ "type": "boolean", "description": "True of voted a leader" }, - "followers" : { + "followers": { "type": "array", "items": { - "type" : "raft_follower_state" + "type": "raft_follower_state" }, "description": "Follower metadata for this leader." }, @@ -1084,7 +1110,7 @@ "description": "Next cloud offset" }, "raft_state": { - "type" : "raft_replica_state", + "type": "raft_replica_state", "description": "Underlying raft replica state of the partition instance" } } @@ -1189,6 +1215,38 @@ "description": "kafka offset" } } + }, + "broker_uuid": { + "id": "broker_uuid", + "description": "Broker UUID and assigned id", + "properties": { + "node_uuid": { + "type": "string", + "description": "Current node UUID" + }, + "node_id": { + "type": "int", + "description": "Assigned node id (may be empty)" + } + } + }, + "broker_id_override": { + "id": "broker_id_override", + "description": "An override for broker id and UUID", + "properties": { + "current_node_uuid": { + "type": "string", + "description": "Current node UUID, used to validate if request is addressed to the correct node" + }, + "new_node_id": { + "type": "int", + "description": "New node id for current node" + }, + "new_node_uuid": { + "type": "string", + "description": "Node unique identifier. UUID is generated when Redpanda starts with empty data folder" + } + } } } -} +} \ No newline at end of file diff --git a/src/v/redpanda/admin/debug.cc b/src/v/redpanda/admin/debug.cc index 8f17327ab9bb9..7a347ba0e42e4 100644 --- a/src/v/redpanda/admin/debug.cc +++ b/src/v/redpanda/admin/debug.cc @@ -11,13 +11,26 @@ #include "cloud_storage/cache_service.h" #include "cluster/cloud_storage_size_reducer.h" #include "cluster/controller.h" +#include "cluster/controller_stm.h" +#include "cluster/members_manager.h" #include "cluster/metadata_cache.h" #include "cluster/shard_table.h" #include "cluster/topics_frontend.h" +#include "cluster/types.h" +#include "config/configuration.h" +#include "config/node_config.h" +#include "json/validator.h" +#include "model/fundamental.h" +#include "model/metadata.h" #include "redpanda/admin/api-doc/debug.json.hh" #include "redpanda/admin/server.h" #include "redpanda/admin/util.h" +#include "resource_mgmt/cpu_profiler.h" +#include "serde/rw/rw.h" +#include "storage/kvstore.h" +#include "utils/lw_shared_container.h" +#include #include namespace { @@ -237,7 +250,7 @@ void admin_server::register_debug_routes() { auto leaders_info = _metadata_cache.local().get_leaders(); return ss::make_ready_future( ss::json::stream_range_as_array( - admin::lw_shared_container(std::move(leaders_info)), + lw_shared_container(std::move(leaders_info)), [](const auto& leader_info) { result_t info; info.ns = leader_info.tp_ns.ns; @@ -450,8 +463,23 @@ void admin_server::register_debug_routes() { [this](std::unique_ptr request) { return put_disk_stat_handler(std::move(request)); }); + + register_route( + ss::httpd::debug_json::override_broker_uuid, + [this](std::unique_ptr req) + -> ss::future { + return override_node_uuid_handler(std::move(req)); + }); + register_route( + ss::httpd::debug_json::get_broker_uuid, + [this](std::unique_ptr) + -> ss::future { + return get_node_uuid_handler(); + }); } +using admin::apply_validator; + ss::future admin_server::cpu_profile_handler(std::unique_ptr req) { vlog(adminlog.info, "Request to sampled cpu profile"); @@ -477,23 +505,23 @@ admin_server::cpu_profile_handler(std::unique_ptr req) { auto profiles = co_await _cpu_profiler.local().results(shard_id); - std::vector response{ - profiles.size()}; - for (size_t i = 0; i < profiles.size(); i++) { - response[i].shard_id = profiles[i].shard; - response[i].dropped_samples = profiles[i].dropped_samples; - - for (auto& sample : profiles[i].samples) { - ss::httpd::debug_json::cpu_profile_sample s; - s.occurrences = sample.occurrences; - s.user_backtrace = sample.user_backtrace; - - response[i].samples.push(s); - } - } - co_return co_await ss::make_ready_future( - std::move(response)); + ss::json::stream_range_as_array( + lw_shared_container(std::move(profiles)), + [](const resources::cpu_profiler::shard_samples& profile) { + ss::httpd::debug_json::cpu_profile_shard_samples ret; + ret.shard_id = profile.shard; + ret.dropped_samples = profile.dropped_samples; + + for (auto& sample : profile.samples) { + ss::httpd::debug_json::cpu_profile_sample s; + s.occurrences = sample.occurrences; + s.user_backtrace = sample.user_backtrace; + + ret.samples.push(s); + } + return ret; + })); } ss::future @@ -742,3 +770,130 @@ admin_server::get_partition_state_handler( } co_return ss::json::json_return_type(std::move(response)); } + +ss::future admin_server::get_node_uuid_handler() { + ss::httpd::debug_json::broker_uuid uuid; + uuid.node_uuid = ssx::sformat( + "{}", _controller->get_storage().local().node_uuid()); + + if (config::node().node_id().has_value()) { + uuid.node_id = config::node().node_id().value(); + } + + co_return ss::json::json_return_type(std::move(uuid)); +} + +static json::validator make_broker_id_override_validator() { + const std::string schema = R"( +{ + "type": "object", + "properties": { + "current_node_uuid": { + "type": "string" + }, + "new_node_uuid": { + "type": "string" + }, + "new_node_id": { + "type" : "integer" + } + }, + "additionalProperties": false, + "required": ["current_node_uuid", "new_node_id","new_node_uuid"] +})"; + return json::validator(schema); +} + +namespace { +ss::future<> override_id_and_uuid( + storage::kvstore& kvs, model::node_uuid uuid, model::node_id id) { + static const bytes node_uuid_key = "node_uuid"; + co_await kvs.put( + storage::kvstore::key_space::controller, + node_uuid_key, + serde::to_iobuf(uuid)); + auto invariants_iobuf = kvs.get( + storage::kvstore::key_space::controller, + cluster::controller::invariants_key); + if (invariants_iobuf) { + auto invariants + = reflection::from_iobuf( + std::move(invariants_iobuf.value())); + invariants.node_id = id; + + co_await kvs.put( + storage::kvstore::key_space::controller, + cluster::controller::invariants_key, + reflection::to_iobuf(std::move(invariants))); + } +} +} // namespace + +ss::future admin_server::override_node_uuid_handler( + std::unique_ptr req) { + static thread_local auto override_id_validator( + make_broker_id_override_validator()); + const auto doc = co_await parse_json_body(req.get()); + /** + * Validate the request body + */ + using admin::apply_validator; + apply_validator(override_id_validator, doc); + /** + * Validate if current UUID matches this node UUID, if not request an error + * is returned as the request may have been sent to incorrect node + */ + model::node_uuid current_uuid; + try { + current_uuid = model::node_uuid( + uuid_t::from_string(doc["current_node_uuid"].GetString())); + } catch (const std::runtime_error& e) { + throw ss::httpd::bad_request_exception(ssx::sformat( + "failed parsing current_node_uuid: {} - {}", + doc["current_node_uuid"].GetString(), + e.what())); + } + auto& storage = _controller->get_storage().local(); + if (storage.node_uuid() != current_uuid) { + throw ss::httpd::bad_request_exception(ssx::sformat( + "Requested current node UUID: {} does not match node UUID: {}", + storage.node_uuid(), + current_uuid)); + } + model::node_uuid new_node_uuid; + try { + new_node_uuid = model::node_uuid( + uuid_t::from_string(doc["new_node_uuid"].GetString())); + } catch (const std::runtime_error& e) { + throw ss::httpd::bad_request_exception(ssx::sformat( + "failed parsing new_node_uuid: {} - {}", + doc["new_node_uuid"].GetString(), + e.what())); + } + model::node_id new_node_id; + try { + new_node_id = model::node_id(doc["new_node_id"].GetInt()); + if (new_node_id < model::node_id{0}) { + throw ss::httpd::bad_request_exception( + "node_id must not be negative"); + } + } catch (const std::runtime_error& e) { + throw ss::httpd::bad_request_exception( + ssx::sformat("failed parsing new node_id: {}", e.what())); + } + + vlog( + adminlog.warn, + "Requested to override node id with new node id: {} and UUID: {}", + new_node_id, + new_node_uuid); + + co_await _controller->get_storage().invoke_on( + cluster::controller_stm_shard, + [new_node_uuid, new_node_id](storage::api& local_storage) { + return override_id_and_uuid( + local_storage.kvs(), new_node_uuid, new_node_id); + }); + + co_return ss::json::json_return_type(ss::json::json_void()); +} diff --git a/src/v/redpanda/admin/partition.cc b/src/v/redpanda/admin/partition.cc index 9a7c4df8f9e03..3b45b1721194a 100644 --- a/src/v/redpanda/admin/partition.cc +++ b/src/v/redpanda/admin/partition.cc @@ -21,6 +21,7 @@ #include "redpanda/admin/server.h" #include "redpanda/admin/util.h" #include "utils/fragmented_vector.h" +#include "utils/lw_shared_container.h" #include @@ -29,7 +30,6 @@ #include using admin::apply_validator; -using admin::lw_shared_container; ss::future admin_server::get_transactions_handler(std::unique_ptr req) { @@ -898,8 +898,7 @@ admin_server::get_topic_partitions_handler( }); co_return ss::json::json_return_type(ss::json::stream_range_as_array( - admin::lw_shared_container(std::move(partitions)), - [](auto& p) { return p; })); + lw_shared_container(std::move(partitions)), [](auto& p) { return p; })); } ss::future diff --git a/src/v/redpanda/admin/server.cc b/src/v/redpanda/admin/server.cc index f2c7d9b5641b6..29cf939069252 100644 --- a/src/v/redpanda/admin/server.cc +++ b/src/v/redpanda/admin/server.cc @@ -105,6 +105,7 @@ #include "ssx/sformat.h" #include "transform/api.h" #include "utils/fragmented_vector.h" +#include "utils/lw_shared_container.h" #include "utils/string_switch.h" #include "utils/utf8.h" #include "vlog.h" @@ -158,11 +159,11 @@ #include #include #include +#include using namespace std::chrono_literals; using admin::apply_validator; -using admin::lw_shared_container; ss::logger adminlog{"admin_api_server"}; @@ -475,7 +476,7 @@ get_integer_query_param(const ss::http::request& req, std::string_view name) { const ss::sstring& str_param = req.query_parameters.at(key); try { - return std::stoi(str_param); + return std::stoull(str_param); } catch (const std::invalid_argument&) { throw ss::httpd::bad_request_exception( fmt::format("Parameter {} must be an integer", name)); @@ -687,11 +688,17 @@ void admin_server::log_exception( void admin_server::rearm_log_level_timer() { _log_level_timer.cancel(); - auto next = std::min_element( - _log_level_resets.begin(), _log_level_resets.end()); + if (_log_level_resets.empty()) { + return; + } - if (next != _log_level_resets.end() && next->second.expires.has_value()) { - _log_level_timer.arm(next->second.expires.value()); + auto reset_values = _log_level_resets | std::views::values; + auto& lvl_rst = *std::ranges::min_element( + reset_values, std::less<>{}, [](level_reset const& l) { + return l.expires.value_or(ss::timer<>::clock::time_point::max()); + }); + if (lvl_rst.expires.has_value()) { + _log_level_timer.arm(lvl_rst.expires.value()); } } @@ -943,10 +950,11 @@ get_brokers(cluster::controller* const controller) { } b.membership_status = fmt::format( "{}", nm.state.get_membership_state()); + b.is_alive = controller->get_health_monitor().local().is_alive(id) + == cluster::alive::yes; // These fields are defaults that will be overwritten with // data from the health report. - b.is_alive = true; b.maintenance_status = fill_maintenance_status(nm.state); b.internal_rpc_address = nm.broker.rpc_address().host(); b.internal_rpc_port = nm.broker.rpc_address().port(); @@ -955,43 +963,32 @@ get_brokers(cluster::controller* const controller) { } // Enrich the broker information with data from the health report. - for (auto& ns : h_report.value().node_states) { - auto it = broker_map.find(ns.id); + for (auto& node_report : h_report.value().node_reports) { + auto it = broker_map.find(node_report->id); if (it == broker_map.end()) { continue; } - it->second.is_alive = static_cast(ns.is_alive); - - auto r_it = std::find_if( - h_report.value().node_reports.begin(), - h_report.value().node_reports.end(), - [id = ns.id](const cluster::node_health_report& nhr) { - return nhr.id == id; - }); - - if (r_it != h_report.value().node_reports.end()) { - it->second.version = r_it->local_state.redpanda_version; - it->second.recovery_mode_enabled - = r_it->local_state.recovery_mode_enabled; - auto nm = members_table.get_node_metadata_ref(r_it->id); - if (nm && r_it->drain_status) { - it->second.maintenance_status = fill_maintenance_status( - nm.value().get().state, r_it->drain_status.value()); - } + it->second.version = node_report->local_state.redpanda_version; + it->second.recovery_mode_enabled + = node_report->local_state.recovery_mode_enabled; + auto nm = members_table.get_node_metadata_ref(node_report->id); + if (nm && node_report->drain_status) { + it->second.maintenance_status = fill_maintenance_status( + nm.value().get().state, node_report->drain_status.value()); + } - auto add_disk = [&ds_list = it->second.disk_space]( - const storage::disk& ds) { - ss::httpd::broker_json::disk_space_info dsi; - dsi.path = ds.path; - dsi.free = ds.free; - dsi.total = ds.total; - ds_list.push(dsi); - }; - add_disk(r_it->local_state.data_disk); - if (!r_it->local_state.shared_disk()) { - add_disk(r_it->local_state.get_cache_disk()); - } + auto add_disk = + [&ds_list = it->second.disk_space](const storage::disk& ds) { + ss::httpd::broker_json::disk_space_info dsi; + dsi.path = ds.path; + dsi.free = ds.free; + dsi.total = ds.total; + ds_list.push(dsi); + }; + add_disk(node_report->local_state.data_disk); + if (!node_report->local_state.shared_disk()) { + add_disk(node_report->local_state.get_cache_disk()); } } @@ -1157,6 +1154,7 @@ ss::future<> admin_server::throw_on_error( case rpc::errc::disconnected_endpoint: case rpc::errc::exponential_backoff: case rpc::errc::shutting_down: + case rpc::errc::service_unavailable: case rpc::errc::missing_node_rpc_client: throw ss::httpd::base_exception( fmt::format("Not ready: {}", ec.message()), @@ -1439,17 +1437,20 @@ void admin_server::register_config_routes() { ss::global_logger_registry().set_logger_level(name, new_level); - // expires=0 is same as not specifying it at all - if (expires_v / 1s > 0) { - auto when = ss::timer<>::clock::now() + expires_v; - auto res = _log_level_resets.try_emplace(name, cur_level, when); - if (!res.second) { - res.first->second.expires = when; + auto when = [&]() -> std::optional { + // expires=0 is same as not specifying it at all + if (expires_v / 1s > 0) { + return ss::timer<>::clock::now() + expires_v; + } else { + // new log level never expires, but we still want an entry in + // the resets map as a record of the default + return std::nullopt; } - } else { - // new log level never expires, but we still want an entry in the - // resets map as a record of the default - _log_level_resets.try_emplace(name, cur_level, std::nullopt); + }(); + + auto res = _log_level_resets.try_emplace(name, cur_level, when); + if (!res.second) { + res.first->second.expires = when; } rsp.expiration = expires_v / 1s; @@ -2296,6 +2297,24 @@ admin_server::get_broker_handler(std::unique_ptr req) { co_return ret; } +ss::future +admin_server::get_broker_uuids_handler() { + auto mappings = co_await _controller->get_members_manager().invoke_on( + cluster::controller_stm_shard, [](cluster::members_manager& mm) { + std::vector ret; + const auto& uuid_map = mm.get_id_by_uuid_map(); + ret.reserve(uuid_map.size()); + for (const auto& [uuid, id] : mm.get_id_by_uuid_map()) { + ss::httpd::broker_json::broker_uuid_mapping mapping; + mapping.node_id = id(); + mapping.uuid = ssx::sformat("{}", uuid); + ret.push_back(mapping); + } + return ret; + }); + co_return ss::json::json_return_type(std::move(mappings)); +} + ss::future admin_server::decomission_broker_handler( std::unique_ptr req) { model::node_id id = parse_broker_id(*req); @@ -2457,6 +2476,11 @@ void admin_server::register_broker_routes() { return ss::json::json_return_type(std::move(brokers)); }); }); + register_route( + ss::httpd::broker_json::get_broker_uuids, + [this](std::unique_ptr) { + return get_broker_uuids_handler(); + }); register_route( ss::httpd::broker_json::get_broker, @@ -4018,7 +4042,7 @@ admin_server::delete_cloud_storage_lifecycle( model::initial_revision_id revision; try { revision = model::initial_revision_id( - std::stoi(req->param["revision"])); + std::stoll(req->param["revision"])); } catch (...) { throw ss::httpd::bad_param_exception(fmt::format( "Revision id must be an integer: {}", req->param["revision"])); @@ -4037,13 +4061,13 @@ admin_server::delete_cloud_storage_lifecycle( ss::future admin_server::post_cloud_storage_cache_trim( std::unique_ptr req) { - auto size_limit = get_integer_query_param(*req, "objects"); - auto bytes_limit = static_cast>( + auto max_objects = get_integer_query_param(*req, "objects"); + auto max_bytes = static_cast>( get_integer_query_param(*req, "bytes")); co_await _cloud_storage_cache.invoke_on( - ss::shard_id{0}, [size_limit, bytes_limit](auto& c) { - return c.trim_manually(size_limit, bytes_limit); + ss::shard_id{0}, [max_objects, max_bytes](auto& c) { + return c.trim_manually(max_bytes, max_objects); }); co_return ss::json::json_return_type(ss::json::json_void()); diff --git a/src/v/redpanda/admin/server.h b/src/v/redpanda/admin/server.h index 2c81654bed941..7ddb2f82f5c32 100644 --- a/src/v/redpanda/admin/server.h +++ b/src/v/redpanda/admin/server.h @@ -462,12 +462,14 @@ class admin_server { put_license_handler(std::unique_ptr); /// Broker routes + ss::future get_broker_handler(std::unique_ptr); ss::future decomission_broker_handler(std::unique_ptr); ss::future get_decommission_progress_handler(std::unique_ptr); + ss::future get_broker_uuids_handler(); ss::future recomission_broker_handler(std::unique_ptr); @@ -599,6 +601,10 @@ class admin_server { ss::future sampled_memory_profile_handler(std::unique_ptr); + ss::future get_node_uuid_handler(); + ss::future + override_node_uuid_handler(std::unique_ptr); + // Transform routes ss::future deploy_transform(std::unique_ptr); @@ -625,16 +631,6 @@ class admin_server { : level(level) , expires(expires) {} - friend bool operator<(const level_reset& l, const level_reset& r) { - if (!l.expires.has_value()) { - return false; - } else if (!r.expires.has_value()) { - return true; - } else { - return l.expires.value() < r.expires.value(); - } - } - ss::log_level level; std::optional expires; }; diff --git a/src/v/redpanda/admin/transaction.cc b/src/v/redpanda/admin/transaction.cc index ff220abc46635..b4ddde20a80f3 100644 --- a/src/v/redpanda/admin/transaction.cc +++ b/src/v/redpanda/admin/transaction.cc @@ -13,6 +13,7 @@ #include "redpanda/admin/api-doc/transaction.json.hh" #include "redpanda/admin/server.h" #include "redpanda/admin/util.h" +#include "utils/lw_shared_container.h" #include #include @@ -134,7 +135,7 @@ admin_server::get_all_transactions_handler( } co_return ss::json::json_return_type(ss::json::stream_range_as_array( - admin::lw_shared_container(std::move(ans)), + lw_shared_container(std::move(ans)), [](auto& tx_info) { return tx_info; })); } diff --git a/src/v/redpanda/admin/transform.cc b/src/v/redpanda/admin/transform.cc index 2facac2285d77..64f704467b4e9 100644 --- a/src/v/redpanda/admin/transform.cc +++ b/src/v/redpanda/admin/transform.cc @@ -17,6 +17,7 @@ #include "redpanda/admin/server.h" #include "redpanda/admin/util.h" #include "transform/api.h" +#include "utils/lw_shared_container.h" #include diff --git a/src/v/redpanda/admin/util.h b/src/v/redpanda/admin/util.h index 1b3885a1f1cdd..1cb46e61911b6 100644 --- a/src/v/redpanda/admin/util.h +++ b/src/v/redpanda/admin/util.h @@ -14,23 +14,6 @@ #pragma once namespace admin { - -template -class lw_shared_container { -public: - using iterator = C::iterator; - using value_type = C::value_type; - - explicit lw_shared_container(C&& c) - : c_{ss::make_lw_shared(std::move(c))} {} - - iterator begin() const { return c_->begin(); } - iterator end() const { return c_->end(); } - -private: - ss::lw_shared_ptr c_; -}; - /** * A helper to apply a schema validator to a request and on error, * string-ize any schema errors in the 400 response to help diff --git a/src/v/redpanda/application.cc b/src/v/redpanda/application.cc index d476c1087b1be..80b147588279b 100644 --- a/src/v/redpanda/application.cc +++ b/src/v/redpanda/application.cc @@ -64,6 +64,7 @@ #include "cluster/tx_topic_manager.h" #include "cluster/types.h" #include "compression/async_stream_zstd.h" +#include "compression/lz4_decompression_buffers.h" #include "compression/stream_zstd.h" #include "config/configuration.h" #include "config/endpoint_tls_config.h" @@ -134,6 +135,7 @@ #include #include +#include #include #include @@ -512,6 +514,12 @@ void application::initialize( .get(); _cpu_profiler.invoke_on_all(&resources::cpu_profiler::start).get(); + /* + * Disable the logger for protobuf; some interfaces don't allow a pluggable + * error collector. + */ + google::protobuf::SetLogHandler(nullptr); + /* * allocate per-core zstd decompression workspace and per-core * async_stream_zstd workspaces. it can be several megabytes in size, so do @@ -525,6 +533,11 @@ void application::initialize( compression::initialize_async_stream_zstd( config::shard_local_cfg().zstd_decompress_workspace_bytes()); + + compression::init_lz4_decompression_buffers( + compression::lz4_decompression_buffers::bufsize, + compression::lz4_decompression_buffers::min_threshold, + config::shard_local_cfg().lz4_decompress_reusable_buffers_disabled()); }).get0(); if (config::shard_local_cfg().enable_pid_file()) { @@ -1627,6 +1640,10 @@ void application::wire_up_redpanda_services( ss::sharded_parameter([] { return config::shard_local_cfg() .cloud_storage_cache_max_objects.bind(); + }), + ss::sharded_parameter([] { + return config::shard_local_cfg() + .cloud_storage_cache_trim_walk_concurrency.bind(); })) .get(); @@ -2599,8 +2616,8 @@ void application::start_runtime_services( std::ref(controller->get_feature_table()), std::ref(controller->get_health_monitor()), std::ref(_connection_cache), - std::ref(controller->get_partition_manager()))); - + std::ref(controller->get_partition_manager()), + std::ref(node_status_backend))); runtime_services.push_back( std::make_unique( sched_groups.cluster_sg(), diff --git a/src/v/redpanda/tests/fixture.h b/src/v/redpanda/tests/fixture.h index 15f8444f06b92..a0a4f1a7ee4bb 100644 --- a/src/v/redpanda/tests/fixture.h +++ b/src/v/redpanda/tests/fixture.h @@ -284,13 +284,14 @@ class redpanda_thread_fixture { } static archival::configuration get_archival_config() { - archival::configuration aconf; + archival::configuration aconf{ + .manifest_upload_timeout = config::mock_binding(1000ms), + }; aconf.bucket_name = cloud_storage_clients::bucket_name("test-bucket"); aconf.ntp_metrics_disabled = archival::per_ntp_metrics_disabled::yes; aconf.svc_metrics_disabled = archival::service_metrics_disabled::yes; aconf.cloud_storage_initial_backoff = 100ms; aconf.segment_upload_timeout = 1s; - aconf.manifest_upload_timeout = 1s; aconf.garbage_collect_timeout = 1s; aconf.time_limit = std::nullopt; return aconf; @@ -322,18 +323,7 @@ class redpanda_thread_fixture { std::optional kafka_admin_topic_api_rate = std::nullopt, bool data_transforms_enabled = false) { auto base_path = std::filesystem::path(data_dir); - ss::smp::invoke_on_all([node_id, - kafka_port, - rpc_port, - seed_servers = std::move(seed_servers), - base_path, - s3_config, - archival_cfg, - cloud_cfg, - use_node_id, - empty_seed_starts_cluster_val, - kafka_admin_topic_api_rate, - data_transforms_enabled]() mutable { + ss::smp::invoke_on_all([=]() { auto& config = config::shard_local_cfg(); config.get("enable_pid_file").set_value(false); @@ -378,25 +368,29 @@ class redpanda_thread_fixture { static_cast(s3_config->server_addr.port())); } if (archival_cfg) { + // Copy archival config to this shard to avoid `config::binding` + // asserting on cross-shard access. + auto local_cfg = archival_cfg; + config.get("cloud_storage_disable_tls").set_value(true); config.get("cloud_storage_bucket") - .set_value(std::make_optional(archival_cfg->bucket_name())); + .set_value(std::make_optional(local_cfg->bucket_name())); config.get("cloud_storage_initial_backoff_ms") .set_value( std::chrono::duration_cast( - archival_cfg->cloud_storage_initial_backoff)); + local_cfg->cloud_storage_initial_backoff)); config.get("cloud_storage_manifest_upload_timeout_ms") .set_value( std::chrono::duration_cast( - archival_cfg->manifest_upload_timeout)); + local_cfg->manifest_upload_timeout())); config.get("cloud_storage_segment_upload_timeout_ms") .set_value( std::chrono::duration_cast( - archival_cfg->segment_upload_timeout)); + local_cfg->segment_upload_timeout)); config.get("cloud_storage_garbage_collect_timeout_ms") .set_value( std::chrono::duration_cast( - archival_cfg->garbage_collect_timeout)); + local_cfg->garbage_collect_timeout)); } if (cloud_cfg) { config.get("cloud_storage_enable_remote_read").set_value(true); @@ -782,7 +776,7 @@ class redpanda_thread_fixture { } kafka::request_context - make_request_context(kafka::fetch_request& request, conn_ptr conn = {}) { + make_request_context(kafka::fetch_request&& request, conn_ptr conn = {}) { if (!conn) { conn = make_connection_context(); } @@ -807,7 +801,7 @@ class redpanda_thread_fixture { // do not use incremental fetch requests request.data.max_wait_ms = std::chrono::milliseconds::zero(); - return make_request_context(request); + return make_request_context(std::move(request)); } application app; diff --git a/src/v/resource_mgmt/cpu_profiler.cc b/src/v/resource_mgmt/cpu_profiler.cc index 6ecc8e0719f24..e29ec0533c492 100644 --- a/src/v/resource_mgmt/cpu_profiler.cc +++ b/src/v/resource_mgmt/cpu_profiler.cc @@ -33,6 +33,11 @@ cpu_profiler::cpu_profiler( , _sample_period(std::move(sample_period)) { _enabled.watch([this] { on_enabled_change(); }); _sample_period.watch([this] { on_sample_period_change(); }); + + for (size_t i = 0; i < number_of_results_buffers; i++) { + _results_buffers.emplace_back(0, std::vector{}); + _results_buffers.back().samples.reserve(ss::max_number_of_traces); + } } ss::future<> cpu_profiler::start() { diff --git a/src/v/rpc/errc.h b/src/v/rpc/errc.h index 7d37b89a03221..3ec574cde5995 100644 --- a/src/v/rpc/errc.h +++ b/src/v/rpc/errc.h @@ -26,6 +26,7 @@ enum class errc { version_not_supported, connection_timeout, shutting_down, + service_unavailable, // Used when receiving an undefined errc (e.g. from a newer version of // Redpanda). @@ -46,15 +47,22 @@ struct errc_category final : public std::error_category { return "rpc::errc::missing_node_rpc_client"; case errc::client_request_timeout: return "rpc::errc::client_request_timeout"; + case errc::service_error: + return "rpc::errc::service_error"; + case errc::method_not_found: + return "rpc::errc::method_not_found"; case errc::version_not_supported: return "rpc::errc::version_not_supported"; case errc::connection_timeout: return "rpc::errc::connection_timeout"; case errc::shutting_down: return "rpc::errc::shutting_down"; - default: - return "rpc::errc::unknown(" + std::to_string(c) + ")"; + case errc::service_unavailable: + return "rpc::errc::service_unavailable"; + case errc::unknown: + break; } + return "rpc::errc::unknown(" + std::to_string(c) + ")"; } }; inline const std::error_category& error_category() noexcept { diff --git a/src/v/rpc/test/rpc_gen_cycling_test.cc b/src/v/rpc/test/rpc_gen_cycling_test.cc index 930b22b4d9908..74524692003db 100644 --- a/src/v/rpc/test/rpc_gen_cycling_test.cc +++ b/src/v/rpc/test/rpc_gen_cycling_test.cc @@ -688,7 +688,7 @@ FIXTURE_TEST(missing_method_test, rpc_integration_fixture) { // the server hasn't added all services, we should see a retriable error // instead of method_not_found. server().set_use_service_unavailable(); - verify_bad_method_errors(rpc::errc::exponential_backoff); + verify_bad_method_errors(rpc::errc::service_unavailable); server().set_all_services_added(); verify_bad_method_errors(rpc::errc::method_not_found); diff --git a/src/v/rpc/transport.h b/src/v/rpc/transport.h index a65637827358f..b7bdcfb9a439e 100644 --- a/src/v/rpc/transport.h +++ b/src/v/rpc/transport.h @@ -248,7 +248,7 @@ inline errc map_server_error(status status) { case status::version_not_supported: return errc::version_not_supported; case status::service_unavailable: - return errc::exponential_backoff; + return errc::service_unavailable; default: return errc::unknown; }; diff --git a/src/v/security/audit/audit_log_manager.cc b/src/v/security/audit/audit_log_manager.cc index cda74bf1db334..a852620a85fc0 100644 --- a/src/v/security/audit/audit_log_manager.cc +++ b/src/v/security/audit/audit_log_manager.cc @@ -296,7 +296,9 @@ ss::future<> audit_client::update_status(kafka::error_code errc) { } } else if (_last_errc == kafka::error_code::illegal_sasl_state) { /// The status changed from erraneous to anything else - if (errc != kafka::error_code::illegal_sasl_state) { + if ( + errc != kafka::error_code::illegal_sasl_state + && errc != kafka::error_code::broker_not_available) { co_await _sink->update_auth_status( audit_sink::auth_misconfigured_t::no); } diff --git a/src/v/security/license.cc b/src/v/security/license.cc index 2b507c035e0ba..7a208d90fe51b 100644 --- a/src/v/security/license.cc +++ b/src/v/security/license.cc @@ -20,6 +20,11 @@ #include #include +#include +#include + +using namespace std::chrono_literals; + namespace security { namespace crypto { @@ -223,9 +228,11 @@ license make_license(const ss::sstring& raw_license) { } bool license::is_expired() const noexcept { - const auto now = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - return now > expiry; + return clock::now() > expiration(); +} + +license::clock::time_point license::expiration() const noexcept { + return clock::time_point{expiry}; } } // namespace security diff --git a/src/v/security/license.h b/src/v/security/license.h index ff3af703893f1..898834ed2bd75 100644 --- a/src/v/security/license.h +++ b/src/v/security/license.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,8 @@ inline std::ostream& operator<<(std::ostream& os, license_type lt) { struct license : serde::envelope, serde::compat_version<0>> { + using clock = std::chrono::system_clock; + /// Expected encoded contents uint8_t format_version; license_type type; @@ -81,6 +84,9 @@ struct license /// Seconds since epoch until license expiration std::chrono::seconds expires() const noexcept; + /// Expiration timepoint + clock::time_point expiration() const noexcept; + auto operator<=>(const license&) const = delete; private: diff --git a/src/v/security/tests/license_test.cc b/src/v/security/tests/license_test.cc index 2e726ba7dd8af..2a9dc64511cc3 100644 --- a/src/v/security/tests/license_test.cc +++ b/src/v/security/tests/license_test.cc @@ -13,6 +13,11 @@ #include #include #include + +#include + +using namespace std::chrono_literals; + namespace security { BOOST_AUTO_TEST_CASE(test_license_invalid_signature) { @@ -76,7 +81,10 @@ BOOST_AUTO_TEST_CASE(test_license_valid_content) { BOOST_CHECK_EQUAL(license.format_version, 0); BOOST_CHECK_EQUAL(license.type, license_type::enterprise); BOOST_CHECK_EQUAL(license.organization, "redpanda-testing"); + BOOST_CHECK(!license.is_expired()); BOOST_CHECK_EQUAL(license.expiry.count(), 4813252273); + BOOST_CHECK( + license.expiration() == license::clock::time_point{4813252273s}); BOOST_CHECK_EQUAL( license.checksum, "2730125070a934ca1067ed073d7159acc9975dc61015892308aae186f7455daf"); diff --git a/src/v/ssx/tests/CMakeLists.txt b/src/v/ssx/tests/CMakeLists.txt index 35e4bd14c9e81..38dfb7c1ee5f1 100644 --- a/src/v/ssx/tests/CMakeLists.txt +++ b/src/v/ssx/tests/CMakeLists.txt @@ -21,6 +21,7 @@ rp_test( SOURCES async_algorithm_test.cc work_queue_test.cc + when_all_test.cc LIBRARIES v::gtest_main v::ssx LABELS ssx ) diff --git a/src/v/ssx/tests/when_all_test.cc b/src/v/ssx/tests/when_all_test.cc new file mode 100644 index 0000000000000..43a965d8e2929 --- /dev/null +++ b/src/v/ssx/tests/when_all_test.cc @@ -0,0 +1,198 @@ +// Copyright 2024 Redpanda Data, Inc. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.md +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0 + +#include "seastarx.h" +#include "ssx/when_all.h" +#include "utils/fragmented_vector.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +namespace { + +// A small number of items that are easy to parse visually if something goes +// wrong. +constexpr size_t few_items = 5; + +template +using static_vector = boost::container::static_vector; + +template +Container make_ready_futures(size_t n_items) { + Container futures; + futures.reserve(n_items); + for (size_t i = 0; i < n_items; ++i) { + futures.push_back(ss::make_ready_future(static_cast(i))); + } + return futures; +} + +template +Container make_expected(size_t n_items) { + using value_type = typename Container::value_type; + Container expected; + expected.reserve(n_items); + for (size_t i = 0; i < n_items; ++i) { + expected.push_back(static_cast(i)); + } + return expected; +} + +} // namespace + +TEST(WhenAllAlgorithm, when_all_basic_testing) { + // all futures ready + { + const auto expected_vector = ::make_expected>( + few_items); + auto futures = ::make_ready_futures>>( + few_items); + auto res = ssx::when_all_succeed>(std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + } + + // all futures ready - boost::static_vector + { + const auto expected_vector = ::make_expected>( + few_items); + auto futures = ::make_ready_futures>>( + few_items); + auto res = ssx::when_all_succeed>( + std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + } + + // all futures ready - chunked_vector + { + const auto expected_vector = ::make_expected>( + few_items); + auto futures = ::make_ready_futures>>( + few_items); + auto res = ssx::when_all_succeed>( + std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + } + + // all futures ready - input/output different containers + { + const auto expected_vector = ::make_expected>( + few_items); + auto futures = ::make_ready_futures>>( + few_items); + auto res = ssx::when_all_succeed>( + std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + } + + // all futures ready - input/output different value type + { + const auto expected_vector = ::make_expected>( + few_items); + auto futures = ::make_ready_futures>>( + few_items); + auto res = ssx::when_all_succeed>( + std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + } + + // all futures ready - move_only value_type + { + using StringPtr = std::unique_ptr; + std::vector> futures; + futures.reserve(2); + futures.push_back(seastar::make_ready_future( + std::make_unique("test string 0"))); + futures.push_back(seastar::make_ready_future( + std::make_unique("test string 1"))); + auto res = ssx::when_all_succeed>( + std::move(futures)); + auto resolved = res.get(); + EXPECT_EQ(resolved.size(), 2); + EXPECT_EQ(*resolved[0], "test string 0"); + EXPECT_EQ(*resolved[1], "test string 1"); + } + + // exceptional future + { + std::vector> futures; + futures.reserve(few_items); + futures.push_back(seastar::make_ready_future(0)); + futures.push_back(seastar::make_ready_future(1)); + futures.emplace_back(seastar::make_exception_future( + std::runtime_error{"Test Exception"})); + futures.push_back(seastar::make_ready_future(3)); + futures.push_back(seastar::make_ready_future(4)); + auto res = ssx::when_all_succeed>(std::move(futures)); + EXPECT_THROW(res.get(), std::runtime_error); + } +} + +TEST(WhenAllAlgorithm, when_all_ongoing_futures) { + constexpr auto time_increment = 10ms; + + const std::vector expected_std_vector{0, 1, 2, 3, 4}; + + // wait for futures + { + std::vector> futures; + futures.reserve(few_items); + for (size_t i = 0; i < few_items; ++i) { + auto f + = ss::sleep(i * time_increment).then([i] { + return seastar::make_ready_future(static_cast(i)); + }); + futures.push_back(std::move(f)); + } + auto res = ssx::when_all_succeed>(std::move(futures)); + EXPECT_EQ(res.get(), expected_std_vector); + } +} + +// This test is only meaningfull in release mode +#ifdef NDEBUG + +TEST(WhenAllAlgorithm, when_all_verify_no_large_allocation) { + // Enougn items to fill 2 fragments in a chunked_vector. + // Also, twice the size of the 128kb expected allocation warning threshold. + constexpr size_t many_items = 2UL * 1024UL * 128UL / sizeof(int); + + // Same as the default memory allocation warning threshold + const size_t large_allocation_threshold = 1024UL * 128UL + 1UL; + ss::smp::invoke_on_all([threshold = large_allocation_threshold] { + ss::memory::set_large_allocation_warning_threshold(threshold); + }).get(); + + // validate that a large vector won't generate a warning + { + const auto expected_vector = ::make_expected>( + many_items); + auto futures = ::make_ready_futures>>( + many_items); + auto res = ssx::when_all_succeed>( + std::move(futures)); + EXPECT_EQ(res.get(), expected_vector); + + const ss::memory::statistics mem = ss::memory::stats(); + EXPECT_EQ(mem.large_allocations(), 0); + } +} + +#endif diff --git a/src/v/ssx/when_all.h b/src/v/ssx/when_all.h new file mode 100644 index 0000000000000..1a28c91cf5333 --- /dev/null +++ b/src/v/ssx/when_all.h @@ -0,0 +1,97 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once + +#include "serde/rw/reservable.h" + +#include +#include + +#include +#include + +namespace ssx { + +namespace detail { + +template +concept emplace_backable = requires(Container c, Args&&... args) { + c.emplace_back(std::forward(args)...); +}; + +} // namespace detail + +/// \brief Wait for a range of futures to complete. +/// +/// Given a range of futures as input, wait for all of them +/// to resolve, and return a future containing a range with the +/// resolved values of the original futures. +/// +/// If any future fails, one of the exceptions will be +/// returned as a failed future. +/// +/// \param futures a \c FutureRange containing the futures to wait for. +/// \pre \c futures must be an owning range, i.e. responsible for managing the +/// lifetime of its elements. +/// +/// \return a \c future with all +/// the resolved values of \c futures +template +requires seastar::Future + && std::ranges::input_range + && std::constructible_from< + typename ResolvedContainer::value_type, + typename std::ranges::range_value_t::value_type> + && detail::emplace_backable< + ResolvedContainer, + typename std::ranges::range_value_t::value_type> +seastar::future when_all_succeed(FutureRange futures) { + ResolvedContainer result{}; + + if constexpr (serde::Reservable) { + if constexpr (std::ranges::sized_range) { + result.reserve(std::ranges::size(futures)); + } else if constexpr (std::ranges::forward_range) { + result.reserve(std::ranges::distance(futures)); + } else { + // Do nothing + // Can't estimate the size of the FutureRange + } + } + + std::exception_ptr excp; + + for (auto& fut : futures) { + auto ready_future = co_await seastar::coroutine::as_future( + std::move(fut)); + + if (excp) { + ready_future.ignore_ready_future(); + continue; + } + + if (ready_future.failed()) { + excp = ready_future.get_exception(); + continue; + } + + result.emplace_back(ready_future.get()); + } + + if (excp) { + co_return seastar::coroutine::exception(excp); + } + + co_return result; +} + +} // namespace ssx diff --git a/src/v/storage/disk_log_impl.cc b/src/v/storage/disk_log_impl.cc index be71230680795..226bd883b3396 100644 --- a/src/v/storage/disk_log_impl.cc +++ b/src/v/storage/disk_log_impl.cc @@ -85,6 +85,12 @@ namespace storage { * from normal cluster operation (leadershp movement) and this cost is not * driven / constrained by historical reads. Similarly for transactions and * idempotence. Controller topic should space can be managed by snapshots. + * + * Note on unsafe_enable_consumer_offsets_delete_retention: This a special + * configuration some select users can use to enable retention on CO topic + * because the compaction logic is ineffective and they would like to use + * retention as a stop gap until that is fixed. This configuration will be + * deprecated once we fix the compaction gaps. */ bool deletion_exempt(const model::ntp& ntp) { bool is_internal_namespace = ntp.ns() == model::redpanda_ns @@ -96,7 +102,7 @@ bool deletion_exempt(const model::ntp& ntp) { && ntp.tp.topic == model::kafka_consumer_offsets_nt.tp; return (!is_tx_manager_ntp && is_internal_namespace) - || is_consumer_offsets_ntp; + || (is_consumer_offsets_ntp && !config::shard_local_cfg().unsafe_enable_consumer_offsets_delete_retention()); } disk_log_impl::disk_log_impl( @@ -1761,7 +1767,8 @@ disk_log_impl::make_reader(timequery_config config) { vassert(!_closed, "make_reader on closed log - {}", *this); return _lock_mngr.range_lock(config).then( [this, cfg = config](std::unique_ptr lease) { - auto start_offset = _start_offset; + auto start_offset = cfg.min_offset; + if (!lease->range.empty()) { const ss::lw_shared_ptr& segment = *lease->range.begin(); std::optional index_entry = std::nullopt; @@ -1876,7 +1883,8 @@ disk_log_impl::timequery(timequery_config cfg) { if ( !batches.empty() && batches.front().header().max_timestamp >= cfg.time) { - return ret_t(batch_timequery(batches.front(), cfg.time)); + return ret_t(batch_timequery( + batches.front(), cfg.min_offset, cfg.time, cfg.max_offset)); } return ret_t(); }); @@ -1924,7 +1932,11 @@ ss::future<> disk_log_impl::remove_prefix_full_segments(truncate_prefix_config cfg) { return ss::do_until( [this, cfg] { + // base_offset check is for the case of an empty segment + // (where dirty = base - 1). We don't want to remove it because + // batches may be concurrently appended to it and we should keep them. return _segs.empty() + || _segs.front()->offsets().base_offset >= cfg.start_offset || _segs.front()->offsets().dirty_offset >= cfg.start_offset; }, [this] { @@ -1964,6 +1976,8 @@ ss::future<> disk_log_impl::do_truncate_prefix(truncate_prefix_config cfg) { * whose max offset falls below the new starting offset. */ { + ssx::semaphore_units seg_rewrite_units + = co_await _segment_rewrite_lock.get_units(); auto cache_lock = co_await _readers_cache->evict_prefix_truncate( cfg.start_offset); co_await remove_prefix_full_segments(cfg); diff --git a/src/v/storage/key_offset_map.h b/src/v/storage/key_offset_map.h index 33b061cef01b6..3d6cb2fb81421 100644 --- a/src/v/storage/key_offset_map.h +++ b/src/v/storage/key_offset_map.h @@ -179,7 +179,8 @@ class hash_key_offset_map : public key_offset_map { hash_type::digest_type hash_key(const compaction_key&) const; mutable hash_type hasher_; - large_fragment_vector entries_; + chunked_vector entries_; + size_t size_{0}; model::offset max_offset_; size_t capacity_{0}; diff --git a/src/v/storage/lock_manager.cc b/src/v/storage/lock_manager.cc index 8817b820738c4..8a4fc1355615a 100644 --- a/src/v/storage/lock_manager.cc +++ b/src/v/storage/lock_manager.cc @@ -9,6 +9,7 @@ #include "storage/lock_manager.h" +#include "model/offset_interval.h" #include "storage/segment.h" #include @@ -38,14 +39,27 @@ range(segment_set::underlying_t segs) { ss::future> lock_manager::range_lock(const timequery_config& cfg) { + auto query_interval = model::bounded_offset_interval::checked( + cfg.min_offset, cfg.max_offset); + segment_set::underlying_t tmp; + // Copy segments that have timestamps >= cfg.time and overlap with the + // offset range [min_offset, max_offset]. std::copy_if( _set.lower_bound(cfg.time), _set.end(), std::back_inserter(tmp), - [&cfg](ss::lw_shared_ptr& s) { - // must be base offset - return s->offsets().base_offset <= cfg.max_offset; + [&query_interval](ss::lw_shared_ptr& s) { + if (s->empty()) { + return false; + } + + // Safety: unchecked is safe here because we did already check + // `!s->empty()` above to ensure that the segment has data. + auto segment_interval = model::bounded_offset_interval::unchecked( + s->offsets().base_offset, s->offsets().dirty_offset); + + return segment_interval.overlaps(query_interval); }); return range(std::move(tmp)); } diff --git a/src/v/storage/log_manager.cc b/src/v/storage/log_manager.cc index f43fed29560be..50e24169d1983 100644 --- a/src/v/storage/log_manager.cc +++ b/src/v/storage/log_manager.cc @@ -174,7 +174,7 @@ ss::future<> log_manager::start() { .log_disable_housekeeping_for_tests.value())) { co_return; } - ssx::spawn_with_gate(_open_gate, [this] { return housekeeping(); }); + ssx::spawn_with_gate(_gate, [this] { return housekeeping(); }); co_return; } @@ -182,7 +182,7 @@ ss::future<> log_manager::stop() { _abort_source.request_abort(); _housekeeping_sem.broken(); - co_await _open_gate.close(); + co_await _gate.close(); co_await ss::coroutine::parallel_for_each( _logs, [this](logs_type::value_type& entry) { return clean_close(entry.second->handle); @@ -287,7 +287,7 @@ log_manager::housekeeping_scan(model::timestamp collection_threshold) { } ss::future<> log_manager::housekeeping() { - while (!_open_gate.is_closed()) { + while (!_gate.is_closed()) { try { co_await housekeeping_loop(); } catch (...) { @@ -452,7 +452,7 @@ ss::future> log_manager::make_log_segment( size_t read_buf_size, unsigned read_ahead, record_version_type version) { - auto gate_holder = _open_gate.hold(); + auto gate_holder = _gate.hold(); auto ntp_sanitizer_cfg = _config.maybe_get_ntp_sanitizer_config(ntp.ntp()); @@ -482,7 +482,7 @@ log_manager::create_cache(with_cache ntp_cache_enabled) { } ss::future> log_manager::manage(ntp_config cfg) { - auto gate = _open_gate.hold(); + auto gate = _gate.hold(); auto units = co_await _resources.get_recovery_units(); co_return co_await do_manage(std::move(cfg)); @@ -554,7 +554,7 @@ ss::future> log_manager::do_manage(ntp_config cfg) { ss::future<> log_manager::shutdown(model::ntp ntp) { vlog(stlog.debug, "Asked to shutdown: {}", ntp); - auto gate = _open_gate.hold(); + auto gate = _gate.hold(); auto handle = _logs.extract(ntp); if (handle.empty()) { co_return; @@ -565,7 +565,7 @@ ss::future<> log_manager::shutdown(model::ntp ntp) { ss::future<> log_manager::remove(model::ntp ntp) { vlog(stlog.info, "Asked to remove: {}", ntp); - auto g = _open_gate.hold(); + auto g = _gate.hold(); auto handle = _logs.extract(ntp); _resources.update_partition_count(_logs.size()); if (handle.empty()) { @@ -652,12 +652,16 @@ ss::future<> log_manager::remove_orphan_files( absl::flat_hash_set namespaces, ss::noncopyable_function orphan_filter) { + auto holder = _gate.hold(); auto data_directory_exist = co_await ss::file_exists(data_directory_path); if (!data_directory_exist) { co_return; } for (const auto& ns : namespaces) { + if (_gate.is_closed()) { + co_return; + } auto namespace_directory = std::filesystem::path(data_directory_path) / std::filesystem::path(ss::sstring(ns)); auto namespace_directory_exist = co_await ss::file_exists( @@ -824,7 +828,7 @@ void log_manager::handle_disk_notification(storage::disk_space_alert alert) { } void log_manager::trigger_gc() { - ssx::spawn_with_gate(_open_gate, [this] { + ssx::spawn_with_gate(_gate, [this] { return ss::sleep_abortable( _trigger_gc_jitter.next_duration(), _abort_source) .then([this] { diff --git a/src/v/storage/log_manager.h b/src/v/storage/log_manager.h index 830821c3a0e4b..4c545349c685a 100644 --- a/src/v/storage/log_manager.h +++ b/src/v/storage/log_manager.h @@ -279,7 +279,7 @@ class log_manager { // Hash key-map to use across multiple compactions to reuse reserved memory // rather than reallocating repeatedly. std::unique_ptr _compaction_hash_key_map; - ss::gate _open_gate; + ss::gate _gate; ss::abort_source _abort_source; friend std::ostream& operator<<(std::ostream&, const log_manager&); diff --git a/src/v/storage/log_reader.cc b/src/v/storage/log_reader.cc index 77b2200a6d4e8..c6ea753637e77 100644 --- a/src/v/storage/log_reader.cc +++ b/src/v/storage/log_reader.cc @@ -10,6 +10,8 @@ #include "storage/log_reader.h" #include "bytes/iobuf.h" +#include "model/fundamental.h" +#include "model/offset_interval.h" #include "model/record.h" #include "storage/logger.h" #include "storage/parser_errc.h" @@ -22,7 +24,10 @@ #include +#include + namespace storage { + using records_t = ss::circular_buffer; batch_consumer::consume_result skipping_consumer::accept_batch_start( @@ -276,88 +281,98 @@ ss::future<> log_reader::find_next_valid_iterator() { ss::future log_reader::do_load_slice(model::timeout_clock::time_point timeout) { - if (is_done()) { - // must keep this function because, the segment might not be done - // but offsets might have exceeded the read - set_end_of_stream(); - return _iterator.close().then( - [] { return ss::make_ready_future(); }); - } - if (_last_base == _config.start_offset) { - set_end_of_stream(); - return _iterator.close().then( - [] { return ss::make_ready_future(); }); - } - /** - * We do not want to close the reader if we stopped because requested range - * was read. This way we make it possible to reset configuration and reuse - * underlying file input stream. - */ - if ( - _config.start_offset > _config.max_offset - || _config.bytes_consumed > _config.max_bytes || _config.over_budget) { - set_end_of_stream(); - return ss::make_ready_future(); - } - _last_base = _config.start_offset; - ss::future<> fut = find_next_valid_iterator(); - if (is_end_of_stream()) { - return fut.then([] { return ss::make_ready_future(); }); - } - return fut - .then([this, timeout] { return _iterator.reader->read_some(timeout); }) - .then([this, timeout](result recs) -> ss::future { - if (!recs) { - set_end_of_stream(); - - if (!_lease->range.empty()) { - // Readers do not know their ntp directly: discover - // it by checking the segments in our lease - auto seg_ptr = *(_lease->range.begin()); - vlog( - stlog.info, - "stopped reading stream[{}]: {}", - seg_ptr->path().get_ntp(), - recs.error().message()); - } else { - // Leases should always have a segment, but this is - // not a strict invariant at present, so handle the - // empty case. - vlog( - stlog.info, - "stopped reading stream: {}", - recs.error().message()); - } - - auto const batch_parse_err - = recs.error() == parser_errc::header_only_crc_missmatch - || recs.error() == parser_errc::input_stream_not_enough_bytes; + while (true) { + if (is_done()) { + // must keep this function because, the segment might not be done + // but offsets might have exceeded the read + set_end_of_stream(); + co_await _iterator.close(); + co_return log_reader::storage_t{}; + } + if (_last_base == _config.start_offset) { + set_end_of_stream(); + co_await _iterator.close(); + co_return log_reader::storage_t{}; + } + /** + * We do not want to close the reader if we stopped because requested + * range was read. This way we make it possible to reset configuration + * and reuse underlying file input stream. + */ + if ( + _config.start_offset > _config.max_offset + || _config.bytes_consumed > _config.max_bytes + || _config.over_budget) { + set_end_of_stream(); + co_return log_reader::storage_t{}; + } + _last_base = _config.start_offset; + ss::future<> fut = find_next_valid_iterator(); + if (is_end_of_stream()) { + co_await std::move(fut); + co_return log_reader::storage_t{}; + } + std::exception_ptr e; + try { + co_await std::move(fut); + auto recs = co_await _iterator.reader->read_some(timeout); + if (!recs) { + set_end_of_stream(); + + if (!_lease->range.empty()) { + // Readers do not know their ntp directly: discover + // it by checking the segments in our lease + auto seg_ptr = *(_lease->range.begin()); + vlog( + stlog.info, + "stopped reading stream[{}]: {}", + seg_ptr->path().get_ntp(), + recs.error().message()); + } else { + // Leases should always have a segment, but this is + // not a strict invariant at present, so handle the + // empty case. + vlog( + stlog.info, + "stopped reading stream: {}", + recs.error().message()); + } + + auto const batch_parse_err + = recs.error() == parser_errc::header_only_crc_missmatch + || recs.error() + == parser_errc::input_stream_not_enough_bytes; + + if (batch_parse_err) { + _probe.batch_parse_error(); + } + co_await _iterator.close(); + co_return log_reader::storage_t{}; + } + if (recs.value().empty()) { + /* + * if no records are returned it may be the case that all of the + * batches in the segment were skipped (e.g. all control + * batches). thus, returning no records does not imply end of + * stream. instead, we continue which will advance the iterator + * and check end of stream. + */ + continue; + } + _probe.add_batches_read(recs.value().size()); - if (batch_parse_err) { - _probe.batch_parse_error(); - } - return _iterator.close().then( - [] { return ss::make_ready_future(); }); - } - if (recs.value().empty()) { - /* - * if no records are returned it may be the case that all of the - * batches in the segment were skipped (e.g. all control batches). - * thus, returning no records does not imply end of stream. - * instead, we recurse which will advance the iterator and check - * end of stream. - */ - return do_load_slice(timeout); - } - _probe.add_batches_read(recs.value().size()); - return ss::make_ready_future(std::move(recs.value())); - }) - .handle_exception([this](std::exception_ptr e) { - set_end_of_stream(); - _probe.batch_parse_error(); - return _iterator.close().then( - [e] { return ss::make_exception_future(e); }); - }); + auto& batches = recs.value(); + co_return std::move(batches); + } catch (...) { + e = std::current_exception(); + set_end_of_stream(); + _probe.batch_parse_error(); + } + // Non-exceptional cases should have continued or early-returned above. + vassert(e, "Expected exception"); + co_await _iterator.close(); + std::rethrow_exception(e); + } } static inline bool is_finished_offset(segment_set& s, model::offset o) { @@ -378,23 +393,28 @@ bool log_reader::is_done() { || is_finished_offset(_lease->range, _config.start_offset); } -timequery_result -batch_timequery(const model::record_batch& b, model::timestamp t) { +timequery_result batch_timequery( + const model::record_batch& b, + model::offset min_offset, + model::timestamp t, + model::offset max_offset) { + auto query_interval = model::bounded_offset_interval::checked( + min_offset, max_offset); + // If the timestamp matches something mid-batch, then // parse into the batch far enough to find it: this // happens when we had CreateTime input, such that // records in the batch have different timestamps. model::offset result_o = b.base_offset(); model::timestamp result_t = b.header().first_timestamp; - if (b.header().first_timestamp < t && !b.compressed()) { + if (!b.compressed()) { b.for_each_record( - [&result_o, &result_t, t, &b]( + [&result_o, &result_t, &b, query_interval, t]( const model::record& r) -> ss::stop_iteration { + auto record_o = model::offset{r.offset_delta()} + b.base_offset(); auto record_t = model::timestamp( b.header().first_timestamp() + r.timestamp_delta()); - if (record_t >= t) { - auto record_o = model::offset{r.offset_delta()} - + b.base_offset(); + if (record_t >= t && query_interval.contains(record_o)) { result_o = record_o; result_t = record_t; return ss::stop_iteration::yes; diff --git a/src/v/storage/log_reader.h b/src/v/storage/log_reader.h index e8af777da7f09..c7e0af449754c 100644 --- a/src/v/storage/log_reader.h +++ b/src/v/storage/log_reader.h @@ -238,14 +238,33 @@ class log_reader final : public model::record_batch_reader::impl { /** * Assuming caller has already determined that this batch contains - * the record that should be the result to the timequery, traverse - * the batch to find which record matches. + * the record that should be the result to the timequery (critical!), + * traverse the batch to find the record with with timestamp >= \ref t. + * + * The min and max offsets are used to limit the search to a specific + * range inside the batch. This is necessary to support the case where + * log was requested to be prefix-truncated (trim-prefix) to an offset + * which lies in the middle of a batch. + * + * If the preconditions aren't met, the result is the timestamp of the first + * record in the batch. * * This is used by both storage's disk_log_impl and by cloud_storage's * remote_partition, to seek to their final result after finding * the batch. + * + * To read more about trim-prefix: + * https://docs.redpanda.com/current/reference/rpk/rpk-topic/rpk-topic-trim-prefix/ + * + * \param b The batch to search in. + * \param min_offset The minimum offset to consider + * \param t The timestamp to search for + * \param max_offset The maximum offset to consider */ -timequery_result -batch_timequery(const model::record_batch& b, model::timestamp t); +timequery_result batch_timequery( + const model::record_batch& b, + model::offset min_offset, + model::timestamp t, + model::offset max_offset); } // namespace storage diff --git a/src/v/storage/offset_translator_state.cc b/src/v/storage/offset_translator_state.cc index e5093f4418945..c7c11305b5787 100644 --- a/src/v/storage/offset_translator_state.cc +++ b/src/v/storage/offset_translator_state.cc @@ -13,6 +13,7 @@ #include "model/fundamental.h" #include "storage/logger.h" +#include "utils/fragmented_vector.h" #include "vassert.h" #include "vlog.h" @@ -348,7 +349,7 @@ struct persisted_batches_map serde::version<0>, serde::compat_version<0>> { int64_t start_delta = 0; - std::vector batches; + chunked_vector batches; }; } // namespace @@ -359,7 +360,7 @@ iobuf offset_translator_state::serialize_map() const { "ntp {}: offsets map shouldn't be empty", _ntp); - std::vector batches; + chunked_vector batches; batches.reserve(_last_offset2batch.size()); for (const auto& [o, b] : _last_offset2batch) { int32_t length = int32_t(o - b.base_offset) + 1; diff --git a/src/v/storage/tests/segment_deduplication_test.cc b/src/v/storage/tests/segment_deduplication_test.cc index 58a1e06e9c507..31671b3182252 100644 --- a/src/v/storage/tests/segment_deduplication_test.cc +++ b/src/v/storage/tests/segment_deduplication_test.cc @@ -7,6 +7,7 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0 +#include "config/configuration.h" #include "gmock/gmock.h" #include "random/generators.h" #include "storage/chunk_cache.h" @@ -158,6 +159,17 @@ TEST(FindSlidingRangeTest, TestCollectOneRecordSegments) { } TEST(BuildOffsetMap, TestBuildSimpleMap) { + ss::smp::invoke_on_all([] { + config::shard_local_cfg().disable_metrics.set_value(true); + config::shard_local_cfg().disable_public_metrics.set_value(true); + }).get(); + auto defer_config_reset = ss::defer([] { + ss::smp::invoke_on_all([] { + config::shard_local_cfg().disable_metrics.reset(); + config::shard_local_cfg().disable_public_metrics.reset(); + }).get(); + }); + storage::disk_log_builder b; build_segments(b, 3); auto cleanup = ss::defer([&] { b.stop().get(); }); diff --git a/src/v/storage/tests/storage_e2e_test.cc b/src/v/storage/tests/storage_e2e_test.cc index 20e0255fe6073..cbdbf68ebd530 100644 --- a/src/v/storage/tests/storage_e2e_test.cc +++ b/src/v/storage/tests/storage_e2e_test.cc @@ -283,10 +283,20 @@ FIXTURE_TEST(test_reading_range_from_a_log, storage_test_fixture) { BOOST_REQUIRE_EQUAL(range.size(), 5); BOOST_REQUIRE_EQUAL(range.front().header().crc, batches[3].header().crc); BOOST_REQUIRE_EQUAL(range.back().header().crc, batches[7].header().crc); - // range from base of beging to the middle of end + + // Range that starts and ends in the middle of the same batch. + range = read_range_to_vector( + log, + batches[3].base_offset() + model::offset(batches[3].record_count() / 3), + batches[3].base_offset() + + model::offset(batches[3].record_count() / 3 * 2LL)); + BOOST_REQUIRE_EQUAL(range.size(), 1); + BOOST_REQUIRE_EQUAL(range.front().header().crc, batches[3].header().crc); + + // Range that starts and ends in the middle of batches. range = read_range_to_vector( log, - batches[3].base_offset(), + batches[3].base_offset() + model::offset(batches[3].record_count() / 2), batches[7].base_offset() + model::offset(batches[7].record_count() / 2)); BOOST_REQUIRE_EQUAL(range.size(), 5); BOOST_REQUIRE_EQUAL(range.front().header().crc, batches[3].header().crc); diff --git a/src/v/storage/tests/storage_test_fixture.h b/src/v/storage/tests/storage_test_fixture.h index 968596e54a5ef..7b92651c23404 100644 --- a/src/v/storage/tests/storage_test_fixture.h +++ b/src/v/storage/tests/storage_test_fixture.h @@ -202,9 +202,14 @@ class storage_test_fixture { resources, feature_table) { configure_unit_test_logging(); - // avoid double metric registrations + // avoid double metric registrations - disk_log_builder and other + // helpers also start a feature_table and other structs that register + // metrics ss::smp::invoke_on_all([] { config::shard_local_cfg().get("disable_metrics").set_value(true); + config::shard_local_cfg() + .get("disable_public_metrics") + .set_value(true); config::shard_local_cfg() .get("log_segment_size_min") .set_value(std::optional{}); @@ -223,6 +228,7 @@ class storage_test_fixture { feature_table.stop().get(); ss::smp::invoke_on_all([] { config::shard_local_cfg().get("disable_metrics").reset(); + config::shard_local_cfg().get("disable_public_metrics").reset(); config::shard_local_cfg().get("log_segment_size_min").reset(); }).get(); } diff --git a/src/v/storage/tests/timequery_test.cc b/src/v/storage/tests/timequery_test.cc index 4c2a95d9415eb..69c7fa526eeb7 100644 --- a/src/v/storage/tests/timequery_test.cc +++ b/src/v/storage/tests/timequery_test.cc @@ -8,17 +8,24 @@ // by the Apache License, Version 2.0 #include "config/configuration.h" +#include "model/fundamental.h" #include "model/tests/random_batch.h" +#include "model/timestamp.h" #include "storage/tests/disk_log_builder_fixture.h" #include "test_utils/fixture.h" #include +#include + namespace { // Make a batch that is big enough to trigger the indexing threshold. -model::record_batch -make_random_batch(model::offset o, bool big_enough_for_index = true) { +model::record_batch make_random_batch( + model::offset o, + model::timestamp ts, + int num_records = 1, + bool big_enough_for_index = true) { auto batch_size = storage::segment_index::default_data_buffer_step + 1; if (!big_enough_for_index) { batch_size = 1024; @@ -26,11 +33,11 @@ make_random_batch(model::offset o, bool big_enough_for_index = true) { return model::test::make_random_batch( model::offset(o), - 1, + num_records, false, model::record_batch_type::raft_data, - std::vector{batch_size}, - std::nullopt); + std::vector(num_records, batch_size), + ts); } } // namespace @@ -43,23 +50,20 @@ FIXTURE_TEST(timequery, log_builder_fixture) { // seg0: timestamps 0..99, offset = timestamp b | add_segment(0); for (auto ts = 0; ts < 100; ts++) { - auto batch = make_random_batch(model::offset(ts)); - batch.header().first_timestamp = model::timestamp(ts); - batch.header().max_timestamp = model::timestamp(ts); + auto batch = make_random_batch(model::offset(ts), model::timestamp(ts)); b | add_batch(std::move(batch)); } // seg1: [(offset, ts)..] // - (100, 100), (101, 100), ... (104, 100) - // - (105, 101), (105, 101), ... (109, 101) + // - (105, 101), (106, 101), ... (109, 101) // ... // - (195, 119), (196, 119), ... (200, 119) b | add_segment(100); for (auto offset = 100; offset <= 200; offset++) { auto ts = 100 + (offset - 100) / 5; - auto batch = make_random_batch(model::offset(offset)); - batch.header().first_timestamp = model::timestamp(ts); - batch.header().max_timestamp = model::timestamp(ts); + auto batch = make_random_batch( + model::offset(offset), model::timestamp(ts)); b | add_batch(std::move(batch)); } @@ -67,11 +71,35 @@ FIXTURE_TEST(timequery, log_builder_fixture) { BOOST_TEST(seg->index().batch_timestamps_are_monotonic()); } + BOOST_TEST_CONTEXT( + "undershoot the timestamp but keep increasing the start offset") { + auto log = b.get_log(); + for (auto start_offset = log->offsets().start_offset; + start_offset < model::offset(10); + start_offset++) { + BOOST_TEST_INFO_SCOPE( + fmt::format("start_offset: {}", start_offset)); + + storage::timequery_config config( + start_offset, + model::timestamp(0), + log->offsets().dirty_offset, + ss::default_priority_class(), + std::nullopt); + + auto res = log->timequery(config).get0(); + BOOST_TEST(res); + BOOST_TEST(res->time == model::timestamp(start_offset)); + BOOST_TEST(res->offset == start_offset); + } + } + // in the first segment check that query(ts) -> batch.offset = ts. for (auto ts = 0; ts < 100; ts++) { auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(ts), log->offsets().dirty_offset, ss::default_priority_class(), @@ -94,6 +122,7 @@ FIXTURE_TEST(timequery, log_builder_fixture) { auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(ts), log->offsets().dirty_offset, ss::default_priority_class(), @@ -110,6 +139,68 @@ FIXTURE_TEST(timequery, log_builder_fixture) { b | stop(); } +FIXTURE_TEST(timequery_multiple_messages_per_batch, log_builder_fixture) { + using namespace storage; // NOLINT + + b | start(); + + b | add_segment(0); + + int num_batches = 10; + int records_per_batch = 10; + + // Half share the same timestamp. + for (auto ts = 0; ts < num_batches * records_per_batch / 2; + ts += records_per_batch) { + b + | add_batch( + model::test::make_random_batch(model::test::record_batch_spec{ + .offset = model::offset(ts), + // It is sad but we can't properly query for timestamps inside + // compressed batches. + .allow_compression = false, + .count = records_per_batch, + .timestamp = model::timestamp(ts), + .all_records_have_same_timestamp = true, + })); + } + + // Half have different timestamps. + for (auto ts = num_batches * records_per_batch / 2; + ts < num_batches * records_per_batch; + ts += records_per_batch) { + auto batch = make_random_batch( + model::offset(ts), model::timestamp(ts), records_per_batch); + b | add_batch(std::move(batch)); + } + + for (const auto& seg : b.get_log_segments()) { + BOOST_TEST(seg->index().batch_timestamps_are_monotonic()); + } + + auto log = b.get_log(); + + for (auto start_offset = log->offsets().start_offset; + start_offset < model::offset(num_batches * records_per_batch); + start_offset++) { + BOOST_TEST_INFO_SCOPE(fmt::format("start_offset: {}", start_offset)); + + storage::timequery_config config( + start_offset, + model::timestamp(0), + log->offsets().dirty_offset, + ss::default_priority_class(), + std::nullopt); + + auto res = log->timequery(config).get0(); + BOOST_TEST(res); + BOOST_TEST(res->time == model::timestamp(start_offset)); + BOOST_TEST(res->offset == start_offset); + } + + b | stop(); +} + FIXTURE_TEST(timequery_single_value, log_builder_fixture) { using namespace storage; // NOLINT @@ -118,15 +209,15 @@ FIXTURE_TEST(timequery_single_value, log_builder_fixture) { // seg0: timestamps [1000...1099], offsets = [0...99] b | add_segment(0); for (auto offset = 0; offset < 100; ++offset) { - auto batch = make_random_batch(model::offset(offset)); - batch.header().first_timestamp = model::timestamp(offset + 1000); - batch.header().max_timestamp = model::timestamp(offset + 1000); + auto batch = make_random_batch( + model::offset(offset), model::timestamp(offset + 1000)); b | add_batch(std::move(batch)); } // ask for time greater than last timestamp f.e 1200 auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(1200), log->offsets().dirty_offset, ss::default_priority_class(), @@ -151,20 +242,15 @@ FIXTURE_TEST(timequery_sparse_index, log_builder_fixture) { b | start(); b | add_segment(0); - auto batch1 = make_random_batch(model::offset(0)); - batch1.header().first_timestamp = model::timestamp(1000); - batch1.header().max_timestamp = model::timestamp(1000); + auto batch1 = make_random_batch(model::offset(0), model::timestamp(1000)); b | add_batch(std::move(batch1)); // This batch will not be indexed. - auto batch2 = make_random_batch(model::offset(1), false); - batch2.header().first_timestamp = model::timestamp(1600); - batch2.header().max_timestamp = model::timestamp(1600); + auto batch2 = make_random_batch( + model::offset(1), model::timestamp(1600), 1, false); b | add_batch(std::move(batch2)); - auto batch3 = make_random_batch(model::offset(2)); - batch3.header().first_timestamp = model::timestamp(2000); - batch3.header().max_timestamp = model::timestamp(2000); + auto batch3 = make_random_batch(model::offset(2), model::timestamp(2000)); b | add_batch(std::move(batch3)); const auto& seg = b.get_log_segments().front(); @@ -173,6 +259,7 @@ FIXTURE_TEST(timequery_sparse_index, log_builder_fixture) { auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(1600), log->offsets().dirty_offset, ss::default_priority_class(), @@ -195,9 +282,8 @@ FIXTURE_TEST(timequery_one_element_index, log_builder_fixture) { // This batch doesn't trigger the size indexing threshold, // but it's the first one so it gets indexed regardless. - auto batch = make_random_batch(model::offset(0), false); - batch.header().first_timestamp = model::timestamp(1000); - batch.header().max_timestamp = model::timestamp(1000); + auto batch = make_random_batch( + model::offset(0), model::timestamp(1000), 1, false); b | add_batch(std::move(batch)); const auto& seg = b.get_log_segments().front(); @@ -206,6 +292,7 @@ FIXTURE_TEST(timequery_one_element_index, log_builder_fixture) { auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(1000), log->offsets().dirty_offset, ss::default_priority_class(), @@ -242,9 +329,7 @@ FIXTURE_TEST(timequery_non_monotonic_log, log_builder_fixture) { b | add_segment(0); for (const auto& [offset, ts] : batch_spec) { - auto batch = make_random_batch(model::offset(offset)); - batch.header().first_timestamp = model::timestamp(ts); - batch.header().max_timestamp = model::timestamp(ts); + auto batch = make_random_batch(offset, ts); b | add_batch(std::move(batch)); } @@ -255,6 +340,7 @@ FIXTURE_TEST(timequery_non_monotonic_log, log_builder_fixture) { auto log = b.get_log(); for (const auto& [offset, ts] : batch_spec) { storage::timequery_config config( + log->offsets().start_offset, model::timestamp(ts), log->offsets().dirty_offset, ss::default_priority_class(), @@ -280,6 +366,7 @@ FIXTURE_TEST(timequery_non_monotonic_log, log_builder_fixture) { // Query for a bogus, really small timestamp. // We should return the first element in the log storage::timequery_config config( + log->offsets().start_offset, model::timestamp(-5000), log->offsets().dirty_offset, ss::default_priority_class(), @@ -310,9 +397,7 @@ FIXTURE_TEST(timequery_clamp, log_builder_fixture) { b | add_segment(0); for (const auto& [offset, ts] : batch_spec) { - auto batch = make_random_batch(model::offset(offset)); - batch.header().first_timestamp = model::timestamp(ts); - batch.header().max_timestamp = model::timestamp(ts); + auto batch = make_random_batch(offset, ts); b | add_batch(std::move(batch)); } @@ -322,6 +407,7 @@ FIXTURE_TEST(timequery_clamp, log_builder_fixture) { auto log = b.get_log(); storage::timequery_config config( + log->offsets().start_offset, model::timestamp(storage::offset_time_index::delta_time_max * 2 + 1), log->offsets().dirty_offset, ss::default_priority_class(), diff --git a/src/v/storage/types.cc b/src/v/storage/types.cc index 2685515f2994c..a13342b2dd8c7 100644 --- a/src/v/storage/types.cc +++ b/src/v/storage/types.cc @@ -87,8 +87,8 @@ std::ostream& operator<<(std::ostream& o, const timequery_result& a) { return o << "{offset:" << a.offset << ", time:" << a.time << "}"; } std::ostream& operator<<(std::ostream& o, const timequery_config& a) { - o << "{max_offset:" << a.max_offset << ", time:" << a.time - << ", type_filter:"; + o << "{min_offset: " << a.min_offset << ", max_offset: " << a.max_offset + << ", time:" << a.time << ", type_filter:"; if (a.type_filter) { o << *a.type_filter; } else { diff --git a/src/v/storage/types.h b/src/v/storage/types.h index de10308fc0b76..565369ffa20da 100644 --- a/src/v/storage/types.h +++ b/src/v/storage/types.h @@ -226,20 +226,25 @@ using opt_abort_source_t using opt_client_address_t = std::optional; +/// A timequery configuration specifies the range of offsets to search for a +/// record with a timestamp equal to or greater than the specified time. struct timequery_config { timequery_config( + model::offset min_offset, model::timestamp t, - model::offset o, + model::offset max_offset, ss::io_priority_class iop, std::optional type_filter, opt_abort_source_t as = std::nullopt, opt_client_address_t client_addr = std::nullopt) noexcept - : time(t) - , max_offset(o) + : min_offset(min_offset) + , time(t) + , max_offset(max_offset) , prio(iop) , type_filter(type_filter) , abort_source(as) , client_address(std::move(client_addr)) {} + model::offset min_offset; model::timestamp time; model::offset max_offset; ss::io_priority_class prio; @@ -289,6 +294,8 @@ struct truncate_prefix_config { operator<<(std::ostream&, const truncate_prefix_config&); }; +using translate_offsets = ss::bool_class; + /** * Log reader configuration. * @@ -300,7 +307,21 @@ struct truncate_prefix_config { * search when the size of the filter set is small (e.g. < 5). If you need to * use a larger filter then this design should be revisited. * - * Start and max offset are inclusive. + * Start and max offset are inclusive. Because the reader only looks at batch + * headers the first batch may start before the start offset and the last batch + * may end after the max offset. + * + * Consider the following case: + * + * cfg = {start offset = 14, max offset = 17} + * + + + * v v + * //-------+-------------+------------+-------------+-------// + * \\...9 | 10...14 | 15..15 | 16.....22 | 23...\\ + * //-------+-------------+------------+-------------+-------// + * ^ ^ + * | | + * The reader will actually return whole batches: [10, 14], [15, 15], [16, 22]. */ struct log_reader_config { model::offset start_offset; diff --git a/src/v/utils/CMakeLists.txt b/src/v/utils/CMakeLists.txt index 641f4bc76f326..b4cd62c670d84 100644 --- a/src/v/utils/CMakeLists.txt +++ b/src/v/utils/CMakeLists.txt @@ -1,5 +1,6 @@ find_package(Hdrhistogram) find_package(Base64) +find_package(unordered_dense REQUIRED) v_cc_library( NAME utils SRCS @@ -21,6 +22,7 @@ v_cc_library( aklomp::base64 absl::hash absl::random_seed_sequences + unordered_dense::unordered_dense v::bytes v::rphashing v::json) diff --git a/src/v/utils/adjustable_semaphore.h b/src/v/utils/adjustable_semaphore.h index b99053ffbaa75..2063b04648c7d 100644 --- a/src/v/utils/adjustable_semaphore.h +++ b/src/v/utils/adjustable_semaphore.h @@ -90,6 +90,14 @@ class adjustable_semaphore { return ss::get_units(_sem, units, as); } + /** + * Attempts to immediately get units from the semaphore, returning + * std::nullopt if no units exist. + */ + std::optional try_get_units(size_t units) { + return ss::try_get_units(_sem, units); + } + size_t current() const noexcept { return _sem.current(); } ssize_t available_units() const noexcept { return _sem.available_units(); } diff --git a/src/v/utils/base64.cc b/src/v/utils/base64.cc index ba8c6a30d747f..be2b1d08fea0b 100644 --- a/src/v/utils/base64.cc +++ b/src/v/utils/base64.cc @@ -110,3 +110,22 @@ ss::sstring iobuf_to_base64(const iobuf& input) { output.resize(written); return output; } + +iobuf base64_to_iobuf(const iobuf& buf) { + base64_state state{}; + base64_stream_decode_init(&state, 0); + iobuf out; + for (const details::io_fragment& frag : buf) { + iobuf::fragment out_frag{frag.size()}; + size_t written{}; + if ( + 1 + != base64_stream_decode( + &state, frag.get(), frag.size(), out_frag.get_write(), &written)) { + throw base64_decoder_exception{}; + } + out_frag.reserve(written); + out.append(std::move(out_frag).release()); + } + return out; +} diff --git a/src/v/utils/base64.h b/src/v/utils/base64.h index fde69875dc633..f5386bbeece69 100644 --- a/src/v/utils/base64.h +++ b/src/v/utils/base64.h @@ -29,5 +29,8 @@ ss::sstring bytes_to_base64(bytes_view); // base64 -> string ss::sstring base64_to_string(std::string_view); +// base64 -> iobuf +iobuf base64_to_iobuf(const iobuf&); + // base64 <-> iobuf ss::sstring iobuf_to_base64(const iobuf&); diff --git a/src/v/utils/chunked_hash_map.h b/src/v/utils/chunked_hash_map.h new file mode 100644 index 0000000000000..c19494b59c199 --- /dev/null +++ b/src/v/utils/chunked_hash_map.h @@ -0,0 +1,72 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ +#pragma once + +#include +#include +#include + +#include + +namespace detail { + +template +concept has_absl_hash = requires(T val) { + { AbslHashValue(std::declval(), val) }; +}; + +/// Wrapper around absl::Hash that disables the extra hash mixing in +/// unordered_dense +template +struct avalanching_absl_hash { + // absl always hash mixes itself so no need to do it again + using is_avalanching = void; + + auto operator()(const T& x) const noexcept -> uint64_t { + return absl::Hash()(x); + } +}; + +} // namespace detail + +/** + * @brief A hash map that uses a chunked vector as the underlying storage. + * + * Use when the hash map is expected to have a large number of elements (e.g.: + * scales with partitions or topics). Performance wise it's equal to the abseil + * hashmaps. + * + * NB: References and iterators are not stable across insertions and deletions. + * + * Both std::hash and abseil's AbslHashValue are supported. We dispatch to the + * latter if available. Given AbslHashValue also supports std::hash we could + * also unconditionally dispatch to it. However, absl's hash mixing seems more + * extensive (and hence less performant) so we only do that when needed. + * + * For more info please see + * https://github.com/martinus/unordered_dense/?tab=readme-ov-file#1-overview + */ +template< + typename Key, + typename Value, + typename Hash = std::conditional_t< + detail::has_absl_hash, + detail::avalanching_absl_hash, + ankerl::unordered_dense::hash>, + typename EqualTo = std::equal_to> +using chunked_hash_map = ankerl::unordered_dense::segmented_map< + Key, + Value, + Hash, + EqualTo, + chunked_vector>, + ankerl::unordered_dense::bucket_type::standard, + chunked_vector>; diff --git a/src/v/utils/exceptions.h b/src/v/utils/exceptions.h new file mode 100644 index 0000000000000..a70b8dd549bd5 --- /dev/null +++ b/src/v/utils/exceptions.h @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ +#pragma once + +#include "seastarx.h" + +#include + +#include + +/// Some objects reference state that changes comparatively rarely (e.g. +/// topic_table state) across yield points and expect these references to remain +/// valid. In case these references are invalidated by a concurrent fiber, this +/// exception is thrown. This is a signal for the caller to restart the +/// computation with up-to-date state. +class concurrent_modification_error : public std::exception { +public: + explicit concurrent_modification_error(ss::sstring s) + : _msg(std::move(s)) {} + + const char* what() const noexcept override { return _msg.c_str(); } + +private: + ss::sstring _msg; +}; diff --git a/src/v/utils/file_io.cc b/src/v/utils/file_io.cc index 9d9b164839abe..8d0e8821908f0 100644 --- a/src/v/utils/file_io.cc +++ b/src/v/utils/file_io.cc @@ -14,6 +14,7 @@ #include "bytes/iobuf.h" #include "bytes/iostream.h" +#include #include #include #include @@ -57,17 +58,11 @@ ss::future<> write_fully(const std::filesystem::path& p, iobuf buf) { | ss::open_flags::truncate; /// Closes file on failure, otherwise file is expected to be closed in the /// success case where the ss::output_stream calls close() - return ss::with_file_close_on_failure( - ss::open_file_dma(p.string(), flags), - [buf = std::move(buf)](ss::file f) mutable { - return ss::make_file_output_stream(std::move(f), buf_size) - .then([buf = std::move(buf)](ss::output_stream out) mutable { - return ss::do_with( - std::move(out), - [buf = std::move(buf)](ss::output_stream& out) mutable { - return write_iobuf_to_output_stream(std::move(buf), out) - .then([&out]() mutable { return out.close(); }); - }); - }); + auto out = co_await ss::with_file_close_on_failure( + ss::open_file_dma(p.string(), flags), [](ss::file f) { + return ss::make_file_output_stream(std::move(f), buf_size); }); + co_await write_iobuf_to_output_stream(std::move(buf), out).finally([&out] { + return out.close(); + }); } diff --git a/src/v/utils/fragmented_vector.h b/src/v/utils/fragmented_vector.h index 00adc802eb720..de41f7c8e57a9 100644 --- a/src/v/utils/fragmented_vector.h +++ b/src/v/utils/fragmented_vector.h @@ -70,10 +70,15 @@ class fragmented_vector { public: using this_type = fragmented_vector; + using backing_type = std::vector>; using value_type = T; using reference = T&; using const_reference = const T&; using size_type = size_t; + using allocator_type = backing_type::allocator_type; + using difference_type = backing_type::difference_type; + using pointer = T*; + using const_pointer = const T*; /** * The maximum number of bytes per fragment as specified in @@ -89,6 +94,8 @@ class fragmented_vector { "max size of a fragment must be <= 128KiB"); fragmented_vector() noexcept = default; + explicit fragmented_vector(allocator_type alloc) + : _frags(alloc) {} fragmented_vector& operator=(const fragmented_vector&) noexcept = delete; fragmented_vector(fragmented_vector&& other) noexcept { *this = std::move(other); @@ -139,6 +146,8 @@ class fragmented_vector { fragmented_vector copy() const noexcept { return *this; } + auto get_allocator() const { return _frags.get_allocator(); } + void swap(fragmented_vector& other) noexcept { std::swap(_size, other._size); std::swap(_capacity, other._capacity); @@ -522,7 +531,7 @@ class fragmented_vector { size_t _size{0}; size_t _capacity{0}; - std::vector> _frags; + backing_type _frags; #ifndef NDEBUG // Add a generation number that is incremented on every mutation to catch // invalidated iterator accesses. diff --git a/src/v/utils/json.h b/src/v/utils/json.h new file mode 100644 index 0000000000000..526f36bbd80d4 --- /dev/null +++ b/src/v/utils/json.h @@ -0,0 +1,28 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ +#pragma once + +#include "json/json.h" +#include "utils/fragmented_vector.h" + +namespace json { + +template +void rjson_serialize( + json::Writer& w, const fragmented_vector& v) { + w.StartArray(); + for (const auto& e : v) { + rjson_serialize(w, e); + } + w.EndArray(); +} + +} // namespace json diff --git a/src/v/utils/lw_shared_container.h b/src/v/utils/lw_shared_container.h new file mode 100644 index 0000000000000..49866e98bff78 --- /dev/null +++ b/src/v/utils/lw_shared_container.h @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "seastarx.h" + +#include + +#pragma once + +template +class lw_shared_container { +public: + using iterator = C::const_iterator; + using value_type = C::value_type; + + explicit lw_shared_container(C&& c) + : c_{ss::make_lw_shared(std::move(c))} {} + + iterator begin() const { return c_->begin(); } + iterator end() const { return c_->end(); } + +private: + ss::lw_shared_ptr c_; +}; diff --git a/src/v/utils/stable_iterator_adaptor.h b/src/v/utils/stable_iterator_adaptor.h index 424bcb82fd32b..fe39fbdbc8785 100644 --- a/src/v/utils/stable_iterator_adaptor.h +++ b/src/v/utils/stable_iterator_adaptor.h @@ -11,20 +11,20 @@ #pragma once #include "seastarx.h" +#include "utils/exceptions.h" #include #include #include -#include -#include #include -class iterator_stability_violation : public std::runtime_error { +class iterator_stability_violation final + : public concurrent_modification_error { public: - explicit iterator_stability_violation(const std::string& why) - : std::runtime_error(why){}; + explicit iterator_stability_violation(ss::sstring why) + : concurrent_modification_error(std::move(why)){}; }; /* diff --git a/src/v/utils/tests/CMakeLists.txt b/src/v/utils/tests/CMakeLists.txt index b0924b2f864e1..0ef4643408aac 100644 --- a/src/v/utils/tests/CMakeLists.txt +++ b/src/v/utils/tests/CMakeLists.txt @@ -63,6 +63,7 @@ rp_test( BINARY_NAME utils_gunit SOURCES fragmented_vector_test.cc + chunked_hash_map_test.cc contiguous_range_map_test.cc LIBRARIES v::utils v::gtest_main LABELS utils diff --git a/src/v/utils/tests/base64_test.cc b/src/v/utils/tests/base64_test.cc index 65015f68e1be3..0958cd0d8129c 100644 --- a/src/v/utils/tests/base64_test.cc +++ b/src/v/utils/tests/base64_test.cc @@ -7,6 +7,7 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0 +#include "bytes/iobuf_parser.h" #include "random/generators.h" #include "utils/base64.h" @@ -48,3 +49,17 @@ BOOST_AUTO_TEST_CASE(iobuf_type) { auto decoded = base64_to_bytes(encoded); BOOST_REQUIRE_EQUAL(decoded, iobuf_to_bytes(buf)); } + +BOOST_AUTO_TEST_CASE(test_base64_to_iobuf) { + const std::string_view a_string = "dGhpcyBpcyBhIHN0cmluZw=="; + iobuf buf; + const size_t half = a_string.size() / 2; + buf.append_fragments(iobuf::from(a_string.substr(0, half))); + buf.append_fragments(iobuf::from(a_string.substr(half))); + BOOST_REQUIRE_EQUAL(std::distance(buf.begin(), buf.end()), 2); + + auto decoded = base64_to_iobuf(buf); + iobuf_parser p{std::move(decoded)}; + auto decoded_str = p.read_string(p.bytes_left()); + BOOST_REQUIRE_EQUAL(decoded_str, "this is a string"); +} diff --git a/src/v/utils/tests/chunked_hash_map_test.cc b/src/v/utils/tests/chunked_hash_map_test.cc new file mode 100644 index 0000000000000..5da7c2d7495b2 --- /dev/null +++ b/src/v/utils/tests/chunked_hash_map_test.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "utils/chunked_hash_map.h" + +struct foo_with_std_hash { + int a; + int b; + auto operator<=>(const foo_with_std_hash&) const = default; +}; + +namespace std { + +template<> +struct hash { + constexpr size_t operator()(const foo_with_std_hash& x) const { + return std::hash()(x.a + x.b); + } +}; + +} // namespace std + +struct foo_with_absl_hash { + int a; + int b; + + auto operator<=>(const foo_with_absl_hash&) const = default; + + template + friend H AbslHashValue(H h, const foo_with_absl_hash& x) { + return H::combine(std::move(h), x.a, x.b); + } +}; + +TEST(chunked_hash_map, basic_compile_std_hash) { + chunked_hash_map map; + map[{1, 2}] = 2; + EXPECT_EQ(map.size(), 1); +} + +TEST(chunked_hash_map, basic_compile_absl_hash) { + static_assert(detail::has_absl_hash); + chunked_hash_map map; + map[{1, 2}] = 2; + EXPECT_EQ(map.size(), 1); +} + +TEST(chunked_hash_map, test_move_assignment) { + chunked_hash_map map; + chunked_hash_map other_map; + other_map = std::move(map); +} diff --git a/src/v/utils/uuid.cc b/src/v/utils/uuid.cc index ef1267f1ef893..ac652cf6cf13a 100644 --- a/src/v/utils/uuid.cc +++ b/src/v/utils/uuid.cc @@ -10,7 +10,10 @@ #include "bytes/details/out_of_range.h" +#include + #include +#include #include #include @@ -29,6 +32,12 @@ uuid_t uuid_t::create() { return uuid_t(uuid_gen()); } +uuid_t uuid_t::from_string(std::string_view str) { + static thread_local ::boost::uuids::string_generator gen; + + return uuid_t(gen(str.begin(), str.end())); +} + std::ostream& operator<<(std::ostream& os, const uuid_t& u) { return os << fmt::format("{}", u._uuid); } diff --git a/src/v/utils/uuid.h b/src/v/utils/uuid.h index 08997ee9c4779..2fe2f0d4c9318 100644 --- a/src/v/utils/uuid.h +++ b/src/v/utils/uuid.h @@ -15,6 +15,8 @@ #include #include +#include +#include #include // Wrapper around Boost's UUID type suitable for serialization with serde. @@ -53,6 +55,8 @@ class uuid_t { underlying_t& mutable_uuid() { return _uuid; } + static uuid_t from_string(std::string_view); + private: explicit uuid_t(const underlying_t& uuid) : _uuid(uuid) {} diff --git a/src/v/wasm/parser/parser.cc b/src/v/wasm/parser/parser.cc index 4f650c16917af..2b962766850b2 100644 --- a/src/v/wasm/parser/parser.cc +++ b/src/v/wasm/parser/parser.cc @@ -162,6 +162,15 @@ val_type parse_val_type(iobuf_parser_base* parser) { } } +val_type parse_ref_type(iobuf_parser_base* parser) { + auto reftype = parse_val_type(parser); + if (reftype != val_type::externref && reftype != val_type::funcref) { + throw parse_exception( + fmt::format("invalid reftype: {}", uint8_t(reftype))); + } + return reftype; +} + ss::sstring parse_name(iobuf_parser_base* parser) { auto str_len = leb128::decode(parser); if (str_len > max_name_length) { @@ -191,8 +200,8 @@ parse_signature_types(iobuf_parser_base* parser, size_t max) { function_signature parse_signature(iobuf_parser_base* parser) { auto magic = parser->consume_type(); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) - if (magic != 0x60) { + constexpr uint8_t signature_start_magic_byte = 0x60; + if (magic != signature_start_magic_byte) { throw parse_exception( fmt::format("function type magic mismatch: {}", magic)); } @@ -215,11 +224,7 @@ declaration::limits parse_limits(iobuf_parser_base* parser) { } declaration::table parse_table_type(iobuf_parser_base* parser) { - auto reftype = parse_val_type(parser); - if (reftype != val_type::externref && reftype != val_type::funcref) { - throw parse_exception( - fmt::format("invalid tabletype type: {}", uint8_t(reftype))); - } + auto reftype = parse_ref_type(parser); auto limits = parse_limits(parser); return {.reftype = reftype, .limits = limits}; } @@ -234,6 +239,45 @@ declaration::global parse_global_type(iobuf_parser_base* parser) { return {.valtype = valtype, .is_mutable = bool(mut)}; } +void skip_global_constexpr(iobuf_parser_base* parser) { + enum class op : uint8_t { + global_get = 0x23, + i32_const = 0x41, + i64_const = 0x42, + f32_const = 0x43, + f64_const = 0x44, + ref_null = 0xD0, + }; + auto opcode = parser->consume_type(); + switch (opcode) { + case op::global_get: + case op::i32_const: + std::ignore = leb128::decode(parser); + break; + case op::i64_const: + std::ignore = leb128::decode(parser); + break; + case op::f32_const: + std::ignore = parser->consume_type(); + break; + case op::f64_const: + std::ignore = parser->consume_type(); + break; + case op::ref_null: + std::ignore = parse_ref_type(parser); + break; + default: + throw parse_exception(fmt::format( + "unimplemented global opcode: {}", static_cast(opcode))); + } + auto end = parser->consume_type(); + constexpr uint8_t end_expression_marker = 0x0B; + if (end != end_expression_marker) { + throw parse_exception( + fmt::format("expected end of global initalizer, got: {}", end)); + } +} + class module_extractor { public: explicit module_extractor(iobuf_parser_base* parser) @@ -298,38 +342,52 @@ class module_extractor { } // The size of this section auto size = leb128::decode(_parser); - // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) - switch (id) { - case 0x00: // Custom section + enum class section : uint8_t { + custom = 0x00, + type = 0x01, + import = 0x02, + function = 0x03, + table = 0x04, + memory = 0x05, + global = 0x06, + exprt = 0x07, // export + start = 0x08, + element = 0x09, + data_count = 0x0C, + code = 0x0A, + data = 0x0B, + }; + switch (static_cast
(id)) { + case section::custom: // Skip over custom sections _parser->skip(size); break; - case 0x01: // type section + case section::type: parse_signature_section(); break; - case 0x02: // import section + case section::import: parse_import_section(); break; - case 0x03: // function section + case section::function: parse_function_decl_section(); break; - case 0x04: // table section + case section::table: parse_table_section(); break; - case 0x05: // memory section + case section::memory: parse_memories_section(); break; - case 0x06: // global section + case section::global: parse_globals_section(); break; - case 0x07: // export section + case section::exprt: parse_export_section(); break; - case 0x08: // start section - case 0x09: // element section - case 0x0C: // data count section - case 0x0A: // code section - case 0x0B: // data section + case section::start: + case section::element: + case section::data_count: + case section::code: + case section::data: // Since we don't need the information from these sections at the // moment, we can skip them. _parser->skip(size); @@ -337,7 +395,6 @@ class module_extractor { default: throw parse_exception(fmt::format("unknown section id: {}", id)); } - // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) } void parse_signature_section() { @@ -457,21 +514,8 @@ class module_extractor { _globals.reserve(vector_size); for (uint32_t i = 0; i < vector_size; ++i) { _globals.push_back(parse_global_type(_parser)); - // We currently don't care about the global constexpr, so just skip - // it (the end is delimited by 0x0B) - skip_global_constexpr(); - } - } - - void skip_global_constexpr() { - constexpr int max_constexpr_bytes = 64; - for (int i = 0; i < max_constexpr_bytes; ++i) { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) - if (_parser->consume_type() == 0x0b) { - return; - } + skip_global_constexpr(_parser); } - throw parse_exception("unexpectedly large global constexpr"); } void parse_export_section() { diff --git a/src/v/wasm/parser/tests/parser_test.cc b/src/v/wasm/parser/tests/parser_test.cc index 2b93a58cd043f..62b0c81a33d14 100644 --- a/src/v/wasm/parser/tests/parser_test.cc +++ b/src/v/wasm/parser/tests/parser_test.cc @@ -48,7 +48,10 @@ struct test_data { std::vector imports; }; -void PrintTo(const test_data& d, std::ostream* os) { *os << d.wat; } +void PrintTo(const test_data& d, std::ostream* os) { + constexpr size_t max_len = 128; + *os << d.wat.substr(0, std::min(d.wat.size(), max_len)); +} class ParserTest : public testing::TestWithParam {}; @@ -364,6 +367,41 @@ INSTANTIATE_TEST_SUITE_P( (memory 1) (data (i32.const 0) "a") ) +)WAT", + .exports = {}, + .imports = {}, + }, + { + .wat = R"WAT( +(module + (global (;0;) (mut i32) (i32.const 306464)) + (global (;1;) i32 (i32.const 0)) + (global (;2;) i32 (i32.const 239492)) + (global (;3;) i32 (i32.const 448)) + (global (;4;) i32 (i32.const 1)) + (global (;5;) i32 (i32.const 193520)) + (global (;6;) i32 (i32.const 449)) + (global (;7;) i32 (i32.const 234008)) + (global (;8;) i32 (i32.const 233968)) + (global (;9;) i32 (i32.const 233868)) + + (global $a i32 (i32.const -2)) + (global (;3;) f32 (f32.const -3)) + (global (;4;) f64 (f64.const -4)) + (global $b i64 (i64.const -5)) + + (global $x (mut i32) (i32.const -12)) + (global (;7;) (mut f32) (f32.const -13)) + (global (;8;) (mut f64) (f64.const -14)) + (global $y (mut i64) (i64.const -15)) + + (global $z1 i32 (global.get 0)) + (global $z2 i64 (global.get 1)) + + (global $r externref (ref.null extern)) + (global $mr (mut externref) (ref.null extern)) + (global funcref (ref.null func)) +) )WAT", .exports = {}, .imports = {}, diff --git a/src/v/wasm/schema_registry.cc b/src/v/wasm/schema_registry.cc index c86f06e0cac8e..5db5aa11eeae6 100644 --- a/src/v/wasm/schema_registry.cc +++ b/src/v/wasm/schema_registry.cc @@ -49,7 +49,7 @@ class schema_registry_impl : public schema_registry { create_schema(ppsr::unparsed_schema schema) override { auto [reader, writer] = co_await service(); co_await writer->read_sync(); - auto parsed = co_await reader->make_canonical_schema(schema); + auto parsed = co_await reader->make_canonical_schema(std::move(schema)); co_return co_await writer->write_subject_version( {.schema = std::move(parsed)}); } diff --git a/src/v/wasm/schema_registry_module.cc b/src/v/wasm/schema_registry_module.cc index f6408b2c648d6..27af3a7ac6b3b 100644 --- a/src/v/wasm/schema_registry_module.cc +++ b/src/v/wasm/schema_registry_module.cc @@ -196,9 +196,8 @@ ss::future schema_registry_module::create_subject_schema( ffi::reader r(buf); using namespace pandaproxy::schema_registry; try { - auto unparsed = read_encoded_schema_def(&r); *out_schema_id = co_await _sr->create_schema( - unparsed_schema(sub, unparsed)); + unparsed_schema(sub, read_encoded_schema_def(&r))); } catch (const std::exception& ex) { vlog(wasm_log.warn, "error registering subject schema: {}", ex); co_return SCHEMA_REGISTRY_ERROR; diff --git a/src/v/wasm/tests/wasm_fixture.cc b/src/v/wasm/tests/wasm_fixture.cc index beb743935ce5d..8e3eee3b79b92 100644 --- a/src/v/wasm/tests/wasm_fixture.cc +++ b/src/v/wasm/tests/wasm_fixture.cc @@ -51,7 +51,7 @@ class fake_schema_registry : public wasm::schema_registry { get_schema_definition(ppsr::schema_id id) const override { for (const auto& s : _schemas) { if (s.id == id) { - co_return s.schema.def(); + co_return s.schema.def().share(); } } throw std::runtime_error("unknown schema id"); @@ -70,9 +70,9 @@ class fake_schema_registry : public wasm::schema_registry { if (found && found->version > s.version) { continue; } - found = s; + found.emplace(s.share()); } - co_return found.value(); + co_return std::move(found).value(); } ss::future @@ -90,13 +90,15 @@ class fake_schema_registry : public wasm::schema_registry { } } // TODO: validate references too + auto [sub, unparsed_def] = std::move(unparsed).destructure(); + auto [def, type, refs] = std::move(unparsed_def).destructure(); _schemas.push_back({ .schema = ppsr::canonical_schema( - unparsed.sub(), + std::move(sub), ppsr::canonical_schema_definition( - unparsed.def().raw(), - unparsed.def().type(), - unparsed.def().refs())), + ppsr::canonical_schema_definition::raw_string{std::move(def)()}, + type, + std::move(refs))), .version = version + 1, .id = ppsr::schema_id(int32_t(_schemas.size() + 1)), .deleted = ppsr::is_deleted::no, @@ -104,7 +106,7 @@ class fake_schema_registry : public wasm::schema_registry { co_return _schemas.back().id; } - std::vector get_all() { return _schemas; } + const std::vector& get_all() { return _schemas; } private: std::vector _schemas; @@ -209,7 +211,7 @@ model::record_batch WasmTestFixture::make_tiny_batch(iobuf record_value) { b.add_raw_kv(model::test::make_iobuf(), std::move(record_value)); return std::move(b).build(); } -std::vector +const std::vector& WasmTestFixture::registered_schemas() const { return _sr->get_all(); } diff --git a/src/v/wasm/tests/wasm_fixture.h b/src/v/wasm/tests/wasm_fixture.h index 85e9e32e80962..f411447625bbd 100644 --- a/src/v/wasm/tests/wasm_fixture.h +++ b/src/v/wasm/tests/wasm_fixture.h @@ -60,7 +60,7 @@ class WasmTestFixture : public ::testing::Test { wasm::engine* engine() { return _engine.get(); } - std::vector + const std::vector& registered_schemas() const; private: diff --git a/src/v/wasm/tests/wasm_transform_test.cc b/src/v/wasm/tests/wasm_transform_test.cc index 2f09a81ec53f1..c8bad67b1da9d 100644 --- a/src/v/wasm/tests/wasm_transform_test.cc +++ b/src/v/wasm/tests/wasm_transform_test.cc @@ -10,6 +10,7 @@ */ #include "bytes/bytes.h" +#include "bytes/streambuf.h" #include "pandaproxy/schema_registry/types.h" #include "test_utils/fixture.h" #include "test_utils/test.h" @@ -57,6 +58,9 @@ TEST_F(WasmTestFixture, HandlesTransformPanic) { TEST_F(WasmTestFixture, HandlesTransformErrors) { load_wasm("transform-error.wasm"); EXPECT_THROW(transform(make_tiny_batch()), wasm::wasm_exception); + engine()->stop().get(); + engine()->start().get(); + EXPECT_THROW(transform(make_tiny_batch()), wasm::wasm_exception); } namespace { @@ -64,7 +68,9 @@ std::string generate_example_avro_record( const pandaproxy::schema_registry::canonical_schema_definition& def) { // Generate a simple avro record that looks like this (as json): // {"a":5,"b":"foo"} - auto schema = avro::compileJsonSchemaFromString(def.raw()().c_str()); + iobuf_istream bis{def.shared_raw()}; + auto is = avro::istreamInputStream(bis.istream()); + auto schema = avro::compileJsonSchemaFromStream(*is); avro::GenericRecord r(schema.root()); r.setFieldAt(r.fieldIndex("a"), int64_t(4)); r.setFieldAt(r.fieldIndex("b"), std::string("foo")); @@ -82,7 +88,7 @@ std::string generate_example_avro_record( TEST_F(WasmTestFixture, SchemaRegistry) { // Test an example schema registry encoded avro value -> JSON transform load_wasm("schema-registry.wasm"); - auto schemas = registered_schemas(); + const auto& schemas = registered_schemas(); ASSERT_EQ(schemas.size(), 1); ASSERT_EQ(schemas[0].id, 1); iobuf record_value; diff --git a/src/v/wasm/transform_module.cc b/src/v/wasm/transform_module.cc index acbfd096409c4..6d8b19cbc2a7f 100644 --- a/src/v/wasm/transform_module.cc +++ b/src/v/wasm/transform_module.cc @@ -86,10 +86,9 @@ transform_module::for_each_record_async( .record_callback = std::move(cb), }); - co_await host_wait_for_proccessing(); - - auto result = std::exchange(_call_ctx, std::nullopt); - co_return std::move(result->output_data); + return host_wait_for_proccessing() + .then([this] { return std::move(_call_ctx->output_data); }) + .finally([this] { _call_ctx = std::nullopt; }); } void transform_module::check_abi_version_1() { diff --git a/src/v/wasm/wasmtime.cc b/src/v/wasm/wasmtime.cc index 5359d43dc0205..8a939afc064af 100644 --- a/src/v/wasm/wasmtime.cc +++ b/src/v/wasm/wasmtime.cc @@ -799,37 +799,6 @@ struct host_function { handle functype{ wasm_functype_new(&inputs, &outputs)}; - if (ssc.enabled()) { - if constexpr (ss::is_future::value) { - handle error( - wasmtime_linker_define_async_func( - linker, - Module::name.data(), - Module::name.size(), - function_name.data(), - function_name.size(), - functype.get(), - &invoke_async_host_fn_with_strict_stack_checking, - /*data=*/ssc.allocator, - /*finalizer=*/nullptr)); - check_error(error.get()); - } else { - handle error( - wasmtime_linker_define_func( - linker, - Module::name.data(), - Module::name.size(), - function_name.data(), - function_name.size(), - functype.get(), - &invoke_sync_host_fn_with_strict_stack_checking, - /*data=*/ssc.allocator, - /*finalizer=*/nullptr)); - check_error(error.get()); - } - return; - } - if constexpr (ss::is_future::value) { handle error( wasmtime_linker_define_async_func( @@ -839,8 +808,9 @@ struct host_function { function_name.data(), function_name.size(), functype.get(), - &invoke_async_host_fn, - /*data=*/nullptr, + ssc.enabled() ? &invoke_async_host_fn_with_strict_stack_checking + : &invoke_async_host_fn, + /*data=*/ssc.allocator, /*finalizer=*/nullptr)); check_error(error.get()); } else { @@ -852,8 +822,9 @@ struct host_function { function_name.data(), function_name.size(), functype.get(), - &invoke_sync_host_fn, - /*data=*/nullptr, + ssc.enabled() ? &invoke_sync_host_fn_with_strict_stack_checking + : &invoke_sync_host_fn, + /*data=*/ssc.allocator, /*finalizer=*/nullptr)); check_error(error.get()); } @@ -994,23 +965,31 @@ struct host_function { memory* mem, std::span args, std::span results) { - auto raw = to_raw_values(args); - auto host_params = ffi::extract_parameters(mem, raw, 0); - using FutureType = typename ReturnType::value_type; - if constexpr (std::is_void_v) { - return ss::futurize_apply( - module_func, - std::tuple_cat( - std::make_tuple(host_module), std::move(host_params))); - } else { - return ss::futurize_apply( - module_func, - std::tuple_cat( - std::make_tuple(host_module), std::move(host_params))) - .then([results](FutureType host_future_result) { - results[0] = convert_to_wasmtime( - host_future_result); - }); + try { + auto raw = to_raw_values(args); + auto host_params = ffi::extract_parameters( + mem, raw, 0); + using FutureType = typename ReturnType::value_type; + if constexpr (std::is_void_v) { + return std::apply( + module_func, + std::tuple_cat( + std::make_tuple(host_module), std::move(host_params))); + } else { + return std::apply( + module_func, + std::tuple_cat( + std::make_tuple(host_module), + std::move(host_params))) + .then([results](FutureType host_future_result) { + // This is safe to write too because wasmtime ensures the + // result is kept alive until the future completes. + results[0] = convert_to_wasmtime( + host_future_result); + }); + } + } catch (...) { + return ss::current_exception_as_future(); } } diff --git a/tests/docker/ducktape-deps/ocsf-server b/tests/docker/ducktape-deps/ocsf-server index e550b1b55ec72..7ab3da1252902 100644 --- a/tests/docker/ducktape-deps/ocsf-server +++ b/tests/docker/ducktape-deps/ocsf-server @@ -33,6 +33,10 @@ if [ $(uname -m) = "aarch64" ]; then fi ./build_server.sh + +# remove added repository for other packages stability +add-apt-repository -ry ppa:rabbitmq/rabbitmq-erlang + # The following is required because the server attempts to write logs # to the `dist/tmp` directory. This is fine in docker ducktape as the user # who kicks it off is root, but in CDT, the user is not root so the server diff --git a/tests/java/e2e-verifiers/src/main/java/org/apache/kafka/tools/VerifiableConsumer.java b/tests/java/e2e-verifiers/src/main/java/org/apache/kafka/tools/VerifiableConsumer.java index 7c2bbfebd0649..74259e5494178 100644 --- a/tests/java/e2e-verifiers/src/main/java/org/apache/kafka/tools/VerifiableConsumer.java +++ b/tests/java/e2e-verifiers/src/main/java/org/apache/kafka/tools/VerifiableConsumer.java @@ -162,10 +162,12 @@ private boolean isFinished() { if (partitionRecords.isEmpty()) continue; long minOffset = partitionRecords.get(0).offset(); - long maxOffset - = partitionRecords.get(partitionRecords.size() - 1).offset(); + var maxRecord = partitionRecords.get(partitionRecords.size() - 1); + long maxOffset = maxRecord.offset(); - offsets.put(tp, new OffsetAndMetadata(maxOffset + 1)); + offsets.put( + tp, + new OffsetAndMetadata(maxOffset + 1, maxRecord.leaderEpoch(), "")); summaries.add(new RecordSetSummary( tp.topic(), tp.partition(), partitionRecords.size(), minOffset, maxOffset)); diff --git a/tests/rptest/archival/s3_client.py b/tests/rptest/archival/s3_client.py index bc5046f302250..2c2777a0844d2 100644 --- a/tests/rptest/archival/s3_client.py +++ b/tests/rptest/archival/s3_client.py @@ -1,4 +1,5 @@ import threading +import logging from rptest.archival.shared_client_utils import key_to_topic @@ -75,9 +76,43 @@ def __init__(self, else: self._signature_version = signature_version self._before_call_headers = before_call_headers + self.logger = logger + self.update_boto3_loggers() self._cli = self.make_client() self.register_custom_events() - self.logger = logger + + def update_boto3_loggers(self): + """Configure loggers related to boto3 to emit messages + with FileHandlers similar to ones from ducktape + using same filenames for corresponding log levels + + loggers updated: boto3, botocore + + loggers list that can be included can be found in ticket: PESDLC-876 + + """ + def populate_handler(filename, level): + # If something really need debugging, add 'urllib3' + loggers_list = ['boto3', 'botocore'] + # get logger, configure it and set handlers + for logger_name in loggers_list: + l = logging.getLogger(logger_name) + l.setLevel(level) + handler = logging.FileHandler(filename) + fmt = logging.Formatter('[%(levelname)-5s - %(asctime)s - ' + f'{logger_name} - %(module)s - ' + '%(funcName)s - lineno:%(lineno)s]: ' + '%(message)s') + handler.setFormatter(fmt) + l.addHandler(handler) + + # Extract info from ducktape loggers + # Assume that there is only one DEBUG and one INFO handler + # + for h in self.logger.handlers: + if isinstance(h, logging.FileHandler): + if h.level == logging.INFO or h.level == logging.DEBUG: + populate_handler(h.baseFilename, h.level) def make_client(self): cfg = Config(region_name=self._region, diff --git a/tests/rptest/clients/offline_log_viewer.py b/tests/rptest/clients/offline_log_viewer.py index 75df420b9b014..1754f6ed414fa 100644 --- a/tests/rptest/clients/offline_log_viewer.py +++ b/tests/rptest/clients/offline_log_viewer.py @@ -37,6 +37,9 @@ def _json_cmd(self, node, suffix): self._redpanda.logger.error(f"Invalid JSON output: {json_out}") raise + def read_kafka_records(self, node, topic): + return self._json_cmd(node, f"--type kafka_records --topic {topic}") + def read_controller(self, node): return self._json_cmd(node, "--type controller") diff --git a/tests/rptest/clients/rpk.py b/tests/rptest/clients/rpk.py index da8a0c140d235..f088a22a73928 100644 --- a/tests/rptest/clients/rpk.py +++ b/tests/rptest/clients/rpk.py @@ -355,14 +355,15 @@ def _check_stdout_success(self, output): if not status_line.endswith("OK"): raise RpkException(f"Bad status: '{status_line}'") - def sasl_allow_principal(self, - principal, - operations, - resource, - resource_name, - username: Optional[str] = None, - password: Optional[str] = None, - mechanism: Optional[str] = None): + def _sasl_set_principal_access(self, + principal, + operations, + resource, + resource_name, + username: Optional[str] = None, + password: Optional[str] = None, + mechanism: Optional[str] = None, + deny=False): username = username if username is not None else self._username password = password if password is not None else self._password @@ -377,14 +378,22 @@ def sasl_allow_principal(self, else: raise Exception(f"unknown resource: {resource}") + perm = '--allow-principal' if not deny else '--deny-principal' + cmd = [ - "acl", "create", "--allow-principal", principal, "--operation", + "acl", "create", perm, principal, "--operation", ",".join(operations), resource, resource_name, "--brokers", self._redpanda.brokers(), "--user", username, "--password", password, "--sasl-mechanism", mechanism ] + self._tls_settings() return self._run(cmd) + def sasl_allow_principal(self, *args, **kwargs): + return self._sasl_set_principal_access(*args, **kwargs, deny=False) + + def sasl_deny_principal(self, *args, **kwargs): + return self._sasl_set_principal_access(*args, **kwargs, deny=True) + def allow_principal(self, principal, operations, resource, resource_name): if resource == "topic": resource = "--topic" @@ -470,10 +479,11 @@ def sasl_create_user_basic_mix(self, return self._run(cmd) - def sasl_update_user(self, user, new_password): + def sasl_update_user(self, user, new_password, new_mechanism): cmd = [ "acl", "user", "update", user, "--new-password", new_password, - "-X", "admin.hosts=" + self._redpanda.admin_endpoints() + "--mechanism", new_mechanism, "-X", + "admin.hosts=" + self._redpanda.admin_endpoints() ] return self._run(cmd) @@ -1090,7 +1100,6 @@ def _execute(self, cmd, stdin=None, timeout=None, log_cmd=True, env=None): self._redpanda.logger.debug(f'\n{output}') if p.returncode: - self._redpanda.logger.error(stderror) raise RpkException( 'command %s returned %d, output: %s' % (' '.join(cmd) if log_cmd else '[redacted]', p.returncode, @@ -1392,13 +1401,18 @@ def try_offset_delete(retries=5): # Retry if NOT_COORDINATOR is observed when command exits 1 return try_offset_delete(retries=5) - def generate_grafana(self, dashboard): - + def generate_grafana(self, dashboard, datasource="", metrics_endpoint=""): cmd = [ self._rpk_binary(), "generate", "grafana-dashboard", "--dashboard", - dashboard + dashboard, "-X", "admin.hosts=" + self._redpanda.admin_endpoints() ] + if datasource != "": + cmd += ["--datasource", datasource] + + if metrics_endpoint != "": + cmd += ["--metrics-endpoint", metrics_endpoint] + return self._execute(cmd) def describe_log_dirs(self): diff --git a/tests/rptest/redpanda_cloud_tests/high_throughput_test.py b/tests/rptest/redpanda_cloud_tests/high_throughput_test.py index e4d5cf01b078e..9dcba262975db 100644 --- a/tests/rptest/redpanda_cloud_tests/high_throughput_test.py +++ b/tests/rptest/redpanda_cloud_tests/high_throughput_test.py @@ -875,8 +875,14 @@ def test_decommission_and_add(self): self.logger.info('verify operator-v2 is not activated') # kubectl get redpanda -n=redpanda + # Even when nothing is found, kubectl always exits 0. Ref: https://github.com/kubernetes/kubectl/issues/821 + # But when nothing is found, kubectl by default prints "No resources found in redpanda namespace." to stderr. + # Which gets merged into the result lines! + # + # So, we add --ignore-not-found. This flag skips the human-readable "No resource" message. + # By the way, exit code is still 0, even with this flag :) get_redpanda = self.redpanda.kubectl.cmd( - ['get', 'redpanda', '-n=redpanda']) + ['get', 'redpanda', '-n=redpanda', '--ignore-not-found']) if len(get_redpanda) > 0: self.logger.warn('cannot run test with operator-v2') return diff --git a/tests/rptest/services/admin.py b/tests/rptest/services/admin.py index 6f8ae2bac5441..b7d1bb9852e40 100644 --- a/tests/rptest/services/admin.py +++ b/tests/rptest/services/admin.py @@ -293,6 +293,9 @@ def is_leader_stable(): backoff_s=backoff_s) if check(info.leader): return True, info.leader + + self.redpanda.logger.debug( + f"check failed (leader id: {info.leader})") return False return wait_until_result( @@ -1209,3 +1212,20 @@ def migrate_tx_manager_in_recovery(self, node): def get_tx_manager_recovery_status(self, node: Optional[ClusterNode] = None): return self._request("GET", "recovery/migrate_tx_manager", node=node) + + def get_broker_uuids(self, node: Optional[ClusterNode] = None): + return self._request("GET", "broker_uuids", node=node).json() + + def get_broker_uuid(self, node: ClusterNode): + return self._request("GET", "debug/broker_uuid", node=node).json() + + def override_node_id(self, node, current_uuid: str, new_node_id: int, + new_node_uuid: str): + return self._request("PUT", + "debug/broker_uuid", + node=node, + json={ + "current_node_uuid": current_uuid, + "new_node_uuid": new_node_uuid, + "new_node_id": new_node_id, + }) diff --git a/tests/rptest/services/cluster.py b/tests/rptest/services/cluster.py index 5387ebf1ab196..ef9afd5d0d3c3 100644 --- a/tests/rptest/services/cluster.py +++ b/tests/rptest/services/cluster.py @@ -154,13 +154,16 @@ def wrapped(self, *args, **kwargs): self.redpanda.validate_controller_log() - if self.redpanda.si_settings is not None: + if self.redpanda.si_settings is not None and not self.redpanda.si_settings.skip_end_of_test_scrubbing: try: self.redpanda.maybe_do_internal_scrub() self.redpanda.stop_and_scrub_object_storage() except: self.redpanda.cloud_storage_diagnostics() raise + else: + # stop here explicitly to fail if stop times out, otherwise ducktape won't catch it + self.redpanda.stop() # Finally, if the test passed and all post-test checks # also passed, we may trim the logs to INFO level to diff --git a/tests/rptest/services/failure_injector.py b/tests/rptest/services/failure_injector.py index 2eec5fcf02ed6..432d555de47f9 100644 --- a/tests/rptest/services/failure_injector.py +++ b/tests/rptest/services/failure_injector.py @@ -70,6 +70,7 @@ def __enter__(self): def __exit__(self, type, value, traceback): self._heal_all() + self._continue_all() def inject_failure(self, spec): if spec in self._in_flight: @@ -155,6 +156,9 @@ def _delete_netem(self, node): def _heal_all(self): pass + def _continue_all(self): + pass + def _suspend(self, node): pass @@ -246,7 +250,22 @@ def _heal_all(self): # Cleanups can fail, e.g. rule does not exist self.redpanda.logger.warn(f"_heal_all: {e}") - self._in_flight.clear() + self._in_flight = { + spec + for spec in self._in_flight + if spec.type != FailureSpec.FAILURE_ISOLATE + } + + def _continue_all(self): + self.redpanda.logger.info(f"continuing execution on all nodes") + for n in self.redpanda.nodes: + if self.redpanda.check_node(n): + self._continue(n) + self._in_flight = { + spec + for spec in self._in_flight + if spec.type != FailureSpec.FAILURE_SUSPEND + } def _suspend(self, node): self.redpanda.logger.info( diff --git a/tests/rptest/services/metrics_check.py b/tests/rptest/services/metrics_check.py index 6466b53b473f6..597e861145f17 100644 --- a/tests/rptest/services/metrics_check.py +++ b/tests/rptest/services/metrics_check.py @@ -94,7 +94,9 @@ def _capture(self, check_metrics): samples[sample.name] = sample.value for k, v in samples.items(): - self.logger.info(f" Captured {k}={v}") + self.logger.info( + f" Captured {k}={v} from {self.node.account.hostname}(node_id = {self.redpanda.node_id(self.node)})" + ) if len(samples) == 0: metrics_endpoint = ("/metrics" if self._metrics_endpoint diff --git a/tests/rptest/services/redpanda.py b/tests/rptest/services/redpanda.py index fdbfde53a5c34..b01917bfc16fa 100644 --- a/tests/rptest/services/redpanda.py +++ b/tests/rptest/services/redpanda.py @@ -139,6 +139,10 @@ re.compile( "cluster - .*exception while executing partition operation:.*std::exception \(std::exception\)" ), + # Failure to handle an internal RPC because the RPC server already handles connections but doesn't yet handle this method. This can happen while the node is still starting up/restarting. + # e.g. "admin_api_server - server.cc:655 - [_anonymous] exception intercepted - url: [http://ip-172-31-9-208:9644/v1/brokers/7/decommission] http_return_status[500] reason - seastar::httpd::server_error_exception (Unexpected error: rpc::errc::method_not_found)" + re.compile( + "admin_api_server - .*Unexpected error: rpc::errc::method_not_found") ] # Log errors emitted by refresh credentials system when cloud storage is enabled with IAM roles @@ -411,7 +415,8 @@ def __init__(self, retention_local_strict=True, cloud_storage_max_throughput_per_shard: Optional[int] = None, cloud_storage_signature_version: str = "s3v4", - before_call_headers: Optional[dict[str, Any]] = None): + before_call_headers: Optional[dict[str, Any]] = None, + skip_end_of_test_scrubbing: bool = False): """ :param fast_uploads: if true, set low upload intervals to help tests run quickly when they wait for uploads to complete. @@ -466,6 +471,12 @@ def __init__(self, self.cloud_storage_signature_version = cloud_storage_signature_version self.before_call_headers = before_call_headers + # Allow disabling end of test scrubbing. + # It takes a long time with lots of segments i.e. as created in scale + # tests. Should figure out how to re-enable it, or consider using + # redpanda's built-in scrubbing capabilities. + self.skip_end_of_test_scrubbing = skip_end_of_test_scrubbing + if fast_uploads: self.cloud_storage_segment_max_upload_interval_sec = 10 self.cloud_storage_manifest_max_upload_interval_sec = 1 @@ -803,6 +814,8 @@ class SchemaRegistryConfig(TlsConfig): SR_TLS_CLIENT_KEY_FILE = "/etc/redpanda/sr_client.key" SR_TLS_CLIENT_CRT_FILE = "/etc/redpanda/sr_client.crt" + mode_mutability = False + def __init__(self): super(SchemaRegistryConfig, self).__init__() @@ -2649,6 +2662,11 @@ def is_allowed_log_line(line: str) -> bool: return False crashes = [] + # We log long encoded AWS/GCP headers that occasionally have 'SEGV' in + # them by chance + cloud_header_strings = [ + 'x-amz-id', 'x-amz-request', 'x-guploader-uploadid' + ] for node in self.nodes: self.logger.info( f"Scanning node {node.account.hostname} log for errors...") @@ -2657,9 +2675,8 @@ def is_allowed_log_line(line: str) -> bool: for line in node.account.ssh_capture( f"grep -e SEGV -e Segmentation\\ fault -e [Aa]ssert -e Sanitizer {RedpandaService.STDOUT_STDERR_CAPTURE} || true", timeout_sec=30): - if 'SEGV' in line and ('x-amz-id' in line - or 'x-amz-request' in line): - # We log long encoded AWS headers that occasionally have 'SEGV' in them by chance + if 'SEGV' in line and any( + [h in line.lower() for h in cloud_header_strings]): continue if is_allowed_log_line(line): diff --git a/tests/rptest/services/redpanda_installer.py b/tests/rptest/services/redpanda_installer.py index 24a9dee04743a..8171684419652 100644 --- a/tests/rptest/services/redpanda_installer.py +++ b/tests/rptest/services/redpanda_installer.py @@ -26,13 +26,21 @@ VERSION_RE = re.compile(".*v(\\d+)\\.(\\d+)\\.(\\d+).*") # strict variant of VERSION_RE that only matches "vX.Y.Z" strings STRICT_VERSION_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$") -RELEASES_CACHE_FILE = "/tmp/redpanda_releases.json" +RELEASES_CACHE_FILE_PARENT = "/tmp/ducktape_cache" +RELEASES_CACHE_FILE = f"{RELEASES_CACHE_FILE_PARENT}/redpanda_releases.json" RELEASES_CACHE_FILE_TTL = timedelta(minutes=30) # environment variable to pass to ducktape the list of released versions. # It's a ":"-separated list of versions, e.g.: v23.2.1:v23.1.12:v23.1.11 RP_GIT_RELEASED_VERSIONS = "RP_GIT_RELEASED_VERSIONS" +REDPANDA_INSTALLER_HEAD_TAG = "head" + +RedpandaVersionTriple = tuple[int, int, int] +RedpandaVersionLine = tuple[int, int] +RedpandaVersion = typing.Literal[ + 'head'] | RedpandaVersionLine | RedpandaVersionTriple + def wait_for_num_versions(redpanda, num_versions): # Use a single node so the metadata about brokers have a consistent source @@ -76,6 +84,14 @@ def ver_string(int_tuple): return f"v{'.'.join(str(i) for i in int_tuple)}" +def ver_triple(version_line: RedpandaVersionLine) -> RedpandaVersionTriple: + """ + Converts "v24.1.0-dev-1940-g8ae241e966 - 8ae241e966bd3d5811951a176fc40a7a77ce2a0c" into (24, 1, 0) + """ + matches = VERSION_RE.findall(version_line) + return RedpandaVersionTriple(int_tuple(matches[0])) + + class InstallOptions: """ Options with which to configure the installation of Redpanda in a cluster. @@ -98,14 +114,6 @@ def __init__(self, self.num_to_upgrade = num_to_upgrade -REDPANDA_INSTALLER_HEAD_TAG = "head" - -RedpandaVersionTriple = tuple[int, int, int] -RedpandaVersionLine = tuple[int, int] -RedpandaVersion = typing.Literal[ - 'head'] | RedpandaVersionLine | RedpandaVersionTriple - - class RedpandaInstaller: """ Provides mechanisms to install multiple Redpanda binaries on a cluster. @@ -307,14 +315,14 @@ def start(self): # use it to get older versions relative to the head version. # NOTE: installing this version may not yield the same binaries being # as 'head', e.g. if an unreleased source is checked out. - self._head_version: RedpandaVersionTriple = int_tuple( - VERSION_RE.findall(initial_version)[0]) + self._head_version: RedpandaVersionTriple = ver_triple(initial_version) self._started = True def _released_versions_json(self): def get_cached_data(): try: + os.makedirs(RELEASES_CACHE_FILE_PARENT, exist_ok=True) st = os.stat(RELEASES_CACHE_FILE) mtime = datetime.fromtimestamp(st.st_mtime, tz=timezone.utc) if datetime.now( @@ -677,7 +685,7 @@ def _async_download_on_node_unlocked(self, node, version): version_root = self.root_for_version(version) tgz = "redpanda.tar.gz" - cmd = f"curl -fsSL {self._version_package_url(version)} --create-dir --output-dir {version_root} -o {tgz} && gunzip -c {version_root}/{tgz} | tar -xf - -C {version_root} && rm {version_root}/{tgz}" + cmd = f"curl -fsSL {self._version_package_url(version)} --retry 3 --retry-connrefused --retry-delay 2 --create-dir -o {version_root}/{tgz} && gunzip -c {version_root}/{tgz} | tar -xf - -C {version_root} && rm {version_root}/{tgz}" return node.account.ssh_capture(cmd) def reset_current_install(self, nodes): diff --git a/tests/rptest/services/rpk_producer.py b/tests/rptest/services/rpk_producer.py index bad9861550ed2..9c5ea6d028765 100644 --- a/tests/rptest/services/rpk_producer.py +++ b/tests/rptest/services/rpk_producer.py @@ -90,3 +90,6 @@ def output_line_count(self): def stop_node(self, node): self._stopping.set() node.account.kill_process("rpk", clean_shutdown=False) + + def clean_node(self, node): + pass diff --git a/tests/rptest/services/templates/redpanda.yaml b/tests/rptest/services/templates/redpanda.yaml index ff3708d67e17b..ef7500f4b57b8 100644 --- a/tests/rptest/services/templates/redpanda.yaml +++ b/tests/rptest/services/templates/redpanda.yaml @@ -132,6 +132,7 @@ schema_registry: {% endif %} api_doc_dir: {{root}}/usr/share/redpanda/proxy-api-doc + mode_mutability: {{ schema_registry_config.mode_mutability }} {% if schema_registry_config.truststore_file is not none %} schema_registry_api_tls: diff --git a/tests/rptest/tests/acls_test.py b/tests/rptest/tests/acls_test.py index 58d89e8b66b4d..a4e59482ab7b8 100644 --- a/tests/rptest/tests/acls_test.py +++ b/tests/rptest/tests/acls_test.py @@ -14,7 +14,7 @@ from rptest.tests.redpanda_test import RedpandaTest from rptest.services.cluster import cluster from rptest.services.admin import Admin -from rptest.clients.rpk import RpkTool, ClusterAuthorizationError, RpkException +from rptest.clients.rpk import RpkTool, ClusterAuthorizationError, RpkException, AclList from rptest.services.redpanda import SecurityConfig, TLSProvider from rptest.services.redpanda_installer import RedpandaInstaller, wait_for_num_versions from rptest.services import tls @@ -245,6 +245,38 @@ def check_super_user_perms(): timeout_sec=timeout_sec, err_msg=f'super user: {err_msg}') + @cluster(num_nodes=3) + def test_invalid_acl_topic_name(self): + self.prepare_cluster(use_sasl=True, use_tls=False, authn_method=None) + + # Ensure creating an ACL topic resource with a valid kafka topic name works + client = self.get_super_client() + resource = 'my_topic' + results = AclList.parse_raw( + client.sasl_allow_principal(principal='base', + operations=['all'], + resource='topic', + resource_name=resource)) + self.redpanda.logger.info(f'{results._acls}') + assert results.has_permission( + 'base', 'all', 'topic', + resource), f'Failed to create_acl for resource {resource}' + + # Assert that appropriate error was returned by the server for invalid + # kafka topic names + resource = 'my bad topic name' + results = AclList.parse_raw( + client.sasl_allow_principal(principal='base', + operations=['all'], + resource='topic', + resource_name=resource)) + acls = results._acls['base'] + assert acls is not None, "Missing principal from create_acls result" + + acl = [acl for acl in acls if acl.resource_name == resource] + assert len(acl) == 1, f'Expected match for {resource} not found' + assert acl[0].error == 'INVALID_REQUEST' + ''' The old config style has use_sasl at the top level, which enables authorization. New config style has kafka_enable_authorization at the diff --git a/tests/rptest/tests/admin_uuid_operations_test.py b/tests/rptest/tests/admin_uuid_operations_test.py new file mode 100644 index 0000000000000..7f2e363877c56 --- /dev/null +++ b/tests/rptest/tests/admin_uuid_operations_test.py @@ -0,0 +1,99 @@ +# Copyright 2024 Redpanda Data, Inc. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.md +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0 + +import random +import requests +from enum import IntEnum + +import numpy as np + +from rptest.tests.redpanda_test import RedpandaTest +from rptest.services.admin import Admin +from rptest.services.cluster import cluster +from ducktape.cluster.cluster import ClusterNode + +from rptest.util import wait_until_result + + +class AdminUUIDOperationsTest(RedpandaTest): + def __init__(self, ctx): + super().__init__(test_context=ctx, num_brokers=3) + + def setUp(self): + self.redpanda.start(auto_assign_node_id=True, + omit_seeds_on_idx_one=False) + self._create_initial_topics() + + @cluster(num_nodes=3) + def test_getting_node_id_to_uuid_map(self): + admin = Admin(self.redpanda) + uuids = admin.get_broker_uuids() + assert len(uuids) == 3, "UUID map should contain 3 brokers" + all_ids = set() + for n in uuids: + assert 'node_id' in n + assert 'uuid' in n + all_ids.add(n['node_id']) + + brokers = admin.get_brokers() + for b in brokers: + assert b['node_id'] in all_ids + + @cluster(num_nodes=3) + def test_overriding_node_id(self): + admin = Admin(self.redpanda) + to_stop = self.redpanda.nodes[0] + initial_to_stop_id = self.redpanda.node_id(to_stop) + # Stop node and clear its data directory + self.redpanda.stop_node(to_stop) + self.redpanda.clean_node(to_stop, + preserve_current_install=True, + preserve_logs=False) + + self.redpanda.start_node(to_stop, + auto_assign_node_id=True, + omit_seeds_on_idx_one=False) + + def _uuids_updated(): + uuids = admin.get_broker_uuids() + if len(uuids) != 4: + return False, None + + return True, uuids + + # wait for the node to join with new ID + uuids = wait_until_result( + _uuids_updated, + timeout_sec=30, + err_msg="Node was unable to join the cluster") + + uuids = admin.get_broker_uuids() + old_uuid = None + + for n in uuids: + id = n['node_id'] + if id == initial_to_stop_id: + old_uuid = n['uuid'] + + # get current node id and UUID + current = admin.get_broker_uuid(to_stop) + + admin.override_node_id(to_stop, + current_uuid=current['node_uuid'], + new_node_id=initial_to_stop_id, + new_node_uuid=old_uuid) + + self.redpanda.restart_nodes(to_stop, + auto_assign_node_id=True, + omit_seeds_on_idx_one=False) + + after_restart = admin.get_broker_uuid(to_stop) + + assert after_restart['node_id'] == initial_to_stop_id + assert after_restart['node_uuid'] == old_uuid diff --git a/tests/rptest/tests/audit_log_test.py b/tests/rptest/tests/audit_log_test.py index b842c2b7a2bc2..0540b4ed7549a 100644 --- a/tests/rptest/tests/audit_log_test.py +++ b/tests/rptest/tests/audit_log_test.py @@ -22,6 +22,7 @@ from ducktape.cluster.cluster import ClusterNode from ducktape.errors import TimeoutError +from ducktape.mark import matrix, ok_to_fail from keycloak import KeycloakOpenID from rptest.clients.default import DefaultClient from rptest.clients.kcl import KCL @@ -415,6 +416,12 @@ def change_max_buffer_size_per_shard(self, new_size: int): self._modify_cluster_config( {'audit_queue_max_buffer_size_per_shard': new_size}) + def modify_audit_enabled(self, enabled: bool): + """ + Modifies value of audit_enabled + """ + self._modify_cluster_config({'audit_enabled': enabled}) + def modify_node_config(self, node, update_fn, skip_readiness_check=True): """Modifies the current node configuration, restarts the node for changes to take effect @@ -667,6 +674,7 @@ def test_app_lifecycle(self): True), lambda record_count: record_count == 3, "Single redpanda start event per node") + @ok_to_fail # https://github.com/redpanda-data/redpanda/issues/16198 @cluster(num_nodes=5) def test_drain_on_audit_disabled(self): """ @@ -1582,6 +1590,129 @@ def test_no_audit_user_authn(self): pass +class AuditLogTestInvalidConfigBase(AuditLogTestBase): + username = 'test' + password = 'test12345' + algorithm = 'SCRAM-SHA-256' + """ + Tests situations where audit log client is not properly configured + """ + def __init__(self, + test_context, + audit_log_config=AuditLogConfig(enabled=False, + num_partitions=1, + event_types=[]), + log_config=LoggingConfig('info', + logger_levels={ + 'auditing': 'trace', + 'kafka': 'trace', + 'security': 'trace' + }), + **kwargs): + self.test_context = test_context + # The 'none' below will cause the audit log client to not be configured properly + self._audit_log_client_config = redpanda.AuditLogConfig( + listener_port=9192, listener_authn_method='none') + self._audit_log_client_config.require_client_auth = False + self._audit_log_client_config.enable_broker_tls = False + + super(AuditLogTestInvalidConfigBase, self).__init__( + test_context=test_context, + audit_log_config=audit_log_config, + log_config=log_config, + audit_log_client_config=self._audit_log_client_config, + **kwargs) + + def setUp(self): + super().setUp() + self.admin.create_user(self.username, self.password, self.algorithm) + self.get_super_rpk().acl_create_allow_cluster(self.username, 'All') + # Following is important so the rest of ducktape functions correctly + self.modify_audit_excluded_principals(['admin']) + self.modify_audit_event_types(['authenticate']) + self.modify_audit_enabled(True) + # Waits for all audit clients to enter the same state where any attempt + # to enqueue an event will be rejected because the client is misconfigured + wait_until(lambda: self.redpanda.search_log_all( + 'error_code: illegal_sasl_state'), + timeout_sec=30, + backoff_sec=2, + err_msg="Did not see illegal_sasl_state error message") + + +class AuditLogTestInvalidConfig(AuditLogTestInvalidConfigBase): + def __init__(self, test_context): + super(AuditLogTestInvalidConfig, self).__init__( + test_context=test_context, + security=AuditLogTestSecurityConfig(user_creds=(self.username, + self.password, + self.algorithm))) + + @cluster(num_nodes=4, + log_allow_list=[ + r'Failed to append authentication event to audit log', + r'Failed to audit.*' + ]) + def test_invalid_config(self): + """ + Test validates that the topic is failed to get created if audit + system is not configured correctly. + """ + try: + self.get_rpk().create_topic('test') + assert False, "Should not have created a topic" + except RpkException as e: + assert "audit system failure: BROKER_NOT_AVAILABLE" in str( + e + ), f'{str(e)} does not contain "audit system failure: BROKER_NOT_AVAILABLE"' + + +class AuditLogTestInvalidConfigMTLS(AuditLogTestInvalidConfigBase): + """ + Tests situations where audit log client is not properly configured and mTLS enabled + """ + def __init__(self, test_context): + self.test_context = test_context + self.tls = tls.TLSCertManager(self.logger) + self.user_cert = self.tls.create_cert(socket.gethostname(), + common_name=self.username, + name='base_client') + self.admin_user_cert = self.tls.create_cert( + socket.gethostname(), + common_name=RedpandaServiceBase.SUPERUSER_CREDENTIALS[0], + name='admin_client') + self._security_config = AuditLogTestSecurityConfig( + admin_cert=self.admin_user_cert, user_cert=self.user_cert) + self._security_config.tls_provider = MTLSProvider(self.tls) + self._security_config.principal_mapping_rules = 'RULE:.*CN=(.*).*/$1/' + + super(AuditLogTestInvalidConfigMTLS, + self).__init__(test_context=test_context, + security=self._security_config) + + @cluster( + num_nodes=4, + log_allow_list=[ + r'Failed to append authentication event to audit log', + r'Failed to audit.*', + r'Failed to enqueue mTLS authentication event - audit log system error' + ]) + def test_invalid_config_mtls(self): + """ + Validates that mTLS authn is rejected when audit client is misconfigured. + Also ensures there is no segfault: https://redpandadata.atlassian.net/browse/CORE-7245 + """ + try: + self.get_rpk().create_topic('test') + assert False, "Should not have created a topic" + except RpkException as e: + pass + + assert self.redpanda.search_log_any( + 'Failed to enqueue mTLS authentication event - audit log system error' + ) + + class AuditLogTestKafkaTlsApi(AuditLogTestBase): """ Tests that validate audit log messages for users authenticated via mTLS @@ -1675,8 +1806,9 @@ def __init__(self, test_context): security = AuditLogTestSecurityConfig( user_creds=RedpandaServiceBase.SUPERUSER_CREDENTIALS) security.enable_sasl = True - security.sasl_mechanisms = ['SCRAM', 'OAUTHBEARER'] - security.http_authentication = ['BASIC', 'OIDC'] + security.sasl_mechanisms = ['SCRAM'] + security.http_authentication = ['BASIC'] + # We'll only enable Oath once keycloak is up and running self.keycloak = KeycloakService(test_context) @@ -1703,11 +1835,18 @@ def setUp(self): self.keycloak.clean_node(kc_node) assert False, f'Keycloak failed to start: {e}' + self.security.sasl_mechanisms += ['OAUTHBEARER'] + self.security.http_authentication += ['OIDC'] + self._modify_cluster_config({ 'oidc_discovery_url': self.keycloak.get_discovery_url(kc_node), "oidc_token_audience": - self.token_audience + self.token_audience, + "sasl_mechanisms": + self.security.sasl_mechanisms, + "http_authentication": + self.security.http_authentication, }) self.keycloak.admin.create_user('norma', diff --git a/tests/rptest/tests/availability_test.py b/tests/rptest/tests/availability_test.py index ab8905ee6cfb1..3490125374706 100644 --- a/tests/rptest/tests/availability_test.py +++ b/tests/rptest/tests/availability_test.py @@ -58,10 +58,9 @@ def test_availability_when_one_node_failed(self): self.start_producer(1, throughput=10000) self.start_consumer(1) self.await_startup() - # start failure injector with default parameters - self.start_finjector() - - self.validate_records() + # run failure injector loop with default parameters + with self.finj_thread(): + self.validate_records() @cluster(num_nodes=5, log_allow_list=CHAOS_LOG_ALLOW_LIST) def test_recovery_after_catastrophic_failure(self): @@ -90,17 +89,14 @@ def test_recovery_after_catastrophic_failure(self): self.start_consumer(1) self.await_startup() - # inject permanent random failure - f_spec = FailureSpec(random.choice(FailureSpec.FAILURE_TYPES), - random.choice(self.redpanda.nodes[0:1])) - - self.inject_failure(f_spec) - - # inject transient failure on other node - f_spec = FailureSpec(random.choice(FailureSpec.FAILURE_TYPES), - self.redpanda.nodes[2], - length=2.0 if self.scale.local else 15.0) - - self.inject_failure(f_spec) - - self.validate_records() + with self.finj_manual() as finj: + # inject permanent random failure + f_spec = FailureSpec(random.choice(FailureSpec.FAILURE_TYPES), + random.choice(self.redpanda.nodes[0:1])) + finj(f_spec) + # inject transient failure on other node + f_spec = FailureSpec(random.choice(FailureSpec.FAILURE_TYPES), + self.redpanda.nodes[2], + length=2.0 if self.scale.local else 15.0) + finj(f_spec) + self.validate_records() diff --git a/tests/rptest/tests/cloud_storage_chunk_read_path_test.py b/tests/rptest/tests/cloud_storage_chunk_read_path_test.py index 1429a067d2bd2..3637969be3320 100644 --- a/tests/rptest/tests/cloud_storage_chunk_read_path_test.py +++ b/tests/rptest/tests/cloud_storage_chunk_read_path_test.py @@ -181,7 +181,12 @@ def _assert_not_in_cache(self, expr: str): def _assert_in_cache(self, expr: str): return self._assert_files_in_cache(expr=expr, must_be_absent=False) - @cluster(num_nodes=4) + @cluster( + num_nodes=4, + log_allow_list=[ + # Ignore trim related errors caused by deleting chunk files manually. + "failed to free sufficient space in exhaustive trim" + ]) def test_read_chunks(self): self.default_chunk_size = 1048576 self._set_params_and_start_redpanda( @@ -207,14 +212,24 @@ def test_read_chunks(self): 'topic': self.topic, 'partition': '0', }) + + # Randomly delete chunks in the background to test re-queueing on failed + # materialization. Cache eviction exercises the same code path, but we + # delete chunks more aggressively to make sure some materialization ops fail. + rm_chunks = DeleteRandomChunks(self.redpanda, self.topic) + rm_chunks.start() rand_cons.start() rand_cons.wait(timeout_sec=300) m.expect([(metric, lambda a, b: a < b <= 2 * self.default_chunk_size)]) # There should be no log files in cache self._assert_not_in_cache(fr'.*kafka/{self.topic}/.*\.log\.[0-9]+$') - # There should be some chunk files in cache - self._assert_in_cache(f'.*kafka/{self.topic}/.*_chunks/[0-9]+') + + # We cannot assert that there are chunks in cache because the deleting thread + # could delete them all before we run the assertion, causing it to fail. + # We can check that the thread deleted some chunks. + assert rm_chunks.deleted_chunks > 0, "Expected to delete some chunk files during rand-cons, none deleted" + rm_chunks.deleted_chunks = 0 consumer = KgoVerifierSeqConsumer(self.test_context, self.redpanda, @@ -223,6 +238,10 @@ def test_read_chunks(self): nodes=self.preallocated_nodes) consumer.start() consumer.wait(timeout_sec=120) + rm_chunks.stop() + rm_chunks.join(timeout=10) + assert rm_chunks.deleted_chunks > 0, "Expected to delete some chunk files during seq-cons, none deleted" + self._assert_not_in_cache(fr'.*kafka/{self.topic}/.*\.log\.[0-9]+$') self._trim_and_verify() @@ -445,3 +464,40 @@ def run(self) -> None: def stop(self): self.stop_requested = True + + +class DeleteRandomChunks(Thread): + def __init__(self, redpanda: RedpandaService, topic: str, n_delete=3): + super().__init__() + self.redpanda = redpanda + self.topic = topic + self.stop_requested = False + # A small sleep interval to force chunk-materialize calls to fail. + self.sleep_interval = 0.05 + self.n_delete = n_delete + self.deleted_chunks = 0 + + def run(self) -> None: + cmd = f""" +find {self.redpanda.DATA_DIR}/cloud_storage_cache -regex '.*kafka/{self.topic}/.*_chunks/[0-9]+' -print0 |\ + sort --zero-terminated --random-sort |\ + head --zero-terminated -n {self.n_delete} |\ + xargs --no-run-if-empty --null rm -v""" + while not self.stop_requested: + sleep(self.sleep_interval) + leader_id = Admin(self.redpanda).get_partition_leader( + namespace='kafka', topic=self.topic, partition=0) + leader_node = self.redpanda.get_node_by_id(leader_id) + + if leader_node in self.redpanda.started_nodes(): + try: + for row in leader_node.account.ssh_capture(cmd): + self.redpanda.logger.debug( + f'{leader_node.account.hostname}: {row.strip()}') + self.deleted_chunks += 1 + except Exception as ex: + self.redpanda.logger.info( + f'ignoring {ex} while deleting chunk files') + + def stop(self): + self.stop_requested = True diff --git a/tests/rptest/tests/cloud_storage_scrubber_test.py b/tests/rptest/tests/cloud_storage_scrubber_test.py index 2e688bb262dff..02eaf84010b33 100644 --- a/tests/rptest/tests/cloud_storage_scrubber_test.py +++ b/tests/rptest/tests/cloud_storage_scrubber_test.py @@ -185,7 +185,7 @@ def get_impacted_offset_ranges(self, ntpr: NTPR, class CloudStorageScrubberTest(RedpandaTest): - scrub_timeout = 90 + scrub_timeout = 200 partition_count = 3 message_size = 16 * 1024 # 16KiB segment_size = 1024 * 1024 # 1MiB @@ -203,12 +203,11 @@ def __init__(self, test_context): super().__init__( test_context=test_context, extra_rp_conf={ - "cloud_storage_enable_scrubbing": True, - "cloud_storage_partial_scrub_interval_ms": 1000, + "cloud_storage_partial_scrub_interval_ms": 100, "cloud_storage_full_scrub_interval_ms": 1000, - "cloud_storage_scrubbing_interval_jitter_ms": 100, + "cloud_storage_scrubbing_interval_jitter_ms": 50, # Small quota forces partial scrubs - "cloud_storage_background_jobs_quota": 30, + "cloud_storage_background_jobs_quota": 40, # Disable segment merging since it can reupload # the deleted segment and remove the gap "cloud_storage_enable_segment_merging": False, diff --git a/tests/rptest/tests/cluster_config_test.py b/tests/rptest/tests/cluster_config_test.py index addde8f95746a..9ca55a4236106 100644 --- a/tests/rptest/tests/cluster_config_test.py +++ b/tests/rptest/tests/cluster_config_test.py @@ -1079,7 +1079,8 @@ class Example(NamedTuple): Example("kafka_qdc_enable", "true", True), Example("append_chunk_size", "32768", 32768), Example("superusers", "['bob','alice']", ["bob", "alice"]), - Example("storage_min_free_bytes", "1234567890", 1234567890) + Example("storage_min_free_bytes", "1234567890", 1234567890), + Example("kafka_memory_share_for_fetch", "0.6", 0.6) ] def yamlize(input) -> str: diff --git a/tests/rptest/tests/cluster_quota_test.py b/tests/rptest/tests/cluster_quota_test.py index f86a2559f05ec..b5e1e4351419f 100644 --- a/tests/rptest/tests/cluster_quota_test.py +++ b/tests/rptest/tests/cluster_quota_test.py @@ -14,7 +14,7 @@ from ducktape.utils.util import wait_until from rptest.clients.rpk import RpkTool -from rptest.services.redpanda import ResourceSettings +from rptest.services.redpanda import ResourceSettings, LoggingConfig from kafka import KafkaProducer, KafkaConsumer, TopicPartition from rptest.clients.kcl import RawKCL, KclCreateTopicsRequestTopic, \ KclCreatePartitionsRequestTopic @@ -22,6 +22,8 @@ from rptest.clients.types import TopicSpec from rptest.services.cluster import cluster +GB = 1_000_000_000 + class ClusterQuotaPartitionMutationTest(RedpandaTest): """ @@ -111,7 +113,7 @@ class ClusterRateQuotaTest(RedpandaTest): """ Ducktape tests for rate quota """ - topics = (TopicSpec(), ) + topics = (TopicSpec(replication_factor=1, max_message_bytes=1 * GB), ) def __init__(self, *args, **kwargs): self.max_throttle_time = 10 @@ -131,8 +133,9 @@ def __init__(self, *args, **kwargs): self.target_default_quota_byte_rate, } super().__init__(*args, - num_brokers=3, extra_rp_conf=additional_options, + log_config=LoggingConfig( + 'info', logger_levels={'kafka': 'trace'}), resource_settings=ResourceSettings(num_cpus=1), **kwargs) self.rpk = RpkTool(self.redpanda) @@ -148,11 +151,16 @@ def init_test_data(self): self.msg = "".join( random.choice(string.ascii_lowercase) for _ in range(self.message_size)) + # A single large message that goes above the default produce/fetch quota + self.large_msg = "".join( + random.choice(string.ascii_lowercase) + for _ in range(self.target_default_quota_byte_rate * 11)) - def check_producer_throttled(self, producer): + def check_producer_throttled(self, producer, ignore_max_throttle=False): throttle_ms = producer.metrics( )["producer-metrics"]["produce-throttle-time-max"] - assert throttle_ms > 0 and throttle_ms <= self.max_throttle_time + assert throttle_ms > 0 and (ignore_max_throttle + or throttle_ms <= self.max_throttle_time) def check_producer_not_throttled(self, producer): throttle_ms = producer.metrics( @@ -169,12 +177,13 @@ def check_consumer_not_throttled(self, consumer): )["consumer-fetch-manager-metrics"]["fetch-throttle-time-max"] assert throttle_ms == 0 - def produce(self, producer, amount): + def produce(self, producer, amount, message=None, timeout=1): + msg = message if message else self.msg response_futures = [ - producer.send(self.topic, self.msg) for _ in range(amount) + producer.send(self.topic, msg) for _ in range(amount) ] for f in response_futures: - f.get(1) + f.get(timeout=timeout) def fetch(self, consumer, messages_amount, timeout_sec=300): deadline = datetime.datetime.now() + datetime.timedelta( @@ -494,3 +503,77 @@ def test_client_response_and_produce_throttle_mechanism(self): # Produce must not be throttled self.produce(producer, 10) self.check_producer_not_throttled(producer) + + def _throttling_enforced_broker_side(self): + return self.redpanda.search_log_all("enforcing throttling delay of") + + @cluster(num_nodes=1) + def test_throttling_ms_enforcement_is_per_connection(self): + # Start with a cluster that has a produce quota (see class configs) + self.init_test_data() + + # Set the max throttling delay to something larger to give us a chance + # to send a request before the throttling delay from the previous + # request expires + self.redpanda.set_cluster_config( + {"max_kafka_throttle_delay_ms": "1000"}) + + # Create two producers sharing a client.id + def make_producer(): + return KafkaProducer( + acks="all", + bootstrap_servers=self.leader_node, + value_serializer=str.encode, + retries=1, + client_id="shared_client_id", + max_request_size=1 * GB, + max_in_flight_requests_per_connection=1, + ) + + producer1 = make_producer() + producer2 = make_producer() + consumer = KafkaConsumer( + self.topic, + bootstrap_servers=self.leader_node, + client_id="shared_client_id", + consumer_timeout_ms=1000, + max_partition_fetch_bytes=self.max_partition_fetch_bytes, + auto_offset_reset='earliest', + enable_auto_commit=False) + + # Produce above the produce quota limit + self.produce(producer1, 1, self.large_msg) + self.check_producer_throttled(producer1, ignore_max_throttle=True) + + assert not self._throttling_enforced_broker_side(), \ + f"On the first request, the throttling delay should not be enforced" + + # Now check that another producer is throttled through throttle_ms but + # the delay is not enforced broker-side initially + self.produce(producer2, 1, self.msg) + self.check_producer_throttled(producer2, ignore_max_throttle=True) + + assert not self._throttling_enforced_broker_side(), \ + f"On the first request, the throttling delay should not be enforced" + + # Also check that non-produce requests are not throttled either + self.fetch(consumer, 1) + self.check_consumer_not_throttled(consumer) + + assert not self._throttling_enforced_broker_side(), \ + f"Non-produce requests should not be throttled either" + + # Wait for logs to propagate + time.sleep(5) + assert not self._throttling_enforced_broker_side(), \ + f"No broker-side throttling should happen up until this point" + + # Because the python client doesn't seem to enforce the quota + # client-side, it is going to be enforced broker-side + self.produce(producer1, 3, self.large_msg, timeout=10) + self.check_producer_throttled(producer1, ignore_max_throttle=True) + wait_until( + self._throttling_enforced_broker_side, + timeout_sec=10, + err_msg="Subsequent messages should be throttled broker-side", + ) diff --git a/tests/rptest/tests/connection_limits_test.py b/tests/rptest/tests/connection_limits_test.py index 4b0fe2fcf9fa6..911aaf96b0ab8 100644 --- a/tests/rptest/tests/connection_limits_test.py +++ b/tests/rptest/tests/connection_limits_test.py @@ -18,6 +18,7 @@ from rptest.util import expect_exception REJECTED_METRIC = "vectorized_kafka_rpc_connections_rejected_total" +ACTIVE_METRIC = "vectorized_kafka_rpc_active_connections" class ConnectionLimitsTest(RedpandaTest): @@ -46,6 +47,16 @@ def test_exceed_broker_limit(self): for c in consumers: c.start() + def connection_count(): + return self.redpanda.metric_sum(ACTIVE_METRIC) + + # wait until the connection count reaches the expected number before starting the + # producer, since otherwise the consumers and the producer race and the producer + # may win in which case it would be one of the consumers that fail to connect + self.redpanda.wait_until( + lambda: connection_count() >= 6, 60, 1, + f"Did not get 6 connections, last count was {connection_count()}") + producer = RpkProducer(self.test_context, self.redpanda, self.topic, diff --git a/tests/rptest/tests/consumer_group_test.py b/tests/rptest/tests/consumer_group_test.py index 06ed83f6ab51f..dcd8ee1d08e61 100644 --- a/tests/rptest/tests/consumer_group_test.py +++ b/tests/rptest/tests/consumer_group_test.py @@ -8,20 +8,30 @@ # the Business Source License, use of this software will be governed # by the Apache License, Version 2.0 +from dataclasses import dataclass +from typing import Dict, List + from rptest.clients.offline_log_viewer import OfflineLogViewer from rptest.services.cluster import cluster from rptest.clients.rpk import RpkException, RpkTool from rptest.clients.types import TopicSpec from rptest.services.kafka_cli_consumer import KafkaCliConsumer -from rptest.services.redpanda import RESTART_LOG_ALLOW_LIST +from rptest.services.redpanda import RESTART_LOG_ALLOW_LIST, RedpandaService from rptest.services.rpk_producer import RpkProducer +from rptest.services.verifiable_consumer import VerifiableConsumer from rptest.tests.redpanda_test import RedpandaTest +from rptest.util import wait_until_result +from rptest.utils.mode_checks import skip_debug_mode + from ducktape.utils.util import wait_until from ducktape.mark import parametrize -from kafka import KafkaConsumer - -from rptest.utils.mode_checks import skip_debug_mode +from kafka import KafkaConsumer, TopicPartition +from kafka import errors as kerr +from kafka.admin import KafkaAdminClient +from kafka.protocol.commit import OffsetFetchRequest_v3 +from kafka.protocol.api import Request, Response +import kafka.protocol.types as types class ConsumerGroupTest(RedpandaTest): @@ -362,6 +372,69 @@ def test_consumer_is_removed_when_timedout(self, static_members): c.wait() c.free() + @cluster(num_nodes=4, log_allow_list=RESTART_LOG_ALLOW_LIST) + def test_group_recovery(self): + """ + Test validating that group state is recovered after broker restart. + """ + self.create_topic(1) + + # Produce some messages. + self.start_producer(msg_cnt=1000) + self.producer.wait() + self.producer.free() + + group_id = 'test-gr-1' + + # Consume all messages and commit offsets. + self.consumer = VerifiableConsumer(self.test_context, + num_nodes=1, + redpanda=self.redpanda, + topic=self.topic_spec.name, + group_id=group_id, + max_messages=1000) + self.consumer.start() + self.consumer.wait() + + test_admin = KafkaTestAdminClient(self.redpanda) + offsets = test_admin.list_offsets( + group_id, [TopicPartition(self.topic_spec.name, 0)]) + + # Test that the consumer committed what we expected. + self.logger.info(f"Got offsets: {offsets}") + assert len(offsets) == 1 + assert offsets[TopicPartition(self.topic_spec.name, 0)].offset == 1000 + assert offsets[TopicPartition(self.topic_spec.name, + 0)].leader_epoch > 0 + + # Remember the old offsets to compare them after the restart. + prev_offsets = offsets + + # Restart the broker. + self.logger.info("Restarting redpanda nodes.") + self.redpanda.restart_nodes(self.redpanda.nodes) + + # Validate that the group state is recovered. + def try_list_offsets(): + try: + test_admin = KafkaTestAdminClient(self.redpanda) + return test_admin.list_offsets( + group_id, [TopicPartition(self.topic_spec.name, 0)]) + except Exception as e: + self.logger.debug(f"Failed to list offsets: {e}") + return None + + offsets = wait_until_result( + try_list_offsets, + timeout_sec=30, + backoff_sec=3, + err_msg="Failed to make list_offsets request") + + self.logger.info(f"Got offsets after restart: {offsets}") + assert len(offsets) == 1 + assert offsets == prev_offsets, \ + f"Expected {prev_offsets}, got {offsets}." + @cluster(num_nodes=6, log_allow_list=RESTART_LOG_ALLOW_LIST) @parametrize(static_members=True) @parametrize(static_members=False) @@ -490,3 +563,159 @@ async def create_groups(r): list = rpk.group_list() assert len(list) == groups_in_round * rounds + + @cluster(num_nodes=5) + def test_consumer_static_member_update(self): + """ + Test validating that re-joining static member will update the client id + """ + self.create_topic(20) + + group = 'test-gr-1' + + rpk = RpkTool(self.redpanda) + + # create and start first consumer + consumer1 = self.create_consumer( + topic=self.topic_spec.name, + group=group, + instance_name="static-consumer", + instance_id="panda-instance", + consumer_properties={"client.id": "my-client-1"}) + + consumer1.start() + + self.wait_for_members(group, 1) + + # wait for some messages + self.start_producer() + wait_until( + lambda: ConsumerGroupTest.consumed_at_least([consumer1], 50), + timeout_sec=30, + backoff_sec=2, + err_msg="consumer1 did not consume messages") + + # validate initial state + rpk_group_1 = rpk.group_describe(group) + + assert rpk_group_1.state == "Stable", f"Describe: {rpk_group_1}" + assert rpk_group_1.members == 1, f"Describe: {rpk_group_1}" + for p in rpk_group_1.partitions: + assert p.client_id == 'my-client-1', f"Describe: {p}" + + # clean up + self.producer.wait() + self.producer.free() + + consumer1.stop() + consumer1.wait() + consumer1.free() + + # create and start consumer with same instance_id but different cliend_id + consumer2 = self.create_consumer( + topic=self.topic_spec.name, + group=group, + instance_name="static-consumer", + instance_id="panda-instance", + consumer_properties={"client.id": "my-client-2"}) + + consumer2.start() + + self.wait_for_members(group, 1) + + # wait for some messages + self.start_producer() + wait_until( + lambda: ConsumerGroupTest.consumed_at_least([consumer2], 50), + timeout_sec=30, + backoff_sec=2, + err_msg="consumer2 did not consume messages") + + # validate updated state + rpk_group_2 = rpk.group_describe(group) + + assert rpk_group_2.state == "Stable", f"Describe: {rpk_group_2}" + assert rpk_group_2.members == 1, f"Describe: {rpk_group_2}" + for p in rpk_group_2.partitions: + assert p.client_id == 'my-client-2', f"Describe: {p}" + + # clean up + consumer2.stop() + consumer2.wait() + consumer2.free() + + self.producer.wait() + self.producer.free() + + +@dataclass +class OffsetAndMetadata(): + offset: int + leader_epoch: int + metadata: str + + +class KafkaTestAdminClient(): + """ + A wrapper around KafkaAdminClient with support for newer Kafka versions. + At the time of writing, KafkaAdminClient doesn't support KIP-320 + (leader epoch) for consumer groups. + """ + def __init__(self, redpanda: RedpandaService): + self._bootstrap_servers = redpanda.brokers() + self._admin = KafkaAdminClient( + bootstrap_servers=self._bootstrap_servers) + + def list_offsets( + self, group_id: str, partitions: List[TopicPartition] + ) -> Dict[TopicPartition, OffsetAndMetadata]: + coordinator = self._admin._find_coordinator_ids([group_id])[group_id] + future = self._list_offsets_send_request(group_id, coordinator, + partitions) + self._admin._wait_for_futures([future]) + response = future.value + return self._list_offsets_send_process_response(response) + + def _list_offsets_send_request(self, group_id: str, coordinator: int, + partitions: List[TopicPartition]): + request = OffsetFetchRequest_v5(consumer_group=group_id, + topics=[(p.topic, [p.partition]) + for p in partitions]) + return self._admin._send_request_to_node(coordinator, request) + + def _list_offsets_send_process_response(self, response): + error_type = kerr.for_code(response.error_code) + if error_type is not kerr.NoError: + raise error_type("Error in list_offsets response") + + offsets = {} + for topic, partitions in response.topics: + for partition, offset, leader_epoch, metadata, error_code in partitions: + if error_code != 0: + raise Exception(f"Error code: {error_code}") + offsets[(topic, partition)] = OffsetAndMetadata( + offset, leader_epoch, metadata) + return offsets + + +class OffsetFetchResponse_v5(Response): + API_KEY = 9 + API_VERSION = 5 + SCHEMA = types.Schema( + ('throttle_time_ms', types.Int32), + ('topics', + types.Array( + ('topic', types.String('utf-8')), + ('partitions', + types.Array(('partition', types.Int32), ('offset', types.Int64), + ('leader_epoch', types.Int32), + ('metadata', types.String('utf-8')), + ('error_code', types.Int16))))), + ('error_code', types.Int16)) + + +class OffsetFetchRequest_v5(Request): + API_KEY = 9 + API_VERSION = 5 + RESPONSE_TYPE = OffsetFetchResponse_v5 + SCHEMA = OffsetFetchRequest_v3.SCHEMA diff --git a/tests/rptest/tests/control_character_flag_test.py b/tests/rptest/tests/control_character_flag_test.py index 98477c1f3a2a9..f6bdf32ea788c 100644 --- a/tests/rptest/tests/control_character_flag_test.py +++ b/tests/rptest/tests/control_character_flag_test.py @@ -45,12 +45,11 @@ def _validate_pre_upgrade(self, version): assert config.keys().isdisjoint( {self.feature_legacy_permit_control_char}) - def _perform_update(self, initial_version, version): - self._installer.install(self.redpanda.nodes, version) - self.redpanda.restart_nodes(self.redpanda.nodes) - - unique_versions = wait_for_num_versions(self.redpanda, 1) - assert ver_string(initial_version) not in unique_versions + def _perform_update(self, initial_version): + versions = self.load_version_range(initial_version)[1:] + for version in self.upgrade_through_versions(versions, + already_running=True): + self.logger.info(f"Updated to {version}") config = self._admin.get_cluster_config() assert config.keys() > {self.feature_legacy_permit_control_char} @@ -84,7 +83,7 @@ def test_upgrade_from_pre_v23_2(self, initial_version): # Creates a user with invalid control characters self._admin.create_user("my\nuser", "password", "SCRAM-SHA-256") - self._perform_update(initial_version, RedpandaInstaller.HEAD) + self._perform_update(initial_version) # Should still be able to create a user self._admin.create_user("my\notheruser", "password", "SCRAM-SHA-256") self._admin.patch_cluster_config( @@ -147,7 +146,7 @@ def test_validate_nag_message(self, initial_version): # Nag shouldn't be in logs assert not self._has_flag_nag() - self._perform_update(initial_version, RedpandaInstaller.HEAD) + self._perform_update(initial_version) assert self._has_flag_nag() diff --git a/tests/rptest/tests/controller_snapshot_test.py b/tests/rptest/tests/controller_snapshot_test.py index 1c0399bf95339..75ed7ca0fa36d 100644 --- a/tests/rptest/tests/controller_snapshot_test.py +++ b/tests/rptest/tests/controller_snapshot_test.py @@ -9,6 +9,7 @@ from rptest.tests.redpanda_test import RedpandaTest from rptest.services.redpanda import RESTART_LOG_ALLOW_LIST, RedpandaInstaller +from rptest.services.redpanda_installer import ver_triple, RedpandaVersionTriple, RedpandaVersionLine from rptest.services.cluster import cluster from rptest.services.admin import Admin from rptest.services.admin_ops_fuzzer import AdminOperationsFuzzer @@ -131,10 +132,21 @@ def __init__(self, redpanda, node): 'internal_rpc_status', 'internal_rpc_port' ] self.brokers = dict() - for broker in admin.get_brokers(node=node): + brokers_resp = admin.get_brokers(node=node) + for broker in brokers_resp: self.brokers[broker['node_id']] = dict( (k, broker.get(k)) for k in broker_fields) + # New brokers may not report the version yet, so gather the cluster version + # based on multiple brokers' reported version + versions = list( + set(broker['version'] for broker in brokers_resp + if 'version' in broker)) + assert len(versions) == 1, f"Unexpected versions: {versions}" + self.version_line = versions[0] + + self.version = ver_triple(self.version_line) + def _check_features(self, other, allow_upgrade_changes=False): for k, v in self.features_response.items(): if k == 'features': @@ -175,6 +187,26 @@ def to_set(configs): for t in self.topics: symdiff = to_set(self.topic_configs[t]) ^ to_set( other.topic_configs[t]) + + # Newer versions include a bugfix to change the source type of cleanup.policy from + # DYNAMIC_TOPIC_CONFIG to DEFAULT_CONFIG if the cleanup.policy wasn't specified as + # a config value when the topic is created. Versions that include the bugfix: + # * v23.3.12+ + # * v24.1.* + # * v23.3.11* versions, except for the v23.3.11 release, i.e. all development and + # PR builder redpanda versions built off of v23.3.11 before v23.3.12. + def contains_config_fix(version: RedpandaVersionTriple, + version_line: RedpandaVersionLine) -> bool: + V23_3_11_RELEASE = "v23.3.11 - 93f88bf377e558132eba04f81e4f83b033cec6e7" + return version >= RedpandaVersionTriple((23, 3, 12)) or \ + (version == RedpandaVersionTriple((23, 3, 11)) and version_line != V23_3_11_RELEASE) + + if contains_config_fix(self.version, self.version_line) ^ \ + contains_config_fix(other.version, other.version_line): + + symdiff -= set({('cleanup.policy', ('delete', + 'DYNAMIC_TOPIC_CONFIG'))}) + assert len(symdiff) == 0, f"configs differ, symdiff: {symdiff}" partitions = self.topic_partitions[t] @@ -397,6 +429,13 @@ def check(node, expected): for iter in range(5): self.redpanda.stop_node(joiner) + # wait for the joiner to step down in case it was the controller + joiner_id = self.redpanda.node_id(joiner) + admin.await_stable_leader(namespace='redpanda', + topic='controller', + check=lambda id: id != joiner_id, + timeout_s=30) + executed = 0 to_execute = random.randint(1, 5) while executed < to_execute: diff --git a/tests/rptest/tests/cpu_stress_injection_test.py b/tests/rptest/tests/cpu_stress_injection_test.py index 602176b81fdfd..d087800f41f0d 100644 --- a/tests/rptest/tests/cpu_stress_injection_test.py +++ b/tests/rptest/tests/cpu_stress_injection_test.py @@ -9,9 +9,22 @@ from rptest.services.admin import Admin from rptest.services.cluster import cluster from rptest.tests.redpanda_test import RedpandaTest +from ducktape.cluster.cluster import ClusterNode from ducktape.utils.util import wait_until +def stop_stress(admin: Admin, node: ClusterNode): + """ + Sends a request to the admin endpoint to stop stress fibers, returning True + if successful and False if there was an error. + """ + try: + admin.stress_fiber_stop(node) + except: + return False + return True + + class CpuStressInjectionTest(RedpandaTest): def __init__(self, test_context): super(CpuStressInjectionTest, self).__init__(test_context, @@ -31,16 +44,23 @@ def test_stress_fibers_ms(self): """ admin = Admin(self.redpanda) node = self.redpanda.nodes[0] - admin.stress_fiber_start(node, - 10, - min_ms_per_scheduling_point=30, - max_ms_per_scheduling_point=300) + try: + admin.stress_fiber_start(node, + 10, + min_ms_per_scheduling_point=30, + max_ms_per_scheduling_point=300) + except: + # Ignore errors, since the HTTP endpoint may be stalled behind a + # fiber. + pass try: wait_until(self.has_reactor_stalls, timeout_sec=10, backoff_sec=1) assert self.has_started_stress() finally: - admin.stress_fiber_stop(node) + wait_until(lambda: stop_stress(admin, node), + timeout_sec=30, + backoff_sec=1) @cluster(num_nodes=1) def test_stress_fibers_spins(self): @@ -49,10 +69,15 @@ def test_stress_fibers_spins(self): """ admin = Admin(self.redpanda) node = self.redpanda.nodes[0] - admin.stress_fiber_start(node, - 10, - min_spins_per_scheduling_point=1000, - max_spins_per_scheduling_point=100000) + try: + admin.stress_fiber_start(node, + 10, + min_spins_per_scheduling_point=1000, + max_spins_per_scheduling_point=100000) + except: + # Ignore errors, since the HTTP endpoint may be stalled behind a + # fiber. + pass try: # A test that relies on counting some number of cycles is subject @@ -60,7 +85,9 @@ def test_stress_fibers_spins(self): # reactor stalls, just look that we started stress fibers. wait_until(self.has_started_stress, timeout_sec=10, backoff_sec=1) finally: - admin.stress_fiber_stop(node) + wait_until(lambda: stop_stress(admin, node), + timeout_sec=30, + backoff_sec=1) @cluster(num_nodes=1) def test_misconfigured_stress_fibers(self): diff --git a/tests/rptest/tests/describe_topics_test.py b/tests/rptest/tests/describe_topics_test.py index 8753c2ca55abe..93bffee8828c8 100644 --- a/tests/rptest/tests/describe_topics_test.py +++ b/tests/rptest/tests/describe_topics_test.py @@ -76,8 +76,7 @@ def test_describe_topics_with_documentation_and_types(self): "cleanup.policy": ConfigProperty(config_type="STRING", value="DELETE", - doc_string="Default topic cleanup policy", - source_type="DYNAMIC_TOPIC_CONFIG"), + doc_string="Default topic cleanup policy"), "compression.type": ConfigProperty(config_type="STRING", value="producer", diff --git a/tests/rptest/tests/e2e_finjector.py b/tests/rptest/tests/e2e_finjector.py index ac1ffa0431b68..6d61da7eca60f 100644 --- a/tests/rptest/tests/e2e_finjector.py +++ b/tests/rptest/tests/e2e_finjector.py @@ -7,6 +7,7 @@ # the Business Source License, use of this software will be governed # by the Apache License, Version 2.0 +from contextlib import contextmanager import random import time import threading @@ -33,7 +34,8 @@ def const_delay(delay_seconds=10): class EndToEndFinjectorTest(EndToEndTest): def __init__(self, test_context): super(EndToEndFinjectorTest, self).__init__(test_context=test_context) - self.enable_failures = True + self.enable_manual = False + self.enable_loop = False self.scale = Scale(test_context) self.finjector_thread = None self.failure_length_provider = scale_dependent_length(self.scale) @@ -56,11 +58,49 @@ def configure_finjector(self, if delay_provider: self.failure_delay_provier = delay_provider - def start_finjector(self): - self.finjector_thread = threading.Thread( - target=self._failure_injector_loop, args=()) - self.finjector_thread.daemon = True - self.finjector_thread.start() + @contextmanager + def finj_thread(self): + """ + Get a context manager that holds the test in manual failure injection + mode. Recoverable failures such as suspended process or network issues + will be repaired on exit. + + :return: void + """ + try: + assert not self.enable_manual and not self.enable_loop + self.enable_loop = True + self.finjector_thread = threading.Thread( + target=self._failure_injector_loop, args=()) + self.finjector_thread.start() + yield + finally: + self.enable_loop = False + if self.finjector_thread: + self.finjector_thread.join() + self._cleanup() + + @contextmanager + def finj_manual(self): + """ + Get a context manager that holds the test in manual failure injection + mode. Recoverable failures such as suspended process or network issues + will be repaired on exit. Caller is supposed to make inject_failure() + calls inside the `with` statement. + + :return: a callable with a single failure spec argument + """ + try: + assert not self.enable_manual and not self.enable_loop + self.enable_manual = True + + def callable(spec): + return self.inject_failure(spec) + + yield callable + finally: + self.enable_manual = False + self._cleanup() def random_failure_spec(self): f_type = random.choice(self.allowed_failures) @@ -70,6 +110,7 @@ def random_failure_spec(self): return FailureSpec(node=node, type=f_type, length=length) def inject_failure(self, spec): + assert self.enable_manual or self.enable_loop f_injector = make_failure_injector(self.redpanda) f_injector.inject_failure(spec) @@ -80,8 +121,7 @@ def _next_failure(self): return self.random_failure_spec() def _failure_injector_loop(self): - - while self.enable_failures: + while self.enable_loop: f_injector = make_failure_injector(self.redpanda) f_injector.inject_failure(self._next_failure()) @@ -90,8 +130,6 @@ def _failure_injector_loop(self): f"waiting {delay} seconds before next failure") time.sleep(delay) - def teardown(self): - self.enable_failures = False - if self.finjector_thread: - self.finjector_thread.join() + def _cleanup(self): make_failure_injector(self.redpanda)._heal_all() + make_failure_injector(self.redpanda)._continue_all() diff --git a/tests/rptest/tests/e2e_shadow_indexing_test.py b/tests/rptest/tests/e2e_shadow_indexing_test.py index 4a830072e78d1..96f6e4036d57b 100644 --- a/tests/rptest/tests/e2e_shadow_indexing_test.py +++ b/tests/rptest/tests/e2e_shadow_indexing_test.py @@ -1310,7 +1310,7 @@ class EndToEndThrottlingTest(RedpandaTest): def __init__(self, test_context): self.si_settings = SISettings( test_context, - log_segment_size=1024, + log_segment_size=1024 * 1024, fast_uploads=True, # Set small throughput limit to trigger throttling cloud_storage_max_throughput_per_shard=8 * 1024 * 1024) @@ -1388,17 +1388,6 @@ def test_throttling(self, cloud_storage_type): self.logger.info("Start consumer") self.consume() - self.logger.info("Stop nodes") - for node in self.redpanda.nodes: - self.redpanda.stop_node(node, timeout=120) - - self.logger.info("Start nodes") - for node in self.redpanda.nodes: - self.redpanda.start_node(node, timeout=120) - - self.logger.info("Restart consumer") - self.consume() - times_throttled = self.get_throttling_metric() self.logger.info(f"Consumer was throttled {times_throttled} times") assert times_throttled > 0, f"Expected consumer to be throttled, metric value: {times_throttled}" diff --git a/tests/rptest/tests/end_to_end.py b/tests/rptest/tests/end_to_end.py index 393fa1d49cb34..c0c12dde7e950 100644 --- a/tests/rptest/tests/end_to_end.py +++ b/tests/rptest/tests/end_to_end.py @@ -79,6 +79,7 @@ def __init__(self, def start_redpanda(self, num_nodes=1, + num_started_nodes=None, extra_rp_conf=None, si_settings=None, environment=None, @@ -122,7 +123,11 @@ def start_redpanda(self, self.redpanda._installer.install(self.redpanda.nodes, version_to_install) - self.redpanda.start(auto_assign_node_id=new_bootstrap, + started_nodes = None + if num_started_nodes is not None: + started_nodes = self.redpanda.nodes[:num_started_nodes] + self.redpanda.start(nodes=started_nodes, + auto_assign_node_id=new_bootstrap, omit_seeds_on_idx_one=not new_bootstrap) if version_to_install and install_opts.num_to_upgrade > 0: # Perform the upgrade rather than starting each node on the diff --git a/tests/rptest/tests/log_level_test.py b/tests/rptest/tests/log_level_test.py index 4c90b8aa258a9..d0ff84908363a 100644 --- a/tests/rptest/tests/log_level_test.py +++ b/tests/rptest/tests/log_level_test.py @@ -7,11 +7,13 @@ # the Business Source License, use of this software will be governed # by the Apache License, Version 2.0 +import time import ducktape.errors import requests.exceptions import urllib.parse import json +from ducktape.mark import parametrize from ducktape.utils.util import wait_until from rptest.services.cluster import cluster from rptest.tests.redpanda_test import RedpandaTest @@ -101,6 +103,60 @@ def test_log_level_control(self): backoff_sec=1, err_msg="Never saw message") + @cluster(num_nodes=1) + @parametrize(loggers=("admin_api_server", "raft")) + @parametrize(loggers=("raft", "admin_api_server")) + def test_log_level_multiple_expiry(self, loggers=tuple[str, str]): + """ + Check that more than one logger can be in a modified level and be expired correctly + see https://redpandadata.atlassian.net/browse/CORE-96 + """ + admin = Admin(self.redpanda) + node = self.redpanda.nodes[0] + + first_logger, second_logger = loggers + # set two loggers to trace, expect that both of them expires in a timely fashion + with self.redpanda.monitor_log(node) as mon: + admin.set_log_level(first_logger, "trace", expires=10) + time.sleep(1) + admin.set_log_level(second_logger, "trace", expires=10) + mon.wait_until(f"Expiring log level for {{{first_logger}}}", + timeout_sec=15, + backoff_sec=1, + err_msg=f"Never saw Expiring for {first_logger}") + mon.wait_until(f"Expiring log level for {{{second_logger}}}", + timeout_sec=15, + backoff_sec=1, + err_msg=f"Never saw Expiring for {second_logger}") + + @cluster(num_nodes=1) + def test_log_level_persist_a_never_expire_request(self): + """ + check that this sequence of actions + set log-level admin_api_server trace 10 + set log-level admin_api_server error 0 + + never resets the logger to the info level + """ + admin = Admin(self.redpanda) + node = self.redpanda.nodes[0] + + with self.redpanda.monitor_log(node) as mon: + admin.set_log_level("admin_api_server", "trace", expires=10) + time.sleep(1) + admin.set_log_level("admin_api_server", "error", expires=0) + + try: + mon.wait_until("Expiring log level for {admin_api_server}", + timeout_sec=15, + backoff_sec=1) + assert False, "Should not have seen message" + except ducktape.errors.TimeoutError: + pass + + level = admin.get_log_level("admin_api_server")[0]["level"] + assert level == "error", f"expected level=error, got {level=}" + @cluster(num_nodes=3) def test_max_expiry(self): admin = Admin(self.redpanda) diff --git a/tests/rptest/tests/maintenance_test.py b/tests/rptest/tests/maintenance_test.py index 7548c745e0c24..9993c8a2540ae 100644 --- a/tests/rptest/tests/maintenance_test.py +++ b/tests/rptest/tests/maintenance_test.py @@ -29,9 +29,9 @@ def __init__(self, *args, **kwargs): # Vary partition count relative to num_cpus. This is to ensure that # leadership is moved back to a node that exits maintenance. num_cpus = self.redpanda.get_node_cpu_count() - self.topics = (TopicSpec(partition_count=num_cpus * 3, + self.topics = (TopicSpec(partition_count=num_cpus * 5, replication_factor=3), - TopicSpec(partition_count=num_cpus * 3, + TopicSpec(partition_count=num_cpus * 10, replication_factor=3)) self.admin = Admin(self.redpanda) self.rpk = RpkTool(self.redpanda) @@ -131,8 +131,12 @@ def _enable_maintenance(self, node): """ self.logger.debug( f"Checking that node {node.name} has a leadership role") + # In case the node is unlucky and doesn't get any leaders "naturally", + # we have to wait for the leadership balancer to do its job. We have to wait + # at least 1 minute for it to unmute just restarted nodes and perform another + # tick. Wait more than leader_balancer_idle_timeout (2 minutes) just to be sure. wait_until(lambda: self._has_leadership_role(node), - timeout_sec=60, + timeout_sec=150, backoff_sec=10) self.logger.debug( diff --git a/tests/rptest/tests/node_folder_deletion_test.py b/tests/rptest/tests/node_folder_deletion_test.py index b7d15b7e28e84..fdf06f6efe800 100644 --- a/tests/rptest/tests/node_folder_deletion_test.py +++ b/tests/rptest/tests/node_folder_deletion_test.py @@ -94,7 +94,22 @@ def test_deleting_node_folder(self): wait_until(lambda: producer.produce_status.acked > 200000, timeout_sec=120, backoff_sec=0.5) + admin = Admin(self.redpanda) + + # validate that the node with deleted folder is recognized as offline + def removed_node_is_reported_offline(): + cluster_health = admin.get_cluster_health_overview() + return id in cluster_health['nodes_down'] + + wait_until( + removed_node_is_reported_offline, + timeout_sec=20, + backoff_sec=0.5, + err_msg= + f"Node {id} is expected to be marked as offline as it was replaced by new node" + ) + # decommission a node that has been cleared admin.decommission_broker(id) waiter = NodeDecommissionWaiter(self.redpanda, diff --git a/tests/rptest/tests/nodes_decommissioning_test.py b/tests/rptest/tests/nodes_decommissioning_test.py index dc4c91d28cdb1..0235f8efff432 100644 --- a/tests/rptest/tests/nodes_decommissioning_test.py +++ b/tests/rptest/tests/nodes_decommissioning_test.py @@ -30,7 +30,7 @@ from rptest.clients.types import TopicSpec from rptest.tests.end_to_end import EndToEndTest from rptest.services.admin import Admin -from rptest.services.redpanda import CHAOS_LOG_ALLOW_LIST, RESTART_LOG_ALLOW_LIST, RedpandaService, make_redpanda_service, SISettings +from rptest.services.redpanda import CHAOS_LOG_ALLOW_LIST, RESTART_LOG_ALLOW_LIST, RedpandaService, SISettings from rptest.utils.node_operations import NodeDecommissionWaiter @@ -422,6 +422,67 @@ def test_decommissioning_cancel_ongoing_movements(self): self.verify() + @skip_debug_mode + @cluster(num_nodes=6) + def test_learner_gap_metrics(self): + self.start_redpanda() + # set small segment size to calculate the gap correctly + self.redpanda.set_cluster_config({"log_segment_size": 1024 * 1024}) + self._create_topics() + + self.start_producer() + self.start_consumer() + # set recovery rate to small value to stop moves + self._set_recovery_rate(1) + + def calculate_total_learners_gap() -> int | None: + gap = self.redpanda.metrics_sample("learners_gap_bytes") + if gap is None: + return None + return sum(g.value for g in gap.samples) + + assert calculate_total_learners_gap( + ) == 0, "when there are no pending partition movements the reported gap should be equal to 0" + + to_decommission = random.choice(self.redpanda.nodes) + to_decommission_id = self.redpanda.node_id(to_decommission) + + self.logger.info(f"decommissioning node: {to_decommission_id}", ) + self._decommission(to_decommission_id) + + def learner_gap_reported(decommissioned_node_id: int): + total_gap = calculate_total_learners_gap() + p_size = self.redpanda.metrics_sample("partition_size") + if not total_gap or not p_size: + return False + total_size = sum( + ps.value for ps in p_size.samples + if self.redpanda.node_id(ps.node) == decommissioned_node_id) + + self.logger.info( + f"decommissioned node total size: {total_size}, total_gap: {total_gap}" + ) + assert total_gap < total_size, "gap can not be larger than the size of partitions" + # assume that the total gap is equal to the total size of + # decommissioned node with the tolerance of 5% + return (total_size - total_gap) < total_size * 0.05 + + wait_until(lambda: learner_gap_reported(to_decommission_id), + timeout_sec=60, + backoff_sec=1) + self._set_recovery_rate(100 * 1024 * 1024) + # wait for decommissioned node to be removed + self._wait_for_node_removed(to_decommission_id) + + # Stop the decommissioned node, because redpanda internally does not + # fence it, it is the responsibility of external orchestrator to + # stop the node they intend to remove. + # This can be removed when we change redpanda to prevent decommissioned nodes + # from responding to client Kafka requests. + self.redpanda.stop_node(to_decommission) + + self.verify() + @cluster(num_nodes=6, log_allow_list=RESTART_LOG_ALLOW_LIST) def test_recommissioning_node(self): self.start_redpanda() @@ -616,6 +677,13 @@ def test_decommissioning_rebalancing_node(self, shutdown_decommissioned): self.start_producer() self.start_consumer() + # wait for more data to be produced, so that the cluster re-balancing will not finish immediately + wait_until(lambda: self.producer.produce_status.acked > + (self.msg_count / 2), + timeout_sec=240, + backoff_sec=1) + # set recovery rate to small value but allow the controller to recover + self._set_recovery_rate(10 * 1024) # throttle recovery self.redpanda.clean_node(self.redpanda.nodes[-1], preserve_current_install=True) diff --git a/tests/rptest/tests/offset_for_leader_epoch_test.py b/tests/rptest/tests/offset_for_leader_epoch_test.py index a4ebf56dca455..0aa19fb906d56 100644 --- a/tests/rptest/tests/offset_for_leader_epoch_test.py +++ b/tests/rptest/tests/offset_for_leader_epoch_test.py @@ -20,6 +20,7 @@ from rptest.clients.types import TopicSpec from rptest.clients.rpk import RpkTool from rptest.services.rpk_producer import RpkProducer +from rptest.utils.mode_checks import skip_debug_mode class OffsetForLeaderEpochTest(PreallocNodesTest): @@ -166,6 +167,7 @@ def test_offset_for_leader_epoch(self): # but the requested assert o.error == '' and o.leader_epoch == 15000 and o.epoch_end_offset == -1 + @skip_debug_mode @cluster(num_nodes=6, log_allow_list=RESTART_LOG_ALLOW_LIST) def test_offset_for_leader_epoch_transfer(self): diff --git a/tests/rptest/tests/partition_balancer_test.py b/tests/rptest/tests/partition_balancer_test.py index f028f5a13c97b..52f7e5991b769 100644 --- a/tests/rptest/tests/partition_balancer_test.py +++ b/tests/rptest/tests/partition_balancer_test.py @@ -1142,3 +1142,67 @@ def test_transfer_controller_leadership(self): target_id=transfer_to_idx) self.wait_until_ready() + + @skip_debug_mode + @cluster(num_nodes=7, log_allow_list=CHAOS_LOG_ALLOW_LIST) + def test_recovery_mode_rebalance_finish(self): + """ + Test that rebalancing on node add correctly finishes + if some (but not all) nodes were in recovery mode. + """ + + # start first 3 nodes and create some partitions on them + self.start_redpanda(num_nodes=5, + num_started_nodes=3, + new_bootstrap=True) + self.topic = TopicSpec(partition_count=50) + self.client().create_topic(self.topic) + + self.start_producer(1) + self.start_consumer(1) + self.await_startup() + + # restart seed nodes in recovery mode + seed_nodes = self.redpanda.nodes[:3] + self.redpanda.restart_nodes( + seed_nodes, + auto_assign_node_id=True, + omit_seeds_on_idx_one=False, + override_cfg_params={"recovery_mode_enabled": True}) + + # add 2 more nodes and make sure the balancer runs on one of them + # (it can't run on seed nodes because of recovery mode) + joiner_nodes = self.redpanda.nodes[3:] + for node in joiner_nodes: + self.redpanda.start_node(node, + auto_assign_node_id=True, + omit_seeds_on_idx_one=False) + self.redpanda.wait_for_membership(first_start=False) + + admin = Admin(self.redpanda) + + admin.transfer_leadership_to(namespace='redpanda', + topic='controller', + partition=0, + target_id=self.redpanda.node_id( + joiner_nodes[0])) + + # the balancer will stall because not all partitions are moveable + self.wait_until_status(lambda s: s["status"] == "stalled") + + # restart seed nodes in normal mode + self.redpanda.restart_nodes(seed_nodes, auto_assign_node_id=True) + self.redpanda.wait_for_membership(first_start=False) + + self.wait_until_ready() + + # check that partition counts are balanced + partition_counts = [ + len(admin.get_partitions(node=n)) for n in self.redpanda.nodes + ] + self.logger.info(f"partition counts: {partition_counts}") + avg = sum(partition_counts) / len(partition_counts) + assert all(abs(c - avg) / avg < 0.05 for c in partition_counts), \ + "partition counts not balanced" + + self.run_validation(consumer_timeout_sec=CONSUMER_TIMEOUT) diff --git a/tests/rptest/tests/raft_availability_test.py b/tests/rptest/tests/raft_availability_test.py index b96264df74fd1..59380b82cc8cb 100644 --- a/tests/rptest/tests/raft_availability_test.py +++ b/tests/rptest/tests/raft_availability_test.py @@ -187,7 +187,7 @@ def test_one_node_down(self): replication=3, timeout_s=ELECTION_TIMEOUT * 2) - leader_node = self.redpanda.get_node(initial_leader_id) + leader_node = self.redpanda.get_node_by_id(initial_leader_id) self.logger.info( f"Initial leader {initial_leader_id} {leader_node.account.hostname}" ) @@ -195,11 +195,12 @@ def test_one_node_down(self): # Priority mechanism should reliably select next replica in list expect_new_leader_id = replicas[1] - expect_new_leader_node = self.redpanda.get_node(expect_new_leader_id) + expect_new_leader_node = self.redpanda.get_node_by_id( + expect_new_leader_id) observer_node_id = (set(replicas) - {expect_new_leader_id, initial_leader_id}).pop() - observer_node = self.redpanda.get_node(observer_node_id) + observer_node = self.redpanda.get_node_by_id(observer_node_id) self.logger.info( f"Tracking stats on observer node {observer_node_id} {observer_node.account.hostname}" ) @@ -250,7 +251,7 @@ def test_one_node_down(self): # elections taking more iterations than expected, once we have a less contended # test environment to execute in. observer_metrics.expect([ - ("vectorized_raft_leadership_changes_total", lambda a, b: b > a), + ("vectorized_raft_leadership_changes_total", lambda a, b: b == a), ("vectorized_raft_leader_for", lambda a, b: int(b) == 0), ("vectorized_raft_received_vote_requests_total", lambda a, b: b > a), @@ -276,9 +277,9 @@ def test_two_nodes_down(self): self.ping_pong().ping_pong() - leader_node = self.redpanda.get_node(initial_leader_id) + leader_node = self.redpanda.get_node_by_id(initial_leader_id) other_node_id = (set(replicas) - {initial_leader_id}).pop() - other_node = self.redpanda.get_node(other_node_id) + other_node = self.redpanda.get_node_by_id(other_node_id) self.logger.info( f"Stopping {initial_leader_id} ({leader_node.account.hostname}) and {other_node_id} ({other_node.account.hostname})" @@ -290,7 +291,7 @@ def test_two_nodes_down(self): self._expect_unavailable() # Bring back one node (not the original leader) - self.redpanda.start_node(self.redpanda.get_node(other_node_id)) + self.redpanda.start_node(self.redpanda.get_node_by_id(other_node_id)) hosts = [ n.account.hostname for n in self.redpanda.nodes @@ -327,7 +328,7 @@ def test_leader_restart(self): the original leader stopped. """ initial_leader_id, replicas = self._wait_for_leader() - initial_leader_node = self.redpanda.get_node(initial_leader_id) + initial_leader_node = self.redpanda.get_node_by_id(initial_leader_id) self.logger.info( f"Stopping initial leader {initial_leader_id} {initial_leader_node.account.hostname}" @@ -337,7 +338,7 @@ def test_leader_restart(self): new_leader_id, _ = self._wait_for_leader( lambda l: l is not None and l != initial_leader_id) self.logger.info( - f"New leader is {new_leader_id} {self.redpanda.get_node(new_leader_id).account.hostname}" + f"New leader is {new_leader_id} {self.redpanda.get_node_by_id(new_leader_id).account.hostname}" ) self.logger.info( @@ -362,6 +363,64 @@ def test_leader_restart(self): time.sleep(ELECTION_TIMEOUT) assert new_leader_id == self._get_leader()[0] + @cluster(num_nodes=3) + def test_leadership_transfer(self): + """ + Validate that when a leadership is transfered to another node, the new leader is able to + continue serving requests. + """ + initial_leader_id, replicas = self._wait_for_leader() + initial_leader_node = self.redpanda.get_node_by_id(initial_leader_id) + + metric_checks = {} + for n in self.redpanda.nodes: + metric_checks[self.redpanda.node_id(n)] = MetricCheck( + self.logger, self.redpanda, n, + re.compile("vectorized_raft_leadership_changes_total"), + {'topic': self.topic}) + + self.logger.info(f"Transferring leadership of {self.topic}/0") + admin = Admin(self.redpanda) + admin.transfer_leadership_to(topic=self.topic, + namespace="kafka", + partition=0, + target_id=None, + leader_id=initial_leader_id) + hosts = [n.account.hostname for n in self.redpanda.nodes] + new_leader_id = admin.await_stable_leader( + topic=self.topic, + partition=0, + hosts=hosts, + check=lambda l: l is not None and l != initial_leader_id) + new_leader_node = self.redpanda.get_node_by_id(new_leader_id) + assert new_leader_node is not None + self.logger.info( + f"New leader is {new_leader_id} {new_leader_node.account.hostname}" + ) + + def metrics_updated(): + results = [] + for [id, metric_check] in metric_checks.items(): + # the metric should be updated only on the node that was elected as a leader + if id == new_leader_id: + results.append( + metric_check.evaluate([ + ("vectorized_raft_leadership_changes_total", + lambda initial, current: current == initial + 1), + ])) + else: + results.append( + metric_check.evaluate([ + ("vectorized_raft_leadership_changes_total", + lambda initial, current: current == initial), + ])) + + return all(results) + + wait_until( + metrics_updated, 30, 1, + "Leadership changes metric should be updated only on the leader") + @cluster(num_nodes=4) @parametrize(acks=1) @parametrize(acks=-1) @@ -417,10 +476,10 @@ def test_leader_transfers_recovery(self, acks): initial_leader_id = leader_node_id for n in range(0, transfer_count): target_idx = (initial_leader_id + n) % len(self.redpanda.nodes) - target_node_id = target_idx + 1 + target_node_by_id_id = target_idx + 1 self._transfer_leadership(admin, "kafka", self.topic, - target_node_id) + target_node_by_id_id) # Wait til we can see producer progressing, to avoid a situation where # we do leadership transfers so quickly that we stall the producer @@ -455,7 +514,7 @@ def test_follower_isolation(self): self._expect_available() - leader_node = self.redpanda.get_node(initial_leader_id) + leader_node = self.redpanda.get_node_by_id(initial_leader_id) self.logger.info( f"Initial leader {initial_leader_id} {leader_node.account.hostname}" ) @@ -481,7 +540,7 @@ def test_follower_isolation(self): # isolate one of the followers fi.inject_failure( FailureSpec(FailureSpec.FAILURE_ISOLATE, - self.redpanda.get_node(follower))) + self.redpanda.get_node_by_id(follower))) # expect messages to be produced and consumed without a timeout connection = self.ping_pong() @@ -503,7 +562,7 @@ def test_id_allocator_leader_isolation(self): replication=3) initial_leader_id = admin.get_partition_leader( namespace='kafka_internal', topic='id_allocator', partition=0) - leader_node = self.redpanda.get_node(initial_leader_id) + leader_node = self.redpanda.get_node_by_id(initial_leader_id) self.logger.info( f"kafka_internal/id_allocator/0 leader: {initial_leader_id}, node: {leader_node.account.hostname}" ) @@ -514,7 +573,7 @@ def test_id_allocator_leader_isolation(self): # isolate id_allocator fi.inject_failure( FailureSpec(FailureSpec.FAILURE_ISOLATE, - self.redpanda.get_node(initial_leader_id))) + self.redpanda.get_node_by_id(initial_leader_id))) # expect messages to be produced and consumed without a timeout connection = self.ping_pong() diff --git a/tests/rptest/tests/read_replica_e2e_test.py b/tests/rptest/tests/read_replica_e2e_test.py index 8daa60abcd017..38e18738eabf4 100644 --- a/tests/rptest/tests/read_replica_e2e_test.py +++ b/tests/rptest/tests/read_replica_e2e_test.py @@ -299,16 +299,19 @@ def set_lwm(new_lwm): def check_lwm(new_lwm): topics_info = list(rpk.describe_topic(self.topic_name)) + self.logger.info(f"{self.topic_name} topics_info: {topics_info}") + if len(topics_info) == 0: + return False topic_info = topics_info[0] for t in topics_info: if t.id == 0: topic_info = t break - assert topic_info.start_offset == new_lwm, topic_info + return topic_info.start_offset == new_lwm, topic_info - check_lwm(0) + wait_until(lambda: check_lwm(0), timeout_sec=10, backoff_sec=1) set_lwm(5) - check_lwm(5) + wait_until(lambda: check_lwm(5), timeout_sec=10, backoff_sec=1) def clusters_report_identical_lwms(): return lwms_are_identical(self.logger, self.redpanda, diff --git a/tests/rptest/tests/recovery_mode_test.py b/tests/rptest/tests/recovery_mode_test.py index f5721ee59c30f..4d7f29121b5fe 100644 --- a/tests/rptest/tests/recovery_mode_test.py +++ b/tests/rptest/tests/recovery_mode_test.py @@ -444,7 +444,9 @@ def get_hwms(topic=None): ts = topics if topic is None else [topic] ret = dict() for topic in ts: + self.logger.info(f"describing {topic}") for p in rpk.describe_topic(topic): + self.logger.info(f"returned partition {p.high_watermark}") ret[f"{topic}/{p.id}"] = p.high_watermark return ret @@ -513,10 +515,32 @@ def all_disabled_shut_down(): self.redpanda.restart_nodes(self.redpanda.nodes) self.redpanda.wait_for_membership(first_start=False) + def all_have_leaders(): + for n in self.redpanda.started_nodes(): + all_partitions_have_leaders = all([ + partition['leader'] != -1 + for partition in admin.get_partitions(node=n) + ]) + if not all_partitions_have_leaders: + return False + return True + + wait_until( + all_have_leaders, + 30, + backoff_sec=1, + err_msg="Failed waiting for partition leadership to stabilize") + # test that partitions are still disabled after restart assert all_disabled_shut_down() + def hw_is_present(): + hwms = get_hwms() + return "mytopic1/0" in hwms + + wait_until(hw_is_present, 30, 1, "high watermark is not present") + self.logger.info(f"current hw: {get_hwms()}, hwms3: {hwms3}") assert get_hwms() == hwms3 self.logger.info("enabling partitions back") diff --git a/tests/rptest/tests/rpk_acl_test.py b/tests/rptest/tests/rpk_acl_test.py index d96f2c682bdd7..26d2b4423c170 100644 --- a/tests/rptest/tests/rpk_acl_test.py +++ b/tests/rptest/tests/rpk_acl_test.py @@ -122,7 +122,8 @@ def test_create_update(self): # We check that we can list the topics: assert topic in topic_list - out = self._rpk.sasl_update_user(self.username, new_password) + out = self._rpk.sasl_update_user(self.username, new_password, + self.mechanism) assert f'Updated user "{self.username}" successfully' in out with expect_exception( diff --git a/tests/rptest/tests/rpk_cluster_test.py b/tests/rptest/tests/rpk_cluster_test.py index 7d222d41fb908..20c763a6d34e6 100644 --- a/tests/rptest/tests/rpk_cluster_test.py +++ b/tests/rptest/tests/rpk_cluster_test.py @@ -15,7 +15,7 @@ import json from rptest.services.cluster import cluster -from rptest.services.redpanda import RESTART_LOG_ALLOW_LIST +from rptest.services.redpanda import RESTART_LOG_ALLOW_LIST, MetricSamples, MetricsEndpoint from rptest.util import expect_exception, get_cluster_license, get_second_cluster_license from ducktape.utils.util import wait_until from rptest.util import wait_until_result @@ -208,6 +208,25 @@ def test_cluster_down(self): else: assert False, f"Unexpected success: '{r}'" + def _get_license_expiry(self) -> int: + METRICS_NAME = "cluster_features_enterprise_license_expiry_sec" + + def get_metrics_value(metrics_endpoint: MetricsEndpoint) -> int: + metrics = self.redpanda.metrics_sample( + sample_pattern=METRICS_NAME, metrics_endpoint=metrics_endpoint) + assert isinstance(metrics, MetricSamples), \ + f'Failed to get metrics for {METRICS_NAME}' + + samples = [sample for sample in metrics.samples] + assert len(samples) == len(self.redpanda.nodes), \ + f'Invalid number of samples: {len(samples)}' + return int(samples[0].value) + + internal_val = get_metrics_value(MetricsEndpoint.METRICS) + public_val = get_metrics_value(MetricsEndpoint.PUBLIC_METRICS) + assert internal_val == public_val, f"Mismatch: {internal_val} != {public_val}" + return internal_val + @cluster(num_nodes=3) def test_upload_and_query_cluster_license_rpk(self): """ @@ -220,6 +239,12 @@ def test_upload_and_query_cluster_license_rpk(self): "Skipping test, REDPANDA_SAMPLE_LICENSE env var not found") return + wait_until(lambda: self._get_license_expiry() == -1, + timeout_sec=10, + backoff_sec=1, + retry_on_exc=True, + err_msg="Unset license should return a -1 expiry") + with tempfile.NamedTemporaryFile() as tf: tf.write(bytes(license, 'UTF-8')) tf.seek(0) @@ -237,15 +262,22 @@ def obtain_license(): retry_on_exc=True, err_msg="unable to retrieve license information") + wait_until( + lambda: self._get_license_expiry() > 0, + timeout_sec=10, + backoff_sec=1, + retry_on_exc=True, + err_msg="The expiry metric should be positive with a valid license" + ) + expected_license = { - 'expires': - "Jul 11 2122", - 'organization': - 'redpanda-testing', - 'type': - 'enterprise', + 'expires': "Jul 11 2122", + 'organization': 'redpanda-testing', + 'type': 'enterprise', 'checksum_sha256': - '2730125070a934ca1067ed073d7159acc9975dc61015892308aae186f7455daf' + '2730125070a934ca1067ed073d7159acc9975dc61015892308aae186f7455daf', + 'expires_unix': 4813252273, + 'license_expired': False, } result = json.loads(rp_license) assert expected_license == result, result diff --git a/tests/rptest/tests/rpk_generate_test.py b/tests/rptest/tests/rpk_generate_test.py index c47f8fc985b32..c24349df3c08b 100644 --- a/tests/rptest/tests/rpk_generate_test.py +++ b/tests/rptest/tests/rpk_generate_test.py @@ -21,28 +21,32 @@ def __init__(self, ctx): self._ctx = ctx self._rpk = RpkTool(self.redpanda) - @cluster(num_nodes=3) + @cluster(num_nodes=1) def test_generate_grafana(self): """ - Test that rpk generate grafana-dashboard will generate the required dashboard - and that it's a proper JSON file. - """ + Test that rpk generate grafana-dashboard will generate the required dashboard + and that it's a proper JSON file. + """ # dashboard is the dictionary of the current dashboards and their title. - dashboards = { - "operations": "Redpanda Ops Dashboard", - "consumer-metrics": "Kafka Consumer", - "consumer-offsets": "Kafka Consumer Offsets", - "topic-metrics": "Kafka Topic Metrics" - } - for name, expectedTitle in dashboards.items(): - out = self._rpk.generate_grafana(name) + dashboards = [ + "operations", "consumer-metrics", "consumer-offsets", + "topic-metrics", "legacy" + ] + for name in dashboards: + datasource = "" + metrics_endpoint = "" + if name == "legacy": + datasource = "redpanda" + n = self.redpanda.get_node(1) + metrics_endpoint = self.redpanda.admin_endpoint( + n) + "/public_metrics" + out = self._rpk.generate_grafana(name, + datasource=datasource, + metrics_endpoint=metrics_endpoint) try: - dash = json.loads(out) - - # We only validate one known value, the main goal is to identify if it's a valid JSON - title = dash["title"] - assert title == expectedTitle, f"Received dashboard title: '{title}', expected: '{expectedTitle}'" + # We just need to assert that it was able to retrieve and parse the dashboard as valid json + json.loads(out) except json.JSONDecodeError as err: self.logger.error( f"unable to parse generated' {name}' dashboard : {err}") diff --git a/tests/rptest/tests/scaling_up_test.py b/tests/rptest/tests/scaling_up_test.py index 9b36a222f58fb..69d5d617c6d9f 100644 --- a/tests/rptest/tests/scaling_up_test.py +++ b/tests/rptest/tests/scaling_up_test.py @@ -218,6 +218,11 @@ def verify(self): @cluster(num_nodes=7) @matrix(partition_count=[1, 20]) def test_adding_nodes_to_cluster(self, partition_count): + # The test implicitly assumes that all internal topics are rf=1 + # As we add more nodes ensure that the health manager does not + # upreplicate the topics and violate the assumption + self.redpanda.add_extra_rp_conf( + {"internal_topic_replication_factor": 1}) # start single node cluster self.redpanda.start(nodes=[self.redpanda.nodes[0]]) # create some topics @@ -394,6 +399,17 @@ def _kafka_usage(self, nodes): return usage_per_node + def _partition_sizes(self, topic, nodes): + sizes = defaultdict(int) + metrics = self.redpanda.metrics_sample("partition_size", nodes=nodes) + if not metrics: + return {} + + for s in metrics.samples: + if s.labels['topic'] == topic: + sizes[self.redpanda.node_id(s.node)] += s.value + return sizes + @skip_debug_mode @cluster(num_nodes=7) def test_fast_node_addition(self): @@ -492,3 +508,121 @@ def verify_disk_usage(usage: dict, added_ids: list, percentage: float): assert self.consumer.consumer_status.validator.invalid_reads == 0, \ f"Invalid reads in topic: {topic.name}, invalid reads count: " "{self.consumer.consumer_status.validator.invalid_reads}" + + @skip_debug_mode + @cluster(num_nodes=7) + @matrix(use_topic_property=[True, False]) + def test_moves_with_local_retention(self, use_topic_property): + + log_segment_size = 2 * 1024 * 1024 + total_segments_per_partition = 40 + partition_cnt = 20 + msg_size = 16 * 1024 # 16 KiB + data_size = log_segment_size * total_segments_per_partition * partition_cnt + msg_cnt = data_size // msg_size + + # configure and start redpanda + extra_rp_conf = { + 'cloud_storage_segment_max_upload_interval_sec': 10, + 'cloud_storage_manifest_max_upload_interval_sec': 10, + } + # shadow indexing is required when we want to leverage fast partition movements + si_settings = SISettings(test_context=self.test_context, + log_segment_size=log_segment_size, + retention_local_strict=False) + + self.redpanda.set_extra_rp_conf(extra_rp_conf) + self.redpanda.set_si_settings(si_settings) + self.redpanda.start(nodes=self.redpanda.nodes[0:4]) + topic = TopicSpec(replication_factor=3, + partition_count=partition_cnt, + redpanda_remote_write=True, + redpanda_remote_read=True) + + total_replicas = 3 * partition_cnt + + self.client().create_topic(topic) + requested_local_retention = log_segment_size * 15 + + if use_topic_property: + self.client().alter_topic_config( + topic.name, TopicSpec.PROPERTY_RETENTION_LOCAL_TARGET_BYTES, + requested_local_retention) + else: + self.redpanda.set_cluster_config({ + "retention_local_target_bytes_default": + requested_local_retention + }) + + self.logger.info( + f"Producing {data_size} bytes of data in {msg_cnt} total messages") + self.producer = KgoVerifierProducer( + self.test_context, + self.redpanda, + topic.name, + msg_size=msg_size, + msg_count=msg_cnt, + custom_node=self.preallocated_nodes) + self.producer.start() + self.producer.wait() + + # add fifth node + self.redpanda.start_node(self.redpanda.nodes[4]) + self.wait_for_partitions_rebalanced(total_replicas=total_replicas, + timeout_sec=self.rebalance_timeout) + + def print_disk_usage(usage): + for n, b in usage.items(): + self.logger.info( + f"node: {n} total partitions size: {b/(1024*1024):02} Mb") + + def disk_usage_correct(nodes, node_id): + size_per_node = self._partition_sizes(topic.name, nodes) + print_disk_usage(size_per_node) + replicas_per_node = self._topic_replicas_per_node() + node_replicas = replicas_per_node[topic.name][node_id] + + target_size = node_replicas * requested_local_retention + + current_usage = size_per_node[node_id] + tolerance = 0.2 + max = target_size * (1.0 + tolerance) + min = target_size * (1.0 - tolerance) + self.logger.info( + f"node {node_id} target size: {target_size}, current size: {size_per_node[node_id]}, expected range ({min}, {max})" + ) + return current_usage > min and current_usage < max + + first_new_id = self.redpanda.node_id(self.redpanda.nodes[4]) + + wait_until( + lambda: disk_usage_correct(self.redpanda.nodes[0:5], first_new_id), + timeout_sec=60, + backoff_sec=1, + err_msg="Timeout waiting for correct disk usage to be reported") + + # add sixth node + self.redpanda.start_node(self.redpanda.nodes[5]) + self.wait_for_partitions_rebalanced(total_replicas=total_replicas, + timeout_sec=self.rebalance_timeout) + + next_new_id = self.redpanda.node_id(self.redpanda.nodes[5]) + + wait_until( + lambda: disk_usage_correct(self.redpanda.nodes, next_new_id), + timeout_sec=60, + backoff_sec=1, + err_msg="Timeout waiting for correct disk usage to be reported") + # verify that data can be read + self.consumer = KgoVerifierSeqConsumer(self.test_context, + self.redpanda, + topic.name, + msg_size, + nodes=self.preallocated_nodes) + + self.consumer.start(clean=False) + self.consumer.wait() + + assert self.consumer.consumer_status.validator.invalid_reads == 0, \ + f"Invalid reads in topic: {topic.name}, invalid reads count: " + "{self.consumer.consumer_status.validator.invalid_reads}" diff --git a/tests/rptest/tests/schema_registry_test.py b/tests/rptest/tests/schema_registry_test.py index 8e37ab8859110..91b15d075dfc8 100644 --- a/tests/rptest/tests/schema_registry_test.py +++ b/tests/rptest/tests/schema_registry_test.py @@ -10,9 +10,10 @@ from enum import Enum import http.client import json -from typing import Optional +from typing import NamedTuple, Optional import uuid import re +import urllib.parse import requests import time import random @@ -31,7 +32,7 @@ from rptest.services import tls from rptest.services.admin import Admin from rptest.services.cluster import cluster -from rptest.services.redpanda import DEFAULT_LOG_ALLOW_LIST, MetricsEndpoint, ResourceSettings, SecurityConfig, LoggingConfig, PandaproxyConfig, SchemaRegistryConfig +from rptest.services.redpanda import DEFAULT_LOG_ALLOW_LIST, MetricsEndpoint, ResourceSettings, SecurityConfig, LoggingConfig, PandaproxyConfig, SchemaRegistryConfig, SaslCredentials from rptest.services.serde_client import SerdeClient from rptest.tests.cluster_config_test import wait_for_version_status_sync from rptest.tests.pandaproxy_test import User, PandaProxyTLSProvider @@ -74,6 +75,8 @@ def get_subject_name(sns: str, topic: str, field: MessageField, "Content-Type": "application/vnd.schemaregistry.v1+json" } +HTTP_DELETE_HEADERS = {"Accept": "application/vnd.schemaregistry.v1+json"} + schema1_def = '{"type":"record","name":"myrecord","fields":[{"name":"f1","type":"string"}]}' # Schema 2 is only backwards compatible schema2_def = '{"type":"record","name":"myrecord","fields":[{"name":"f1","type":["null","string"]},{"name":"f2","type":"string","default":"foo"}]}' @@ -97,6 +100,95 @@ def get_subject_name(sns: str, topic: str, field: MessageField, Simple id = 1; }""" +validation_schemas = dict( + proto3=""" +syntax = "proto3"; + +message myrecord { + message Msg1 { + int32 f1 = 1; + } + Msg1 m1 = 1; + Msg1 m2 = 2; +} +""", + proto3_incompat=""" +syntax = "proto3"; + +message myrecord { + // MESSAGE_REMOVED + message Msg1d { + int32 f1 = 1; + } + // FIELD_NAMED_TYPE_CHANGED + Msg1d m1 = 1; +} +""", + proto2=""" +syntax = "proto2"; + +message myrecord { + message Msg1 { + required int32 f1 = 1; + } + required Msg1 m1 = 1; + required Msg1 m2 = 2; +} +""", + proto2_incompat=""" +syntax = "proto2"; + +message myrecord { + // MESSAGE_REMOVED + message Msg1d { + required int32 f1 = 1; + } + // FIELD_NAMED_TYPE_CHANGED + required Msg1d m1 = 1; +} +""", + avro=""" +{ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "f1", + "type": "string" + }, + { + "name": "enumF", + "type": { + "name": "ABorC", + "type": "enum", + "symbols": ["a", "b", "c"] + } + } + ] +} +""", + avro_incompat=""" +{ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "f1", + "type": "int" + }, + { + "name": "enumF", + "type": { + "name": "ABorC", + "type": "enum", + "symbols": ["a"] + } + } + ] +} +""", +) + log_config = LoggingConfig('info', logger_levels={ 'security': 'trace', @@ -105,6 +197,87 @@ def get_subject_name(sns: str, topic: str, field: MessageField, }) +class TestNormalizeDataset(NamedTuple): + type: SchemaType + schema_base: str + schema_canonical: str + schema_normalized: str + + +def get_normalize_dataset(type: SchemaType) -> TestNormalizeDataset: + if type == SchemaType.AVRO: + return TestNormalizeDataset(type=SchemaType.AVRO, + schema_base="""{ + "name": "myrecord", + "type": "record", + "fields": [ + { + "name": "nested", + "type": { + "type": "array", + "items": { + "fields": [ + { + "type": "string", + "name": "f1" + } + ], + "name": "nested_item", + "type": "record" + } + } + } + ] +}""", + schema_canonical=re.sub( + r"[\n\t\s]*", "", """{ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "nested", + "type": { + "type": "array", + "items": { + "type": "record", + "name": "nested_item", + "fields": [ + { + "name": "f1", + "type": "string" + } + ] + } + } + } + ] +}"""), + schema_normalized=re.sub( + r"[\n\t\s]*", "", """{ + "type": "record", + "name": "myrecord", + "fields": [ + { + "name": "nested", + "type": { + "type": "array", + "items": { + "type": "record", + "name": "nested_item", + "fields": [ + { + "name": "f1", + "type": "string" + } + ] + } + } + } + ] +}""")) + assert False, f"Unsupported schema {type=}" + + class SchemaRegistryEndpoints(RedpandaTest): """ Test schema registry against a redpanda cluster. @@ -306,7 +479,7 @@ def _set_config_subject(self, def _delete_config_subject(self, subject, - headers=HTTP_POST_HEADERS, + headers=HTTP_DELETE_HEADERS, **kwargs): return self._request("DELETE", f"config/{subject}", @@ -316,6 +489,49 @@ def _delete_config_subject(self, def _get_mode(self, headers=HTTP_GET_HEADERS, **kwargs): return self._request("GET", "mode", headers=headers, **kwargs) + def _set_mode(self, + data, + force=False, + headers=HTTP_POST_HEADERS, + **kwargs): + return self._request("PUT", + f"mode{'?force=true' if force else ''}", + headers=headers, + data=data, + **kwargs) + + def _get_mode_subject(self, + subject, + fallback=False, + headers=HTTP_GET_HEADERS, + **kwargs): + return self._request( + "GET", + f"mode/{subject}{'?defaultToGlobal=true' if fallback else ''}", + headers=headers, + **kwargs) + + def _set_mode_subject(self, + subject, + data, + force=False, + headers=HTTP_POST_HEADERS, + **kwargs): + return self._request("PUT", + f"mode/{subject}{'?force=true' if force else ''}", + headers=headers, + data=data, + **kwargs) + + def _delete_mode_subject(self, + subject, + headers=HTTP_DELETE_HEADERS, + **kwargs): + return self._request("DELETE", + f"mode/{subject}", + headers=headers, + **kwargs) + def _get_schemas_types(self, headers=HTTP_GET_HEADERS, tls_enabled: bool = False, @@ -352,32 +568,55 @@ def _get_schemas_ids_id_subjects(self, headers=headers, **kwargs) - def _get_subjects(self, deleted=False, headers=HTTP_GET_HEADERS, **kwargs): + def _get_subjects(self, + deleted=False, + subject_prefix=None, + headers=HTTP_GET_HEADERS, + **kwargs): + params = {} + if deleted: + params['deleted'] = 'true' + if subject_prefix: + params['subjectPrefix'] = subject_prefix return self._request("GET", - f"subjects{'?deleted=true' if deleted else ''}", + "subjects", + params=params, headers=headers, **kwargs) def _post_subjects_subject(self, subject, data, + deleted=False, + normalize=False, headers=HTTP_POST_HEADERS, **kwargs): + params = {} + if (deleted): + params['deleted'] = 'true' + if (normalize): + params['normalize'] = 'true' return self._request("POST", f"subjects/{subject}", headers=headers, data=data, + params=params, **kwargs) def _post_subjects_subject_versions(self, subject, data, + normalize=False, headers=HTTP_POST_HEADERS, **kwargs): + params = {} + if (normalize): + params['normalize'] = 'true' return self._request("POST", f"subjects/{subject}/versions", headers=headers, data=data, + params=params, **kwargs) def _get_subjects_subject_versions_version(self, @@ -431,7 +670,7 @@ def _delete_subject_version(self, subject, version, permanent=False, - headers=HTTP_GET_HEADERS, + headers=HTTP_DELETE_HEADERS, **kwargs): return self._request( "DELETE", @@ -444,10 +683,16 @@ def _post_compatibility_subject_version(self, version, data, headers=HTTP_POST_HEADERS, + verbose: bool | None = None, **kwargs): + params = {} + if verbose is not None: + params['verbose'] = verbose + return self._request( "POST", f"compatibility/subjects/{subject}/versions/{version}", + params=params, headers=headers, data=data, **kwargs) @@ -755,6 +1000,34 @@ def test_post_subjects_subject_versions_version_many(self): assert result_raw.status_code == requests.codes.ok assert result_raw.json()["id"] == 1 + @cluster(num_nodes=3) + def test_post_subjects_subject_versions_metadata_ruleset(self): + """ + Verify posting a schema with metatada and ruleSet + These are not supported, but if they're null, we let it pass. + """ + + topic = create_topic_names(1)[0] + + self.logger.debug("Dump the schema with null metadata and ruleSet") + schema_1_data = json.dumps({ + "schema": schema1_def, + "metadata": None, + "ruleSet": None + }) + + self.logger.debug("Posting schema as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Retrieving schema") + result_raw = self._post_subjects_subject(subject=f"{topic}-key", + data=schema_1_data) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + @cluster(num_nodes=3) def test_post_subjects_subject(self): """ @@ -833,6 +1106,55 @@ def test_post_subjects_subject(self): assert result["error_code"] == 40403 assert result["message"] == f"Schema not found" + self.logger.info("Soft deleting the schema") + result_raw = self._delete_subject_version(subject=subject, + version=1, + permanent=False) + assert result_raw.status_code == requests.codes.ok + + self.logger.info( + "Posting deleted existing schema should be fail (no subject)") + result_raw = self._post_subjects_subject(subject=subject, + data=json.dumps( + {"schema": schema1_def})) + self.logger.info(result_raw) + self.logger.info(result_raw.content) + assert result_raw.status_code == requests.codes.not_found + result = result_raw.json() + assert result["error_code"] == 40401 + assert result["message"] == f"Subject '{subject}' not found." + + self.logger.info("Posting deleted existing schema should be success") + result_raw = self._post_subjects_subject( + subject=subject, + data=json.dumps({"schema": schema1_def}, ), + deleted=True) + self.logger.info(result_raw) + self.logger.info(result_raw.content) + assert result_raw.status_code == requests.codes.ok + result = result_raw.json() + assert result["subject"] == subject + assert result["id"] == 1 + assert result["version"] == 1 + assert result["schema"] + + self.logger.info("Posting compatible schema should be success") + result_raw = self._post_subjects_subject_versions( + subject=subject, data=json.dumps({"schema": schema2_def})) + assert result_raw.status_code == requests.codes.ok + + self.logger.info( + "Posting deleted existing schema should be fail (no schema)") + result_raw = self._post_subjects_subject(subject=subject, + data=json.dumps( + {"schema": schema1_def})) + self.logger.info(result_raw) + self.logger.info(result_raw.content) + assert result_raw.status_code == requests.codes.not_found + result = result_raw.json() + assert result["error_code"] == 40403 + assert result["message"] == f"Schema not found" + @cluster(num_nodes=3) def test_config(self): """ @@ -941,13 +1263,46 @@ def test_config(self): )["message"] == f"Subject 'foo-key' not found.", f"{json.dumps(result_raw.json(), indent=1)}" @cluster(num_nodes=3) - def test_mode(self): - """ - Smoketest get_mode endpoint - """ - self.logger.debug("Get initial global mode") - result_raw = self._get_mode() - assert result_raw.json()["mode"] == "READWRITE" + @parametrize(dataset_type=SchemaType.AVRO) + def test_normalize(self, dataset_type: SchemaType): + dataset = get_normalize_dataset(dataset_type) + self.logger.debug(f"testing with {dataset=}") + + topics = create_topic_names(2)[0] + canonical_topic = topics[0] + normalize_topic = topics[1] + + base_schema = json.dumps({ + "schema": dataset.schema_base, + "schemaType": str(dataset.type) + }) + + self.logger.debug( + f"Register a schema against a subject - not normalized") + result_raw = self._post_subjects_subject_versions( + subject=f"{canonical_topic}-key", + data=base_schema, + normalize=False) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + v1_id = result_raw.json()["id"] + + self.logger.debug(f"Checking that the returned schema is canonical") + result_raw = self._get_schemas_ids_id(id=v1_id) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()['schema'] == dataset.schema_canonical + + self.logger.debug(f"Register a schema against a subject - normalized") + result_raw = self._post_subjects_subject_versions( + subject=f"{normalize_topic}-key", data=base_schema, normalize=True) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + v1_id = result_raw.json()["id"] + + self.logger.debug(f"Checking that the returned schema is normalized") + result_raw = self._get_schemas_ids_id(id=v1_id) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()['schema'] == dataset.schema_normalized @cluster(num_nodes=3) def test_post_compatibility_subject_version(self): @@ -980,6 +1335,7 @@ def test_post_compatibility_subject_version(self): subject=f"{topic}-key", version=1, data=schema_2_data) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["is_compatible"] == True + assert result_raw.json().get("messages", None) == None self.logger.debug("Set subject config - BACKWARD") result_raw = self._set_config_subject( @@ -992,14 +1348,31 @@ def test_post_compatibility_subject_version(self): subject=f"{topic}-key", version=1, data=schema_2_data) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["is_compatible"] == True + assert result_raw.json().get("messages", None) == None + + self.logger.debug("Check compatibility backward, no default, verbose") + result_raw = self._post_compatibility_subject_version( + subject=f"{topic}-key", + version=1, + data=schema_3_data, + verbose=True) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()["is_compatible"] == False - self.logger.debug("Check compatibility backward, no default") + self.logger.debug( + "Check compatibility backward, no default, not verbose") result_raw = self._post_compatibility_subject_version( - subject=f"{topic}-key", version=1, data=schema_3_data) + subject=f"{topic}-key", + version=1, + data=schema_3_data, + verbose=False) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["is_compatible"] == False + assert result_raw.json().get("messages", None) == None, \ + f"Expected no messages, got {result_raw.json()['messages']}" self.logger.debug("Posting incompatible schema 3 as a subject key") + result_raw = self._post_subjects_subject_versions( subject=f"{topic}-key", data=schema_3_data) assert result_raw.status_code == requests.codes.conflict @@ -1023,12 +1396,134 @@ def test_post_compatibility_subject_version(self): version=1) assert result_raw.status_code == requests.codes.ok - self.logger.debug("Posting schema 1 again, expect same version") + self.logger.debug("Posting schema 1 again, expect incompatible") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data) + assert result_raw.status_code == requests.codes.conflict + + self.logger.debug("Set subject config - NONE") + result_raw = self._set_config_subject(subject=f"{topic}-key", + data=json.dumps( + {"compatibility": "NONE"})) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Posting schema 1 again, expect same id") result_raw = self._post_subjects_subject_versions( subject=f"{topic}-key", data=schema_1_data) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["id"] == v1_id + @cluster(num_nodes=3) + def test_post_compatibility_subject_version_transitive_order(self): + """ + Verify the compatibility message shows the latest failing schema + """ + + topic = create_topic_names(1)[0] + + schema_1_data = json.dumps({"schema": schema1_def}) + schema_2_data = json.dumps({"schema": schema2_def}) + schema_3_data = json.dumps({"schema": schema3_def}) + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data) + self.logger.debug(f"{result_raw=}") + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Set subject config - BACKWARD_TRANSITIVE") + result_raw = self._set_config_subject( + subject=f"{topic}-key", + data=json.dumps({"compatibility": "BACKWARD_TRANSITIVE"})) + self.logger.debug(f"{result_raw=}") + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Posting schema 2 (compatible with schema 1)") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_2_data) + self.logger.debug(result_raw, result_raw.json()) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug( + "Check compatibility schema 3 (incompatible with both schema 1 and 2) with verbose=True" + ) + result_raw = self._post_compatibility_subject_version( + subject=f"{topic}-key", + version=1, + data=schema_3_data, + verbose=True) + self.logger.debug(result_raw, result_raw.json()) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()["is_compatible"] == False + + messages = result_raw.json().get("messages", []) + assert not any(schema1_def in m for m in messages), \ + f"Expected schema 3 to be compared against schema 2 only (not schema 1)" + assert any(schema2_def in m for m in messages), \ + f"Expected schema 3 to be compared against schema 2 only (not schema 1)" + + @cluster(num_nodes=3) + @parametrize(schemas=("avro", "avro_incompat", "AVRO")) + @parametrize(schemas=("proto3", "proto3_incompat", "PROTOBUF")) + @parametrize(schemas=("proto2", "proto2_incompat", "PROTOBUF")) + def test_compatibility_messages(self, schemas): + """ + Verify compatibility messages + """ + + topic = create_topic_names(1)[0] + + self.logger.debug(f"Register a schema against a subject") + schema_data = json.dumps({ + "schema": validation_schemas[schemas[0]], + "schemaType": schemas[2], + }) + incompatible_data = json.dumps({ + "schema": validation_schemas[schemas[1]], + "schemaType": schemas[2], + }) + + super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + + self.logger.debug("Posting schema as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_data) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + v1_id = result_raw.json()["id"] + + self.logger.debug("Set subject config - BACKWARD") + result_raw = self._set_config_subject( + subject=f"{topic}-key", + data=json.dumps({"compatibility": "BACKWARD"})) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Check compatibility full") + result_raw = self._post_compatibility_subject_version( + subject=f"{topic}-key", + version=1, + data=incompatible_data, + verbose=True) + + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()["is_compatible"] == False + msgs = result_raw.json()["messages"] + for message in ["oldSchemaVersion", "oldSchema", "compatibility"]: + assert any( + message in m for m in msgs + ), f"Expected to find an instance of '{message}', got {msgs}" + + self.logger.debug( + "Check post incompatible schema error message (expect verbose messages)" + ) + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=incompatible_data) + + assert result_raw.status_code == 409 + msg = result_raw.json()["message"] + for message in ["oldSchemaVersion", "oldSchema", "compatibility"]: + assert message in msg, f"Expected to find an instance of '{message}', got {msgs}" + @cluster(num_nodes=3) def test_delete_subject(self): """ @@ -1740,146 +2235,488 @@ def check_each_schema(subject: str, schemas: list[str], subjects[subject]["subject_versions"]) -class SchemaRegistryBasicAuthTest(SchemaRegistryEndpoints): +class SchemaRegistryModeNotMutableTest(SchemaRegistryEndpoints): """ - Test schema registry against a redpanda cluster with HTTP Basic Auth enabled. + Test that mode cannot be mutated when mode_mutability=False. """ - username = 'red' - password = 'panda' + def __init__(self, context, **kwargs): + self.schema_registry_config = SchemaRegistryConfig() + self.schema_registry_config.mode_mutability = False - def __init__(self, context): - security = SecurityConfig() - security.enable_sasl = True - security.endpoint_authn_method = 'sasl' + super(SchemaRegistryEndpoints, self).__init__( + context, + schema_registry_config=self.schema_registry_config, + **kwargs) - schema_registry_config = SchemaRegistryConfig() - schema_registry_config.authn_method = 'http_basic' + @cluster(num_nodes=3) + def test_mode_immutable(self): - super(SchemaRegistryBasicAuthTest, - self).__init__(context, - security=security, - schema_registry_config=schema_registry_config) + subject = f"{create_topic_names(1)[0]}-key" - @cluster(num_nodes=3) - def test_schemas_types(self): - """ - Verify the schema registry returns the supported types - """ - result_raw = self._get_schemas_types(auth=(self.username, - self.password)) - assert result_raw.json()['error_code'] == 40101 + result_raw = self._get_mode() + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + result_raw = self._set_mode(data=json.dumps({"mode": "INVALID"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42204 + + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json()["message"] == "Mode changes are not allowed" + + # Check that setting it to the same value is still refused + result_raw = self._set_mode(data=json.dumps({"mode": "READWRITE"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json()["message"] == "Mode changes are not allowed" + + result_raw = self._set_mode_subject(subject=subject, + data=json.dumps( + {"mode": "READONLY"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json()["message"] == "Mode changes are not allowed" + + result_raw = self._delete_mode_subject(subject=subject) + assert result_raw.status_code == 404 + assert result_raw.json()["error_code"] == 40401 + assert result_raw.json( + )["message"] == f"Subject '{subject}' not found." - self.logger.debug(f"Request schema types with default accept header") - result_raw = self._get_schemas_types(auth=(super_username, - super_password)) - assert result_raw.status_code == requests.codes.ok - result = result_raw.json() - assert set(result) == {"PROTOBUF", "AVRO"} + +class SchemaRegistryModeMutableTest(SchemaRegistryEndpoints): + """ + Test schema registry mode against a redpanda cluster. + """ + def __init__(self, context, **kwargs): + self.schema_registry_config = SchemaRegistryConfig() + self.schema_registry_config.mode_mutability = True + super(SchemaRegistryEndpoints, self).__init__( + context, + schema_registry_config=self.schema_registry_config, + **kwargs) @cluster(num_nodes=3) - def test_get_schema_id_versions(self): + def test_mode(self): """ - Verify schema versions + Smoketest mode endpoints """ - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + subject = f"{create_topic_names(1)[0]}-key" + not_subject = f"{create_topic_names(1)[0]}-key" - topic = create_topic_names(1)[0] - subject = f"{topic}-key" - - schema_1_data = json.dumps({"schema": schema1_def}) + self.logger.debug("Get initial global mode") + result_raw = self._get_mode() + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" - self.logger.debug("Posting schema 1 as a subject key") + self.logger.debug("Set invalid global mode") + result_raw = self._set_mode(data=json.dumps({"mode": "INVALID"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42204 + + self.logger.debug("Set global mode") + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"})) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READONLY" + + self.logger.debug("Get global mode") + result_raw = self._get_mode() + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READONLY" + + self.logger.debug("Get mode for non-existant subject") + result_raw = self._get_mode_subject(subject=not_subject) + assert result_raw.status_code == 404 + assert result_raw.json()["error_code"] == 40409 + + self.logger.debug("Get mode for non-existant subject, with fallback") + result_raw = self._get_mode_subject(subject=not_subject, fallback=True) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READONLY" + + self.logger.debug("Set mode for non-existant subject (allowed)") + result_raw = self._set_mode_subject(subject=subject, + data=json.dumps( + {"mode": "READWRITE"})) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" + + self.logger.debug("Set invalid subject mode") + result_raw = self._set_mode_subject(subject="test-sub", + data=json.dumps( + {"mode": "INVALID"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42204 + + self.logger.debug("Get mode for non-existant subject") + result_raw = self._get_mode_subject(subject=subject, fallback=False) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" + + self.logger.debug("Delete mode for non-existant subject") + result_raw = self._delete_mode_subject(subject=subject) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" + + self.logger.debug("Get mode for non-existant subject") + result_raw = self._get_mode_subject(subject=subject, fallback=False) + assert result_raw.status_code == 404 + assert result_raw.json()["error_code"] == 40409 + + self.logger.debug("Set global mode to READWRITE") + result_raw = self._set_mode(data=json.dumps({"mode": "READWRITE"})) + assert result_raw.status_code == 200 + + self.logger.debug("Add a schema") result_raw = self._post_subjects_subject_versions( - subject=subject, - data=schema_1_data, - auth=(super_username, super_password)) + subject=subject, data=json.dumps({"schema": schema1_def})) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Set global mode to IMPORT") + result_raw = self._set_mode(data=json.dumps({"mode": "IMPORT"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42204 + assert result_raw.json( + )["message"] == "Invalid mode. Valid values are READWRITE, READONLY" + + self.logger.debug("Set subject mode to IMPORT") + result_raw = self._set_mode_subject(subject="test-sub", + data=json.dumps({"mode": + "IMPORT"})) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42204 + assert result_raw.json( + )["message"] == "Invalid mode. Valid values are READWRITE, READONLY" + + @cluster(num_nodes=3) + def test_mode_readonly(self): + """ + Test endpoints when in READONLY + """ + ro_subject = f"ro-{create_topic_names(1)[0]}-key" + rw_subject = f"rw-{create_topic_names(1)[0]}-key" + + schema1 = json.dumps({"schema": schema1_def}) + schema2 = json.dumps({"schema": schema2_def}) + + self.logger.info("Posting schema 1 as ro_subject key") + result_raw = self._post_subjects_subject_versions( + subject=ro_subject, data=json.dumps({"schema": schema1_def})) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Set global mode to readonly") + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"})) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READONLY" + + self.logger.debug("Override mode for rw_subject") + result_raw = self._set_mode_subject(subject=rw_subject, + data=json.dumps( + {"mode": "READWRITE"})) + assert result_raw.status_code == 200 + assert result_raw.json()["mode"] == "READWRITE" + + self.logger.info("Posting schema 1 as rw_subject key") + result_raw = self._post_subjects_subject_versions(subject=rw_subject, + data=schema1) + assert result_raw.status_code == requests.codes.ok + + # mode + result_raw = self._get_mode() + assert result_raw.status_code == 200 + + for sub in [ro_subject, rw_subject]: + result_raw = self._get_mode_subject(subject=sub, fallback=True) + assert result_raw.status_code == 200 + + # config + result_raw = self._get_config() + assert result_raw.status_code == 200 + + for sub in [ro_subject, rw_subject]: + result_raw = self._get_config_subject(subject=sub, fallback=True) + assert result_raw.status_code == 200 + + # This is the default, check that setting it to the default/existing is failure, not quiet success + compat_back = json.dumps({"compatibility": "BACKWARD"}) + result_raw = self._set_config(data=compat_back) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json( + )["message"] == "Subject null is in read-only mode" + + result_raw = self._set_config_subject(subject=ro_subject, + data=compat_back) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json( + )["message"] == f"Subject {ro_subject} is in read-only mode" + + result_raw = self._set_config_subject(subject=rw_subject, + data=compat_back) + assert result_raw.status_code == 200 + + # The config doesn't exist, but the mode is checked first + result_raw = self._delete_config_subject(subject=ro_subject) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json( + )["message"] == f"Subject {ro_subject} is in read-only mode" + + result_raw = self._delete_config_subject(subject=rw_subject) + assert result_raw.status_code == 200 + + # subjects + result_raw = self._get_subjects() + assert result_raw.status_code == 200 + + for sub in [ro_subject, rw_subject]: + result_raw = self._get_subjects_subject_versions(subject=sub) + assert result_raw.status_code == 200 + + result_raw = self._get_subjects_subject_versions_version( + subject=sub, version=1) + assert result_raw.status_code == 200 + + result_raw = self._get_subjects_subject_versions_version_referenced_by( + subject=sub, version=1) + assert result_raw.status_code == 200 + + self.logger.info("Checking for schema 1 as subject key") + result_raw = self._post_subjects_subject(subject=sub, data=schema1) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()["id"] == 1 + assert result_raw.json()["version"] == 1 + + self.logger.info("Checking for schema 1 as subject key") + result_raw = self._post_subjects_subject_versions(subject=sub, + data=schema1) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json()["id"] == 1 + + self.logger.info("Checking schema 2 as subject key") + result_raw = self._post_subjects_subject(subject=sub, data=schema2) + assert result_raw.status_code == 404 + assert result_raw.json()["error_code"] == 40403 + assert result_raw.json()["message"] == f"Schema not found" + + self.logger.info("Posting schema 2 as ro_subject key") + result_raw = self._post_subjects_subject_versions(subject=ro_subject, + data=schema2) + assert result_raw.status_code == 422 + assert result_raw.json()["error_code"] == 42205 + assert result_raw.json( + )["message"] == f"Subject {ro_subject} is in read-only mode" + + self.logger.info("Posting schema 2 as rw_subject key") + result_raw = self._post_subjects_subject_versions(subject=rw_subject, + data=schema2) + assert result_raw.status_code == 200 + + # compatibility + for sub in [ro_subject, rw_subject]: + result_raw = self._post_compatibility_subject_version(subject=sub, + version=1, + data=schema2) + assert result_raw.status_code == 200 + + # schemas + result_raw = self._get_schemas_types() + assert result_raw.status_code == 200 + + result_raw = self._get_schemas_ids_id(id=1) + assert result_raw.status_code == 200 + + result_raw = self._get_schemas_ids_id_subjects(id=1) + assert result_raw.status_code == 200 + + result_raw = self._get_schemas_ids_id_versions(id=1) + assert result_raw.status_code == 200 + + +class SchemaRegistryBasicAuthTest(SchemaRegistryEndpoints): + """ + Test schema registry against a redpanda cluster with HTTP Basic Auth enabled. + """ + def __init__(self, context): + security = SecurityConfig() + security.enable_sasl = True + security.endpoint_authn_method = 'sasl' + + schema_registry_config = SchemaRegistryConfig() + schema_registry_config.authn_method = 'http_basic' + schema_registry_config.mode_mutability = True + + super(SchemaRegistryBasicAuthTest, + self).__init__(context, + security=security, + schema_registry_config=schema_registry_config) + + superuser = self.redpanda.SUPERUSER_CREDENTIALS + self.user = SaslCredentials('user', 'panda', superuser.algorithm) + public_user = SaslCredentials('red', 'panda', superuser.algorithm) + + self.super_auth = (superuser.username, superuser.password) + self.user_auth = (self.user.username, self.user.password) + self.public_auth = (public_user.username, public_user.password) + + def _init_users(self): + admin = Admin(self.redpanda) + admin.create_user(username=self.user.username, + password=self.user.password, + algorithm=self.user.algorithm) + + @cluster(num_nodes=3) + def test_schemas_types(self): + """ + Verify the schema registry returns the supported types + """ + self._init_users() + + result_raw = self._get_schemas_types(auth=self.public_auth) + assert result_raw.json()['error_code'] == 40101 + + self.logger.debug(f"Request schema types with default accept header") + result_raw = self._get_schemas_types(auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok + result = result_raw.json() + assert set(result) == {"PROTOBUF", "AVRO"} + + @cluster(num_nodes=3) + def test_get_schema_id_versions(self): + """ + Verify schema versions + """ + self._init_users() + + topic = create_topic_names(1)[0] + subject = f"{topic}-key" + + schema_1_data = json.dumps({"schema": schema1_def}) + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions(subject=subject, + data=schema_1_data, + auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["id"] == 1 self.logger.debug("Checking schema 1 versions") result_raw = self._get_schemas_ids_id_versions(id=1, - auth=(self.username, - self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._get_schemas_ids_id_versions(id=1, - auth=(super_username, - super_password)) + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok assert result_raw.json() == [{"subject": subject, "version": 1}] + @cluster(num_nodes=3) + def test_get_subjects(self): + """ + Verify getting subjects + """ + self._init_users() + + topics = ['a', 'aa', 'b', 'ab', 'bb'] + + schema_1_data = json.dumps({"schema": schema1_def}) + + self.logger.debug("Posting schemas 1 as subject keys") + + def post(topic): + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", + data=schema_1_data, + auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + for t in topics: + post(t) + + def get_subjects(prefix: Optional[str]): + result_raw = self._get_subjects(subject_prefix=prefix, + auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok + + return result_raw.json() + + assert len(get_subjects(prefix=None)) == 5 + assert len(get_subjects(prefix="")) == 5 + assert len(get_subjects(prefix="a")) == 3 + assert len(get_subjects(prefix="aa")) == 1 + assert len(get_subjects(prefix="aaa")) == 0 + assert len(get_subjects(prefix="b")) == 2 + assert len(get_subjects(prefix="bb")) == 1 + @cluster(num_nodes=3) def test_post_subjects_subject_versions(self): """ Verify posting a schema """ + self._init_users() topic = create_topic_names(1)[0] schema_1_data = json.dumps({"schema": schema1_def}) result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(self.username, self.password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS - self.logger.debug("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(super_username, super_password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["id"] == 1 self.logger.debug("Get subjects") - result_raw = self._get_subjects(auth=(self.username, self.password)) + result_raw = self._get_subjects(auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 - result_raw = self._get_subjects(auth=(super_username, super_password)) + result_raw = self._get_subjects(auth=self.super_auth) assert result_raw.json() == [f"{topic}-key"] self.logger.debug("Get schema versions for subject key") result_raw = self._get_subjects_subject_versions( - subject=f"{topic}-key", auth=(self.username, self.password)) + subject=f"{topic}-key", auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._get_subjects_subject_versions( - subject=f"{topic}-key", auth=(super_username, super_password)) + subject=f"{topic}-key", auth=self.super_auth) assert result_raw.status_code == requests.codes.ok assert result_raw.json() == [1] self.logger.debug("Get latest schema version for subject key") result_raw = self._get_subjects_subject_versions_version( - subject=f"{topic}-key", - version="latest", - auth=(self.username, self.password)) + subject=f"{topic}-key", version="latest", auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._get_subjects_subject_versions_version( - subject=f"{topic}-key", - version="latest", - auth=(super_username, super_password)) + subject=f"{topic}-key", version="latest", auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok + result = result_raw.json() + assert result["subject"] == f"{topic}-key" + assert result["version"] == 1 + + self.logger.debug("Get latest (-1) schema version for subject key") + result_raw = self._get_subjects_subject_versions_version( + subject=f"{topic}-key", version="-1", auth=self.super_auth) assert result_raw.status_code == requests.codes.ok result = result_raw.json() assert result["subject"] == f"{topic}-key" assert result["version"] == 1 self.logger.debug("Get schema version 1") - result_raw = self._get_schemas_ids_id(id=1, - auth=(self.username, - self.password)) + result_raw = self._get_schemas_ids_id(id=1, auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 - result_raw = self._get_schemas_ids_id(id=1, - auth=(super_username, - super_password)) + result_raw = self._get_schemas_ids_id(id=1, auth=self.super_auth) assert result_raw.status_code == requests.codes.ok @cluster(num_nodes=3) @@ -1887,33 +2724,32 @@ def test_post_subjects_subject(self): """ Verify posting a schema """ + self._init_users() topic = create_topic_names(1)[0] subject = f"{topic}-key" - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS - self.logger.info("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( subject=subject, data=json.dumps({"schema": schema1_def}), - auth=(super_username, super_password)) + auth=self.super_auth) self.logger.info(result_raw) self.logger.info(result_raw.content) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["id"] == 1 - result_raw = self._post_subjects_subject( - subject=subject, - data=json.dumps({"schema": schema1_def}), - auth=(self.username, self.password)) + result_raw = self._post_subjects_subject(subject=subject, + data=json.dumps( + {"schema": schema1_def}), + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 self.logger.info("Posting existing schema should be success") - result_raw = self._post_subjects_subject( - subject=subject, - data=json.dumps({"schema": schema1_def}), - auth=(super_username, super_password)) + result_raw = self._post_subjects_subject(subject=subject, + data=json.dumps( + {"schema": schema1_def}), + auth=self.super_auth) self.logger.info(result_raw) self.logger.info(result_raw.content) assert result_raw.status_code == requests.codes.ok @@ -1928,24 +2764,24 @@ def test_config(self): """ Smoketest config endpoints """ - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + self._init_users() self.logger.debug("Get initial global config") - result_raw = self._get_config(auth=(self.username, self.password)) + result_raw = self._get_config(auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 - result_raw = self._get_config(auth=(super_username, super_password)) + result_raw = self._get_config(auth=self.super_auth) assert result_raw.json()["compatibilityLevel"] == "BACKWARD" self.logger.debug("Set global config") result_raw = self._set_config(data=json.dumps( {"compatibility": "FULL"}), - auth=(self.username, self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._set_config(data=json.dumps( {"compatibility": "FULL"}), - auth=(super_username, super_password)) + auth=self.super_auth) assert result_raw.json()["compatibility"] == "FULL" schema_1_data = json.dumps({"schema": schema1_def}) @@ -1954,102 +2790,141 @@ def test_config(self): self.logger.debug("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(super_username, super_password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) self.logger.debug("Set subject config") self.logger.debug("Set subject config") result_raw = self._set_config_subject( subject=f"{topic}-key", data=json.dumps({"compatibility": "BACKWARD_TRANSITIVE"}), - auth=(self.username, self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._set_config_subject( subject=f"{topic}-key", data=json.dumps({"compatibility": "BACKWARD_TRANSITIVE"}), - auth=(super_username, super_password)) + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["compatibility"] == "BACKWARD_TRANSITIVE" self.logger.debug("Get subject config - should be overriden") result_raw = self._get_config_subject(subject=f"{topic}-key", - auth=(self.username, - self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._get_config_subject(subject=f"{topic}-key", - auth=(super_username, - super_password)) + auth=self.super_auth) assert result_raw.json()["compatibilityLevel"] == "BACKWARD_TRANSITIVE" - global_config = self._get_config(auth=(super_username, - super_password)).json() + global_config = self._get_config(auth=self.super_auth).json() old_config = result_raw.json() result_raw = self._delete_config_subject(subject=f"{topic}-key", - auth=(super_username, - super_password)) + auth=self.super_auth) assert result_raw.json( )["compatibilityLevel"] == old_config["compatibilityLevel"] #, f"{json.dumps(result_raw.json(), indent=1)}, {json.dumps(global_config, indent=1)}" result_raw = self._get_config_subject(subject=f"{topic}-key", fallback=True, - auth=(super_username, - super_password)) + auth=self.super_auth) assert result_raw.json( )["compatibilityLevel"] == global_config["compatibilityLevel"] @cluster(num_nodes=3) def test_mode(self): """ - Smoketest get_mode endpoint + Smoketest mode endpoints """ - result_raw = self._get_mode(auth=(self.username, self.password)) + self._init_users() + + self.logger.debug("Get initial global mode") + result_raw = self._get_mode(auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + result_raw = self._get_mode(auth=self.user_auth) + assert result_raw.json()["mode"] == "READWRITE" - self.logger.debug("Get initial global mode") - result_raw = self._get_mode(auth=(super_username, super_password)) + result_raw = self._get_mode(auth=self.super_auth) assert result_raw.json()["mode"] == "READWRITE" + self.logger.debug("Set global mode") + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"}), + auth=self.public_auth) + assert result_raw.json()['error_code'] == 40101 + + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"}), + auth=self.user_auth) + assert result_raw.json()['error_code'] == 403 + + result_raw = self._set_mode(data=json.dumps({"mode": "READONLY"}), + auth=self.super_auth) + assert result_raw.json()["mode"] == "READONLY" + + sub = "test-sub" + self.logger.debug("Set subject mode") + result_raw = self._set_mode_subject(subject=sub, + data=json.dumps( + {"mode": "READONLY"}), + auth=self.public_auth) + assert result_raw.json()['error_code'] == 40101 + + result_raw = self._set_mode_subject(subject=sub, + data=json.dumps( + {"mode": "READONLY"}), + auth=self.user_auth) + assert result_raw.json()['error_code'] == 403 + + result_raw = self._set_mode_subject(subject=sub, + data=json.dumps( + {"mode": "READONLY"}), + auth=self.super_auth) + assert result_raw.json()["mode"] == "READONLY" + + self.logger.debug("Delete subject mode") + result_raw = self._delete_mode_subject(subject=sub, + auth=self.public_auth) + assert result_raw.json()['error_code'] == 40101 + + result_raw = self._delete_mode_subject(subject=sub, + auth=self.user_auth) + assert result_raw.json()['error_code'] == 403 + + result_raw = self._delete_mode_subject(subject=sub, + auth=self.super_auth) + assert result_raw.json()["mode"] == "READONLY" + @cluster(num_nodes=3) def test_post_compatibility_subject_version(self): """ Verify compatibility """ + self._init_users() topic = create_topic_names(1)[0] self.logger.debug(f"Register a schema against a subject") schema_1_data = json.dumps({"schema": schema1_def}) - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS - self.logger.debug("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(super_username, super_password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok self.logger.debug("Set subject config - NONE") - result_raw = self._set_config_subject( - subject=f"{topic}-key", - data=json.dumps({"compatibility": "NONE"}), - auth=(super_username, super_password)) + result_raw = self._set_config_subject(subject=f"{topic}-key", + data=json.dumps( + {"compatibility": "NONE"}), + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok result_raw = self._post_compatibility_subject_version( subject=f"{topic}-key", version=1, data=schema_1_data, - auth=(self.username, self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 self.logger.debug("Check compatibility none, no default") @@ -2057,7 +2932,7 @@ def test_post_compatibility_subject_version(self): subject=f"{topic}-key", version=1, data=schema_1_data, - auth=(super_username, super_password)) + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok assert result_raw.json()["is_compatible"] == True @@ -2066,43 +2941,38 @@ def test_delete_subject(self): """ Verify delete subject """ + self._init_users() topic = create_topic_names(1)[0] self.logger.debug(f"Register a schema against a subject") schema_1_data = json.dumps({"schema": schema1_def}) - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS - self.logger.debug("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(super_username, super_password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok self.logger.debug("Soft delete subject") result_raw = self._delete_subject(subject=f"{topic}-key", - auth=(self.username, self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._delete_subject(subject=f"{topic}-key", - auth=(super_username, - super_password)) + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok assert result_raw.json() == [1] self.logger.debug("Permanently delete subject") result_raw = self._delete_subject(subject=f"{topic}-key", permanent=True, - auth=(self.username, self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._delete_subject(subject=f"{topic}-key", permanent=True, - auth=(super_username, - super_password)) + auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok @@ -2111,40 +2981,35 @@ def test_delete_subject_version(self): """ Verify delete subject version """ + self._init_users() topic = create_topic_names(1)[0] self.logger.debug(f"Register a schema against a subject") schema_1_data = json.dumps({"schema": schema1_def}) - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS - self.logger.debug("Posting schema 1 as a subject key") result_raw = self._post_subjects_subject_versions( - subject=f"{topic}-key", - data=schema_1_data, - auth=(super_username, super_password)) + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok self.logger.debug("Set subject config - NONE") - result_raw = self._set_config_subject( - subject=f"{topic}-key", - data=json.dumps({"compatibility": "NONE"}), - auth=(super_username, super_password)) + result_raw = self._set_config_subject(subject=f"{topic}-key", + data=json.dumps( + {"compatibility": "NONE"}), + auth=self.super_auth) assert result_raw.status_code == requests.codes.ok self.logger.debug("Soft delete version 1") result_raw = self._delete_subject_version(subject=f"{topic}-key", version=1, - auth=(self.username, - self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._delete_subject_version(subject=f"{topic}-key", version=1, - auth=(super_username, - super_password)) + auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok @@ -2152,15 +3017,13 @@ def test_delete_subject_version(self): result_raw = self._delete_subject_version(subject=f"{topic}-key", version=1, permanent=True, - auth=(self.username, - self.password)) + auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._delete_subject_version(subject=f"{topic}-key", version=1, permanent=True, - auth=(super_username, - super_password)) + auth=self.super_auth) self.logger.debug(result_raw) assert result_raw.status_code == requests.codes.ok @@ -2169,8 +3032,7 @@ def test_protobuf(self): """ Verify basic protobuf functionality """ - - super_username, super_password, _ = self.redpanda.SUPERUSER_CREDENTIALS + self._init_users() self.logger.info("Posting failed schema should be 422") result_raw = self._post_subjects_subject_versions( @@ -2179,19 +3041,20 @@ def test_protobuf(self): "schema": imported_proto_def, "schemaType": "PROTOBUF" }), - auth=(super_username, super_password)) + auth=self.super_auth) self.logger.info(result_raw) self.logger.info(result_raw.content) assert result_raw.status_code == requests.codes.unprocessable_entity self.logger.info("Posting simple as a subject key") - result_raw = self._post_subjects_subject_versions( - subject="simple", - data=json.dumps({ - "schema": simple_proto_def, - "schemaType": "PROTOBUF" - }), - auth=(super_username, super_password)) + result_raw = self._post_subjects_subject_versions(subject="simple", + data=json.dumps({ + "schema": + simple_proto_def, + "schemaType": + "PROTOBUF" + }), + auth=self.super_auth) self.logger.info(result_raw) self.logger.info(result_raw.content) assert result_raw.status_code == requests.codes.ok @@ -2211,7 +3074,7 @@ def test_protobuf(self): "version": 1 }] }), - auth=(super_username, super_password)) + auth=self.super_auth) self.logger.info(result_raw) self.logger.info(result_raw.content) assert result_raw.status_code == requests.codes.ok @@ -2220,7 +3083,7 @@ def test_protobuf(self): result_raw = self._request("GET", f"subjects/simple/versions/1/schema", headers=HTTP_GET_HEADERS, - auth=(super_username, super_password)) + auth=self.super_auth) self.logger.info(result_raw) assert result_raw.status_code == requests.codes.ok assert result_raw.text.strip() == simple_proto_def.strip() @@ -2228,7 +3091,7 @@ def test_protobuf(self): result_raw = self._request("GET", f"schemas/ids/1", headers=HTTP_GET_HEADERS, - auth=(super_username, super_password)) + auth=self.super_auth) self.logger.info(result_raw) assert result_raw.status_code == requests.codes.ok result = result_raw.json() @@ -2237,15 +3100,176 @@ def test_protobuf(self): # Regular user should fail result_raw = self._get_subjects_subject_versions_version_referenced_by( - "simple", 1, auth=(self.username, self.password)) + "simple", 1, auth=self.public_auth) assert result_raw.json()['error_code'] == 40101 result_raw = self._get_subjects_subject_versions_version_referenced_by( - "simple", 1, auth=(super_username, super_password)) + "simple", 1, auth=self.super_auth) self.logger.info(result_raw) assert result_raw.status_code == requests.codes.ok assert result_raw.json() == [2] + @cluster(num_nodes=3) + def test_delete_subject_bug(self): + topic = 'foo' + self.logger.debug(f"Register a schema against a subject") + schema_1_data = json.dumps({"schema": schema1_def}) + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug(f"Register a schema against a subject") + schema_2_data = json.dumps({"schema": schema2_def}) + + self.logger.debug("Posting schema 2 as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_2_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Soft delete subject 1 version 1") + result_raw = self._delete_subject_version(subject=f"{topic}-key", + version=1, + auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == 1, f"Json: {result_raw.json()}" + + self.logger.debug("Soft delete subject 1 version 2") + result_raw = self._delete_subject_version(subject=f"{topic}-key", + version=2, + auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == 2, f"Json: {result_raw.json()}" + + self.logger.debug("Posting schema 1 - again - as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json() == {'id': 1}, f"Json: {result_raw.json()}" + + self.logger.debug("Get subject versions") + result_raw = self._get_subjects_subject_versions( + subject=f"{topic}-key", auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == [3], f"Json: {result_raw.json()}" + + self.logger.debug("Posting schema 2 - again - as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_2_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + assert result_raw.json() == {'id': 2}, f"Json: {result_raw.json()}" + + self.logger.debug("Get subject versions") + result_raw = self._get_subjects_subject_versions( + subject=f"{topic}-key", auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == [3, 4], f"Json: {result_raw.json()}" + + @cluster(num_nodes=3) + def test_delete_subject_last_clears_config(self): + topic = 'foo' + + self.logger.debug("Set subject config - NONE") + result_raw = self._set_config_subject(subject=f"{topic}-key", + data=json.dumps( + {"compatibility": "NONE"}), + auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug(f"Register a schema against a subject") + schema_1_data = json.dumps({"schema": schema1_def}) + schema_3_data = json.dumps({"schema": schema3_def}) + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug("Get subject config - should be overriden") + result_raw = self._get_config_subject(subject=f"{topic}-key", + auth=self.super_auth) + assert result_raw.json()["compatibilityLevel"] == "NONE" + + self.logger.debug("Soft delete subject 1 version 1") + result_raw = self._delete_subject_version(subject=f"{topic}-key", + version=1, + auth=self.super_auth) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == 1, f"Json: {result_raw.json()}" + + self.logger.debug("Get subject config - should fail") + result_raw = self._get_config_subject(subject=f"{topic}-key", + auth=self.super_auth) + assert result_raw.status_code == requests.codes.not_found + assert result_raw.json()["error_code"] == 40408 + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_1_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok + + self.logger.debug( + "Posting incompatible schema 3 as a subject key - expect conflict") + result_raw = self._post_subjects_subject_versions( + subject=f"{topic}-key", data=schema_3_data, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.conflict + + self.logger.debug("Get subject config - should fail") + result_raw = self._get_config_subject(subject=f"{topic}-key", + auth=self.super_auth) + assert result_raw.status_code == requests.codes.not_found + assert result_raw.json()["error_code"] == 40408 + + @cluster(num_nodes=3) + def test_hard_delete_subject_deletes_schema(self): + subject = "example_topic-key" + schema_1_data = json.dumps({"schema": schema1_def}) + + self.logger.debug("Posting schema 1 as a subject key") + result_raw = self._post_subjects_subject_versions(subject=subject, + data=schema_1_data, + auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + assert result_raw.json() == {'id': 1}, f"Json: {result_raw.json()}" + + self.logger.debug("Soft delete subject") + result_raw = self._delete_subject(subject=subject, + permanent=False, + auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + + self.logger.debug("Then hard delete subject") + result_raw = self._delete_subject(subject=subject, + permanent=True, + auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.ok, f'Code: {result_raw.status_code}' + + def schema_no_longer_present(): + self.logger.debug("Sending get schema 1") + result_raw = self._get_schemas_ids_id(id=1, auth=self.super_auth) + self.logger.debug(result_raw) + assert result_raw.status_code == requests.codes.not_found, f'Code: {result_raw.status_code}' + assert result_raw.json()["error_code"] == 40403, \ + f"Json: {result_raw.json()}" + return True + + self.logger.debug("Wait until get schema 1 now eventually fails") + wait_until(schema_no_longer_present, + timeout_sec=30, + retry_on_exc=True, + err_msg="Failed to delete schema 1 in time") + class SchemaRegistryTest(SchemaRegistryTestMethods): """ diff --git a/tests/rptest/tests/tiered_storage_enable_test.py b/tests/rptest/tests/tiered_storage_enable_test.py new file mode 100644 index 0000000000000..359a072587602 --- /dev/null +++ b/tests/rptest/tests/tiered_storage_enable_test.py @@ -0,0 +1,89 @@ +# Copyright 2024 Redpanda Data, Inc. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.md +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0 + +from rptest.clients.rpk import RpkTool +from rptest.services.cluster import cluster +from ducktape.utils.util import wait_until +from rptest.clients.types import TopicSpec +from rptest.services.kgo_verifier_services import KgoVerifierProducer +from rptest.services.redpanda import SISettings + +from rptest.tests.prealloc_nodes import PreallocNodesTest +from rptest.utils.mode_checks import skip_debug_mode + + +class TestEnablingTieredStorage(PreallocNodesTest): + def __init__(self, test_context): + super().__init__(test_context, + num_brokers=3, + node_prealloc_count=1, + si_settings=SISettings(test_context=test_context, + fast_uploads=True)) + + @property + def producer_throughput(self): + return 5 * (1024 * 1024) if not self.debug_mode else 1000 + + @property + def msg_count(self): + return 20 * int(self.producer_throughput / self.msg_size) + + @property + def msg_size(self): + return 128 + + def start_producer(self): + self.logger.info( + f"starting kgo-verifier producer with {self.msg_count} messages of size {self.msg_size} and throughput: {self.producer_throughput} bps" + ) + self.producer = KgoVerifierProducer( + self.test_context, + self.redpanda, + self._topic, + self.msg_size, + self.msg_count, + custom_node=self.preallocated_nodes, + rate_limit_bps=self.producer_throughput) + + self.producer.start(clean=False) + self.producer.wait_for_acks( + 5 * (self.producer_throughput / self.msg_size), 120, 1) + + @cluster(num_nodes=4) + @skip_debug_mode + def test_enabling_tiered_storage_on_old_topic(self): + # disable cloud storage and restart cluster + self.redpanda.set_cluster_config({"cloud_storage_enabled": False}, + expect_restart=True) + # create topic without tiered storage enabled + topic = TopicSpec(partition_count=3, + segment_bytes=1024 * 1024, + retention_bytes=5 * 1024 * 1024) + + self.client().create_topic(topic) + self._topic = topic.name + self.start_producer() + rpk = RpkTool(self.redpanda) + + def _start_offset_updated(): + partitions = rpk.describe_topic(self._topic) + return all([p.start_offset > 0 for p in partitions]) + + wait_until( + _start_offset_updated, + timeout_sec=60, + backoff_sec=1, + err_msg= + "timed out waiting for local retention to clean up some some data") + + # enable cloud storage + self.redpanda.set_cluster_config({"cloud_storage_enabled": True}, + expect_restart=True) + + self.redpanda.wait_for_manifest_uploads() diff --git a/tests/rptest/tests/timequery_test.py b/tests/rptest/tests/timequery_test.py index ec23dac50623a..19207f1950fa2 100644 --- a/tests/rptest/tests/timequery_test.py +++ b/tests/rptest/tests/timequery_test.py @@ -8,10 +8,11 @@ # by the Apache License, Version 2.0 import concurrent.futures +import datetime import re -import time import threading from logging import Logger +import time from typing import Callable from rptest.services.admin import Admin @@ -22,9 +23,8 @@ from rptest.clients.types import TopicSpec from rptest.clients.rpk import RpkTool from rptest.clients.kafka_cat import KafkaCat -from rptest.clients.rpk_remote import RpkRemoteTool -from rptest.util import (wait_until, segments_count, - wait_for_local_storage_truncate) +from rptest.util import (segments_count, wait_until, + wait_for_local_storage_truncate, wait_until_result) from rptest.services.kgo_verifier_services import KgoVerifierProducer from rptest.utils.si_utils import BucketView, NTP @@ -38,7 +38,6 @@ from kafkatest.version import V_3_0_0 from ducktape.tests.test import Test from rptest.clients.default import DefaultClient -from rptest.utils.mode_checks import skip_debug_mode class BaseTimeQuery: @@ -106,6 +105,18 @@ def _test_timequery(self, cluster, cloud_storage: bool, batch_cache: bool): base_ts = 1664453149000 msg_count = (self.log_segment_size * total_segments) // record_size local_retention = self.log_segment_size * 4 + kcat = KafkaCat(cluster) + + # Test the base case with an empty topic. + empty_topic = TopicSpec(name="tq_empty_topic", + partition_count=1, + replication_factor=3) + self.client().create_topic(empty_topic) + offset = kcat.query_offset(empty_topic.name, 0, base_ts) + self.logger.info(f"Time query returned offset {offset}") + assert offset == -1, f"Expected -1, got {offset}" + + # Create a topic and produce a run of messages we will query. topic, timestamps = self._create_and_produce(cluster, cloud_storage, local_retention, base_ts, record_size, msg_count) @@ -166,7 +177,6 @@ def __init__(self, offset, ts=None, expect_read=True): # offset should cause cloud downloads. hit_offsets = set() - kcat = KafkaCat(cluster) cloud_metrics = None local_metrics = None @@ -458,6 +468,215 @@ def query_slices(tid): assert not any([e > 0 for e in errors]) + @cluster(num_nodes=4) + @parametrize(cloud_storage=True, spillover=False) + @parametrize(cloud_storage=True, spillover=True) + @parametrize(cloud_storage=False, spillover=False) + def test_timequery_with_trim_prefix(self, cloud_storage: bool, + spillover: bool): + self.set_up_cluster(cloud_storage=cloud_storage, + batch_cache=False, + spillover=spillover) + total_segments = 12 + record_size = 1024 + base_ts = 1664453149000 + msg_count = (self.log_segment_size * total_segments) // record_size + local_retention = self.log_segment_size * 4 + topic, timestamps = self._create_and_produce(self.redpanda, True, + local_retention, base_ts, + record_size, msg_count) + + # Confirm messages written + rpk = RpkTool(self.redpanda) + p = next(rpk.describe_topic(topic.name)) + assert p.high_watermark == msg_count + + if cloud_storage: + # If using cloud storage, we must wait for some segments + # to fall out of local storage, to ensure we are really + # hitting the cloud storage read path when querying. + wait_for_local_storage_truncate(redpanda=self.redpanda, + topic=topic.name, + target_bytes=local_retention) + + num_batches_per_segment = self.log_segment_size // record_size + new_lwm = int(num_batches_per_segment * 2.5) + trim_response = rpk.trim_prefix(topic.name, + offset=new_lwm, + partitions=[0]) + assert len(trim_response) == 1 + assert new_lwm == trim_response[0].new_start_offset + + # Double check that the start offset has advanced. + p = next(rpk.describe_topic(topic.name)) + assert new_lwm == p.start_offset, f"Expected {new_lwm}, got {p.start_offset}" + + # Query below valid timestamps the offset of the first message. + kcat = KafkaCat(self.redpanda) + offset = kcat.query_offset(topic.name, 0, timestamps[0] - 1000) + assert offset == new_lwm, f"Expected {new_lwm}, got {offset}" + + # Leave just the last message in the log. + trim_response = rpk.trim_prefix(topic.name, + offset=p.high_watermark - 1, + partitions=[0]) + + # Query below valid timestamps the offset of the only message left. + # This is an edge-case where tiered storage, if in use, becomes + # completely irrelevant. + kcat = KafkaCat(self.redpanda) + offset = kcat.query_offset(topic.name, 0, timestamps[0] - 1000) + assert offset == msg_count - 1, f"Expected {msg_count - 1}, got {offset}" + + # Trim everything, leaving an empty log. + rpk.trim_prefix(topic.name, offset=p.high_watermark, partitions=[0]) + kcat = KafkaCat(self.redpanda) + offset = kcat.query_offset(topic.name, 0, timestamps[0] - 1000) + assert offset == -1, f"Expected -1, got {offset}" + + @cluster( + num_nodes=4, + log_allow_list=["Failed to upload spillover manifest {timed_out}"]) + def test_timequery_with_spillover_gc_delayed(self): + self.set_up_cluster(cloud_storage=True, + batch_cache=False, + spillover=True) + total_segments = 16 + record_size = 1024 + base_ts = 1664453149000 + msg_count = (self.log_segment_size * total_segments) // record_size + local_retention = self.log_segment_size * 4 + topic_retention = self.log_segment_size * 8 + topic, timestamps = self._create_and_produce(self.redpanda, True, + local_retention, base_ts, + record_size, msg_count) + + # Confirm messages written + rpk = RpkTool(self.redpanda) + p = next(rpk.describe_topic(topic.name)) + assert p.high_watermark == msg_count + + # If using cloud storage, we must wait for some segments + # to fall out of local storage, to ensure we are really + # hitting the cloud storage read path when querying. + wait_for_local_storage_truncate(redpanda=self.redpanda, + topic=topic.name, + target_bytes=local_retention) + + # Set timeout to 0 to prevent the cloud storage housekeeping from + # running, triggering gc, and advancing clean offset. + self.redpanda.set_cluster_config( + {"cloud_storage_manifest_upload_timeout_ms": 0}) + # Disable internal scrubbing as it won't be able to make progress. + self.redpanda.si_settings.skip_end_of_test_scrubbing = True + + self.client().alter_topic_config(topic.name, 'retention.bytes', + topic_retention) + self.logger.info("Waiting for start offset to advance...") + start_offset = wait_until_result( + lambda: next(rpk.describe_topic(topic.name)).start_offset > 0, + timeout_sec=120, + backoff_sec=5, + err_msg="Start offset did not advance") + + start_offset = next(rpk.describe_topic(topic.name)).start_offset + + # Query below valid timestamps the offset of the first message. + kcat = KafkaCat(self.redpanda) + + test_cases = [ + (timestamps[0] - 1000, start_offset, "before start of log"), + (timestamps[0], start_offset, + "first message but out of retention now"), + (timestamps[start_offset - 1], start_offset, + "before new HWM, out of retention"), + (timestamps[start_offset], start_offset, "new HWM"), + (timestamps[start_offset + 10], start_offset + 10, + "few messages after new HWM"), + (timestamps[msg_count - 1] + 1000, -1, "after last message"), + ] + + # Basic time query cases. + for ts, expected_offset, desc in test_cases: + self.logger.info(f"Querying ts={ts} ({desc})") + offset = kcat.query_offset(topic.name, 0, ts) + self.logger.info(f"Time query returned offset {offset}") + assert offset == expected_offset, f"Expected {expected_offset}, got {offset}" + + # Now check every single one of them to make sure there are no + # off-by-one errors, iterators aren't getting stuck on segment and + # spillover boundaries, etc. The segment boundaries are not exact + # due to internal messages, segment roll logic, etc. but the tolerance + # should cover that. + boundary_ranges = [] + for i in range(1, total_segments): + boundary_ranges.append( + (int(i * self.log_segment_size / record_size - 100), + int(i * self.log_segment_size / record_size + 100))) + + for r in boundary_ranges: + self.logger.debug(f"Checking range {r}") + for o in range(int(r[0]), int(r[1])): + ts = timestamps[o] + self.logger.debug(f" Querying ts={ts}") + offset = kcat.query_offset(topic.name, 0, ts) + if o < start_offset: + assert offset == start_offset, f"Expected {start_offset}, got {offset}" + else: + assert offset == o, f"Expected {o}, got {offset}" + + @cluster(num_nodes=4) + def test_timequery_empty_local_log(self): + self.set_up_cluster(cloud_storage=True, + batch_cache=False, + spillover=False) + + total_segments = 3 + record_size = 1024 + base_ts = 1664453149000 + msg_count = (self.log_segment_size * total_segments) // record_size + local_retention = 1 # Any value works for this test. + topic, timestamps = self._create_and_produce(self.redpanda, True, + local_retention, base_ts, + record_size, msg_count) + + # Confirm messages written + rpk = RpkTool(self.redpanda) + p = next(rpk.describe_topic(topic.name)) + assert p.high_watermark == msg_count + + # Restart the cluster to force segment roll. The newly created segment + # will have no user data which is what we want to test. + self.redpanda.restart_nodes(self.redpanda.nodes) + wait_until(lambda: len(list(rpk.describe_topic(topic.name))) > 0, + 30, + backoff_sec=2) + + wait_until( + lambda: next(segments_count(self.redpanda, topic.name, 0)) == 1, + timeout_sec=30, + backoff_sec=2, + err_msg="Expected only one segment to be present") + + kcat = KafkaCat(self.redpanda) + + # Query below valid timestamps the offset of the first message. + offset = kcat.query_offset(topic.name, 0, timestamps[0] - 1000) + assert offset == 0, f"Expected 0, got {offset}" + + # Query with a timestamp in-between cloud log and the configuration + # batch present in the local log. + offset = kcat.query_offset(topic.name, 0, + timestamps[msg_count - 1] + 1000) + assert offset == -1, f"Expected -1, got {offset}" + + # Query with a timestamp in the future. + offset = kcat.query_offset( + topic.name, 0, + int(time.time() + datetime.timedelta(days=1).total_seconds()) * + 1000) + assert offset == -1, f"Expected -1, got {offset}" + class TimeQueryKafkaTest(Test, BaseTimeQuery): """ diff --git a/tests/rptest/tests/tls_metrics_test.py b/tests/rptest/tests/tls_metrics_test.py index bd8c2da1ce895..0904ba896297e 100644 --- a/tests/rptest/tests/tls_metrics_test.py +++ b/tests/rptest/tests/tls_metrics_test.py @@ -12,6 +12,7 @@ import time from datetime import datetime, timedelta from typing import Optional, Callable +import crc32c from ducktape.cluster.cluster import ClusterNode @@ -40,7 +41,10 @@ class FaketimeTLSProvider(TLSProvider): - def __init__(self, tls, broker_faketime='-0d', client_faketime='-0d'): + def __init__(self, + tls: tls.TLSCertManager, + broker_faketime='-0d', + client_faketime='-0d'): self.tls = tls self.broker_faketime = broker_faketime self.client_faketime = client_faketime @@ -67,6 +71,7 @@ class TLSMetricsTestBase(RedpandaTest): 'loaded_at_timestamp_seconds', 'certificate_valid', 'certificate_serial', + 'trust_file_crc32c', ] EXPECTED_LABELS: list[str] = [ @@ -252,6 +257,84 @@ def set_cfg(node): assert 'rest_proxy' in areas assert 'admin' in areas + @cluster(num_nodes=3) + def test_expiry_reload(self): + """ + Verify that when replacing certificat X by certificate Y s.t. + expiry(Y) > expiry(X), the new expiry is reflected in the metrics. + """ + node = self.redpanda.nodes[0] + + metrics_samples = self._get_metrics_from_node(node, self.CERT_METRICS) + assert metrics_samples is not None, "Failed to get metrics" + vals = self._unpack_samples(metrics_samples) + + status_before = dict( + expiry=vals['certificate_expires_at_timestamp_seconds'][0] + ['value'], + loaded=vals['loaded_at_timestamp_seconds'][0]['value']) + self.logger.debug( + f"Before reload: {json.dumps(status_before, indent=1)}") + + time.sleep(5) + + self.security.tls_provider = FaketimeTLSProvider( + tls=tls.TLSCertManager(self.logger, cert_expiry_days=10)) + + self.redpanda.set_security_settings(self.security) + self.redpanda.write_tls_certs() + + metrics_samples = self._get_metrics_from_node(node, self.CERT_METRICS) + assert metrics_samples is not None, "Failed to get metrics" + vals = self._unpack_samples(metrics_samples) + + status_after = dict( + expiry=vals['certificate_expires_at_timestamp_seconds'][0] + ['value'], + loaded=vals['loaded_at_timestamp_seconds'][0]['value']) + self.logger.debug( + f"After reload: {json.dumps(status_after, indent=1)}") + + five_days = 5 * 24 * 60 * 60 + + assert status_before['loaded'] < status_after[ + 'loaded'], f"Unexpected status after reload: {json.dumps(status_after)}" + assert status_before['expiry'] + five_days < status_after[ + 'expiry'], f"Unexpected status after reload: {json.dumps(status_after)}" + + @cluster(num_nodes=3) + def test_crc32c(self): + node = self.redpanda.nodes[0] + + def check_crc(): + metrics_samples = self._get_metrics_from_node( + node, ['trust_file_crc32c']) + assert metrics_samples is not None, "Failed to get metrics" + vals = self._unpack_samples(metrics_samples)['trust_file_crc32c'] + + assert len(vals) > 0, "Missing crc metrics for some reason" + + expected = crc32c.crc32c( + open(self.security.tls_provider.ca.crt, 'rb').read()) + + for v in vals: + got = int(v['value']) + assert got == expected, f"Expected {expected}; Got {got}" + + return expected + + original = check_crc() + + self.security.tls_provider = FaketimeTLSProvider( + tls=tls.TLSCertManager(self.logger, cert_expiry_days=10)) + + self.redpanda.set_security_settings(self.security) + self.redpanda.write_tls_certs() + + reloaded = check_crc() + + assert original != reloaded, f"Checksums unexpectedly equal" + class TLSMetricsTestChain(TLSMetricsTestBase): def __init__(self, *args, **kwargs): diff --git a/tests/rptest/tests/topic_creation_test.py b/tests/rptest/tests/topic_creation_test.py index 24de7a76f1d6b..6e25596b49ef0 100644 --- a/tests/rptest/tests/topic_creation_test.py +++ b/tests/rptest/tests/topic_creation_test.py @@ -377,6 +377,11 @@ class CreateTopicsResponseTest(RedpandaTest): DEFAULT_CLEANUP_POLICY = 'delete' DEFAULT_CONFIG_SOURCE = 5 + CONFIG_SOURCE_MAPPING = { + 1: 'DYNAMIC_TOPIC_CONFIG', + 5: 'DEFAULT_CONFIG', + } + def __init__(self, test_context): super(CreateTopicsResponseTest, self).__init__(test_context=test_context) @@ -399,6 +404,16 @@ def create_topics(self, p_cnt, r_fac, n=1, validate_only=False): topics=topics, validate_only=validate_only) + def create_topic(self, name): + topics = [{ + 'name': f"{name}", + 'partition_count': 1, + 'replication_factor': 1 + }] + return self.kcl_client.create_topics(6, + topics=topics, + validate_only=False) + def get_np(self, tp): return tp['NumPartitions'] @@ -458,14 +473,22 @@ def test_create_topic_response_configs(self): b. serialized correctly """ - topics = self.create_topics(1, 1) - for topic in topics: - cleanup_policy = self.get_config_by_name(topic, 'cleanup.policy') - assert cleanup_policy is not None, "cleanup.policy missing from topic config" - assert cleanup_policy[ - 'Value'] == self.DEFAULT_CLEANUP_POLICY, f"cleanup.policy = {cleanup_policy['Value']}, expected {self.DEFAULT_CLEANUP_POLICY}" - assert cleanup_policy[ - 'Source'] == self.DEFAULT_CONFIG_SOURCE, f"cleanup.policy = {cleanup_policy['Source']}, expected {self.DEFAULT_CONFIG_SOURCE}" + topic_name = 'test-create-topic-response' + create_topics_response = self.create_topic(topic_name) + topic_response = create_topics_response[0] + + res = self.kcl_client.describe_topic(topic_name) + describe_configs = [line.split() for line in res.strip().split('\n')] + + for (key, value, source) in describe_configs: + topic_config = self.get_config_by_name(topic_response, key) + + assert topic_config, f"Config '{key}' returned by DescribeConfigs is missing from configs response in CreateTopic" + assert topic_config[ + 'Value'] == value, f"config value mismatch for {key} across CreateTopic and DescribeConfigs: {topic_config['Value']} != {value}" + + assert self.CONFIG_SOURCE_MAPPING[topic_config[ + 'Source']] == source, f"config source mismatch for {key} across CreateTopic and DescribeConfigs: {self.CONFIG_SOURCE_MAPPING[topic_config['Source']]} != {source}" @cluster(num_nodes=3) def test_create_topic_validate_only(self): @@ -629,7 +652,7 @@ def topic_alter_config_test(self): # overriden. topic_config = rpk.describe_topic_configs(topic) value, src = topic_config["cleanup.policy"] - assert value == "delete" and src == "DYNAMIC_TOPIC_CONFIG" + assert value == "delete" and src == "DEFAULT_CONFIG" kcl.alter_topic_config({"cleanup.policy": "compact"}, incremental=False, diff --git a/tests/rptest/tests/topic_recovery_test.py b/tests/rptest/tests/topic_recovery_test.py index 1686606a22a64..97ef706776218 100644 --- a/tests/rptest/tests/topic_recovery_test.py +++ b/tests/rptest/tests/topic_recovery_test.py @@ -1113,12 +1113,13 @@ def _wipe_data(self): self.redpanda.remove_local_data(node) @staticmethod - def _normalize_lw_seg_key(key: str, lw_seg_meta: dict) -> str: - """adds archiver_term to key, to match the actual object key in cloud storage""" - if not key.endswith(".log"): - # key already has archiver term set in the name - return key - return f"{key}.{lw_seg_meta['archiver_term']}" if "archiver_term" in lw_seg_meta else key + def _cloud_segment_key(lw_seg_meta: dict) -> str: + """calculates the segment key in cloud storage from segment meta""" + assert lw_seg_meta.get("sname_format") == 3 + sm = lw_seg_meta + return ( + f"{sm['base_offset']}-{sm['committed_offset']}-{sm['size_bytes']}" + f"-{sm['segment_term']}-v1.log.{sm['archiver_term']}") def _collect_replaced_segments(self, replaced: dict[NTPR, dict[str, dict]], manifest_key: str): @@ -1133,7 +1134,7 @@ def _collect_replaced_segments(self, replaced: dict[NTPR, dict[str, dict]], assert manifest is not None, f"failed to load manifest from path {manifest_key}" if replaced_segments := manifest.get('replaced'): replaced[parse_s3_manifest_path(manifest_key)] = { - TopicRecoveryTest._normalize_lw_seg_key(k, v): v + TopicRecoveryTest._cloud_segment_key(v): v for k, v in replaced_segments.items() } @@ -1203,8 +1204,7 @@ def verify(): tmp_size += size size_on_disk = max(tmp_size, size_on_disk) - size_in_cloud = sum(obj.content_length for obj in segments - if obj.content_length > EMPTY_SEGMENT_SIZE) + size_in_cloud = sum(obj.content_length for obj in segments) self.logger.debug( f'segments in cloud: {pprint.pformat(segments, indent=2)}, ' f'size in cloud: {size_in_cloud}') diff --git a/tests/rptest/tests/transactions_test.py b/tests/rptest/tests/transactions_test.py index c831c783bdde9..c37430bc4742d 100644 --- a/tests/rptest/tests/transactions_test.py +++ b/tests/rptest/tests/transactions_test.py @@ -21,7 +21,9 @@ import random from ducktape.utils.util import wait_until +from ducktape.errors import TimeoutError +from rptest.clients.offline_log_viewer import OfflineLogViewer from rptest.tests.redpanda_test import RedpandaTest from rptest.services.admin import Admin from rptest.services.redpanda import RedpandaService, SecurityConfig, SaslCredentials @@ -102,6 +104,37 @@ def __init__(self, test_context): self.max_records = 100 self.admin = Admin(self.redpanda) + def wait_for_eviction(self, max_concurrent_producer_ids, num_to_evict): + samples = [ + "idempotency_pid_cache_size", + "producer_state_manager_evicted_producers" + ] + brokers = self.redpanda.started_nodes() + metrics = self.redpanda.metrics_samples(samples, brokers) + producers_per_node = defaultdict(int) + evicted_per_node = defaultdict(int) + for pattern, metric in metrics.items(): + for m in metric.samples: + id = self.redpanda.node_id(m.node) + if pattern == "idempotency_pid_cache_size": + producers_per_node[id] += int(m.value) + elif pattern == "producer_state_manager_evicted_producers": + evicted_per_node[id] += int(m.value) + + self.redpanda.logger.debug(f"active producers: {producers_per_node}") + self.redpanda.logger.debug(f"evicted producers: {evicted_per_node}") + + remaining_match = all([ + num == max_concurrent_producer_ids + for num in producers_per_node.values() + ]) + + evicted_match = all( + [val == num_to_evict for val in evicted_per_node.values()]) + + return len(producers_per_node) == len( + brokers) and remaining_match and evicted_match + @cluster(num_nodes=3) def find_coordinator_creates_tx_topics_test(self): for node in self.redpanda.started_nodes(): @@ -130,7 +163,6 @@ def init_transactions_creates_eos_topics_test(self): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) producer.init_transactions() @@ -149,7 +181,6 @@ def simple_test(self): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) consumer1 = ck.Consumer({ @@ -214,6 +245,12 @@ def simple_test(self): ), f'Records value does not match from input {consumed_from_input_topic[index_from_input].value()}, from output {record.value()}' index_from_input += 1 + log_viewer = OfflineLogViewer(self.redpanda) + for node in self.redpanda.started_nodes(): + records = log_viewer.read_kafka_records(node=node, + topic=self.input_t.name) + self.logger.info(f"Read {len(records)} from node {node.name}") + @cluster(num_nodes=3) def rejoin_member_test(self): self.generate_data(self.input_t, self.max_records) @@ -221,7 +258,6 @@ def rejoin_member_test(self): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) group_name = "test" @@ -279,7 +315,6 @@ def change_static_member_test(self): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) group_name = "test" @@ -326,6 +361,49 @@ def change_static_member_test(self): producer.abort_transaction() + @cluster(num_nodes=3) + def transaction_id_expiration_test(self): + admin = Admin(self.redpanda) + rpk = RpkTool(self.redpanda) + # Create an open transaction. + producer = ck.Producer({ + 'bootstrap.servers': self.redpanda.brokers(), + 'transactional.id': '0', + 'transaction.timeout.ms': 3600000, # to avoid timing out + }) + producer.init_transactions() + producer.begin_transaction() + producer.produce(self.output_t.name, "x", "y") + producer.flush() + + # Default transactional id expiration is 7d, so the transaction + # should be hung. + def no_running_transactions(): + return len(admin.get_all_transactions()) == 0 + + wait_timeout_s = 20 + try: + wait_until(no_running_transactions, + timeout_sec=wait_timeout_s, + backoff_sec=2, + err_msg="Transactions still running") + assert False, "No running transactions found." + except TimeoutError as e: + assert "Transactions still running" in str(e) + + # transaction should be aborted. + rpk.cluster_config_set("transactional_id_expiration_ms", 5000) + wait_until(no_running_transactions, + timeout_sec=wait_timeout_s, + backoff_sec=2, + err_msg="Transactions still running") + + try: + producer.commit_transaction() + assert False, "transaction should have been aborted by now." + except ck.KafkaException: + pass + @cluster(num_nodes=3) def expired_tx_test(self): # confluent_kafka client uses the same timeout both for init_transactions @@ -717,6 +795,43 @@ def segments_removed(): for n, consumed_bytes in consumed_per_node.items() ]) + @cluster(num_nodes=3) + def check_progress_after_fencing_test(self): + """Checks that a fencing producer makes progress after fenced producers are evicted.""" + + producer = ck.Producer({ + 'bootstrap.servers': self.redpanda.brokers(), + 'transactional.id': 'test', + 'transaction.timeout.ms': 100000, + }) + + topic_name = self.topics[0].name + + # create a pid, do not commit/abort transaction. + producer.init_transactions() + producer.begin_transaction() + producer.produce(topic_name, "0", "0", 0, self.on_delivery) + producer.flush() + + # fence the above pid with another producer + producer0 = ck.Producer({ + 'bootstrap.servers': self.redpanda.brokers(), + 'transactional.id': 'test', + 'transaction.timeout.ms': 100000, + }) + producer0.init_transactions() + producer0.begin_transaction() + producer0.produce(topic_name, "0", "0", 0, self.on_delivery) + + max_concurrent_pids = 1 + rpk = RpkTool(self.redpanda) + rpk.cluster_config_set("max_concurrent_producer_ids", + str(max_concurrent_pids)) + + self.wait_for_eviction(max_concurrent_pids, 1) + + producer0.commit_transaction() + @cluster(num_nodes=3) def check_pids_overflow_test(self): rpk = RpkTool(self.redpanda) @@ -756,41 +871,8 @@ def check_pids_overflow_test(self): evicted_count = max_producers - max_concurrent_producer_ids - # Wait until eviction kicks in. - def wait_for_eviction(): - samples = [ - "idempotency_pid_cache_size", - "producer_state_manager_evicted_producers" - ] - brokers = self.redpanda.started_nodes() - metrics = self.redpanda.metrics_samples(samples, brokers) - producers_per_node = defaultdict(int) - evicted_per_node = defaultdict(int) - for pattern, metric in metrics.items(): - for m in metric.samples: - id = self.redpanda.node_id(m.node) - if pattern == "idempotency_pid_cache_size": - producers_per_node[id] += int(m.value) - elif pattern == "producer_state_manager_evicted_producers": - evicted_per_node[id] += int(m.value) - - self.redpanda.logger.debug( - f"active producers: {producers_per_node}") - self.redpanda.logger.debug( - f"evicted producers: {evicted_per_node}") - - remaining_match = all([ - num == max_concurrent_producer_ids - for num in producers_per_node.values() - ]) - - evicted_match = all( - [val == evicted_count for val in evicted_per_node.values()]) - - return len(producers_per_node) == len( - brokers) and remaining_match and evicted_match - - wait_until(wait_for_eviction, + wait_until(lambda: self.wait_for_eviction(max_concurrent_producer_ids, + evicted_count), timeout_sec=30, backoff_sec=2, err_msg="Producers not evicted in time") @@ -846,6 +928,105 @@ def wait_for_eviction(): assert num_consumed == should_be_consumed +class TransactionsStreamsTest(RedpandaTest, TransactionsMixin): + topics = (TopicSpec(partition_count=1, replication_factor=3), + TopicSpec(partition_count=1, replication_factor=3)) + + def __init__(self, test_context): + extra_rp_conf = { + 'unsafe_enable_consumer_offsets_delete_retention': True, + 'group_topic_partitions': 1, # to reduce log noise + 'log_segment_size_min': 99, + # to be able to make changes to CO + 'kafka_nodelete_topics': [], + 'kafka_noproduce_topics': [], + } + super(TransactionsStreamsTest, + self).__init__(test_context=test_context, + extra_rp_conf=extra_rp_conf) + self.input_t = self.topics[0] + self.output_t = self.topics[1] + + def setup_consumer_offsets(self, rpk: RpkTool): + # initialize consumer groups topic + rpk.consume(topic=self.input_t.name, n=1, group="test-group") + topic = "__consumer_offsets" + # Aggressive roll settings to clear multiple small segments + rpk.alter_topic_config(topic, TopicSpec.PROPERTY_CLEANUP_POLICY, + TopicSpec.CLEANUP_DELETE) + rpk.alter_topic_config(topic, TopicSpec.PROPERTY_SEGMENT_SIZE, 100) + + @cluster(num_nodes=3) + def consumer_offsets_retention_test(self): + """Ensure consumer offsets replays correctly after transactional offset commits""" + input_records = 10000 + self.generate_data(self.input_t, input_records) + rpk = RpkTool(self.redpanda) + self.setup_consumer_offsets(rpk) + # Populate consumer offsets with transactional offset commits/aborts + producer_conf = { + 'bootstrap.servers': self.redpanda.brokers(), + 'transactional.id': 'streams', + } + producer = ck.Producer(producer_conf) + consumer_conf = { + 'bootstrap.servers': self.redpanda.brokers(), + 'group.id': "test", + 'auto.offset.reset': 'earliest', + 'enable.auto.commit': False, + } + consumer = ck.Consumer(consumer_conf) + consumer.subscribe([self.input_t]) + + producer.init_transactions() + consumed = 0 + while consumed != input_records: + records = self.consume(consumer) + producer.begin_transaction() + for record in records: + producer.produce(self.output_t.name, + record.value(), + record.key(), + on_delivery=self.on_delivery) + + producer.send_offsets_to_transaction( + consumer.position(consumer.assignment()), + consumer.consumer_group_metadata()) + + producer.flush() + + if random.randint(0, 9) < 5: + producer.commit_transaction() + else: + producer.abort_transaction() + consumed += len(records) + + admin = Admin(self.redpanda) + co_topic = "__consumer_offsets" + + def get_offsets(): + topic_info = list(rpk.describe_topic(co_topic))[0] + assert topic_info + return (topic_info.start_offset, topic_info.high_watermark) + + # trim prefix, change leadership and validate the log is replayed successfully on + # the new leader. + attempts = 30 + truncate_offset = 100 + while attempts > 0: + (start, end) = get_offsets() + self.redpanda.logger.debug(f"Current offsets: {start} - {end}") + if truncate_offset > end: + break + rpk.trim_prefix(co_topic, truncate_offset, partitions=[0]) + admin.partition_transfer_leadership("kafka", co_topic, partition=0) + admin.await_stable_leader(topic=co_topic, + replication=3, + timeout_s=30) + truncate_offset += 200 + attempts = attempts - 1 + + @contextmanager def expect_kafka_error(err: Optional[ck.KafkaError] = None): try: @@ -966,7 +1147,6 @@ def init_transactions_authz_test(self): producer_cfg = { 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, } user = self.USER_1 @@ -995,7 +1175,6 @@ def simple_authz_test(self): producer_cfg = { 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, } consumer_cfg = { 'bootstrap.servers': self.redpanda.brokers(), @@ -1181,7 +1360,6 @@ def do_upgrade_with_tx(self, selector): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) producer.init_transactions() @@ -1204,7 +1382,6 @@ def do_upgrade_with_tx(self, selector): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': '0', - 'transaction.timeout.ms': 10000, }) producer.init_transactions() @@ -1286,7 +1463,6 @@ def delivery_callback(err, msg): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': self._tx_id(i), - 'transaction.timeout.ms': 10000, }) producer.init_transactions() producer.begin_transaction() diff --git a/tests/rptest/tests/tx_coordinator_migration_test.py b/tests/rptest/tests/tx_coordinator_migration_test.py index b4241f6b67494..7e77dc21ecf90 100644 --- a/tests/rptest/tests/tx_coordinator_migration_test.py +++ b/tests/rptest/tests/tx_coordinator_migration_test.py @@ -53,7 +53,6 @@ def delivery_callback(err, msg): producer = ck.Producer({ 'bootstrap.servers': self.redpanda.brokers(), 'transactional.id': self._tx_id(i), - 'transaction.timeout.ms': 10000, }) producer.init_transactions() producer.begin_transaction() diff --git a/tests/rptest/util.py b/tests/rptest/util.py index 67afbdb33d654..ee7c8be1b3248 100644 --- a/tests/rptest/util.py +++ b/tests/rptest/util.py @@ -371,7 +371,8 @@ def __enter__(self): """Isolate certain ips from the nodes using firewall rules""" cmd = [ f"iptables -A INPUT -p tcp --{self.mode_for_input} {self._port} -j DROP", - f"iptables -A OUTPUT -p tcp --dport {self._port} -j DROP" + f"iptables -A OUTPUT -p tcp --dport {self._port} -j DROP", + f"ss -K dport {self._port}", ] cmd = " && ".join(cmd) for node in self._nodes: diff --git a/tests/rptest/utils/node_operations.py b/tests/rptest/utils/node_operations.py index fb3f998509a18..f28c9547cfa4e 100644 --- a/tests/rptest/utils/node_operations.py +++ b/tests/rptest/utils/node_operations.py @@ -98,18 +98,6 @@ def __init__(self, node_id ] if decommissioned_node_ids == None else decommissioned_node_ids - def _nodes_with_decommission_progress_api(self): - def has_decommission_progress_api(node): - v = int_tuple( - VERSION_RE.findall(self.redpanda.get_version(node))[0]) - # decommission progress api is available since v22.3.12 - return v[0] >= 23 or (v[0] == 22 and v[1] == 3 and v[2] >= 12) - - return [ - n for n in self.redpanda.started_nodes() - if has_decommission_progress_api(n) - ] - def _dump_partition_move_available_bandwidth(self): def get_metric(self, node): try: @@ -134,7 +122,7 @@ def get_metric(self, node): def _not_decommissioned_node(self): return random.choice([ - n for n in self._nodes_with_decommission_progress_api() + n for n in self.redpanda.started_nodes() if self.redpanda.node_id(n) not in self.decommissioned_node_ids ]) diff --git a/tools/offline_log_viewer/kafka.py b/tools/offline_log_viewer/kafka.py index 813c09a409077..2f91d935cba97 100644 --- a/tools/offline_log_viewer/kafka.py +++ b/tools/offline_log_viewer/kafka.py @@ -47,6 +47,14 @@ def spec(rdr, version): return rdr.read_envelope(spec, max_version=3) +def get_control_record_type(key): + rdr = Reader(BytesIO(key)) + rdr.skip(2) # skip the 16bit version. + # Encoded as big endian + type_rdr = Reader(BytesIO(struct.pack(">h", rdr.read_int16()))) + return KafkaControlRecordType(type_rdr.read_int16()).name + + def decode_archival_metadata_command(kr, vr): key = kr.read_int8() if key == ArchivalMetadataCommand.add_segment: @@ -82,7 +90,7 @@ def decode_record(batch, header, record): is_ctrl = attrs["control_batch"] is_tx_ctrl = is_txn and is_ctrl if is_tx_ctrl: - record_dict["type"] = self.get_control_record_type(record.key) + record_dict["type"] = get_control_record_type(record.key) kr = Reader(BytesIO(record.key)) vr = Reader(BytesIO(record.value)) @@ -102,14 +110,7 @@ def __init__(self, ntp, headers_only): self.ntp = ntp self.headers_only = headers_only - def get_control_record_type(self, key): - rdr = Reader(BytesIO(key)) - rdr.skip(2) # skip the 16bit version. - # Encoded as big endian - type_rdr = Reader(BytesIO(struct.pack(">h", rdr.read_int16()))) - return KafkaControlRecordType(type_rdr.read_int16()).name - - def decode(self): + def __iter__(self): self.results = [] for batch in self.batches(): header = batch.header_dict() diff --git a/tools/offline_log_viewer/viewer.py b/tools/offline_log_viewer/viewer.py index 3872ac745f957..64a40db790372 100644 --- a/tools/offline_log_viewer/viewer.py +++ b/tools/offline_log_viewer/viewer.py @@ -68,8 +68,10 @@ def print_kafka(store, topic, headers_only): logger.info(f'topic: {ntp.topic}, partition: {ntp.partition}') log = KafkaLog(ntp, headers_only=headers_only) - for result in log.decode(): - logger.info(json.dumps(result, indent=2)) + json_iter = json.JSONEncoder(indent=2).iterencode( + SerializableGenerator(log)) + for record in json_iter: + print(record, end='') def print_groups(store):