diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 0000000000000..68b3f1e6d603e --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +github: + description: "Apache Pulsar - distributed pub-sub messaging system" + homepage: https://pulsar.apache.org/ + labels: + - pulsar + - pubsub + - messaging + - streaming + - queuing + - event-streaming + features: + # Enable wiki for documentation + wiki: true + # Enable issues management + issues: true + # Enable projects for project management boards + projects: true + enabled_merge_buttons: + # enable squash button: + squash: true + # disable merge button: + merge: false + # disable rebase button: + rebase: false \ No newline at end of file diff --git a/.github/workflows/ci-cpp.yaml b/.github/workflows/ci-cpp.yaml index d7d746adbb828..d339b7106e947 100644 --- a/.github/workflows/ci-cpp.yaml +++ b/.github/workflows/ci-cpp.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,24 +31,35 @@ jobs: timeout-minutes: 120 steps: + + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: build pacakge + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR package -DskipTests - name: build cpp artifacts + if: steps.docs.outputs.changed_only == 'no' run: | echo "Build C++ client library" export CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Debug -DBUILD_DYNAMIC_LIB=OFF" pulsar-client-cpp/docker-build.sh - name: run c++ tests + if: steps.docs.outputs.changed_only == 'no' run: pulsar-client-cpp/docker-tests.sh diff --git a/.github/workflows/ci-go-functions-style.yaml b/.github/workflows/ci-go-functions-style.yaml index cf30e520cfe42..7acb091fc0aa1 100644 --- a/.github/workflows/ci-go-functions-style.yaml +++ b/.github/workflows/ci-go-functions-style.yaml @@ -31,28 +31,37 @@ jobs: name: Build runs-on: ubuntu-latest steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml - name: Set up Go 1.12 uses: actions/setup-go@v1 + if: steps.docs.outputs.changed_only == 'no' with: go-version: 1.12 id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v1 - - name: InstallTool + if: steps.docs.outputs.changed_only == 'no' run: | cd pulsar-function-go wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.18.0 ./bin/golangci-lint --version - name: Build + if: steps.docs.outputs.changed_only == 'no' run: | cd pulsar-function-go - go build ./pf + go build ./... - name: CheckStyle + if: steps.docs.outputs.changed_only == 'no' run: | cd pulsar-function-go ./bin/golangci-lint run -c ./golangci.yml ./pf diff --git a/.github/workflows/ci-go-functions-test.yaml b/.github/workflows/ci-go-functions-test.yaml index 059499c1f085e..5609ad706aee5 100644 --- a/.github/workflows/ci-go-functions-test.yaml +++ b/.github/workflows/ci-go-functions-test.yaml @@ -34,16 +34,24 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up Go 1.12 uses: actions/setup-go@v1 + if: steps.docs.outputs.changed_only == 'no' with: go-version: 1.12 id: go - - name: checkout - uses: actions/checkout@v1 - - name: run tests + if: steps.docs.outputs.changed_only == 'no' run: | cd pulsar-function-go go test -v $(go list ./... | grep -v examples) diff --git a/.github/workflows/ci-integration-backwards-compatibility.yaml b/.github/workflows/ci-integration-backwards-compatibility.yaml index 47fc5f6215f61..aa827897ea07b 100644 --- a/.github/workflows/ci-integration-backwards-compatibility.yaml +++ b/.github/workflows/ci-integration-backwards-compatibility.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,28 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 + if: steps.docs.outputs.changed_only == 'no' uses: actions/setup-java@v1 with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: build artifacts and docker image + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' run: docker system prune -f - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-backwards-compatibility.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-cli.yaml b/.github/workflows/ci-integration-cli.yaml index 7e07789426170..f12f8f341f092 100644 --- a/.github/workflows/ci-integration-cli.yaml +++ b/.github/workflows/ci-integration-cli.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-cli.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-function-state.yaml b/.github/workflows/ci-integration-function-state.yaml index 51a44451c07b7..548beecf9fbac 100644 --- a/.github/workflows/ci-integration-function-state.yaml +++ b/.github/workflows/ci-integration-function-state.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-function-state.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-messaging.yaml b/.github/workflows/ci-integration-messaging.yaml index 6c008931db90d..795ce5b8143fd 100644 --- a/.github/workflows/ci-integration-messaging.yaml +++ b/.github/workflows/ci-integration-messaging.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-messaging.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-process.yaml b/.github/workflows/ci-integration-process.yaml index 4fda27149fe6a..09ce922b75c24 100644 --- a/.github/workflows/ci-integration-process.yaml +++ b/.github/workflows/ci-integration-process.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,34 +31,49 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: build artifacts and docker image + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' run: docker system prune -f - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration function + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-process.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=function - name: run integration source + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-process.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=source - name: run integraion sink + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-process.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=sink diff --git a/.github/workflows/ci-integration-schema.yaml b/.github/workflows/ci-integration-schema.yaml index af6f457e59833..0b73ddbea4686 100644 --- a/.github/workflows/ci-integration-schema.yaml +++ b/.github/workflows/ci-integration-schema.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-schema.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-sql.yaml b/.github/workflows/ci-integration-sql.yaml index 1a68323045c70..c495eb720cea4 100644 --- a/.github/workflows/ci-integration-sql.yaml +++ b/.github/workflows/ci-integration-sql.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,21 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 - with: - maven-version: 3.6.1 + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest + + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests + + - name: build artifacts and docker pulsar latest test image + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests - - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-sql.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-standalone.yaml b/.github/workflows/ci-integration-standalone.yaml index 6d39512969813..0347f1e53e049 100644 --- a/.github/workflows/ci-integration-standalone.yaml +++ b/.github/workflows/ci-integration-standalone.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-standalone.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-thread.yaml b/.github/workflows/ci-integration-thread.yaml index 282d44a959738..f650c553b54ca 100644 --- a/.github/workflows/ci-integration-thread.yaml +++ b/.github/workflows/ci-integration-thread.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,34 +31,49 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: build artifacts and docker image + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' run: docker system prune -f - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration function + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-thread.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=function - name: run integration source + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-thread.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=source - name: run integraion sink + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=pulsar-thread.xml -DintegrationTests -DredirectTestOutputToFile=false -Dgroups=sink diff --git a/.github/workflows/ci-integration-tiered-filesystem.yaml b/.github/workflows/ci-integration-tiered-filesystem.yaml index 73f59d90af3a6..01b86c4a7d168 100644 --- a/.github/workflows/ci-integration-tiered-filesystem.yaml +++ b/.github/workflows/ci-integration-tiered-filesystem.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=tiered-filesystem-storage.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-integration-tiered-jcloud.yaml b/.github/workflows/ci-integration-tiered-jcloud.yaml index 04116633ee308..f06f1eec4f26c 100644 --- a/.github/workflows/ci-integration-tiered-jcloud.yaml +++ b/.github/workflows/ci-integration-tiered-jcloud.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,16 +31,41 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 + - name: run install by skip tests + if: steps.docs.outputs.changed_only == 'no' + run: mvn clean install -DskipTests - name: build artifacts and docker image - run: mvn -B install -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR -Pdocker -DskipTests + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -f tests/docker-images/pom.xml install -am -Pdocker -DskipTests + + - name: clean docker container + if: steps.docs.outputs.changed_only == 'no' + run: docker system prune -f + + - name: remove docker node image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f node:10 && docker rmi -f node:12 && docker rmi -f buildpack-deps:stretch + + - name: remove docker builder and microsoft image + if: steps.docs.outputs.changed_only == 'no' + run: docker rmi -f jekyll/builder:latest && docker rmi -f mcr.microsoft.com/azure-pipelines/node8-typescript:latest - name: run integration tests + if: steps.docs.outputs.changed_only == 'no' run: mvn -B -f tests/pom.xml test -DintegrationTestSuiteFile=tiered-jcloud-storage.xml -DintegrationTests -DredirectTestOutputToFile=false diff --git a/.github/workflows/ci-license.yaml b/.github/workflows/ci-license.yaml index 820e433e7e643..54672a2bcba10 100644 --- a/.github/workflows/ci-license.yaml +++ b/.github/workflows/ci-license.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,17 +31,17 @@ jobs: timeout-minutes: 60 steps: + - name: checkout + uses: actions/checkout@v1 + - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - # license check fails with 3.6.2 so we have to downgrade - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master with: maven-version: 3.6.1 diff --git a/.github/workflows/ci-pulsarbot.yaml b/.github/workflows/ci-pulsarbot.yaml new file mode 100644 index 0000000000000..c00aaa5ee2492 --- /dev/null +++ b/.github/workflows/ci-pulsarbot.yaml @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: Pulsar Bot +on: + issue_comment: + types: [created] + +jobs: + + action-runner: + name: + runs-on: ubuntu-latest + timeout-minutes: 120 + + steps: + - name: checkout + uses: actions/checkout@v1 + - name: Execute pulsarbot command + id: pulsarbot + env: + GITHUB_TOKEN: ${{ secrets.PULSARBOT_TOKEN }} + uses: apache/pulsar-test-infra/pulsarbot@master diff --git a/.github/workflows/ci-unit-adaptors.yml b/.github/workflows/ci-unit-adaptors.yml index a5853e155656a..83ebcd218bd8e 100644 --- a/.github/workflows/ci-unit-adaptors.yml +++ b/.github/workflows/ci-unit-adaptors.yml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,24 +31,50 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 - name: run unit tests install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: run unit tests pulsar io kafka connect adaptor + if: steps.docs.outputs.changed_only == 'no' run: mvn test -pl pulsar-io/kafka-connect-adaptor - name: run unit tests pulsar storm tests + if: steps.docs.outputs.changed_only == 'no' run: mvn test -pl tests/pulsar-storm-test + + - name: package surefire artifacts + if: failure() + run: | + rm -rf artifacts + mkdir artifacts + find . -type d -name "*surefire*" -exec cp --parents -R {} artifacts/ \; + zip -r artifacts.zip artifacts + + - uses: actions/upload-artifact@master + name: upload surefire-artifacts + if: failure() + with: + name: surefire-artifacts + path: artifacts.zip diff --git a/.github/workflows/ci-unit-broker-sasl.yml b/.github/workflows/ci-unit-broker-sasl.yml index 6270efe01bc51..57aceb9c8bf37 100644 --- a/.github/workflows/ci-unit-broker-sasl.yml +++ b/.github/workflows/ci-unit-broker-sasl.yml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,21 +31,46 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 - name: run unit tests install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: run unit tests pulsar auth sasl + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false -pl pulsar-broker-auth-sasl + + - name: package surefire artifacts + if: failure() + run: | + rm -rf artifacts + mkdir artifacts + find . -type d -name "*surefire*" -exec cp --parents -R {} artifacts/ \; + zip -r artifacts.zip artifacts + + - uses: actions/upload-artifact@master + name: upload surefire-artifacts + if: failure() + with: + name: surefire-artifacts + path: artifacts.zip diff --git a/.github/workflows/ci-unit-broker.yml b/.github/workflows/ci-unit-broker.yml index 5485bc2595135..4538af439650f 100644 --- a/.github/workflows/ci-unit-broker.yml +++ b/.github/workflows/ci-unit-broker.yml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,42 +31,73 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 - name: run unit tests install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: run unit tests pulsar broker reader test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=ReaderTest' -pl pulsar-broker - name: run unit tests pulsar broker rack aware test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=RackAwareTest' -pl pulsar-broker - name: run unit tests pulsar broker simple producer consumer test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=SimpleProducerConsumerTest' -pl pulsar-broker - name: run unit tests pulsar broker V1 producer consumer test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=V1_ProducerConsumerTest' -pl pulsar-broker - name: run unit tests pulsar broker persistent failover end to end test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=PersistentFailoverE2ETest' -pl pulsar-broker - name: run unit tests pulsar broker client integration test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=BrokerClientIntegrationTest' -pl pulsar-broker - name: run unit tests pulsar broker replicatior rate limiter test + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=ReplicatorRateLimiterTest' -pl pulsar-broker - name: run unit test pulsar-broker + if: steps.docs.outputs.changed_only == 'no' run: mvn test '-Dtest=!PersistentTransactionBufferTest,!PulsarFunctionE2ESecurityTest,!ServerCnxTest,!AdminApiOffloadTest,!AdminApiSchemaValidationEnforced,!V1_AdminApiTest2,!ProxyPublishConsumeTlsTest,!PulsarFunctionE2ETest,!MessageIdSerialization,!AdminApiTest2,!PulsarFunctionLocalRunTest,!PartitionedProducerConsumerTest,!KafkaProducerSimpleConsumerTest,!MessagePublishThrottlingTest,!ReaderTest,!RackAwareTest,!SimpleProducerConsumerTest,!V1_ProducerConsumerTest,!PersistentFailoverE2ETest,!BrokerClientIntegrationTest,!ReplicatorRateLimiterTest' -DfailIfNoTests=false -pl pulsar-broker + + - name: package surefire artifacts + if: failure() + run: | + rm -rf artifacts + mkdir artifacts + find . -type d -name "*surefire*" -exec cp --parents -R {} artifacts/ \; + zip -r artifacts.zip artifacts + - uses: actions/upload-artifact@master + name: upload surefire-artifacts + if: failure() + with: + name: surefire-artifacts + path: artifacts.zip diff --git a/.github/workflows/ci-unit-flaky.yaml b/.github/workflows/ci-unit-flaky.yaml index f47fa406a28d3..8d3f0fb5e8895 100644 --- a/.github/workflows/ci-unit-flaky.yaml +++ b/.github/workflows/ci-unit-flaky.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,33 +31,54 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 + - name: build the project + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B clean install -DskipTests + - name: run PersistentTransactionBuffer test - run: mvn -B -am -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=PersistentTransactionBufferTest -DfailIfNoTests=false + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=PersistentTransactionBufferTest -DfailIfNoTests=false -fn - name: run ServerCnx test - run: mvn -B -am -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=ServerCnxTest -DfailIfNoTests=false + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=ServerCnxTest -DfailIfNoTests=false -fn - name: run MessagePublishThrottling test - run: mvn -B -am -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=MessagePublishThrottlingTest -DfailIfNoTests=false + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -pl pulsar-broker -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=MessagePublishThrottlingTest -DfailIfNoTests=false -fn - name: run PrimitiveSchema test - run: mvn -B -am -pl pulsar-client -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=PrimitiveSchemaTest -DfailIfNoTests=false + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -pl pulsar-client -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=PrimitiveSchemaTest -DfailIfNoTests=false -fn + + - name: run BlobStoreManagedLedgerOffloaderTest + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -pl tiered-storage/jcloud -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install -Dtest=BlobStoreManagedLedgerOffloaderTest -DfailIfNoTests=false -fn - name: run unit tests - run: mvn -B -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR clean install '-Dtest=PulsarFunctionE2ESecurityTest,AdminApiOffloadTest,AdminApiSchemaValidationEnforced,V1_AdminApiTest2,PulsarFunctionE2ETest,MessageIdSerialization,AdminApiTest2,PulsarFunctionLocalRunTest,PartitionedProducerConsumerTest,KafkaProducerSimpleConsumerTest,ProxyTest' -DfailIfNoTests=false + if: steps.docs.outputs.changed_only == 'no' + run: mvn -B -Dorg.slf4j.simpleLogger.defaultLogLevel=ERROR test '-Dtest=PulsarFunctionE2ESecurityTest,AdminApiOffloadTest,AdminApiSchemaValidationEnforced,V1_AdminApiTest2,PulsarFunctionE2ETest,MessageIdSerialization,AdminApiTest2,PulsarFunctionLocalRunTest,PartitionedProducerConsumerTest,KafkaProducerSimpleConsumerTest,ProxyTest' -DfailIfNoTests=false -fn - name: package surefire artifacts if: failure() diff --git a/.github/workflows/ci-unit-proxy.yaml b/.github/workflows/ci-unit-proxy.yaml index d957b53223ecd..83e3f3d1112e6 100644 --- a/.github/workflows/ci-unit-proxy.yaml +++ b/.github/workflows/ci-unit-proxy.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,24 +31,50 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 - name: run unit tests install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: run unit tests pulsar proxy + if: steps.docs.outputs.changed_only == 'no' run: mvn test -DfailIfNoTests=false '-Dtest=!ProxyTest,!ProxyLookupThrottlingTest' -pl pulsar-proxy - - name: run unit tests pulsar proxy - run: mvn test -DfailIfNoTests=false '-Dtest=ProxyTest,ProxyLookupThrottlingTest' -pl pulsar-proxy + - name: run proxy lookup throttling test + if: steps.docs.outputs.changed_only == 'no' + run: mvn test -DfailIfNoTests=false '-Dtest=ProxyLookupThrottlingTest' -pl pulsar-proxy + + - name: package surefire artifacts + if: failure() + run: | + rm -rf artifacts + mkdir artifacts + find . -type d -name "*surefire*" -exec cp --parents -R {} artifacts/ \; + zip -r artifacts.zip artifacts + + - uses: actions/upload-artifact@master + name: upload surefire-artifacts + if: failure() + with: + name: surefire-artifacts + path: artifacts.zip diff --git a/.github/workflows/ci-unit.yaml b/.github/workflows/ci-unit.yaml index a0b566ca3a764..b429d1639bcb0 100644 --- a/.github/workflows/ci-unit.yaml +++ b/.github/workflows/ci-unit.yaml @@ -22,8 +22,6 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' jobs: @@ -33,24 +31,34 @@ jobs: timeout-minutes: 120 steps: + - name: checkout + uses: actions/checkout@v1 + + - name: Check if this pull request only changes documentation + id: docs + uses: apache/pulsar-test-infra/diff-only@master + with: + args: site2 .github deployment .asf.yaml + - name: Set up JDK 1.8 uses: actions/setup-java@v1 + if: steps.docs.outputs.changed_only == 'no' with: java-version: 1.8 - - name: checkout - uses: actions/checkout@v1 - - name: Set up Maven - uses: aahmed-se/setup-maven@v3 + uses: apache/pulsar-test-infra/setup-maven@master + if: steps.docs.outputs.changed_only == 'no' with: maven-version: 3.6.1 - name: run unit tests install by skip tests + if: steps.docs.outputs.changed_only == 'no' run: mvn clean install -DskipTests - name: run unit tests - run: mvn clean install -DfailIfNoTests=false '-Dtest=!KafkaProducerSimpleConsumerTest,!PrimitiveSchemaTest' -pl '!pulsar-broker,!pulsar-proxy,!pulsar-broker-auth-sasl,!pulsar-io/kafka-connect-adaptor,!tests/pulsar-storm-test' + if: steps.docs.outputs.changed_only == 'no' + run: mvn install -DfailIfNoTests=false '-Dtest=!KafkaProducerSimpleConsumerTest,!PrimitiveSchemaTest,!BlobStoreManagedLedgerOffloaderTest' -pl '!pulsar-broker,!pulsar-proxy,!pulsar-broker-auth-sasl,!pulsar-io/kafka-connect-adaptor,!tests/pulsar-storm-test' - name: package surefire artifacts if: failure() @@ -59,6 +67,7 @@ jobs: mkdir artifacts find . -type d -name "*surefire*" -exec cp --parents -R {} artifacts/ \; zip -r artifacts.zip artifacts + - uses: actions/upload-artifact@master name: upload surefire-artifacts if: failure() diff --git a/README.md b/README.md index 016a68642e64b..e149361f322c2 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ $ bin/pulsar standalone ``` Check https://pulsar.apache.org for documentation and examples. - + ## Setting up your IDE Apache Pulsar is using [lombok](https://projectlombok.org/) so you have to ensure your IDE setup with diff --git a/bin/bookkeeper b/bin/bookkeeper index 7024e6546ba55..e1fb3a606816f 100755 --- a/bin/bookkeeper +++ b/bin/bookkeeper @@ -48,7 +48,7 @@ else fi BINDIR=`dirname "$0"` -BK_HOME=`cd $BINDIR/..;pwd` +BK_HOME=`cd -P $BINDIR/..;pwd` DEFAULT_CONF=$BK_HOME/conf/bookkeper.conf DEFAULT_LOG_CONF=$BK_HOME/conf/log4j2.yaml diff --git a/bin/function-localrunner b/bin/function-localrunner index a7aad76b7d200..fb36eb20383ce 100755 --- a/bin/function-localrunner +++ b/bin/function-localrunner @@ -19,7 +19,7 @@ # BINDIR=$(dirname "$0") -export PULSAR_HOME=`cd $BINDIR/..;pwd` +export PULSAR_HOME=`cd -P $BINDIR/..;pwd` . "$PULSAR_HOME/bin/pulsar-admin-common.sh" # functions related variables diff --git a/bin/pulsar b/bin/pulsar index f93fbc28eedc1..ffcdf536793ca 100755 --- a/bin/pulsar +++ b/bin/pulsar @@ -19,7 +19,7 @@ # BINDIR=$(dirname "$0") -export PULSAR_HOME=`cd $BINDIR/..;pwd` +export PULSAR_HOME=`cd -P $BINDIR/..;pwd` DEFAULT_BROKER_CONF=$PULSAR_HOME/conf/broker.conf DEFAULT_BOOKKEEPER_CONF=$PULSAR_HOME/conf/bookkeeper.conf @@ -112,16 +112,18 @@ if [ ! -f "${PY_INSTANCE_FILE}" ]; then fi # find pulsar sql presto distribution location -if [ ! -d "${PRESTO_HOME}" ]; then - - BUILT_PRESTO_HOME="${SQL_HOME}/presto-distribution/target/pulsar-presto-distribution" - if [ ! -d "${BUILT_PRESTO_HOME}" ]; then - echo "\nCouldn't find presto distribution."; - echo "Make sure you've run 'mvn package'\n"; - exit 1; +check_presto_libraries() { + if [ ! -d "${PRESTO_HOME}" ]; then + + BUILT_PRESTO_HOME="${SQL_HOME}/presto-distribution/target/pulsar-presto-distribution" + if [ ! -d "${BUILT_PRESTO_HOME}" ]; then + echo "\nCouldn't find presto distribution."; + echo "Make sure you've run 'mvn package'\n"; + exit 1; + fi + PRESTO_HOME=${BUILT_PRESTO_HOME} fi - PRESTO_HOME=${BUILT_PRESTO_HOME} -fi +} pulsar_help() { cat <0, it will reject all HTTP requests with bodies larged than the configured limit +httpMaxRequestSize=-1 + ### --- BookKeeper Client --- ### # Authentication plugin to use when connecting to bookies @@ -528,6 +548,10 @@ bookkeeperTLSTrustCertsFilePath= # Enable/disable disk weight based placement. Default is false bookkeeperDiskWeightBasedPlacementEnabled=false +# Set the interval to check the need for sending an explicit LAC +# A value of '0' disables sending any explicit LACs. Default is 0. +bookkeeperExplicitLacIntervalInMills=0 + ### --- Managed Ledger --- ### # Number of bookies to use when creating a ledger @@ -693,6 +717,14 @@ loadBalancerOverrideBrokerNicSpeedGbps= # Name of load manager to use loadManagerClassName=org.apache.pulsar.broker.loadbalance.impl.ModularLoadManagerImpl +# Supported algorithms name for namespace bundle split. +# "range_equally_divide" divides the bundle into two parts with the same hash range size. +# "topic_count_equally_divide" divides the bundle into two parts with the same topics count. +supportedNamespaceBundleSplitAlgorithms=[range_equally_divide,topic_count_equally_divide] + +# Default algorithm name for namespace bundle split +defaultNamespaceBundleSplitAlgorithm=range_equally_divide + ### --- Replication --- ### # Enable replication metrics @@ -758,6 +790,11 @@ exposePublisherStats=true statsUpdateFrequencyInSecs=60 statsUpdateInitialDelayInSecs=60 +# Enable expose the precise backlog stats. +# Set false to use published counter and consumed counter to calculate, this would be more efficient but may be inaccurate. +# Default is false. +exposePreciseBacklogInPrometheus=false + ### --- Schema storage --- ### # The schema storage implementation used by this broker schemaRegistryStorageClassName=org.apache.pulsar.broker.service.schema.BookkeeperSchemaStorageFactory diff --git a/conf/standalone.conf b/conf/standalone.conf index 6d1023bb40274..7dff0c3c913ab 100644 --- a/conf/standalone.conf +++ b/conf/standalone.conf @@ -334,6 +334,10 @@ bookkeeperTLSTrustCertsFilePath= # Enable/disable disk weight based placement. Default is false bookkeeperDiskWeightBasedPlacementEnabled=false +# Set the interval to check the need for sending an explicit LAC +# A value of '0' disables sending any explicit LACs. Default is 0. +bookkeeperExplicitLacIntervalInMills=0 + ### --- Managed Ledger --- ### # Number of bookies to use when creating a ledger @@ -527,6 +531,11 @@ exposeTopicLevelMetricsInPrometheus=true # Enable topic level metrics exposePublisherStats=true +# Enable expose the precise backlog stats. +# Set false to use published counter and consumed counter to calculate, this would be more efficient but may be inaccurate. +# Default is false. +exposePreciseBacklogInPrometheus=false + ### --- Deprecated config variables --- ### # Deprecated. Use configurationStoreServers diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile index b9fa47cf1509f..7388481f7567e 100644 --- a/dashboard/Dockerfile +++ b/dashboard/Dockerfile @@ -25,7 +25,7 @@ RUN apt-get update RUN apt-get -y install postgresql python sudo nginx supervisor # Postgres configuration -COPY conf/postgresql.conf /etc/postgresql/9.6/main/ +COPY conf/postgresql.conf /etc/postgresql/11/main/ # Configure nginx and supervisor RUN echo "daemon off;" >> /etc/nginx/nginx.conf diff --git a/dashboard/conf/postgresql.conf b/dashboard/conf/postgresql.conf index aabad0dd96244..85dfe1b1bdfdd 100644 --- a/dashboard/conf/postgresql.conf +++ b/dashboard/conf/postgresql.conf @@ -24,15 +24,15 @@ synchronous_commit = off # Default configs data_directory = '/data' -hba_file = '/etc/postgresql/9.6/main/pg_hba.conf' -ident_file = '/etc/postgresql/9.6/main/pg_ident.conf' -external_pid_file = '/var/run/postgresql/9.6-main.pid' +hba_file = '/etc/postgresql/11/main/pg_hba.conf' +ident_file = '/etc/postgresql/11/main/pg_ident.conf' +external_pid_file = '/var/run/postgresql/11-main.pid' port = 5432 max_connections = 100 datestyle = 'iso, mdy' default_text_search_config = 'pg_catalog.english' -stats_temp_directory = '/var/run/postgresql/9.6-main.pg_stat_tmp' +stats_temp_directory = '/var/run/postgresql/11-main.pg_stat_tmp' timezone = 'UTC' log_timezone = 'UTC' diff --git a/dashboard/init-postgres.sh b/dashboard/init-postgres.sh index 064b7e37041a1..5f4c62757f5f8 100755 --- a/dashboard/init-postgres.sh +++ b/dashboard/init-postgres.sh @@ -23,7 +23,7 @@ set -x -e rm -rf /data/* chown -R postgres: /data chmod 700 /data -sudo -u postgres /usr/lib/postgresql/9.6/bin/initdb /data/ +sudo -u postgres /usr/lib/postgresql/11/bin/initdb /data/ sudo -u postgres /etc/init.d/postgresql start sudo -u postgres psql --command "CREATE USER docker WITH PASSWORD 'docker';" sudo -u postgres createdb -O docker pulsar_dashboard diff --git a/deployment/kubernetes/aws/bookkeeper.yaml b/deployment/kubernetes/aws/bookkeeper.yaml index e0cd38dcf5096..f44de68128dd9 100644 --- a/deployment/kubernetes/aws/bookkeeper.yaml +++ b/deployment/kubernetes/aws/bookkeeper.yaml @@ -21,13 +21,12 @@ # SSDs for bookie journal storage kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: bookie-ssd provisioner: kubernetes.io/aws-ebs parameters: type: gp2 - zones: us-east-2a fsType: xfs # To create encrypted ebs volume using kms # encrypted: "true" @@ -36,13 +35,12 @@ reclaimPolicy: Retain --- # HDDs for bookie ledger storage kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 +apiVersion: storage.k8s.io/v1 metadata: name: bookie-hdd provisioner: kubernetes.io/aws-ebs parameters: type: st1 - zones: us-east-2a fsType: xfs # To create encrypted ebs volume using kms # encrypted: "true" @@ -54,14 +52,15 @@ kind: ConfigMap metadata: name: bookie-config data: - PULSAR_MEM: "\"-Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -verbosegc -XX:G1LogLevel=finest -Xms28g -Xmx28g -XX:MaxDirectMemorySize=28g\"" - PULSAR_PREFIX_dbStorage_writeCacheMaxSizeMb: "2048" # Write cache size (direct memory) - PULSAR_PREFIX_dbStorage_readAheadCacheMaxSizeMb: "2048" # Read cache size (direct memory) - PULSAR_PREFIX_dbStorage_rocksDB_blockCacheSize: "4294967296" - PULSAR_PREFIX_journalMaxSizeMB: "2048" - PULSAR_PREFIX_zkServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper - PULSAR_PREFIX_statsProviderClass: org.apache.bookkeeper.stats.prometheus.PrometheusMetricsProvider - PULSAR_PREFIX_useHostNameAsBookieID: "true" + BOOKIE_MEM: "\"-Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -verbosegc -XX:G1LogLevel=finest -Xms28g -Xmx28g -XX:MaxDirectMemorySize=28g\"" + BOOKIE_GC: "\"-XX:+UseG1GC -XX:MaxGCPauseMillis=10\"" + dbStorage_writeCacheMaxSizeMb: "2048" # Write cache size (direct memory) + dbStorage_readAheadCacheMaxSizeMb: "2048" # Read cache size (direct memory) + dbStorage_rocksDB_blockCacheSize: "4294967296" + journalMaxSizeMB: "2048" + zkServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper + statsProviderClass: org.apache.bookkeeper.stats.prometheus.PrometheusMetricsProvider + useHostNameAsBookieID: "true" --- apiVersion: apps/v1beta1 kind: StatefulSet @@ -203,9 +202,9 @@ spec: name: bookie-config env: ## Configure for lower mem usage - - name: PULSAR_MEM + - name: BOOKIE_MEM value: "\" -Xmx256m \"" - - name: PULSAR_GC + - name: BOOKIE_GC value: "\" \"" # Uncomment the following if bookie should be deployed on a specific instance type # nodeSelector: diff --git a/deployment/kubernetes/aws/broker.yaml b/deployment/kubernetes/aws/broker.yaml index 06827c8ec89f2..908a3c4185eb4 100644 --- a/deployment/kubernetes/aws/broker.yaml +++ b/deployment/kubernetes/aws/broker.yaml @@ -27,13 +27,13 @@ data: # better GC behavior at high throughput PULSAR_MEM: "\" -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -Xms12g -Xmx12g -XX:MaxDirectMemorySize=14g -Dpulsar.root.logger=DEBUG,FILE \"" PULSAR_GC: "\" -XX:+UseG1GC -XX:MaxGCPauseMillis=10\"" - PULSAR_PREFIX_zookeeperServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper - PULSAR_PREFIX_configurationStoreServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper - PULSAR_PREFIX_clusterName: us-east - PULSAR_PREFIX_managedLedgerDefaultEnsembleSize: "2" - PULSAR_PREFIX_managedLedgerDefaultWriteQuorum: "2" - PULSAR_PREFIX_managedLedgerDefaultAckQuorum: "2" - PULSAR_PREFIX_deduplicationEnabled: "false" + zookeeperServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper + configurationStoreServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper + clusterName: us-east + managedLedgerDefaultEnsembleSize: "2" + managedLedgerDefaultWriteQuorum: "2" + managedLedgerDefaultAckQuorum: "2" + deduplicationEnabled: "false" --- ## ## Broker deployment definition @@ -63,7 +63,7 @@ spec: # dnsPolicy: ClusterFirstWithHostNet containers: - name: broker - image: apachepulsar/pulsar:1.20.0-incubating + image: apachepulsar/pulsar:latest command: ["sh", "-c"] args: - > @@ -127,7 +127,7 @@ metadata: spec: containers: - name: pulsar-admin - image: apachepulsar/pulsar:1.20.0-incubating + image: apachepulsar/pulsar:latest command: ["sh", "-c"] args: - > diff --git a/deployment/kubernetes/aws/cluster-metadata.yaml b/deployment/kubernetes/aws/cluster-metadata.yaml new file mode 100644 index 0000000000000..fd55f372ff839 --- /dev/null +++ b/deployment/kubernetes/aws/cluster-metadata.yaml @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: batch/v1 +kind: Job +metadata: + name: pulsar-cluster-metadata-init + labels: + app: pulsar + component: broker +spec: + template: + spec: + containers: + - name: pulsar-cluster-metadata-init-container + image: apachepulsar/pulsar:latest + command: ["sh", "-c"] + args: + - > + bin/pulsar initialize-cluster-metadata \ + --cluster us-east \ + --zookeeper zookeeper \ + --configuration-store zookeeper \ + --web-service-url http://broker:8080/ \ + --broker-service-url pulsar://broker:6650/ || true; + restartPolicy: Never diff --git a/deployment/kubernetes/aws/proxy.yaml b/deployment/kubernetes/aws/proxy.yaml index 844e1bb9c0592..589162baff6fa 100644 --- a/deployment/kubernetes/aws/proxy.yaml +++ b/deployment/kubernetes/aws/proxy.yaml @@ -23,10 +23,11 @@ metadata: name: pulsar-proxy-config data: PULSAR_MEM: "\" -Xms4g -Xmx4g -XX:MaxDirectMemorySize=4g\"" - PULSAR_PREFIX_brokerServiceURL: pulsar://broker:6650 - PULSAR_PREFIX_brokerWebServiceURL: http://broker:8080 - PULSAR_PREFIX_clusterName: pulsar-eks - + brokerServiceURL: pulsar://broker:6650 + brokerWebServiceURL: http://broker:8080 + clusterName: us-east + zookeeperServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper + configurationStoreServers: zk-0.zookeeper,zk-1.zookeeper,zk-2.zookeeper --- apiVersion: apps/v1beta1 kind: Deployment diff --git a/deployment/kubernetes/helm/pulsar/templates/bookkeeper-statefulset.yaml b/deployment/kubernetes/helm/pulsar/templates/bookkeeper-statefulset.yaml index eb2597d237ab7..012eb4e6246f4 100644 --- a/deployment/kubernetes/helm/pulsar/templates/bookkeeper-statefulset.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/bookkeeper-statefulset.yaml @@ -115,6 +115,7 @@ spec: - > bin/apply-config-from-env.py conf/bookkeeper.conf && bin/apply-config-from-env.py conf/pulsar_env.sh && + bin/apply-config-from-env.py conf/bkenv.sh && bin/pulsar bookie ports: - name: client diff --git a/deployment/kubernetes/helm/pulsar/templates/broker-configmap.yaml b/deployment/kubernetes/helm/pulsar/templates/broker-configmap.yaml index d57782675e8c0..cf7b5c1226954 100644 --- a/deployment/kubernetes/helm/pulsar/templates/broker-configmap.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/broker-configmap.yaml @@ -39,4 +39,9 @@ data: clusterName: {{ template "pulsar.fullname" . }} functionsWorkerEnabled: "true" PF_pulsarFunctionsCluster: {{ template "pulsar.fullname" . }} -{{ toYaml .Values.broker.configData | indent 2 }} + {{- if .Values.extra.functionsAsPods }} + PF_functionRuntimeFactoryClassName: "org.apache.pulsar.functions.runtime.kubernetes.KubernetesRuntimeFactory" + PF_functionRuntimeFactoryConfigs_submittingInsidePod: "true" + PF_functionRuntimeFactoryConfigs_jobNamespace: {{ .Values.namespace }} + {{- end }} + {{ toYaml .Values.broker.configData | indent 2 }} diff --git a/deployment/kubernetes/helm/pulsar/templates/broker-deployment.yaml b/deployment/kubernetes/helm/pulsar/templates/broker-deployment.yaml index 9c74764e0f1cd..4abd2b6cbd191 100644 --- a/deployment/kubernetes/helm/pulsar/templates/broker-deployment.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/broker-deployment.yaml @@ -55,6 +55,9 @@ spec: tolerations: {{ toYaml .Values.broker.tolerations | indent 8 }} {{- end }} + {{- if .Values.extra.functionsAsPods }} + serviceAccount: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" + {{- end }} affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/deployment/kubernetes/helm/pulsar/templates/broker-rbac.yaml b/deployment/kubernetes/helm/pulsar/templates/broker-rbac.yaml new file mode 100644 index 0000000000000..8f37ed729e584 --- /dev/null +++ b/deployment/kubernetes/helm/pulsar/templates/broker-rbac.yaml @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + {{- if .Values.extra.functionsAsPods }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" +rules: +- apiGroups: [""] + resources: + - services + - configmaps + - pods + verbs: + - '*' +- apiGroups: + - apps + resources: + - statefulsets + verbs: + - '*' +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" + namespace: {{ .Values.namespace }} +--- + +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" +subjects: +- kind: ServiceAccount + name: "{{ template "pulsar.fullname" . }}-{{ .Values.broker.functions.component }}" + namespace: {{ .Values.namespace }} + {{- end }} diff --git a/deployment/kubernetes/helm/pulsar/templates/dashboard-ingress.yaml b/deployment/kubernetes/helm/pulsar/templates/dashboard-ingress.yaml index ff9106f2f1acb..10dc4f99e08e7 100644 --- a/deployment/kubernetes/helm/pulsar/templates/dashboard-ingress.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/dashboard-ingress.yaml @@ -17,6 +17,7 @@ # under the License. # +{{- if .Values.extra.dashboard }} {{- if .Values.dashboard.ingress.enabled }} apiVersion: extensions/v1beta1 kind: Ingress @@ -51,3 +52,4 @@ spec: serviceName: "{{ template "pulsar.fullname" . }}-{{ .Values.dashboard.component }}" servicePort: {{ .Values.dashboard.ingress.port }} {{- end }} +{{- end }} diff --git a/deployment/kubernetes/helm/pulsar/templates/grafana-ingress.yaml b/deployment/kubernetes/helm/pulsar/templates/grafana-ingress.yaml new file mode 100644 index 0000000000000..79f4a8babeb3f --- /dev/null +++ b/deployment/kubernetes/helm/pulsar/templates/grafana-ingress.yaml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + {{- if .Values.extra.monitoring}} + {{- if .Values.grafana.ingress.enabled }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.grafana.component }}" + namespace: {{ .Values.namespace }} + labels: + app: {{ template "pulsar.name" . }} + chart: {{ template "pulsar.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.grafana.ingress.tls }} + tls: +{{ toYaml .Values.grafana.ingress.tls | indent 4 }} +{{- end }} + rules: + - host: {{ required "Grafana ingress hostname not provided" .Values.grafana.ingress.hostname }} + http: + paths: + - path: {{ .Values.grafana.ingress.path }} + backend: + serviceName: "{{ template "pulsar.fullname" . }}-{{ .Values.grafana.component }}" + servicePort: {{ .Values.grafana.ingress.port }} + {{- end }} + {{- end }} diff --git a/deployment/kubernetes/helm/pulsar/templates/grafana-service.yaml b/deployment/kubernetes/helm/pulsar/templates/grafana-service.yaml index cd73f43a75492..56daddec7dc3b 100644 --- a/deployment/kubernetes/helm/pulsar/templates/grafana-service.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/grafana-service.yaml @@ -35,9 +35,10 @@ metadata: spec: ports: {{ toYaml .Values.grafana.service.ports | indent 2 }} - clusterIP: None selector: app: {{ template "pulsar.name" . }} release: {{ .Release.Name }} component: {{ .Values.grafana.component }} + type: ClusterIP + sessionAffinity: None {{- end }} diff --git a/deployment/kubernetes/helm/pulsar/templates/proxy-deployment.yaml b/deployment/kubernetes/helm/pulsar/templates/proxy-deployment.yaml index 94c5d760fc156..476f6086ff0f8 100644 --- a/deployment/kubernetes/helm/pulsar/templates/proxy-deployment.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/proxy-deployment.yaml @@ -76,7 +76,7 @@ spec: terminationGracePeriodSeconds: {{ .Values.proxy.gracePeriod }} initContainers: # This init container will wait for zookeeper to be ready before - # deploying the bookies + # deploying the proxies - name: wait-zookeeper-ready image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} @@ -86,6 +86,21 @@ spec: until bin/pulsar zookeeper-shell -server {{ template "pulsar.fullname" . }}-{{ .Values.zookeeper.component }} ls /admin/clusters/{{ template "pulsar.fullname" . }}; do sleep 3; done; + # This init container will wait for at least one broker to be ready before + # deploying the proxy + - name: wait-broker-ready + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["bash", "-c"] + args: + - >- + for i in {0..{{ .Values.broker.replicaCount }}}; do + brokerServiceNumber="$(nslookup -timeout=10 {{ template "pulsar.fullname" . }}-{{ .Values.broker.component }} | grep Name | wc -l)" + if [[ ${brokerServiceNumber} -ge 1 ]]; then + break + fi + sleep 30; + done; containers: - name: "{{ template "pulsar.fullname" . }}-{{ .Values.proxy.component }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" diff --git a/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-configmap.yaml b/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-configmap.yaml new file mode 100644 index 0000000000000..5444469cba53d --- /dev/null +++ b/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-configmap.yaml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.pulsar_manager.component }}" + namespace: {{ .Values.namespace }} + labels: + app: {{ template "pulsar.name" . }} + chart: {{ template "pulsar.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: {{ .Values.pulsar_manager.component }} + cluster: {{ template "pulsar.fullname" . }} +data: +{{ toYaml .Values.pulsar_manager.configData | indent 2 }} diff --git a/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-deployment.yaml b/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-deployment.yaml index 674ebf4e73399..8a8493aa53c92 100644 --- a/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-deployment.yaml +++ b/deployment/kubernetes/helm/pulsar/templates/pulsar-manager-deployment.yaml @@ -60,27 +60,21 @@ spec: - name: "{{ template "pulsar.fullname" . }}-{{ .Values.pulsar_manager.component }}" image: "{{ .Values.pulsar_manager.image.repository }}:{{ .Values.pulsar_manager.image.tag }}" imagePullPolicy: {{ .Values.pulsar_manager.image.pullPolicy }} - {{- if .Values.grafana.resources }} + {{- if .Values.pulsar_manager.resources }} resources: -{{ toYaml .Values.pulsar_manager.resources | indent 10 }} +{{ toYaml .Values.pulsar_manager.resources | indent 12 }} {{- end }} ports: - containerPort: 9527 volumeMounts: - name: pulsar-manager-data mountPath: /data + envFrom: + - configMapRef: + name: "{{ template "pulsar.fullname" . }}-{{ .Values.pulsar_manager.component }}" env: - # for supporting apachepulsar/pulsar-manager - name: PULSAR_CLUSTER value: {{ template "pulsar.fullname" . }} - - name: REDIRECT_HOST - value: http://127.0.0.1 - - name: REDIRECT_PORT - value: "9527" - - name: DRIVER_CLASS_NAME - value: org.postgresql.Driver - - name: URL - value: jdbc:postgresql://127.0.0.1:5432/pulsar_manager - name: USERNAME valueFrom: secretKeyRef: @@ -91,8 +85,6 @@ spec: secretKeyRef: key: PULSAR_MANAGER_ADMIN_PASSWORD name: "{{ template "pulsar.fullname" . }}-{{ .Values.pulsar_manager.component }}-secret" - - name: LOG_LEVEL - value: DEBUG volumes: - name: pulsar-manager-data emptyDir: {} diff --git a/deployment/kubernetes/helm/pulsar/values-mini.yaml b/deployment/kubernetes/helm/pulsar/values-mini.yaml index deca8193de86f..796e232b5251c 100644 --- a/deployment/kubernetes/helm/pulsar/values-mini.yaml +++ b/deployment/kubernetes/helm/pulsar/values-mini.yaml @@ -49,6 +49,8 @@ extra: bastion: yes # Monitoring stack (prometheus and grafana) monitoring: yes + # Configure Kubernetes runtime for Functions + functionsAsPods: no ## Which pulsar image to use image: @@ -70,7 +72,7 @@ zookeeper: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -140,7 +142,7 @@ bookkeeper: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -179,7 +181,8 @@ bookkeeper: ## templates/bookkeeper-configmap.yaml ## configData: - PULSAR_MEM: "\"-Xms128m -Xmx256m -XX:MaxDirectMemorySize=128m -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -verbosegc -XX:G1LogLevel=finest\"" + BOOKIE_MEM: "\"-Xms128m -Xmx256m -XX:MaxDirectMemorySize=128m -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem\"" + BOOKIE_GC: "\"-XX:+UseG1GC -XX:MaxGCPauseMillis=10\"" PULSAR_PREFIX_dbStorage_writeCacheMaxSizeMb: "32" PULSAR_PREFIX_dbStorage_readAheadCacheMaxSizeMb: "32" PULSAR_PREFIX_journalMaxSizeMB: "2048" @@ -212,7 +215,7 @@ broker: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -245,6 +248,11 @@ broker: pdb: usePolicy: yes maxUnavailable: 1 + ## Broker rbac + ## templates/broker-rbac.yaml + ## + functions: + component: functions-worker ## Pulsar Extra: Proxy ## templates/proxy-deployment.yaml @@ -257,7 +265,7 @@ proxy: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -299,7 +307,7 @@ autoRecovery: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -321,7 +329,7 @@ dashboard: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-dashboard @@ -347,7 +355,7 @@ dashboard: ## Optional. Leave it blank if your Ingress Controller can provide a default certificate. secretName: "" - + ## Required if ingress is enabled hostname: "" path: "/" @@ -363,7 +371,7 @@ bastion: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -384,7 +392,7 @@ prometheus: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: prom/prometheus @@ -393,7 +401,7 @@ prometheus: resources: requests: memory: 64Mi - cpu: 0.1 + cpu: 0.1 volumes: data: name: data @@ -427,7 +435,7 @@ grafana: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-grafana @@ -445,6 +453,31 @@ grafana: ports: - name: server port: 3000 + plugins: [] + ## Grafana ingress + ## templates/grafana-ingress.yaml + ## + ingress: + enabled: false + annotations: + kubernetes.io/ingress.class: nginx + # nginx.ingress.kubernetes.io/rewrite-target: /$1 + # ingress.kubernetes.io/force-ssl-redirect: "true" + ingress.kubernetes.io/rewrite-target: / + labels: {} + + tls: [] + + ## Optional. Leave it blank if your Ingress Controller can provide a default certificate. + #- secretName: "" + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + ## Required if ingress is enabled + hostname: "" + protocol: http + path: /grafana + port: 80 zookeeper_metadata: resources: @@ -462,7 +495,7 @@ pulsar_manager: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-manager @@ -472,6 +505,15 @@ pulsar_manager: requests: memory: 250Mi cpu: 0.1 + configData: + REDIRECT_HOST: "http://127.0.0.1" + REDIRECT_PORT: "9527" + DRIVER_CLASS_NAME: org.postgresql.Driver + URL: jdbc:postgresql://127.0.0.1:5432/pulsar_manager + LOG_LEVEL: DEBUG + ## If you enabled authentication support + #JWT_TOKEN: + #SECRET_KEY: data:base64, ## Pulsar manager service ## templates/pulsar-manager-service.yaml ## @@ -484,4 +526,3 @@ pulsar_manager: admin: user: pulsar password: pulsar - diff --git a/deployment/kubernetes/helm/pulsar/values.yaml b/deployment/kubernetes/helm/pulsar/values.yaml index 90811b3a47a8f..d9fc5025805f9 100644 --- a/deployment/kubernetes/helm/pulsar/values.yaml +++ b/deployment/kubernetes/helm/pulsar/values.yaml @@ -49,6 +49,8 @@ extra: bastion: yes # Monitoring stack (prometheus and grafana) monitoring: yes + # Configure Kubernetes runtime for Functions + functionsAsPods: no ## Which pulsar image to use image: @@ -70,7 +72,7 @@ zookeeper: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -140,7 +142,7 @@ bookkeeper: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -179,7 +181,8 @@ bookkeeper: ## templates/bookkeeper-configmap.yaml ## configData: - PULSAR_MEM: "\"-Xms15g -Xmx15g -XX:MaxDirectMemorySize=15g -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -verbosegc -XX:G1LogLevel=finest\"" + BOOKIE_MEM: "\"-Xms15g -Xmx15g -XX:MaxDirectMemorySize=15g -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -verbosegc -XX:G1LogLevel=finest\"" + BOOKIE_GC: "\"-XX:+UseG1GC -XX:MaxGCPauseMillis=10\"" PULSAR_PREFIX_dbStorage_writeCacheMaxSizeMb: "2048" PULSAR_PREFIX_dbStorage_readAheadCacheMaxSizeMb: "2048" PULSAR_PREFIX_dbStorage_rocksDB_blockCacheSize: "268435456" @@ -213,7 +216,7 @@ broker: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -246,6 +249,11 @@ broker: pdb: usePolicy: yes maxUnavailable: 1 + ## Broker rbac + ## templates/broker-rbac.yaml + ## + functions: + component: functions-worker ## Pulsar Extra: Proxy ## templates/proxy-deployment.yaml @@ -258,7 +266,7 @@ proxy: annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -300,7 +308,7 @@ autoRecovery: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -322,7 +330,7 @@ dashboard: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-dashboard @@ -348,7 +356,7 @@ dashboard: ## Optional. Leave it blank if your Ingress Controller can provide a default certificate. secretName: "" - + ## Required if ingress is enabled hostname: "" path: "/" @@ -364,7 +372,7 @@ bastion: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 resources: requests: @@ -385,7 +393,7 @@ prometheus: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: prom/prometheus @@ -428,16 +436,16 @@ grafana: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-grafana tag: latest pullPolicy: IfNotPresent -# resources: -# requests: -# memory: 4Gi -# cpu: 1 + resources: + requests: + memory: 4Gi + cpu: 1 ## Grafana service ## templates/grafana-service.yaml ## @@ -446,6 +454,31 @@ grafana: ports: - name: server port: 3000 + plugins: [] + ## Grafana ingress + ## templates/grafana-ingress.yaml + ## + ingress: + enabled: false + annotations: + kubernetes.io/ingress.class: nginx + # nginx.ingress.kubernetes.io/rewrite-target: /$1 + # ingress.kubernetes.io/force-ssl-redirect: "true" + ingress.kubernetes.io/rewrite-target: / + labels: {} + + tls: [] + + ## Optional. Leave it blank if your Ingress Controller can provide a default certificate. + #- secretName: "" + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + ## Required if ingress is enabled + hostname: "" + protocol: http + path: /grafana + port: 80 ## Components Stack: pulsar_manager ## templates/pulsar-manager.yaml @@ -457,7 +490,7 @@ pulsar_manager: # nodeSelector: # cloud.google.com/gke-nodepool: default-pool annotations: {} - tolarations: [] + tolerations: [] gracePeriod: 0 image: repository: apachepulsar/pulsar-manager @@ -467,6 +500,15 @@ pulsar_manager: requests: memory: 250Mi cpu: 0.1 + configData: + REDIRECT_HOST: "http://127.0.0.1" + REDIRECT_PORT: "9527" + DRIVER_CLASS_NAME: org.postgresql.Driver + URL: jdbc:postgresql://127.0.0.1:5432/pulsar_manager + LOG_LEVEL: DEBUG + ## If you enabled authentication support + #JWT_TOKEN: + #SECRET_KEY: data:base64, ## Pulsar manager service ## templates/pulsar-manager-service.yaml ## @@ -479,4 +521,3 @@ pulsar_manager: admin: user: pulsar password: pulsar - diff --git a/distribution/server/src/assemble/LICENSE.bin.txt b/distribution/server/src/assemble/LICENSE.bin.txt index 9a57a48791066..a817987f0006b 100644 --- a/distribution/server/src/assemble/LICENSE.bin.txt +++ b/distribution/server/src/assemble/LICENSE.bin.txt @@ -358,9 +358,10 @@ The Apache Software License, Version 2.0 - io.netty-netty-resolver-4.1.43.Final.jar - io.netty-netty-resolver-dns-4.1.43.Final.jar - io.netty-netty-transport-4.1.43.Final.jar - - io.netty-netty-transport-native-epoll-4.1.33.Final-linux-x86_64.jar + - io.netty-netty-transport-native-epoll-4.1.43.Final-linux-x86_64.jar - io.netty-netty-transport-native-epoll-4.1.43.Final.jar - io.netty-netty-transport-native-unix-common-4.1.43.Final.jar + - io.netty-netty-transport-native-unix-common-4.1.43.Final-linux-x86_64.jar - io.netty-netty-tcnative-boringssl-static-2.0.26.Final.jar * Prometheus client - io.prometheus-simpleclient-0.5.0.jar @@ -496,7 +497,7 @@ The Apache Software License, Version 2.0 - io.vertx-vertx-core-3.4.1.jar - io.vertx-vertx-web-3.4.1.jar * Apache ZooKeeper - - org.apache.zookeeper-zookeeper-jute-3.5.6.jar + - org.apache.zookeeper-zookeeper-jute-3.5.7.jar BSD 3-clause "New" or "Revised" License * Google auth library diff --git a/docker/pulsar-standalone/Dockerfile b/docker/pulsar-standalone/Dockerfile index 076d356949d80..42a70ab166c59 100644 --- a/docker/pulsar-standalone/Dockerfile +++ b/docker/pulsar-standalone/Dockerfile @@ -26,24 +26,16 @@ FROM apachepulsar/pulsar-dashboard:latest as dashboard # Restart from FROM openjdk:8-jdk -# Copy pulsar files from pulsar-all -COPY --from=pulsar /pulsar /pulsar - -# Copy dashboard files from pulsar-dashboard -COPY --from=dashboard /pulsar/django /pulsar/django -COPY --from=dashboard /pulsar/requirements.txt /pulsar/django -COPY --from=dashboard /pulsar/conf/* /pulsar/conf/ - # Note that the libpq-dev package is needed here in order to install # the required python psycopg2 package (for postgresql) later RUN apt-get update -RUN apt-get -y install python2.7 python-pip postgresql-9.6 sudo nginx supervisor libpq-dev +RUN apt-get -y install python2.7 python-pip postgresql sudo nginx supervisor libpq-dev # Python dependencies RUN pip2 install -r /pulsar/django/requirements.txt # Postgres configuration -COPY --from=dashboard /etc/postgresql/9.6/main/postgresql.conf /etc/postgresql/9.6/main/postgresql.conf +COPY --from=dashboard /etc/postgresql/11/main/postgresql.conf /etc/postgresql/11/main/postgresql.conf # Configure supervisor COPY --from=dashboard /etc/supervisor/conf.d/supervisor-app.conf /etc/supervisor/conf.d/supervisor-app.conf @@ -59,6 +51,17 @@ redirect_stderr=true" >> /etc/supervisor/conf.d/supervisor-app.conf RUN echo "daemon off;" >> /etc/nginx/nginx.conf COPY --from=dashboard /etc/nginx/sites-available/default /etc/nginx/sites-available/default +# Copy pulsar files from pulsar-all +COPY --from=pulsar /pulsar /pulsar + +# Copy web-app sources +COPY . /pulsar/ + +# Copy dashboard files from pulsar-dashboard +COPY --from=dashboard /pulsar/django /pulsar/django +COPY --from=dashboard /pulsar/requirements.txt /pulsar/django +COPY --from=dashboard /pulsar/conf/* /pulsar/conf/ + # Copy web-app sources # Setup database and create tables COPY --from=dashboard /pulsar/init-postgres.sh /pulsar/django/init-postgres.sh diff --git a/docker/pulsar/Dockerfile b/docker/pulsar/Dockerfile index d4b86469312c9..d8ceac8b7d678 100644 --- a/docker/pulsar/Dockerfile +++ b/docker/pulsar/Dockerfile @@ -39,7 +39,6 @@ COPY scripts/install-pulsar-client-37.sh /pulsar/bin ### and add Python dependencies (for Pulsar functions) FROM openjdk:8-jdk-slim -COPY --from=pulsar /pulsar /pulsar # Install some utilities RUN apt-get update \ @@ -57,16 +56,20 @@ RUN python3.7 get-pip.py ADD target/python-client/ /pulsar/pulsar-client ADD target/cpp-client/ /pulsar/cpp-client -RUN /pulsar/bin/install-pulsar-client-27.sh -RUN /pulsar/bin/install-pulsar-client-37.sh RUN echo networkaddress.cache.ttl=1 >> $JAVA_HOME/jre/lib/security/java.security RUN apt-get update \ && apt install -y /pulsar/cpp-client/*.deb \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -WORKDIR /pulsar - VOLUME ["/pulsar/conf", "/pulsar/data"] ENV PULSAR_ROOT_LOGGER=INFO,CONSOLE + + +COPY --from=pulsar /pulsar /pulsar +WORKDIR /pulsar + +RUN /pulsar/bin/install-pulsar-client-27.sh +RUN /pulsar/bin/install-pulsar-client-37.sh + diff --git a/docker/pulsar/scripts/apply-config-from-env.py b/docker/pulsar/scripts/apply-config-from-env.py index 3d0e5ee8e295d..92f555403ebe1 100755 --- a/docker/pulsar/scripts/apply-config-from-env.py +++ b/docker/pulsar/scripts/apply-config-from-env.py @@ -68,6 +68,9 @@ if k not in keys: print('[%s] Adding config %s = %s' % (conf_filename, k, v)) lines.append('%s=%s\n' % (k, v)) + else: + print('[%s] Updating config %s = %s' %(conf_filename, k, v)) + lines[keys[k]] = '%s=%s\n' % (k, v) # Store back the updated config in the same file f = open(conf_filename, 'w') diff --git a/managed-ledger/pom.xml b/managed-ledger/pom.xml index 06d057263317b..b883c8661d9c4 100644 --- a/managed-ledger/pom.xml +++ b/managed-ledger/pom.xml @@ -60,6 +60,12 @@ pulsar-common ${project.version} + + + org.apache.pulsar + pulsar-metadata + ${project.version} + com.google.guava diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloader.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloader.java index c85fe9fd79107..9402e82ff2215 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloader.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloader.java @@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture; import org.apache.bookkeeper.client.api.ReadHandle; +import org.apache.pulsar.common.policies.data.OffloadPolicies; /** * Interface for offloading ledgers to long-term storage @@ -112,5 +113,17 @@ CompletableFuture readOffloaded(long ledgerId, UUID uid, */ CompletableFuture deleteOffloaded(long ledgerId, UUID uid, Map offloadDriverMetadata); + + /** + * Get offload policies of this LedgerOffloader + * + * @return offload policies + */ + OffloadPolicies getOffloadPolicies(); + + /** + * Close the resources if necessary + */ + void close(); } diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloaderFactory.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloaderFactory.java index f0a6890aeb272..7a0e6dc38d0d9 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloaderFactory.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/LedgerOffloaderFactory.java @@ -24,6 +24,7 @@ import org.apache.bookkeeper.common.annotation.InterfaceAudience.LimitedPrivate; import org.apache.bookkeeper.common.annotation.InterfaceStability.Evolving; import org.apache.bookkeeper.common.util.OrderedScheduler; +import org.apache.pulsar.common.policies.data.OffloadPolicies; /** * Factory to create {@link LedgerOffloader} to offload ledgers into long-term storage. @@ -43,13 +44,13 @@ public interface LedgerOffloaderFactory { /** * Create a ledger offloader with the provided configuration, user-metadata and scheduler. * - * @param properties service configuration + * @param offloadPolicies offload policies * @param userMetadata user metadata * @param scheduler scheduler * @return the offloader instance * @throws IOException when fail to create an offloader */ - T create(Properties properties, + T create(OffloadPolicies offloadPolicies, Map userMetadata, OrderedScheduler scheduler) throws IOException; diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedCursor.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedCursor.java index ae0426935637c..6f12fb2cb20dc 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedCursor.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedCursor.java @@ -193,9 +193,10 @@ void asyncGetNthEntry(int n, IndividualDeletedEntries deletedEntries, ReadEntryC * *

This method has linear time complexity on the number of ledgers included in the managed ledger. * + * @param isPrecise set to true to get precise backlog count * @return the number of entries */ - long getNumberOfEntriesInBacklog(); + long getNumberOfEntriesInBacklog(boolean isPrecise); /** * This signals that the reader is done with all the entries up to "position" (included). This can potentially diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerException.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerException.java index 698acfd33e74d..6e6e6a2b848c7 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerException.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerException.java @@ -39,8 +39,12 @@ public static ManagedLedgerException getManagedLedgerException(Throwable e) { } public static class MetaStoreException extends ManagedLedgerException { - public MetaStoreException(Exception e) { - super(e); + public MetaStoreException(Throwable t) { + super(t); + } + + public MetaStoreException(String msg) { + super(msg); } } @@ -48,12 +52,20 @@ public static class BadVersionException extends MetaStoreException { public BadVersionException(Exception e) { super(e); } + + public BadVersionException(String msg) { + super(msg); + } } public static class MetadataNotFoundException extends MetaStoreException { public MetadataNotFoundException(Exception e) { super(e); } + + public MetadataNotFoundException(String msg) { + super(msg); + } } diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerInfo.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerInfo.java index ff3f6b505a57a..c03da89921e7c 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerInfo.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedgerInfo.java @@ -24,7 +24,7 @@ @SuppressWarnings("checkstyle:javadoctype") public class ManagedLedgerInfo { /** Z-Node version. */ - public int version; + public long version; public String creationDate; public String modificationDate; @@ -42,7 +42,7 @@ public static class LedgerInfo { public static class CursorInfo { /** Z-Node version. */ - public int version; + public long version; public String creationDate; public String modificationDate; diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java index d76553877fbef..9498145de2823 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java @@ -83,7 +83,6 @@ import org.apache.bookkeeper.mledger.Position; import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl.PositionBound; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats; import org.apache.bookkeeper.mledger.proto.MLDataFormats.LongProperty; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; @@ -93,6 +92,7 @@ import org.apache.pulsar.common.util.collections.ConcurrentOpenLongPairRangeSet; import org.apache.pulsar.common.util.collections.LongPairRangeSet; import org.apache.pulsar.common.util.collections.LongPairRangeSet.LongPairConsumer; +import org.apache.pulsar.metadata.api.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -734,12 +734,16 @@ public long getEstimatedSizeSinceMarkDeletePosition() { } @Override - public long getNumberOfEntriesInBacklog() { + public long getNumberOfEntriesInBacklog(boolean isPrecise) { if (log.isDebugEnabled()) { log.debug("[{}] Consumer {} cursor ml-entries: {} -- deleted-counter: {} other counters: mdPos {} rdPos {}", ledger.getName(), name, ManagedLedgerImpl.ENTRIES_ADDED_COUNTER_UPDATER.get(ledger), messagesConsumedCounter, markDeletePosition, readPosition); } + if (isPrecise) { + return getNumberOfEntries(Range.closed(markDeletePosition, ledger.getLastPosition())) - 1; + } + long backlog = ManagedLedgerImpl.ENTRIES_ADDED_COUNTER_UPDATER.get(ledger) - messagesConsumedCounter; if (backlog < 0) { // In some case the counters get incorrect values, fall back to the precise backlog count @@ -948,14 +952,21 @@ public void asyncResetCursor(Position newPos, AsyncCallbacks.ResetCursorCallback // order trim and reset operations on a ledger ledger.getExecutor().executeOrdered(ledger.getName(), safeRun(() -> { - if (ledger.isValidPosition(newPosition) || newPosition.equals(PositionImpl.earliest) - || newPosition.equals(PositionImpl.latest)) { - internalResetCursor(newPosition, callback); - } else { - // caller (replay) should handle this error and retry cursor reset - callback.resetFailed(new ManagedLedgerException.InvalidCursorPositionException(newPosition.toString()), - newPosition); + PositionImpl actualPosition = newPosition; + + if (!ledger.isValidPosition(actualPosition) && + !actualPosition.equals(PositionImpl.earliest) && + !actualPosition.equals(PositionImpl.latest)) { + actualPosition = ledger.getNextValidPosition(actualPosition); + + if (actualPosition == null) { + // next valid position would only return null when newPos + // is larger than all available positions, then it's latest in effect. + actualPosition = PositionImpl.latest; + } } + + internalResetCursor(actualPosition, callback); })); } diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerFactoryImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerFactoryImpl.java index f8abcebafda4f..b88bc7f3444f1 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerFactoryImpl.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerFactoryImpl.java @@ -68,7 +68,6 @@ import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl.ManagedLedgerInitializeLedgerCallback; import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl.State; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats; import org.apache.bookkeeper.mledger.proto.MLDataFormats.LongProperty; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; @@ -76,7 +75,9 @@ import org.apache.bookkeeper.mledger.util.Futures; import org.apache.bookkeeper.zookeeper.ZooKeeperClient; import org.apache.pulsar.common.util.DateFormatter; -import org.apache.zookeeper.KeeperException; +import org.apache.pulsar.metadata.api.MetadataStore; +import org.apache.pulsar.metadata.api.Stat; +import org.apache.pulsar.metadata.impl.zookeeper.ZKMetadataStore; import org.apache.zookeeper.ZooKeeper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,6 +102,7 @@ public class ManagedLedgerFactoryImpl implements ManagedLedgerFactory { private final ScheduledFuture statsTask; private final long cacheEvictionTimeThresholdNanos; + private final MetadataStore metadataStore; private static final int StatsPeriodSeconds = 60; @@ -160,7 +162,8 @@ private ManagedLedgerFactoryImpl(BookkeeperFactoryForCustomEnsemblePlacementPoli this.bookkeeperFactory = bookKeeperGroupFactory; this.isBookkeeperManaged = isBookkeeperManaged; this.zookeeper = isBookkeeperManaged ? zooKeeper : null; - this.store = new MetaStoreImplZookeeper(zooKeeper, orderedExecutor); + this.metadataStore = new ZKMetadataStore(zooKeeper); + this.store = new MetaStoreImpl(metadataStore, orderedExecutor); this.config = config; this.mbean = new ManagedLedgerFactoryMBeanImpl(this); this.entryCacheManager = new EntryCacheManager(this); @@ -464,6 +467,11 @@ public void closeFailed(ManagedLedgerException exception, Object ctx) { cacheEvictionExecutor.shutdownNow(); entryCacheManager.clear(); + try { + metadataStore.close(); + } catch (Exception e) { + throw new ManagedLedgerException(e); + } } @Override diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java index 85850acb5373c..ecf99a426dbd4 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java @@ -24,6 +24,7 @@ import static org.apache.bookkeeper.mledger.util.Errors.isNoSuchLedgerExistsException; import static org.apache.bookkeeper.mledger.util.SafeRun.safeRun; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -106,7 +107,6 @@ import org.apache.bookkeeper.mledger.Position; import org.apache.bookkeeper.mledger.impl.ManagedCursorImpl.VoidCallback; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.offload.OffloadUtils; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo.LedgerInfo; @@ -117,6 +117,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.pulsar.common.api.proto.PulsarApi.CommandSubscribe.InitialPosition; import org.apache.pulsar.common.util.collections.ConcurrentLongHashMap; +import org.apache.pulsar.metadata.api.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -142,6 +143,7 @@ public class ManagedLedgerImpl implements ManagedLedger, CreateCallback { private final ManagedCursorContainer activeCursors = new ManagedCursorContainer(); // Ever increasing counter of entries added + @VisibleForTesting static final AtomicLongFieldUpdater ENTRIES_ADDED_COUNTER_UPDATER = AtomicLongFieldUpdater .newUpdater(ManagedLedgerImpl.class, "entriesAddedCounter"); @SuppressWarnings("unused") @@ -3176,6 +3178,11 @@ public long getOffloadedSize() { return offloadedSize; } + @VisibleForTesting + public void setEntriesAddedCounter(long count) { + ENTRIES_ADDED_COUNTER_UPDATER.set(this, count); + } + private static final Logger log = LoggerFactory.getLogger(ManagedLedgerImpl.class); } diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerMBeanImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerMBeanImpl.java index 38c67f49f8085..ec1dc74a290bd 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerMBeanImpl.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerMBeanImpl.java @@ -263,7 +263,7 @@ public long getNumberOfMessagesInBacklog() { long count = 0; for (ManagedCursor cursor : managedLedger.getCursors()) { - count += cursor.getNumberOfEntriesInBacklog(); + count += cursor.getNumberOfEntriesInBacklog(false); } return count; diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerOfflineBacklog.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerOfflineBacklog.java index 382f457688b4a..d8b49ad959b62 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerOfflineBacklog.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerOfflineBacklog.java @@ -41,6 +41,7 @@ import org.apache.pulsar.common.naming.TopicName; import org.apache.pulsar.common.policies.data.PersistentOfflineTopicStats; import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap; +import org.apache.pulsar.metadata.api.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -146,7 +147,7 @@ private void readLedgerMeta(final ManagedLedgerFactoryImpl factory, final TopicN store.getManagedLedgerInfo(managedLedgerName, false /* createIfMissing */, new MetaStore.MetaStoreCallback() { @Override - public void operationComplete(MLDataFormats.ManagedLedgerInfo mlInfo, MetaStore.Stat version) { + public void operationComplete(MLDataFormats.ManagedLedgerInfo mlInfo, Stat stat) { for (MLDataFormats.ManagedLedgerInfo.LedgerInfo ls : mlInfo.getLedgerInfoList()) { ledgers.put(ls.getLedgerId(), ls); } @@ -230,7 +231,7 @@ private void calculateCursorBacklogs(final ManagedLedgerFactoryImpl factory, fin store.getCursors(managedLedgerName, new MetaStore.MetaStoreCallback>() { @Override - public void operationComplete(List cursors, MetaStore.Stat v) { + public void operationComplete(List cursors, Stat v) { // Load existing cursors if (log.isDebugEnabled()) { log.debug("[{}] Found {} cursors", managedLedgerName, cursors.size()); @@ -336,7 +337,7 @@ public void readComplete(int rc, LedgerHandle lh, Enumeration seq, new MetaStore.MetaStoreCallback() { @Override public void operationComplete(MLDataFormats.ManagedCursorInfo info, - MetaStore.Stat version) { + Stat stat) { long cursorLedgerId = info.getCursorsLedgerId(); if (log.isDebugEnabled()) { log.debug("[{}] Cursor {} meta-data read ledger id {}", managedLedgerName, diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStore.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStore.java index 72ef566a9cc7d..8b99203c91a57 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStore.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStore.java @@ -22,19 +22,13 @@ import org.apache.bookkeeper.mledger.ManagedLedgerException.MetaStoreException; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; +import org.apache.pulsar.metadata.api.Stat; /** * Interface that describes the operations that the ManagedLedger need to do on the metadata store. */ public interface MetaStore { - @SuppressWarnings("checkstyle:javadoctype") - interface Stat { - int getVersion(); - long getCreationTimestamp(); - long getModificationTimestamp(); - } - @SuppressWarnings("checkstyle:javadoctype") interface UpdateLedgersIdsCallback { void updateLedgersIdsComplete(MetaStoreException status, Stat stat); @@ -64,12 +58,12 @@ interface MetaStoreCallback { * the name of the ManagedLedger * @param mlInfo * managed ledger info - * @param version + * @param stat * version object associated with current state * @param callback * callback object */ - void asyncUpdateLedgerIds(String ledgerName, ManagedLedgerInfo mlInfo, Stat version, + void asyncUpdateLedgerIds(String ledgerName, ManagedLedgerInfo mlInfo, Stat stat, MetaStoreCallback callback); /** @@ -98,12 +92,12 @@ void asyncUpdateLedgerIds(String ledgerName, ManagedLedgerInfo mlInfo, Stat vers * the name of the ManagedLedger * @param cursorName * @param info - * @param version + * @param stat * @param callback * the callback * @throws MetaStoreException */ - void asyncUpdateCursorInfo(String ledgerName, String cursorName, ManagedCursorInfo info, Stat version, + void asyncUpdateCursorInfo(String ledgerName, String cursorName, ManagedCursorInfo info, Stat stat, MetaStoreCallback callback); /** diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImpl.java new file mode 100644 index 0000000000000..8f229fdb478e9 --- /dev/null +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImpl.java @@ -0,0 +1,286 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bookkeeper.mledger.impl; + +import com.google.protobuf.InvalidProtocolBufferException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; + +import lombok.extern.slf4j.Slf4j; + +import org.apache.bookkeeper.common.util.OrderedExecutor; +import org.apache.bookkeeper.mledger.ManagedLedgerException; +import org.apache.bookkeeper.mledger.ManagedLedgerException.MetaStoreException; +import org.apache.bookkeeper.mledger.ManagedLedgerException.MetadataNotFoundException; +import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; +import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; +import org.apache.bookkeeper.util.SafeRunnable; +import org.apache.pulsar.metadata.api.MetadataStore; +import org.apache.pulsar.metadata.api.MetadataStoreException; +import org.apache.pulsar.metadata.api.Stat; + +@Slf4j +public class MetaStoreImpl implements MetaStore { + + private static final String BASE_NODE = "/managed-ledgers"; + private static final String PREFIX = BASE_NODE + "/"; + + private final MetadataStore store; + private final OrderedExecutor executor; + + public MetaStoreImpl(MetadataStore store, OrderedExecutor executor) { + this.store = store; + this.executor = executor; + } + + @Override + public void getManagedLedgerInfo(String ledgerName, boolean createIfMissing, + MetaStoreCallback callback) { + // Try to get the content or create an empty node + String path = PREFIX + ledgerName; + store.get(path) + .thenAcceptAsync(optResult -> { + if (optResult.isPresent()) { + ManagedLedgerInfo info; + try { + info = ManagedLedgerInfo.parseFrom(optResult.get().getValue()); + info = updateMLInfoTimestamp(info); + callback.operationComplete(info, optResult.get().getStat()); + } catch (InvalidProtocolBufferException e) { + callback.operationFailed(getException(e)); + } + } else { + // Z-node doesn't exist + if (createIfMissing) { + log.info("Creating '{}'", path); + + store.put(path, new byte[0], Optional.of(-1L)) + .thenAccept(stat -> { + ManagedLedgerInfo info = ManagedLedgerInfo.getDefaultInstance(); + callback.operationComplete(info, stat); + }).exceptionally(ex -> { + callback.operationFailed(getException(ex)); + return null; + }); + } else { + // Tried to open a managed ledger but it doesn't exist and we shouldn't creating it at this + // point + callback.operationFailed(new MetadataNotFoundException("Managed ledger not found")); + } + } + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void asyncUpdateLedgerIds(String ledgerName, ManagedLedgerInfo mlInfo, Stat stat, + MetaStoreCallback callback) { + if (log.isDebugEnabled()) { + log.debug("[{}] Updating metadata version={} with content={}", ledgerName, stat, mlInfo); + } + + byte[] serializedMlInfo = mlInfo.toByteArray(); // Binary format + String path = PREFIX + ledgerName; + store.put(path, serializedMlInfo, Optional.of(stat.getVersion())) + .thenAcceptAsync(newVersion -> { + callback.operationComplete(null, newVersion); + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void getCursors(String ledgerName, MetaStoreCallback> callback) { + if (log.isDebugEnabled()) { + log.debug("[{}] Get cursors list", ledgerName); + } + + String path = PREFIX + ledgerName; + store.getChildren(path) + .thenAcceptAsync(cursors -> { + callback.operationComplete(cursors, null); + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void asyncGetCursorInfo(String ledgerName, String cursorName, + MetaStoreCallback callback) { + String path = PREFIX + ledgerName + "/" + cursorName; + if (log.isDebugEnabled()) { + log.debug("Reading from {}", path); + } + + store.get(path) + .thenAcceptAsync(optRes -> { + if (optRes.isPresent()) { + try { + ManagedCursorInfo info = ManagedCursorInfo.parseFrom(optRes.get().getValue()); + callback.operationComplete(info, optRes.get().getStat()); + } catch (InvalidProtocolBufferException e) { + callback.operationFailed(getException(e)); + } + } else { + callback.operationFailed(new MetadataNotFoundException("Cursor metadata not found")); + } + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void asyncUpdateCursorInfo(String ledgerName, String cursorName, ManagedCursorInfo info, Stat stat, + MetaStoreCallback callback) { + log.info("[{}] [{}] Updating cursor info ledgerId={} mark-delete={}:{}", ledgerName, cursorName, + info.getCursorsLedgerId(), info.getMarkDeleteLedgerId(), info.getMarkDeleteEntryId()); + + String path = PREFIX + ledgerName + "/" + cursorName; + byte[] content = info.toByteArray(); // Binary format + + long expectedVersion; + + if (stat != null) { + expectedVersion = stat.getVersion(); + if (log.isDebugEnabled()) { + log.debug("[{}] Creating consumer {} on meta-data store with {}", ledgerName, cursorName, info); + } + } else { + expectedVersion = -1; + if (log.isDebugEnabled()) { + log.debug("[{}] Updating consumer {} on meta-data store with {}", ledgerName, cursorName, info); + } + } + + store.put(path, content, Optional.of(expectedVersion)) + .thenAcceptAsync(optStat -> { + callback.operationComplete(null, optStat); + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void asyncRemoveCursor(String ledgerName, String cursorName, MetaStoreCallback callback) { + String path = PREFIX + ledgerName + "/" + cursorName; + log.info("[{}] Remove consumer={}", ledgerName, cursorName); + + store.delete(path, Optional.empty()) + .thenAcceptAsync(v -> { + if (log.isDebugEnabled()) { + log.debug("[{}] [{}] cursor delete done", ledgerName, cursorName); + } + callback.operationComplete(null, null); + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public void removeManagedLedger(String ledgerName, MetaStoreCallback callback) { + log.info("[{}] Remove ManagedLedger", ledgerName); + + String path = PREFIX + ledgerName; + store.delete(path, Optional.empty()) + .thenAcceptAsync(v -> { + if (log.isDebugEnabled()) { + log.debug("[{}] managed ledger delete done", ledgerName); + } + callback.operationComplete(null, null); + }, executor.chooseThread(ledgerName)) + .exceptionally(ex -> { + executor.executeOrdered(ledgerName, SafeRunnable.safeRun(() -> { + callback.operationFailed(getException(ex)); + })); + return null; + }); + } + + @Override + public Iterable getManagedLedgers() throws MetaStoreException { + try { + return store.getChildren(BASE_NODE).join(); + } catch (CompletionException e) { + throw getException(e); + } + } + + // + // update timestamp if missing or 0 + // 3 cases - timestamp does not exist for ledgers serialized before + // - timestamp is 0 for a ledger in recovery + // - ledger has timestamp which is the normal case now + + private static ManagedLedgerInfo updateMLInfoTimestamp(ManagedLedgerInfo info) { + List infoList = new ArrayList<>(info.getLedgerInfoCount()); + long currentTime = System.currentTimeMillis(); + + for (ManagedLedgerInfo.LedgerInfo ledgerInfo : info.getLedgerInfoList()) { + if (!ledgerInfo.hasTimestamp() || ledgerInfo.getTimestamp() == 0) { + ManagedLedgerInfo.LedgerInfo.Builder singleInfoBuilder = ledgerInfo.toBuilder(); + singleInfoBuilder.setTimestamp(currentTime); + infoList.add(singleInfoBuilder.build()); + } else { + infoList.add(ledgerInfo); + } + } + ManagedLedgerInfo.Builder mlInfo = ManagedLedgerInfo.newBuilder(); + mlInfo.addAllLedgerInfo(infoList); + if (info.hasTerminatedPosition()) { + mlInfo.setTerminatedPosition(info.getTerminatedPosition()); + } + return mlInfo.build(); + } + + private static MetaStoreException getException(Throwable t) { + if (t.getCause() instanceof MetadataStoreException.BadVersionException) { + return new ManagedLedgerException.BadVersionException(t.getMessage()); + } else { + return new MetaStoreException(t); + } + } +} diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeper.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeper.java deleted file mode 100644 index 2e69614f65000..0000000000000 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeper.java +++ /dev/null @@ -1,422 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.bookkeeper.mledger.impl; - -import static org.apache.bookkeeper.mledger.util.SafeRun.safeRun; - -import com.google.common.base.Charsets; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.TextFormat; -import com.google.protobuf.TextFormat.ParseException; - -import java.io.File; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ForkJoinPool; -import java.util.function.Consumer; - -import org.apache.bookkeeper.common.util.OrderedExecutor; -import org.apache.bookkeeper.mledger.ManagedLedgerException; -import org.apache.bookkeeper.mledger.ManagedLedgerException.BadVersionException; -import org.apache.bookkeeper.mledger.ManagedLedgerException.MetaStoreException; -import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; -import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; -import org.apache.zookeeper.AsyncCallback.StringCallback; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.KeeperException.Code; -import org.apache.zookeeper.ZooDefs; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.data.ACL; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@SuppressWarnings("checkstyle:javadoctype") -public class MetaStoreImplZookeeper implements MetaStore { - - private static final Charset Encoding = Charsets.UTF_8; - private static final List Acl = ZooDefs.Ids.OPEN_ACL_UNSAFE; - - private static final String prefixName = "/managed-ledgers"; - private static final String prefix = prefixName + "/"; - - private final ZooKeeper zk; - private final OrderedExecutor executor; - - private static class ZKStat implements Stat { - private final int version; - private final long creationTimestamp; - private final long modificationTimestamp; - - ZKStat(org.apache.zookeeper.data.Stat stat) { - this.version = stat.getVersion(); - this.creationTimestamp = stat.getCtime(); - this.modificationTimestamp = stat.getMtime(); - } - - ZKStat() { - this.version = 0; - this.creationTimestamp = System.currentTimeMillis(); - this.modificationTimestamp = System.currentTimeMillis(); - } - - @Override - public int getVersion() { - return version; - } - - @Override - public long getCreationTimestamp() { - return creationTimestamp; - } - - @Override - public long getModificationTimestamp() { - return modificationTimestamp; - } - } - - public MetaStoreImplZookeeper(ZooKeeper zk, OrderedExecutor executor) - throws Exception { - this.zk = zk; - this.executor = executor; - } - - // - // update timestamp if missing or 0 - // 3 cases - timestamp does not exist for ledgers serialized before - // - timestamp is 0 for a ledger in recovery - // - ledger has timestamp which is the normal case now - - private ManagedLedgerInfo updateMLInfoTimestamp(ManagedLedgerInfo info) { - List infoList = new ArrayList<>(info.getLedgerInfoCount()); - long currentTime = System.currentTimeMillis(); - - for (ManagedLedgerInfo.LedgerInfo ledgerInfo : info.getLedgerInfoList()) { - if (!ledgerInfo.hasTimestamp() || ledgerInfo.getTimestamp() == 0) { - ManagedLedgerInfo.LedgerInfo.Builder singleInfoBuilder = ledgerInfo.toBuilder(); - singleInfoBuilder.setTimestamp(currentTime); - infoList.add(singleInfoBuilder.build()); - } else { - infoList.add(ledgerInfo); - } - } - ManagedLedgerInfo.Builder mlInfo = ManagedLedgerInfo.newBuilder(); - mlInfo.addAllLedgerInfo(infoList); - if (info.hasTerminatedPosition()) { - mlInfo.setTerminatedPosition(info.getTerminatedPosition()); - } - return mlInfo.build(); - } - - @Override - public void getManagedLedgerInfo(final String ledgerName, boolean createIfMissing, - final MetaStoreCallback callback) { - // Try to get the content or create an empty node - zk.getData(prefix + ledgerName, false, - (rc, path, ctx, readData, stat) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (rc == Code.OK.intValue()) { - try { - ManagedLedgerInfo info = parseManagedLedgerInfo(readData); - info = updateMLInfoTimestamp(info); - callback.operationComplete(info, new ZKStat(stat)); - } catch (ParseException | InvalidProtocolBufferException e) { - callback.operationFailed(new MetaStoreException(e)); - } - } else if (rc == Code.NONODE.intValue()) { - // Z-node doesn't exist - if (createIfMissing) { - log.info("Creating '{}{}'", prefix, ledgerName); - - StringCallback createcb = (rc1, path1, ctx1, name) -> { - if (rc1 == Code.OK.intValue()) { - ManagedLedgerInfo info = ManagedLedgerInfo.getDefaultInstance(); - callback.operationComplete(info, new ZKStat()); - } else { - callback.operationFailed( - new MetaStoreException(KeeperException.create(Code.get(rc1)))); - } - }; - - asyncCreateFullPathOptimistic(prefixName, ledgerName, new byte[0], Acl, - CreateMode.PERSISTENT, createcb); - } else { - // Tried to open a managed ledger but it doesn't exist and we shouldn't creating it at this - // point - - callback.operationFailed(new ManagedLedgerException.MetadataNotFoundException( - KeeperException.create(Code.get(rc)))); - } - } else { - // Other ZK error - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } - })), null); - } - - @Override - public void asyncUpdateLedgerIds(String ledgerName, ManagedLedgerInfo mlInfo, Stat stat, - final MetaStoreCallback callback) { - - ZKStat zkStat = (ZKStat) stat; - if (log.isDebugEnabled()) { - log.debug("[{}] Updating metadata version={} with content={}", ledgerName, zkStat.version, mlInfo); - } - - byte[] serializedMlInfo = mlInfo.toByteArray(); // Binary format - - zk.setData(prefix + ledgerName, serializedMlInfo, zkStat.getVersion(), - (rc, path, zkCtx, stat1) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (log.isDebugEnabled()) { - log.debug("[{}] UpdateLedgersIdsCallback.processResult rc={} newVersion={}", ledgerName, - Code.get(rc), stat != null ? stat.getVersion() : "null"); - } - MetaStoreException status = null; - if (rc == Code.BADVERSION.intValue()) { - // Content has been modified on ZK since our last read - status = new BadVersionException(KeeperException.create(Code.get(rc))); - callback.operationFailed(status); - } else if (rc != Code.OK.intValue()) { - status = new MetaStoreException(KeeperException.create(Code.get(rc))); - callback.operationFailed(status); - } else { - callback.operationComplete(null, new ZKStat(stat1)); - } - })), null); - } - - @Override - public void getCursors(final String ledgerName, final MetaStoreCallback> callback) { - if (log.isDebugEnabled()) { - log.debug("[{}] Get cursors list", ledgerName); - } - zk.getChildren(prefix + ledgerName, false, - (rc, path, ctx, children, stat) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (log.isDebugEnabled()) { - log.debug("[{}] getConsumers complete rc={} children={}", ledgerName, Code.get(rc), children); - } - if (rc != Code.OK.intValue()) { - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - return; - } - - if (log.isDebugEnabled()) { - log.debug("[{}] Get childrend completed version={}", ledgerName, stat.getVersion()); - } - callback.operationComplete(children, new ZKStat(stat)); - })), null); - } - - @Override - public void asyncGetCursorInfo(String ledgerName, String consumerName, - final MetaStoreCallback callback) { - String path = prefix + ledgerName + "/" + consumerName; - if (log.isDebugEnabled()) { - log.debug("Reading from {}", path); - } - - zk.getData(path, false, (rc, path1, ctx, data, stat) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (rc != Code.OK.intValue()) { - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } else { - try { - ManagedCursorInfo info = parseManagedCursorInfo(data); - callback.operationComplete(info, new ZKStat(stat)); - } catch (ParseException | InvalidProtocolBufferException e) { - callback.operationFailed(new MetaStoreException(e)); - } - } - })), null); - - if (log.isDebugEnabled()) { - log.debug("Reading from {} ok", path); - } - } - - @Override - public void asyncUpdateCursorInfo(final String ledgerName, final String cursorName, final ManagedCursorInfo info, - Stat stat, final MetaStoreCallback callback) { - log.info("[{}] [{}] Updating cursor info ledgerId={} mark-delete={}:{}", ledgerName, cursorName, - info.getCursorsLedgerId(), info.getMarkDeleteLedgerId(), info.getMarkDeleteEntryId()); - - String path = prefix + ledgerName + "/" + cursorName; - byte[] content = info.toByteArray(); // Binary format - - if (stat == null) { - if (log.isDebugEnabled()) { - log.debug("[{}] Creating consumer {} on meta-data store with {}", ledgerName, cursorName, info); - } - zk.create(path, content, Acl, CreateMode.PERSISTENT, - (rc, path1, ctx, name) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (rc != Code.OK.intValue()) { - log.warn("[{}] Error creating cosumer {} node on meta-data store with {}: ", ledgerName, - cursorName, info, Code.get(rc)); - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } else { - if (log.isDebugEnabled()) { - log.debug("[{}] Created consumer {} on meta-data store with {}", ledgerName, cursorName, - info); - } - callback.operationComplete(null, new ZKStat()); - } - })), null); - } else { - ZKStat zkStat = (ZKStat) stat; - if (log.isDebugEnabled()) { - log.debug("[{}] Updating consumer {} on meta-data store with {}", ledgerName, cursorName, info); - } - zk.setData(path, content, zkStat.getVersion(), - (rc, path1, ctx, stat1) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (rc == Code.BADVERSION.intValue()) { - callback.operationFailed(new BadVersionException(KeeperException.create(Code.get(rc)))); - } else if (rc != Code.OK.intValue()) { - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } else { - callback.operationComplete(null, new ZKStat(stat1)); - } - })), null); - } - } - - @Override - public void asyncRemoveCursor(final String ledgerName, final String consumerName, - final MetaStoreCallback callback) { - log.info("[{}] Remove consumer={}", ledgerName, consumerName); - zk.delete(prefix + ledgerName + "/" + consumerName, -1, - (rc, path, ctx) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (log.isDebugEnabled()) { - log.debug("[{}] [{}] zk delete done. rc={}", ledgerName, consumerName, Code.get(rc)); - } - if (rc == Code.OK.intValue()) { - callback.operationComplete(null, null); - } else { - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } - })), null); - } - - @Override - public void removeManagedLedger(String ledgerName, MetaStoreCallback callback) { - log.info("[{}] Remove ManagedLedger", ledgerName); - zk.delete(prefix + ledgerName, -1, (rc, path, ctx) -> executor.executeOrdered(ledgerName, safeRun(() -> { - if (log.isDebugEnabled()) { - log.debug("[{}] zk delete done. rc={}", ledgerName, Code.get(rc)); - } - if (rc == Code.OK.intValue()) { - callback.operationComplete(null, null); - } else { - callback.operationFailed(new MetaStoreException(KeeperException.create(Code.get(rc)))); - } - })), null); - } - - @Override - public Iterable getManagedLedgers() throws MetaStoreException { - try { - return zk.getChildren(prefixName, false); - } catch (Exception e) { - throw new MetaStoreException(e); - } - } - - private ManagedLedgerInfo parseManagedLedgerInfo(byte[] data) - throws ParseException, InvalidProtocolBufferException { - // First try binary format, then fallback to text - try { - return ManagedLedgerInfo.parseFrom(data); - } catch (InvalidProtocolBufferException e) { - // Fallback to parsing protobuf text format - ManagedLedgerInfo.Builder builder = ManagedLedgerInfo.newBuilder(); - TextFormat.merge(new String(data, Encoding), builder); - return builder.build(); - } - } - - private ManagedCursorInfo parseManagedCursorInfo(byte[] data) - throws ParseException, InvalidProtocolBufferException { - // First try binary format, then fallback to text - try { - return ManagedCursorInfo.parseFrom(data); - } catch (InvalidProtocolBufferException e) { - // Fallback to parsing protobuf text format - ManagedCursorInfo.Builder builder = ManagedCursorInfo.newBuilder(); - TextFormat.merge(new String(data, Encoding), builder); - return builder.build(); - } - - } - - void asyncCreateFullPathOptimistic( - final String basePath, final String nodePath, final byte[] data, - final List acl, final CreateMode createMode, final StringCallback callback) { - String fullPath = basePath + "/" + nodePath; - - zk.create(fullPath, data, acl, createMode, - (rc, path, ignoreCtx1, name) -> { - Runnable retry = () -> { - asyncCreateFullPathOptimistic(basePath, nodePath, data, - acl, createMode, callback); - }; - - Consumer complete = (finalrc) -> { - executor.executeOrdered(nodePath, safeRun(() -> { - callback.processResult(finalrc, path, null, name); - })); - }; - - if (rc != Code.NONODE.intValue()) { - complete.accept(rc); - return; - } - - // Since I got a nonode, it means that my parents don't exist - // create mode is persistent since ephemeral nodes can't be - // parents - String nodeParent = new File(nodePath).getParent(); - if (nodeParent == null) { - zk.exists(basePath, false, - (existsRc, existsPath, ignoreCtx2, stat) -> { - if (existsRc == Code.OK.intValue()) { - if (stat != null) { - retry.run(); - } else { - complete.accept(Code.NONODE.intValue()); - } - } else { - complete.accept(existsRc); - } - }, null); - } else { - nodeParent = nodeParent.replace("\\", "/"); - asyncCreateFullPathOptimistic( - basePath, nodeParent, new byte[0], acl, CreateMode.PERSISTENT, - (parentRc, parentPath, ignoreCtx3, parentName) -> { - if (parentRc == Code.OK.intValue() || parentRc == Code.NODEEXISTS.intValue()) { - retry.run(); - } else { - complete.accept(parentRc); - } - }); - } - }, null); - } - - private static final Logger log = LoggerFactory.getLogger(MetaStoreImplZookeeper.class); -} diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/NullLedgerOffloader.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/NullLedgerOffloader.java index 3401f1bbbb647..aa3aae71d1db7 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/NullLedgerOffloader.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/NullLedgerOffloader.java @@ -24,6 +24,7 @@ import org.apache.bookkeeper.client.api.ReadHandle; import org.apache.bookkeeper.mledger.LedgerOffloader; +import org.apache.pulsar.common.policies.data.OffloadPolicies; /** * Null implementation that throws an error on any invokation. @@ -60,4 +61,14 @@ public CompletableFuture deleteOffloaded(long ledgerId, UUID uid, promise.completeExceptionally(new UnsupportedOperationException()); return promise; } + + @Override + public OffloadPolicies getOffloadPolicies() { + return null; + } + + @Override + public void close() { + + } } diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/OpFindNewest.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/OpFindNewest.java index 4bce5690ad35f..861d2472e05d6 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/OpFindNewest.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/OpFindNewest.java @@ -67,7 +67,10 @@ public void readEntryComplete(Entry entry, Object ctx) { switch (state) { case checkFirst: if (!condition.apply(entry)) { - callback.findEntryComplete(startPosition, OpFindNewest.this.ctx); + // If no entry is found that matches the condition, it is expected to pass null to the callback. + // Otherwise, a message before the expiration date will be deleted due to message TTL. + // cf. https://github.com/apache/pulsar/issues/5579 + callback.findEntryComplete(null, OpFindNewest.this.ctx); return; } else { lastMatchedPosition = position; diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ReadOnlyManagedLedgerImpl.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ReadOnlyManagedLedgerImpl.java index 9721b15bd4280..621131752720a 100644 --- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ReadOnlyManagedLedgerImpl.java +++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ReadOnlyManagedLedgerImpl.java @@ -34,9 +34,9 @@ import org.apache.bookkeeper.mledger.ManagedLedgerException.MetadataNotFoundException; import org.apache.bookkeeper.mledger.ReadOnlyCursor; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo.LedgerInfo; +import org.apache.pulsar.metadata.api.Stat; @Slf4j public class ReadOnlyManagedLedgerImpl extends ManagedLedgerImpl { diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorContainerTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorContainerTest.java index 21f5747919b2f..99e7aec740bed 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorContainerTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorContainerTest.java @@ -92,7 +92,7 @@ public long getNumberOfEntries() { } @Override - public long getNumberOfEntriesInBacklog() { + public long getNumberOfEntriesInBacklog(boolean isPrecise) { return 0; } diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorListAckTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorListAckTest.java index aaf7cd1c6f282..dc876e19eb1ab 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorListAckTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorListAckTest.java @@ -51,24 +51,24 @@ void testMultiPositionDelete() throws Exception { Position p7 = ledger.addEntry("dummy-entry-7".getBytes(Encoding)); assertEquals(c1.getNumberOfEntries(), 7); - assertEquals(c1.getNumberOfEntriesInBacklog(), 7); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 7); c1.delete(Lists.newArrayList(p2, p3, p5, p7)); assertEquals(c1.getNumberOfEntries(), 3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); assertEquals(c1.getMarkDeletedPosition(), p0); c1.delete(Lists.newArrayList(p1)); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); assertEquals(c1.getMarkDeletedPosition(), p3); c1.delete(Lists.newArrayList(p4, p6, p7)); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); assertEquals(c1.getMarkDeletedPosition(), p7); } diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java index b0db9264d8f91..75d0280ca52b7 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedCursorTest.java @@ -77,10 +77,10 @@ import org.apache.bookkeeper.mledger.Position; import org.apache.bookkeeper.mledger.impl.ManagedCursorImpl.VoidCallback; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.PositionInfo; import org.apache.bookkeeper.test.MockedBookKeeperTestCase; +import org.apache.pulsar.metadata.api.Stat; import org.apache.zookeeper.KeeperException.Code; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -92,7 +92,7 @@ public class ManagedCursorTest extends MockedBookKeeperTestCase { private static final Charset Encoding = Charsets.UTF_8; - + @DataProvider(name = "useOpenRangeSet") public static Object[][] useOpenRangeSet() { return new Object[][] { { Boolean.TRUE }, { Boolean.FALSE } }; @@ -265,31 +265,31 @@ void testNumberOfEntriesInBacklog() throws Exception { Position p4 = ledger.addEntry("dummy-entry-4".getBytes(Encoding)); ManagedCursor c5 = ledger.openCursor("c5"); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); - assertEquals(c2.getNumberOfEntriesInBacklog(), 3); - assertEquals(c3.getNumberOfEntriesInBacklog(), 2); - assertEquals(c4.getNumberOfEntriesInBacklog(), 1); - assertEquals(c5.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 3); + assertEquals(c3.getNumberOfEntriesInBacklog(false), 2); + assertEquals(c4.getNumberOfEntriesInBacklog(false), 1); + assertEquals(c5.getNumberOfEntriesInBacklog(false), 0); List entries = c1.readEntries(2); assertEquals(entries.size(), 2); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); c1.markDelete(p1); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.delete(p3); assertEquals(c1.getNumberOfEntries(), 1); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); c1.markDelete(p4); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); } @Test(timeOut = 20000) @@ -315,11 +315,11 @@ void testNumberOfEntriesInBacklogWithFallback() throws Exception { field.setLong(c4, counter); field.setLong(c5, counter); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); - assertEquals(c2.getNumberOfEntriesInBacklog(), 3); - assertEquals(c3.getNumberOfEntriesInBacklog(), 2); - assertEquals(c4.getNumberOfEntriesInBacklog(), 1); - assertEquals(c5.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 3); + assertEquals(c3.getNumberOfEntriesInBacklog(false), 2); + assertEquals(c4.getNumberOfEntriesInBacklog(false), 1); + assertEquals(c5.getNumberOfEntriesInBacklog(false), 0); } @Test(timeOut = 20000) @@ -552,6 +552,77 @@ void testResetCursor() throws Exception { ledger.close(); } + @Test(timeOut = 20000) + void testResetCursor1() throws Exception { + ManagedLedger ledger = factory.open("my_test_move_cursor_ledger", + new ManagedLedgerConfig().setMaxEntriesPerLedger(2)); + ManagedCursor cursor = ledger.openCursor("trc1"); + PositionImpl actualEarliest = (PositionImpl) ledger.addEntry("dummy-entry-1".getBytes(Encoding)); + ledger.addEntry("dummy-entry-2".getBytes(Encoding)); + ledger.addEntry("dummy-entry-3".getBytes(Encoding)); + PositionImpl lastInPrev = (PositionImpl) ledger.addEntry("dummy-entry-4".getBytes(Encoding)); + PositionImpl firstInNext = (PositionImpl) ledger.addEntry("dummy-entry-5".getBytes(Encoding)); + ledger.addEntry("dummy-entry-6".getBytes(Encoding)); + ledger.addEntry("dummy-entry-7".getBytes(Encoding)); + ledger.addEntry("dummy-entry-8".getBytes(Encoding)); + ledger.addEntry("dummy-entry-9".getBytes(Encoding)); + PositionImpl last = (PositionImpl) ledger.addEntry("dummy-entry-10".getBytes(Encoding)); + + final AtomicBoolean moveStatus = new AtomicBoolean(false); + + // reset to earliest + PositionImpl earliest = PositionImpl.earliest; + try { + cursor.resetCursor(earliest); + moveStatus.set(true); + } catch (Exception e) { + log.warn("error in reset cursor", e.getCause()); + } + assertTrue(moveStatus.get()); + PositionImpl earliestPos = new PositionImpl(actualEarliest.getLedgerId(), -1); + assertEquals(earliestPos, cursor.getReadPosition()); + moveStatus.set(false); + + // reset to one after last entry in a ledger should point to the first entry in the next ledger + PositionImpl resetPosition = new PositionImpl(lastInPrev.getLedgerId(), lastInPrev.getEntryId() + 1); + try { + cursor.resetCursor(resetPosition); + moveStatus.set(true); + } catch (Exception e) { + log.warn("error in reset cursor", e.getCause()); + } + assertTrue(moveStatus.get()); + assertEquals(firstInNext, cursor.getReadPosition()); + moveStatus.set(false); + + // reset to a non exist larger ledger should point to the first non-exist entry in the last ledger + PositionImpl latest = new PositionImpl(last.getLedgerId() + 2, 0); + try { + cursor.resetCursor(latest); + moveStatus.set(true); + } catch (Exception e) { + log.warn("error in reset cursor", e.getCause()); + } + assertTrue(moveStatus.get()); + PositionImpl lastPos = new PositionImpl(last.getLedgerId(), last.getEntryId() + 1); + assertEquals(lastPos, cursor.getReadPosition()); + moveStatus.set(false); + + // reset to latest should point to the first non-exist entry in the last ledger + PositionImpl anotherLast = PositionImpl.latest; + try { + cursor.resetCursor(anotherLast); + moveStatus.set(true); + } catch (Exception e) { + log.warn("error in reset cursor", e.getCause()); + } + assertTrue(moveStatus.get()); + assertEquals(lastPos, cursor.getReadPosition()); + + cursor.close(); + ledger.close(); + } + @Test(timeOut = 20000) void testasyncResetCursor() throws Exception { ManagedLedger ledger = factory.open("my_test_move_cursor_ledger", @@ -740,34 +811,34 @@ void rewind() throws Exception { log.debug("p4: {}", p4); assertEquals(c1.getNumberOfEntries(), 4); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); c1.markDelete(p1); assertEquals(c1.getNumberOfEntries(), 3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); List entries = c1.readEntries(10); assertEquals(entries.size(), 3); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.markDelete(p2); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); entries = c1.readEntries(10); assertEquals(entries.size(), 2); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 2); c1.markDelete(p4); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 0); ledger.addEntry("dummy-entry-5".getBytes(Encoding)); @@ -1253,26 +1324,26 @@ void testFilteringReadEntries() throws Exception { /* Position p6 = */ledger.addEntry("entry6".getBytes()); assertEquals(cursor.getNumberOfEntries(), 6); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 6); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 6); List entries = cursor.readEntries(3); assertEquals(entries.size(), 3); entries.forEach(e -> e.release()); assertEquals(cursor.getNumberOfEntries(), 3); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 6); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 6); log.info("Deleting {}", p5); cursor.delete(p5); assertEquals(cursor.getNumberOfEntries(), 2); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 5); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 5); entries = cursor.readEntries(3); assertEquals(entries.size(), 2); entries.forEach(e -> e.release()); assertEquals(cursor.getNumberOfEntries(), 0); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 5); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 5); } @Test(timeOut = 20000) @@ -1313,21 +1384,21 @@ void testCountingWithDeletedEntries() throws Exception { Position p8 = ledger.addEntry("entry8".getBytes()); assertEquals(cursor.getNumberOfEntries(), 8); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 8); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 8); cursor.delete(p8); assertEquals(cursor.getNumberOfEntries(), 7); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 7); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 7); cursor.delete(p1); assertEquals(cursor.getNumberOfEntries(), 6); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 6); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 6); cursor.delete(p7); cursor.delete(p6); cursor.delete(p5); assertEquals(cursor.getNumberOfEntries(), 3); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 3); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 3); } @Test(timeOut = 20000, dataProvider = "useOpenRangeSet") @@ -1433,22 +1504,22 @@ void testClearBacklog(boolean useOpenRangeSet) throws Exception { ManagedCursor c3 = ledger.openCursor("c3"); ledger.addEntry("dummy-entry-3".getBytes(Encoding)); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); assertEquals(c1.getNumberOfEntries(), 3); assertTrue(c1.hasMoreEntries()); c1.clearBacklog(); c3.clearBacklog(); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); assertEquals(c1.getNumberOfEntries(), 0); assertFalse(c1.hasMoreEntries()); - assertEquals(c2.getNumberOfEntriesInBacklog(), 2); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 2); assertEquals(c2.getNumberOfEntries(), 2); assertTrue(c2.hasMoreEntries()); - assertEquals(c3.getNumberOfEntriesInBacklog(), 0); + assertEquals(c3.getNumberOfEntriesInBacklog(false), 0); assertEquals(c3.getNumberOfEntries(), 0); assertFalse(c3.hasMoreEntries()); @@ -1459,15 +1530,15 @@ void testClearBacklog(boolean useOpenRangeSet) throws Exception { c2 = ledger.openCursor("c2"); c3 = ledger.openCursor("c3"); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); assertEquals(c1.getNumberOfEntries(), 0); assertFalse(c1.hasMoreEntries()); - assertEquals(c2.getNumberOfEntriesInBacklog(), 2); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 2); assertEquals(c2.getNumberOfEntries(), 2); assertTrue(c2.hasMoreEntries()); - assertEquals(c3.getNumberOfEntriesInBacklog(), 0); + assertEquals(c3.getNumberOfEntriesInBacklog(false), 0); assertEquals(c3.getNumberOfEntries(), 0); assertFalse(c3.hasMoreEntries()); factory2.shutdown(); @@ -1484,12 +1555,12 @@ void testRateLimitMarkDelete(boolean useOpenRangeSet) throws Exception { Position p2 = ledger.addEntry("dummy-entry-2".getBytes(Encoding)); Position p3 = ledger.addEntry("dummy-entry-3".getBytes(Encoding)); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.markDelete(p1); c1.markDelete(p2); c1.markDelete(p3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); // Re-open to recover from storage ManagedLedgerFactory factory2 = new ManagedLedgerFactoryImpl(bkc, bkc.getZkHandle()); @@ -1498,7 +1569,7 @@ void testRateLimitMarkDelete(boolean useOpenRangeSet) throws Exception { c1 = ledger.openCursor("c1"); // Only the 1st mark-delete was persisted - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); factory2.shutdown(); } @@ -1514,57 +1585,57 @@ void deleteSingleMessageTwice(boolean useOpenRangeSet) throws Exception { Position p3 = ledger.addEntry("entry-3".getBytes(Encoding)); Position p4 = ledger.addEntry("entry-4".getBytes(Encoding)); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); assertEquals(c1.getNumberOfEntries(), 4); c1.delete(p1); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); assertEquals(c1.getNumberOfEntries(), 3); assertEquals(c1.getMarkDeletedPosition(), p1); assertEquals(c1.getReadPosition(), p2); // Should have not effect since p1 is already deleted c1.delete(p1); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); assertEquals(c1.getNumberOfEntries(), 3); assertEquals(c1.getMarkDeletedPosition(), p1); assertEquals(c1.getReadPosition(), p2); c1.delete(p2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); assertEquals(c1.getNumberOfEntries(), 2); assertEquals(c1.getMarkDeletedPosition(), p2); assertEquals(c1.getReadPosition(), p3); // Should have not effect since p2 is already deleted c1.delete(p2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); assertEquals(c1.getNumberOfEntries(), 2); assertEquals(c1.getMarkDeletedPosition(), p2); assertEquals(c1.getReadPosition(), p3); c1.delete(p3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 1); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 1); assertEquals(c1.getNumberOfEntries(), 1); assertEquals(c1.getMarkDeletedPosition(), p3); assertEquals(c1.getReadPosition(), p4); // Should have not effect since p3 is already deleted c1.delete(p3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 1); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 1); assertEquals(c1.getNumberOfEntries(), 1); assertEquals(c1.getMarkDeletedPosition(), p3); assertEquals(c1.getReadPosition(), p4); c1.delete(p4); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); assertEquals(c1.getNumberOfEntries(), 0); assertEquals(c1.getMarkDeletedPosition(), p4); assertEquals(c1.getReadPosition(), p4.getNext()); // Should have not effect since p4 is already deleted c1.delete(p4); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); assertEquals(c1.getNumberOfEntries(), 0); assertEquals(c1.getMarkDeletedPosition(), p4); assertEquals(c1.getReadPosition(), p4.getNext()); @@ -1654,7 +1725,7 @@ void testFindNewestMatching() throws Exception { ledger.addEntry("not-expired".getBytes(Encoding)); ledger.addEntry("not-expired".getBytes(Encoding)); - assertEquals(c1.readPosition, + assertNull( c1.findNewestMatching(entry -> Arrays.equals(entry.getDataAndRelease(), "expired".getBytes(Encoding)))); } @@ -2117,7 +2188,7 @@ void internalTestFindNewestMatchingAllEntries(final String name, final int entri ManagedLedger ledger = factory.open(ledgerAndCursorName, config); ManagedCursorImpl c1 = (ManagedCursorImpl) ledger.openCursor(ledgerAndCursorName); - Position firstPosition = ledger.addEntry(getEntryPublishTime("retained1")); + ledger.addEntry(getEntryPublishTime("retained1")); // space apart message publish times Thread.sleep(100); ledger.addEntry(getEntryPublishTime("retained2")); @@ -2146,7 +2217,7 @@ void internalTestFindNewestMatchingAllEntries(final String name, final int entri assertEquals(found.getEntryId(), expectedEntryId); found = (PositionImpl) findPositionFromAllEntries(c1, 0); - assertEquals(found, firstPosition); + assertNull(found); } @Test(timeOut = 20000) @@ -2296,19 +2367,19 @@ void outOfOrderAcks() throws Exception { positions.add(ledger.addEntry("entry".getBytes())); } - assertEquals(c1.getNumberOfEntriesInBacklog(), N); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N); c1.delete(positions.get(3)); - assertEquals(c1.getNumberOfEntriesInBacklog(), N - 1); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N - 1); c1.delete(positions.get(2)); - assertEquals(c1.getNumberOfEntriesInBacklog(), N - 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N - 2); c1.delete(positions.get(1)); - assertEquals(c1.getNumberOfEntriesInBacklog(), N - 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N - 3); c1.delete(positions.get(0)); - assertEquals(c1.getNumberOfEntriesInBacklog(), N - 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N - 4); } @Test(timeOut = 20000) @@ -2323,17 +2394,17 @@ void randomOrderAcks() throws Exception { positions.add(ledger.addEntry("entry".getBytes())); } - assertEquals(c1.getNumberOfEntriesInBacklog(), N); + assertEquals(c1.getNumberOfEntriesInBacklog(false), N); // Randomize the ack sequence Collections.shuffle(positions); int toDelete = N; for (Position p : positions) { - assertEquals(c1.getNumberOfEntriesInBacklog(), toDelete); + assertEquals(c1.getNumberOfEntriesInBacklog(false), toDelete); c1.delete(p); --toDelete; - assertEquals(c1.getNumberOfEntriesInBacklog(), toDelete); + assertEquals(c1.getNumberOfEntriesInBacklog(false), toDelete); } } @@ -2501,7 +2572,7 @@ public void testOutOfOrderDeletePersistenceWithClose() throws Exception { c1.delete(addedPositions.get(8)); c1.delete(addedPositions.get(9)); - assertEquals(c1.getNumberOfEntriesInBacklog(), 20 - 5); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 20 - 5); ledger.close(); factory.shutdown(); @@ -2510,7 +2581,7 @@ public void testOutOfOrderDeletePersistenceWithClose() throws Exception { factory = new ManagedLedgerFactoryImpl(bkc, bkc.getZkHandle()); ledger = factory.open("my_test_ledger", new ManagedLedgerConfig()); c1 = ledger.openCursor("c1"); - assertEquals(c1.getNumberOfEntriesInBacklog(), 20 - 5); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 20 - 5); List entries = c1.readEntries(20); assertEquals(entries.size(), 20 - 5); @@ -2546,13 +2617,13 @@ public void testOutOfOrderDeletePersistenceAfterCrash() throws Exception { c1.delete(addedPositions.get(8)); c1.delete(addedPositions.get(9)); - assertEquals(c1.getNumberOfEntriesInBacklog(), 20 - 5); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 20 - 5); // Re-Open ManagedLedgerFactory factory2 = new ManagedLedgerFactoryImpl(bkc, bkc.getZkHandle()); ledger = factory2.open("my_test_ledger", new ManagedLedgerConfig()); c1 = ledger.openCursor("c1"); - assertEquals(c1.getNumberOfEntriesInBacklog(), 20 - 5); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 20 - 5); List entries = c1.readEntries(20); assertEquals(entries.size(), 20 - 5); @@ -2665,7 +2736,7 @@ public void testOutOfOrderDeletePersistenceIntoLedgerWithClose() throws Exceptio } } - assertEquals(c1.getNumberOfEntriesInBacklog(), totalAddEntries / 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalAddEntries / 2); // Close ledger to persist individual-deleted positions into cursor-ledger ledger.close(); @@ -2717,7 +2788,7 @@ public void operationFailed(MetaStoreException e) { ledger = (ManagedLedgerImpl) factory.open(ledgerName, managedLedgerConfig); c1 = (ManagedCursorImpl) ledger.openCursor("c1"); // verify cursor has been recovered - assertEquals(c1.getNumberOfEntriesInBacklog(), totalAddEntries / 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalAddEntries / 2); // try to read entries which should only read non-deleted positions List entries = c1.readEntries(totalAddEntries); @@ -2749,7 +2820,7 @@ public void testOutOfOrderDeletePersistenceIntoZkWithClose() throws Exception { } } - assertEquals(c1.getNumberOfEntriesInBacklog(), totalAddEntries / 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalAddEntries / 2); // Close ledger to persist individual-deleted positions into cursor-ledger ledger.close(); @@ -2777,7 +2848,7 @@ public void operationFailed(MetaStoreException e) { ledger = (ManagedLedgerImpl) factory.open(ledgerName, managedLedgerConfig); c1 = (ManagedCursorImpl) ledger.openCursor(cursorName); // verify cursor has been recovered - assertEquals(c1.getNumberOfEntriesInBacklog(), totalAddEntries / 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalAddEntries / 2); // try to read entries which should only read non-deleted positions List entries = c1.readEntries(totalAddEntries); @@ -2873,7 +2944,7 @@ public void testRecoverCursorAheadOfLastPosition() throws Exception { final long markDeleteLedgerId = 2L; final long markDeleteEntryId = -1L; - MetaStoreImplZookeeper mockMetaStore = mock(MetaStoreImplZookeeper.class); + MetaStore mockMetaStore = mock(MetaStore.class); doAnswer(new Answer() { public Object answer(InvocationOnMock invocation) { ManagedCursorInfo info = ManagedCursorInfo.newBuilder().setCursorsLedgerId(cursorsLedgerId) @@ -2948,7 +3019,7 @@ public void deleteMessagesCheckhMarkDelete() throws Exception { totalDeletedMessages += 1; } } - assertEquals(c1.getNumberOfEntriesInBacklog(), totalEntries - totalDeletedMessages); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalEntries - totalDeletedMessages); assertEquals(c1.getNumberOfEntries(), totalEntries - totalDeletedMessages); assertEquals(c1.getMarkDeletedPosition(), positions[0]); assertEquals(c1.getReadPosition(), positions[1]); @@ -2962,11 +3033,11 @@ public void deleteMessagesCheckhMarkDelete() throws Exception { } } int markDelete = totalEntries / 2 - 1; - assertEquals(c1.getNumberOfEntriesInBacklog(), totalEntries - totalDeletedMessages); + assertEquals(c1.getNumberOfEntriesInBacklog(false), totalEntries - totalDeletedMessages); assertEquals(c1.getNumberOfEntries(), totalEntries - totalDeletedMessages); assertEquals(c1.getMarkDeletedPosition(), positions[markDelete]); assertEquals(c1.getReadPosition(), positions[markDelete + 1]); } - + private static final Logger log = LoggerFactory.getLogger(ManagedCursorTest.class); } diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerBkTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerBkTest.java index ea69c63f2c21c..5a9e8d4bafc7b 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerBkTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerBkTest.java @@ -124,7 +124,7 @@ public void testBookieFailure() throws Exception { // Next add should succeed ledger.addEntry("entry-2".getBytes()); - assertEquals(3, cursor.getNumberOfEntriesInBacklog()); + assertEquals(3, cursor.getNumberOfEntriesInBacklog(false)); List entries = cursor.readEntries(1); assertEquals(1, entries.size()); @@ -357,13 +357,13 @@ public void ledgerFencedByAutoReplication() throws Exception { ledger.addEntry("entry-2".getBytes()); assertEquals(2, c1.getNumberOfEntries()); - assertEquals(2, c1.getNumberOfEntriesInBacklog()); + assertEquals(2, c1.getNumberOfEntriesInBacklog(false)); PositionImpl p3 = (PositionImpl) ledger.addEntry("entry-3".getBytes()); // Now entry-2 should have been written before entry-3 assertEquals(3, c1.getNumberOfEntries()); - assertEquals(3, c1.getNumberOfEntriesInBacklog()); + assertEquals(3, c1.getNumberOfEntriesInBacklog(false)); assertTrue(p1.getLedgerId() != p3.getLedgerId()); factory.shutdown(); } @@ -402,7 +402,7 @@ public void ledgerFencedByFailover() throws Exception { // Ok } - assertEquals(2, c2.getNumberOfEntriesInBacklog()); + assertEquals(2, c2.getNumberOfEntriesInBacklog(false)); factory1.shutdown(); factory2.shutdown(); } @@ -459,12 +459,12 @@ void testResetCursorAfterRecovery() throws Exception { assertEquals(cursor.getMarkDeletedPosition(), p3); assertEquals(cursor.getReadPosition(), p4); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); cursor.resetCursor(p2); assertEquals(cursor.getMarkDeletedPosition(), p1); assertEquals(cursor.getReadPosition(), p2); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 3); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 3); factory2.shutdown(); factory.shutdown(); @@ -531,7 +531,7 @@ public void testChangeCrcType() throws Exception { ledger.addEntry("entry-3".getBytes()); assertEquals(c1.getNumberOfEntries(), 4); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); List entries = c1.readEntries(4); assertEquals(entries.size(), 4); diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerErrorsTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerErrorsTest.java index 3d47a6f5697d7..99b6bd59e91f8 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerErrorsTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerErrorsTest.java @@ -343,7 +343,7 @@ public void recoverAfterZnodeVersionError() throws Exception { ledger.addEntry("entry".getBytes()); fail("should fail"); } catch (ManagedLedgerFencedException e) { - assertEquals(e.getCause().getCause().getClass(), org.apache.zookeeper.KeeperException.BadVersionException.class); + assertEquals(e.getCause().getClass(), ManagedLedgerException.BadVersionException.class); // ok } @@ -366,7 +366,7 @@ public void recoverAfterWriteError() throws Exception { // With one single error, the write should succeed ledger.addEntry("entry-1".getBytes()); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); bkc.failNow(BKException.Code.BookieHandleNotAvailableException); zkc.failNow(Code.CONNECTIONLOSS); @@ -385,7 +385,7 @@ public void recoverAfterWriteError() throws Exception { // ok } - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); // Signal that ManagedLedger has recovered from write error and will be availbe for writes again ledger.readyToCreateNewLedger(); @@ -393,7 +393,7 @@ public void recoverAfterWriteError() throws Exception { // Next add should succeed, and the previous write should not appear ledger.addEntry("entry-4".getBytes()); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 2); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 2); List entries = cursor.readEntries(10); assertEquals(entries.size(), 2); @@ -435,7 +435,7 @@ public void addFailed(ManagedLedgerException exception, Object ctx) { counter.await(); assertNull(ex.get()); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 2); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 2); // Ensure that we are only creating one new ledger // even when there are multiple (here, 2) add entry failed ops diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerTest.java index b954f654a9442..a31416f8dbd03 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerTest.java @@ -97,7 +97,6 @@ import org.apache.bookkeeper.mledger.ManagedLedgerFactoryConfig; import org.apache.bookkeeper.mledger.Position; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo.LedgerInfo; import org.apache.bookkeeper.test.MockedBookKeeperTestCase; @@ -108,6 +107,8 @@ import org.apache.pulsar.common.api.proto.PulsarApi.MessageMetadata; import org.apache.pulsar.common.protocol.ByteBufPair; import org.apache.pulsar.common.util.protobuf.ByteBufCodedOutputStream; +import org.apache.pulsar.metadata.api.Stat; +import org.apache.pulsar.metadata.impl.zookeeper.ZKMetadataStore; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.ZooDefs; @@ -175,14 +176,14 @@ public void simple() throws Exception { assertFalse(cursor.hasMoreEntries()); assertEquals(cursor.getNumberOfEntries(), 0); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 0); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 0); assertEquals(cursor.readEntries(100), new ArrayList()); ledger.addEntry("dummy-entry-2".getBytes(Encoding)); assertTrue(cursor.hasMoreEntries()); assertEquals(cursor.getNumberOfEntries(), 1); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); assertEquals(ledger.getNumberOfActiveEntries(), 1); List entries = cursor.readEntries(100); @@ -242,7 +243,7 @@ public void acknowledge1() throws Exception { assertEquals(entries.size(), 2); assertEquals(cursor.getNumberOfEntries(), 0); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 2); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 2); assertFalse(cursor.hasMoreEntries()); assertEquals(ledger.getNumberOfEntries(), 2); @@ -251,7 +252,7 @@ public void acknowledge1() throws Exception { entries.forEach(e -> e.release()); assertEquals(cursor.getNumberOfEntries(), 0); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); assertFalse(cursor.hasMoreEntries()); assertEquals(ledger.getNumberOfActiveEntries(), 1); @@ -266,7 +267,7 @@ public void acknowledge1() throws Exception { assertEquals(ledger.getTotalSize(), "dummy-entry-1".getBytes(Encoding).length * 2); assertEquals(cursor.getNumberOfEntries(), 1); - assertEquals(cursor.getNumberOfEntriesInBacklog(), 1); + assertEquals(cursor.getNumberOfEntriesInBacklog(false), 1); assertTrue(cursor.hasMoreEntries()); entries = cursor.readEntries(100); @@ -1814,7 +1815,7 @@ public void testBackwardCompatiblityForMeta() throws Exception { ml.addEntry("msg2".getBytes()); ml.close(); - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); CountDownLatch l1 = new CountDownLatch(1); // obtain the ledger info @@ -2313,7 +2314,7 @@ public void testConsumerSubscriptionInitializePosition() throws Exception{ assertEquals(earliestPositionAndCounter.getLeft().getNext(), p2); assertEquals(latestPositionAndCounter.getRight().longValue(), totalInsertedEntries); - assertEquals(earliestPositionAndCounter.getRight().longValue(), totalInsertedEntries - earliestCursor.getNumberOfEntriesInBacklog()); + assertEquals(earliestPositionAndCounter.getRight().longValue(), totalInsertedEntries - earliestCursor.getNumberOfEntriesInBacklog(false)); ledger.close(); diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeperTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplTest.java similarity index 70% rename from managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeperTest.java rename to managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplTest.java index 021e4c153dbc9..2337b4dd28b8b 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplZookeeperTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/MetaStoreImplTest.java @@ -18,32 +18,30 @@ */ package org.apache.bookkeeper.mledger.impl; -import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; + import org.apache.bookkeeper.mledger.ManagedLedgerException.MetaStoreException; import org.apache.bookkeeper.mledger.impl.MetaStore.MetaStoreCallback; -import org.apache.bookkeeper.mledger.impl.MetaStore.Stat; import org.apache.bookkeeper.mledger.proto.MLDataFormats; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedCursorInfo; import org.apache.bookkeeper.mledger.proto.MLDataFormats.ManagedLedgerInfo; import org.apache.bookkeeper.test.MockedBookKeeperTestCase; +import org.apache.pulsar.metadata.api.Stat; +import org.apache.pulsar.metadata.impl.zookeeper.ZKMetadataStore; import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.ZooDefs; import org.testng.annotations.Test; -public class MetaStoreImplZookeeperTest extends MockedBookKeeperTestCase { +public class MetaStoreImplTest extends MockedBookKeeperTestCase { @Test void getMLList() throws Exception { - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); zkc.failNow(Code.CONNECTIONLOSS); @@ -57,7 +55,7 @@ void getMLList() throws Exception { @Test void deleteNonExistingML() throws Exception { - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); AtomicReference exception = new AtomicReference<>(); CountDownLatch counter = new CountDownLatch(1); @@ -82,7 +80,7 @@ public void operationFailed(MetaStoreException e) { @Test(timeOut = 20000) void readMalformedML() throws Exception { - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); zkc.create("/managed-ledgers/my_test", "non-valid".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -105,7 +103,7 @@ public void operationComplete(ManagedLedgerInfo result, Stat version) { @Test(timeOut = 20000) void readMalformedCursorNode() throws Exception { - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); zkc.create("/managed-ledgers/my_test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zkc.create("/managed-ledgers/my_test/c1", "non-valid".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, @@ -129,7 +127,7 @@ public void operationComplete(ManagedCursorInfo result, Stat version) { @Test(timeOut = 20000) void failInCreatingMLnode() throws Exception { - MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); final CountDownLatch latch = new CountDownLatch(1); @@ -151,7 +149,7 @@ public void operationComplete(ManagedLedgerInfo result, Stat version) { @Test(timeOut = 20000) void updatingCursorNode() throws Exception { - final MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); zkc.create("/managed-ledgers/my_test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -187,7 +185,7 @@ public void operationComplete(Void result, Stat version) { @Test(timeOut = 20000) void updatingMLNode() throws Exception { - final MetaStore store = new MetaStoreImplZookeeper(zkc, executor); + MetaStore store = new MetaStoreImpl(new ZKMetadataStore(zkc), executor); zkc.create("/managed-ledgers/my_test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -218,55 +216,4 @@ public void operationComplete(Void result, Stat version) { latch.await(); } - - @Test(timeOut = 20000) - public void createOptimisticBaseNotExist() throws Exception { - CompletableFuture promise = new CompletableFuture<>(); - - MetaStoreImplZookeeper store = new MetaStoreImplZookeeper(zkc, executor); - store.asyncCreateFullPathOptimistic( - "/foo", "bar/zar/gar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, - (rc, path, ctx, name) -> { - if (rc != KeeperException.Code.OK.intValue()) { - promise.completeExceptionally(KeeperException.create(rc)); - } else { - promise.complete(null); - } - }); - try { - promise.get(); - fail("should have failed"); - } catch (ExecutionException ee) { - assertEquals(ee.getCause().getClass(), KeeperException.NoNodeException.class); - } - } - - @Test(timeOut = 20000) - public void createOptimisticBaseExists() throws Exception { - MetaStoreImplZookeeper store = new MetaStoreImplZookeeper(zkc, executor); - zkc.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - CompletableFuture promise = new CompletableFuture<>(); - store.asyncCreateFullPathOptimistic( - "/foo", "bar/zar/gar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, - (rc, path, ctx, name) -> { - if (rc != KeeperException.Code.OK.intValue()) { - promise.completeExceptionally(KeeperException.create(rc)); - } else { - promise.complete(null); - } - }); - promise.get(); - - CompletableFuture promise2 = new CompletableFuture<>(); - store.asyncCreateFullPathOptimistic( - "/foo", "blah", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, - (rc, path, ctx, name) -> { - if (rc != KeeperException.Code.OK.intValue()) { - promise2.completeExceptionally(KeeperException.create(rc)); - } else { - promise2.complete(null); - } - }); - promise2.get(); - } } diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/NonDurableCursorTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/NonDurableCursorTest.java index 957e63bfa3e6a..26cad0a02260f 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/NonDurableCursorTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/NonDurableCursorTest.java @@ -221,31 +221,31 @@ void testNumberOfEntriesInBacklog() throws Exception { Position p4 = ledger.addEntry("dummy-entry-4".getBytes(Encoding)); ManagedCursor c5 = ledger.newNonDurableCursor(PositionImpl.latest); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); - assertEquals(c2.getNumberOfEntriesInBacklog(), 3); - assertEquals(c3.getNumberOfEntriesInBacklog(), 2); - assertEquals(c4.getNumberOfEntriesInBacklog(), 1); - assertEquals(c5.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 3); + assertEquals(c3.getNumberOfEntriesInBacklog(false), 2); + assertEquals(c4.getNumberOfEntriesInBacklog(false), 1); + assertEquals(c5.getNumberOfEntriesInBacklog(false), 0); List entries = c1.readEntries(2); assertEquals(entries.size(), 2); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); c1.markDelete(p1); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.delete(p3); assertEquals(c1.getNumberOfEntries(), 1); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); c1.markDelete(p4); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); } @Test(timeOut = 20000) @@ -371,34 +371,34 @@ void rewind() throws Exception { log.debug("p4: {}", p4); assertEquals(c1.getNumberOfEntries(), 4); - assertEquals(c1.getNumberOfEntriesInBacklog(), 4); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 4); c1.markDelete(p1); assertEquals(c1.getNumberOfEntries(), 3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); List entries = c1.readEntries(10); assertEquals(entries.size(), 3); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 3); - assertEquals(c1.getNumberOfEntriesInBacklog(), 3); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 3); c1.markDelete(p2); assertEquals(c1.getNumberOfEntries(), 2); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); entries = c1.readEntries(10); assertEquals(entries.size(), 2); entries.forEach(e -> e.release()); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 2); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 2); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 2); c1.markDelete(p4); assertEquals(c1.getNumberOfEntries(), 0); - assertEquals(c1.getNumberOfEntriesInBacklog(), 0); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 0); c1.rewind(); assertEquals(c1.getNumberOfEntries(), 0); ledger.addEntry("dummy-entry-5".getBytes(Encoding)); @@ -573,13 +573,13 @@ void subscribeToEarliestPositionWithDeferredDeletion() throws Exception { assertEquals(c1.getReadPosition(), p1); assertEquals(c1.getMarkDeletedPosition(), new PositionImpl(3, -1)); assertEquals(c1.getNumberOfEntries(), 6); - assertEquals(c1.getNumberOfEntriesInBacklog(), 6); + assertEquals(c1.getNumberOfEntriesInBacklog(false), 6); ManagedCursor c2 = ledger.newNonDurableCursor(p1); assertEquals(c2.getReadPosition(), p2); assertEquals(c2.getMarkDeletedPosition(), p1); assertEquals(c2.getNumberOfEntries(), 5); - assertEquals(c2.getNumberOfEntriesInBacklog(), 5); + assertEquals(c2.getNumberOfEntriesInBacklog(false), 5); } @Test diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixReadTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixReadTest.java index 17d54fa6e71f3..0a96a1fdc894e 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixReadTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixReadTest.java @@ -54,6 +54,7 @@ import org.apache.bookkeeper.mledger.ManagedLedgerConfig; import org.apache.bookkeeper.net.BookieSocketAddress; import org.apache.bookkeeper.test.MockedBookKeeperTestCase; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.testng.Assert; import org.testng.annotations.Test; @@ -145,6 +146,16 @@ public CompletableFuture deleteOffloaded(long ledgerId, UUID uuid, offloads.remove(uuid); return CompletableFuture.completedFuture(null); }; + + @Override + public OffloadPolicies getOffloadPolicies() { + return null; + } + + @Override + public void close() { + + } } static class MockOffloadReadHandle implements ReadHandle { diff --git a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixTest.java b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixTest.java index bf2f2e845d66b..997f3f66f3310 100644 --- a/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixTest.java +++ b/managed-ledger/src/test/java/org/apache/bookkeeper/mledger/impl/OffloadPrefixTest.java @@ -51,6 +51,7 @@ import org.apache.bookkeeper.test.MockedBookKeeperTestCase; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1025,6 +1026,16 @@ public CompletableFuture deleteOffloaded(long ledgerId, UUID uuid, } return promise; }; + + @Override + public OffloadPolicies getOffloadPolicies() { + return null; + } + + @Override + public void close() { + + } } static class ErroringMockLedgerOffloader extends MockLedgerOffloader { diff --git a/managed-ledger/src/test/java/org/apache/zookeeper/MockZooKeeper.java b/managed-ledger/src/test/java/org/apache/zookeeper/MockZooKeeper.java index f4160edf3737b..11c84f85a93f3 100644 --- a/managed-ledger/src/test/java/org/apache/zookeeper/MockZooKeeper.java +++ b/managed-ledger/src/test/java/org/apache/zookeeper/MockZooKeeper.java @@ -354,7 +354,7 @@ public void getChildren(final String path, final Watcher watcher, final Children } String child = item.substring(path.length() + 1); - if (!child.contains("/")) { + if (item.charAt(path.length()) == '/' && !child.contains("/")) { children.add(child); } } diff --git a/pom.xml b/pom.xml index 163be08196395..d6c3ea3e13e1c 100644 --- a/pom.xml +++ b/pom.xml @@ -149,7 +149,7 @@ flexible messaging model and an intuitive client API. 1.19 4.10.0 - 3.5.6 + 3.5.7 4.1.43.Final 2.0.26.Final 2.0.0 @@ -179,7 +179,7 @@ flexible messaging model and an intuitive client API. 0.8.3 2.2.0 3.6.0 - 4.1.11 + 4.4.8 2.3.0 5.1.1 1.11.297 @@ -215,6 +215,7 @@ flexible messaging model and an intuitive client API. 2.0.2 3.25.0-GA 2.3.1 + 1.5.0 0.6.1 @@ -473,12 +474,26 @@ flexible messaging model and an intuitive client API. io.netty netty-transport-native-epoll ${netty.version} + linux-x86_64 + + + + io.netty + netty-transport-native-epoll + ${netty.version} + + + + io.netty + netty-transport-native-unix-common + ${netty.version} io.netty netty-transport-native-unix-common ${netty.version} + linux-x86_64 diff --git a/pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/zookeeper/ZKMetadataStore.java b/pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/zookeeper/ZKMetadataStore.java index 58b1c107dce13..77b3ba2494643 100644 --- a/pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/zookeeper/ZKMetadataStore.java +++ b/pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/zookeeper/ZKMetadataStore.java @@ -18,13 +18,13 @@ */ package org.apache.pulsar.metadata.impl.zookeeper; -import com.google.common.annotations.VisibleForTesting; - import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.apache.bookkeeper.util.ZkUtils; import org.apache.bookkeeper.zookeeper.BoundExponentialBackoffRetryPolicy; @@ -43,26 +43,35 @@ import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; +import com.google.common.annotations.VisibleForTesting; + +import io.netty.util.concurrent.DefaultThreadFactory; + public class ZKMetadataStore implements MetadataStore { + private final boolean isZkManaged; private final ZooKeeper zkc; + private final ExecutorService executor; public ZKMetadataStore(String metadataURL, MetadataStoreConfig metadataStoreConfig) throws IOException { try { - zkc = ZooKeeperClient.newBuilder() - .connectString(metadataURL) + isZkManaged = true; + zkc = ZooKeeperClient.newBuilder().connectString(metadataURL) .connectRetryPolicy(new BoundExponentialBackoffRetryPolicy(100, 60_000, Integer.MAX_VALUE)) .allowReadOnlyMode(metadataStoreConfig.isAllowReadOnlyOperations()) - .sessionTimeoutMs(metadataStoreConfig.getSessionTimeoutMillis()) - .build(); + .sessionTimeoutMs(metadataStoreConfig.getSessionTimeoutMillis()).build(); } catch (KeeperException | InterruptedException e) { throw new IOException(e); } + + this.executor = Executors.newSingleThreadExecutor(new DefaultThreadFactory("zk-metadata-store-callback")); } @VisibleForTesting public ZKMetadataStore(ZooKeeper zkc) { + this.isZkManaged = false; this.zkc = zkc; + this.executor = Executors.newSingleThreadExecutor(new DefaultThreadFactory("zk-metadata-store-callback")); } @Override @@ -71,14 +80,16 @@ public CompletableFuture> get(String path) { try { zkc.getData(path, null, (rc, path1, ctx, data, stat) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - future.complete(Optional.of(new GetResult(data, getStat(stat)))); - } else if (code == Code.NONODE) { - future.complete(Optional.empty()); - } else { - future.completeExceptionally(getException(code, path)); - } + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + future.complete(Optional.of(new GetResult(data, getStat(stat)))); + } else if (code == Code.NONODE) { + future.complete(Optional.empty()); + } else { + future.completeExceptionally(getException(code, path)); + } + }); }, null); } catch (Throwable t) { future.completeExceptionally(new MetadataStoreException(t)); @@ -93,36 +104,37 @@ public CompletableFuture> getChildren(String path) { try { zkc.getChildren(path, null, (rc, path1, ctx, children) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - Collections.sort(children); - future.complete(children); - } else if (code == Code.NONODE) { - // The node we want may not exist yet, so put a watcher on its existence - // before throwing up the exception. Its possible that the node could have - // been created after the call to getChildren, but before the call to exists(). - // If this is the case, exists will return true, and we just call getChildren again. - exists(path).thenAccept(exists -> { - if (exists) { - getChildren(path) - .thenAccept(c -> future.complete(c)) - .exceptionally(ex -> { - future.completeExceptionally(ex); - return null; - }); - } else { - // Z-node does not exist - future.complete(Collections.emptyList()); - } - }).exceptionally(ex -> { - future.completeExceptionally(ex); - return null; - }); + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + Collections.sort(children); + future.complete(children); + } else if (code == Code.NONODE) { + // The node we want may not exist yet, so put a watcher on its existence + // before throwing up the exception. Its possible that the node could have + // been created after the call to getChildren, but before the call to exists(). + // If this is the case, exists will return true, and we just call getChildren + // again. + exists(path).thenAccept(exists -> { + if (exists) { + getChildren(path).thenAccept(c -> future.complete(c)).exceptionally(ex -> { + future.completeExceptionally(ex); + return null; + }); + } else { + // Z-node does not exist + future.complete(Collections.emptyList()); + } + }).exceptionally(ex -> { + future.completeExceptionally(ex); + return null; + }); - future.complete(Collections.emptyList()); - } else { - future.completeExceptionally(getException(code, path)); - } + future.complete(Collections.emptyList()); + } else { + future.completeExceptionally(getException(code, path)); + } + }); }, null); } catch (Throwable t) { future.completeExceptionally(new MetadataStoreException(t)); @@ -137,14 +149,16 @@ public CompletableFuture exists(String path) { try { zkc.exists(path, null, (StatCallback) (rc, path1, ctx, stat) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - future.complete(true); - } else if (code == Code.NONODE) { - future.complete(false); - } else { - future.completeExceptionally(getException(code, path)); - } + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + future.complete(true); + } else if (code == Code.NONODE) { + future.complete(false); + } else { + future.completeExceptionally(getException(code, path)); + } + }); }, future); } catch (Throwable t) { future.completeExceptionally(new MetadataStoreException(t)); @@ -163,39 +177,42 @@ public CompletableFuture put(String path, byte[] value, Optional opt try { if (hasVersion && expectedVersion == -1) { ZkUtils.asyncCreateFullPathOptimistic(zkc, path, value, ZooDefs.Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT, - (rc, path1, ctx, name) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - future.complete(new Stat(0, 0, 0)); - } else if (code == Code.NODEEXISTS) { - // We're emulating a request to create node, so the version is invalid - future.completeExceptionally(getException(Code.BADVERSION, path)); - } else { - future.completeExceptionally(getException(code, path)); - } + CreateMode.PERSISTENT, (rc, path1, ctx, name) -> { + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + future.complete(new Stat(0, 0, 0)); + } else if (code == Code.NODEEXISTS) { + // We're emulating a request to create node, so the version is invalid + future.completeExceptionally(getException(Code.BADVERSION, path)); + } else { + future.completeExceptionally(getException(code, path)); + } + }); }, null); } else { zkc.setData(path, value, expectedVersion, (rc, path1, ctx, stat) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - future.complete(getStat(stat)); - } else if (code == Code.NONODE) { - if (hasVersion) { - // We're emulating here a request to update or create the znode, depending on the version - future.completeExceptionally(getException(Code.BADVERSION, path)); + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + future.complete(getStat(stat)); + } else if (code == Code.NONODE) { + if (hasVersion) { + // We're emulating here a request to update or create the znode, depending on + // the version + future.completeExceptionally(getException(Code.BADVERSION, path)); + } else { + // The z-node does not exist, let's create it first + put(path, value, Optional.of(-1L)).thenAccept(s -> future.complete(s)) + .exceptionally(ex -> { + future.completeExceptionally(ex.getCause()); + return null; + }); + } } else { - // The z-node does not exist, let's create it first - put(path, value, Optional.of(-1L)) - .thenAccept(s -> future.complete(s)) - .exceptionally(ex -> { - future.completeExceptionally(ex.getCause()); - return null; - }); + future.completeExceptionally(getException(code, path)); } - } else { - future.completeExceptionally(getException(code, path)); - } + }); }, null); } } catch (Throwable t) { @@ -213,12 +230,14 @@ public CompletableFuture delete(String path, Optional optExpectedVer try { zkc.delete(path, expectedVersion, (rc, path1, ctx) -> { - Code code = Code.get(rc); - if (code == Code.OK) { - future.complete(null); - } else { - future.completeExceptionally(getException(code, path)); - } + executor.execute(() -> { + Code code = Code.get(rc); + if (code == Code.OK) { + future.complete(null); + } else { + future.completeExceptionally(getException(code, path)); + } + }); }, null); } catch (Throwable t) { future.completeExceptionally(new MetadataStoreException(t)); @@ -229,7 +248,10 @@ public CompletableFuture delete(String path, Optional optExpectedVer @Override public void close() throws Exception { - zkc.close(); + if (isZkManaged) { + zkc.close(); + } + executor.shutdownNow(); } private static Stat getStat(org.apache.zookeeper.data.Stat zkStat) { diff --git a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyParserTest.java b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyParserTest.java index 7ae6cc03a00c5..3557441d80612 100644 --- a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyParserTest.java +++ b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyParserTest.java @@ -141,7 +141,8 @@ public void testProducerConsumer() throws Exception { @Test public void testPartitions() throws Exception { - admin.tenants().createTenant("sample", new TenantInfo()); + TenantInfo tenantInfo = createDefaultTenantInfo(); + admin.tenants().createTenant("sample", tenantInfo); PulsarClient client = PulsarClient.builder().serviceUrl(proxyService.getServiceUrl()) .build(); admin.topics().createPartitionedTopic("persistent://sample/test/local/partitioned-topic", 2); diff --git a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTest.java b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTest.java index f9e4e11203cc3..7b308d198e401 100644 --- a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTest.java +++ b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTest.java @@ -45,6 +45,7 @@ import org.apache.pulsar.client.api.Producer; import org.apache.pulsar.client.api.PulsarClient; import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.client.api.SubscriptionInitialPosition; import org.apache.pulsar.client.api.SubscriptionType; import org.apache.pulsar.client.impl.ClientCnx; import org.apache.pulsar.client.impl.ConnectionPool; @@ -112,22 +113,27 @@ protected void cleanup() throws Exception { @Test public void testProducer() throws Exception { + @Cleanup PulsarClient client = PulsarClient.builder().serviceUrl(proxyService.getServiceUrl()) .build(); - Producer producer = client.newProducer(Schema.BYTES).topic("persistent://sample/test/local/producer-topic") - .create(); + + @Cleanup + Producer producer = client.newProducer() + .topic("persistent://sample/test/local/producer-topic") + .create(); for (int i = 0; i < 10; i++) { producer.send("test".getBytes()); } - - client.close(); } @Test public void testProducerConsumer() throws Exception { + @Cleanup PulsarClient client = PulsarClient.builder().serviceUrl(proxyService.getServiceUrl()) .build(); + + @Cleanup Producer producer = client.newProducer(Schema.BYTES) .topic("persistent://sample/test/local/producer-consumer-topic") .enableBatching(false) @@ -135,6 +141,7 @@ public void testProducerConsumer() throws Exception { .create(); // Create a consumer directly attached to broker + @Cleanup Consumer consumer = pulsarClient.newConsumer() .topic("persistent://sample/test/local/producer-consumer-topic").subscriptionName("my-sub").subscribe(); @@ -150,24 +157,25 @@ public void testProducerConsumer() throws Exception { Message msg = consumer.receive(0, TimeUnit.SECONDS); checkArgument(msg == null); - - consumer.close(); - client.close(); } @Test public void testPartitions() throws Exception { - admin.tenants().createTenant("sample", new TenantInfo()); + TenantInfo tenantInfo = createDefaultTenantInfo(); + admin.tenants().createTenant("sample", tenantInfo); + @Cleanup PulsarClient client = PulsarClient.builder().serviceUrl(proxyService.getServiceUrl()) .build(); admin.topics().createPartitionedTopic("persistent://sample/test/local/partitioned-topic", 2); + @Cleanup Producer producer = client.newProducer(Schema.BYTES) .topic("persistent://sample/test/local/partitioned-topic") .enableBatching(false) .messageRoutingMode(MessageRoutingMode.RoundRobinPartition).create(); // Create a consumer directly attached to broker + @Cleanup Consumer consumer = pulsarClient.newConsumer().topic("persistent://sample/test/local/partitioned-topic") .subscriptionName("my-sub").subscribe(); @@ -179,8 +187,6 @@ public void testPartitions() throws Exception { Message msg = consumer.receive(1, TimeUnit.SECONDS); checkNotNull(msg); } - - client.close(); } @Test @@ -191,12 +197,12 @@ public void testRegexSubscription() throws Exception { // create two topics by subscribing to a topic and closing it try (Consumer ignored = client.newConsumer() - .topic("persistent://sample/test/local/topic1") + .topic("persistent://sample/test/local/regex-sub-topic1") .subscriptionName("proxy-ignored") .subscribe()) { } try (Consumer ignored = client.newConsumer() - .topic("persistent://sample/test/local/topic2") + .topic("persistent://sample/test/local/regex-sub-topic2") .subscriptionName("proxy-ignored") .subscribe()) { } @@ -204,11 +210,12 @@ public void testRegexSubscription() throws Exception { String subName = "regex-sub-proxy-test-" + System.currentTimeMillis(); // make sure regex subscription - String regexSubscriptionPattern = "persistent://sample/test/local/topic.*"; + String regexSubscriptionPattern = "persistent://sample/test/local/regex-sub-topic.*"; log.info("Regex subscribe to topics {}", regexSubscriptionPattern); try (Consumer consumer = client.newConsumer() .topicsPattern(regexSubscriptionPattern) .subscriptionName(subName) + .subscriptionInitialPosition(SubscriptionInitialPosition.Earliest) .subscribe()) { log.info("Successfully subscribe to topics using regex {}", regexSubscriptionPattern); @@ -231,13 +238,14 @@ public void testRegexSubscription() throws Exception { @Test public void testGetSchema() throws Exception { + @Cleanup PulsarClient client = PulsarClient.builder().serviceUrl(proxyService.getServiceUrl()) .build(); - Producer producer; Schema schema = Schema.AVRO(Foo.class); try { - producer = client.newProducer(schema).topic("persistent://sample/test/local/get-schema") - .create(); + try (Producer ignored = client.newProducer(schema).topic("persistent://sample/test/local/get-schema") + .create()) { + } } catch (Exception ex) { Assert.fail("Should not have failed since can acquire LookupRequestSemaphore"); } @@ -246,10 +254,9 @@ public void testGetSchema() throws Exception { for (int i = 0; i<8; i++){ schemaVersion[i] = b; } - SchemaInfo schemaInfo = ((PulsarClientImpl)client).getLookup() + SchemaInfo schemaInfo = ((PulsarClientImpl) client).getLookup() .getSchema(TopicName.get("persistent://sample/test/local/get-schema"), schemaVersion).get().orElse(null); Assert.assertEquals(new String(schemaInfo.getSchema()), new String(schema.getSchemaInfo().getSchema())); - client.close(); } @Test @@ -259,9 +266,14 @@ public void testProtocolVersionAdvertisement() throws Exception { ClientConfigurationData conf = new ClientConfigurationData(); conf.setServiceUrl(proxyService.getServiceUrl()); + + @Cleanup PulsarClient client = getClientActiveConsumerChangeNotSupported(conf); + @Cleanup Producer producer = client.newProducer().topic(topic).create(); + + @Cleanup Consumer consumer = client.newConsumer().topic(topic).subscriptionName(sub) .subscriptionType(SubscriptionType.Failover).subscribe(); @@ -274,10 +286,6 @@ public void testProtocolVersionAdvertisement() throws Exception { checkNotNull(msg); consumer.acknowledge(msg); } - - producer.close(); - consumer.close(); - client.close(); } diff --git a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTlsTest.java b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTlsTest.java index 7fdd9fd95bf87..847e6a569ca6e 100644 --- a/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTlsTest.java +++ b/pulsar-proxy/src/test/java/org/apache/pulsar/proxy/server/ProxyTlsTest.java @@ -98,7 +98,8 @@ public void testPartitions() throws Exception { PulsarClient client = PulsarClient.builder() .serviceUrl(proxyService.getServiceUrlTls()) .allowTlsInsecureConnection(false).tlsTrustCertsFilePath(TLS_TRUST_CERT_FILE_PATH).build(); - admin.tenants().createTenant("sample", new TenantInfo()); + TenantInfo tenantInfo = createDefaultTenantInfo(); + admin.tenants().createTenant("sample", tenantInfo); admin.topics().createPartitionedTopic("persistent://sample/test/local/partitioned-topic", 2); Producer producer = client.newProducer(Schema.BYTES).topic("persistent://sample/test/local/partitioned-topic") diff --git a/pulsar-sql/presto-distribution/LICENSE b/pulsar-sql/presto-distribution/LICENSE index d130836de8d0f..32b2396743f0b 100644 --- a/pulsar-sql/presto-distribution/LICENSE +++ b/pulsar-sql/presto-distribution/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -246,8 +245,9 @@ The Apache Software License, Version 2.0 - netty-tcnative-boringssl-static-2.0.26.Final.jar - netty-transport-4.1.43.Final.jar - netty-transport-native-epoll-4.1.43.Final.jar - - netty-transport-native-epoll-4.1.33.Final-linux-x86_64.jar + - netty-transport-native-epoll-4.1.43.Final-linux-x86_64.jar - netty-transport-native-unix-common-4.1.43.Final.jar + - netty-transport-native-unix-common-4.1.43.Final-linux-x86_64.jar * Joda Time - joda-time-2.9.9.jar - joda-time-2.10.1.jar @@ -355,6 +355,8 @@ The Apache Software License, Version 2.0 * Avro - avro-1.9.1.jar - avro-protobuf-1.9.1.jar + * Caffeine + - caffeine-2.6.2.jar * Javax - javax.inject-1.jar - javax.inject-1.jar @@ -446,8 +448,8 @@ The Apache Software License, Version 2.0 - memory-0.8.3.jar - sketches-core-0.8.3.jar * Apache Zookeeper - - zookeeper-3.5.6.jar - - zookeeper-jute-3.5.6.jar + - zookeeper-3.5.7.jar + - zookeeper-jute-3.5.7.jar * Apache Yetus Audience Annotations - audience-annotations-0.5.0.jar * Swagger diff --git a/pulsar-sql/presto-pulsar/pom.xml b/pulsar-sql/presto-pulsar/pom.xml index 87dfa2838534c..4d184d58c37bd 100644 --- a/pulsar-sql/presto-pulsar/pom.xml +++ b/pulsar-sql/presto-pulsar/pom.xml @@ -124,6 +124,7 @@ org.apache.pulsar:pulsar-client-original org.apache.pulsar:pulsar-client-admin-original org.apache.pulsar:managed-ledger + org.apache.pulsar:pulsar-metadata org.glassfish.jersey*:* javax.ws.rs:* diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/AvroSchemaHandler.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/AvroSchemaHandler.java index f6807a2b19a14..3e3685ebd6175 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/AvroSchemaHandler.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/AvroSchemaHandler.java @@ -77,6 +77,11 @@ public Object deserialize(ByteBuf payload) { return null; } + @Override + public Object deserialize(ByteBuf keyPayload, ByteBuf dataPayload) { + return null; + } + @Override public Object extractField(int index, Object currentRecord) { try { diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/JSONSchemaHandler.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/JSONSchemaHandler.java index 8649e41fc3c7a..99863a36dae5c 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/JSONSchemaHandler.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/JSONSchemaHandler.java @@ -73,6 +73,11 @@ public Object deserialize(ByteBuf payload) { } } + @Override + public Object deserialize(ByteBuf keyPayload, ByteBuf dataPayload) { + return null; + } + @Override public Object extractField(int index, Object currentRecord) { try { diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/KeyValueSchemaHandler.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/KeyValueSchemaHandler.java new file mode 100644 index 0000000000000..2f056d3abb8a3 --- /dev/null +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/KeyValueSchemaHandler.java @@ -0,0 +1,91 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pulsar.sql.presto; + +import io.airlift.log.Logger; +import io.netty.buffer.ByteBuf; +import java.util.List; +import java.util.Objects; +import org.apache.pulsar.client.impl.schema.KeyValueSchemaInfo; +import org.apache.pulsar.common.schema.KeyValue; +import org.apache.pulsar.common.schema.KeyValueEncodingType; +import org.apache.pulsar.common.schema.SchemaInfo; + + +/** + * Schema handler for payload in the KeyValue format. + */ +public class KeyValueSchemaHandler implements SchemaHandler { + + private static final Logger log = Logger.get(KeyValueSchemaHandler.class); + + private final List columnHandles; + + private final SchemaHandler keySchemaHandler; + + private final SchemaHandler valueSchemaHandler; + + private KeyValueEncodingType keyValueEncodingType; + + public KeyValueSchemaHandler(SchemaInfo schemaInfo, List columnHandles) { + this.columnHandles = columnHandles; + KeyValue kvSchemaInfo = KeyValueSchemaInfo.decodeKeyValueSchemaInfo(schemaInfo); + keySchemaHandler = PulsarSchemaHandlers.newPulsarSchemaHandler(kvSchemaInfo.getKey(), columnHandles); + valueSchemaHandler = PulsarSchemaHandlers.newPulsarSchemaHandler(kvSchemaInfo.getValue(), columnHandles); + keyValueEncodingType = KeyValueSchemaInfo.decodeKeyValueEncodingType(schemaInfo); + } + + @Override + public Object deserialize(ByteBuf payload) { + return null; + } + + @Override + public Object deserialize(ByteBuf keyPayload, ByteBuf dataPayload) { + ByteBuf keyByteBuf; + ByteBuf valueByteBuf; + if (Objects.equals(keyValueEncodingType, KeyValueEncodingType.INLINE)) { + dataPayload.resetReaderIndex(); + int keyLength = dataPayload.readInt(); + keyByteBuf = dataPayload.readSlice(keyLength); + + int valueLength = dataPayload.readInt(); + valueByteBuf = dataPayload.readSlice(valueLength); + } else { + keyByteBuf = keyPayload; + valueByteBuf = dataPayload; + } + Object keyObj = keySchemaHandler.deserialize(keyByteBuf); + Object valueObj = valueSchemaHandler.deserialize(valueByteBuf); + return new KeyValue<>(keyObj, valueObj); + } + + @Override + public Object extractField(int index, Object currentRecord) { + PulsarColumnHandle pulsarColumnHandle = this.columnHandles.get(index); + KeyValue keyValue = (KeyValue) currentRecord; + if (pulsarColumnHandle.isKey()) { + return keySchemaHandler.extractField(index, keyValue.getKey()); + } else if (pulsarColumnHandle.isValue()) { + return valueSchemaHandler.extractField(index, keyValue.getValue()); + } + return null; + } + +} diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnHandle.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnHandle.java index 2a1bd43e07e97..2a07f52e548bd 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnHandle.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnHandle.java @@ -24,8 +24,10 @@ import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.type.Type; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Arrays; +import java.util.Objects; /** * This class represents the basic information about a presto column. @@ -58,6 +60,26 @@ public class PulsarColumnHandle implements ColumnHandle { private final Integer[] positionIndices; + private HandleKeyValueType handleKeyValueType; + + /** + * Column Handle keyValue type, used for keyValue schema. + */ + public enum HandleKeyValueType { + /** + * The handle not for keyValue schema. + */ + NONE, + /** + * The key schema handle for keyValue schema. + */ + KEY, + /** + * The value schema handle for keyValue schema. + */ + VALUE + } + @JsonCreator public PulsarColumnHandle( @JsonProperty("connectorId") String connectorId, @@ -66,7 +88,8 @@ public PulsarColumnHandle( @JsonProperty("hidden") boolean hidden, @JsonProperty("internal") boolean internal, @JsonProperty("fieldNames") String[] fieldNames, - @JsonProperty("positionIndices") Integer[] positionIndices) { + @JsonProperty("positionIndices") Integer[] positionIndices, + @JsonProperty("handleKeyValueType") HandleKeyValueType handleKeyValueType) { this.connectorId = requireNonNull(connectorId, "connectorId is null"); this.name = requireNonNull(name, "name is null"); this.type = requireNonNull(type, "type is null"); @@ -74,6 +97,11 @@ public PulsarColumnHandle( this.internal = internal; this.fieldNames = fieldNames; this.positionIndices = positionIndices; + if (handleKeyValueType == null) { + this.handleKeyValueType = HandleKeyValueType.NONE; + } else { + this.handleKeyValueType = handleKeyValueType; + } } @JsonProperty @@ -111,6 +139,20 @@ public Integer[] getPositionIndices() { return positionIndices; } + @JsonProperty + public HandleKeyValueType getHandleKeyValueType() { + return handleKeyValueType; + } + + @JsonIgnore + public boolean isKey() { + return Objects.equals(handleKeyValueType, HandleKeyValueType.KEY); + } + + @JsonIgnore + public boolean isValue() { + return Objects.equals(handleKeyValueType, HandleKeyValueType.VALUE); + } ColumnMetadata getColumnMetadata() { return new ColumnMetadata(name, type, null, hidden); @@ -145,7 +187,10 @@ public boolean equals(Object o) { if (!Arrays.deepEquals(fieldNames, that.fieldNames)) { return false; } - return Arrays.deepEquals(positionIndices, that.positionIndices); + if (!Arrays.deepEquals(positionIndices, that.positionIndices)) { + return false; + } + return Objects.equals(handleKeyValueType, that.handleKeyValueType); } @Override @@ -157,6 +202,7 @@ public int hashCode() { result = 31 * result + (internal ? 1 : 0); result = 31 * result + Arrays.hashCode(fieldNames); result = 31 * result + Arrays.hashCode(positionIndices); + result = 31 * result + (handleKeyValueType != null ? handleKeyValueType.hashCode() : 0); return result; } @@ -170,6 +216,7 @@ public String toString() { + ", internal=" + internal + ", fieldNames=" + Arrays.toString(fieldNames) + ", positionIndices=" + Arrays.toString(positionIndices) + + ", handleKeyValueType=" + handleKeyValueType + '}'; } } diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnMetadata.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnMetadata.java index 5b033fac86fec..789ea7f580ac4 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnMetadata.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarColumnMetadata.java @@ -21,6 +21,7 @@ import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.type.Type; import java.util.Arrays; +import java.util.Objects; /** * Description of the column metadata. @@ -32,15 +33,19 @@ public class PulsarColumnMetadata extends ColumnMetadata { private String nameWithCase; private String[] fieldNames; private Integer[] positionIndices; + private PulsarColumnHandle.HandleKeyValueType handleKeyValueType; + public final static String KEY_SCHEMA_COLUMN_PREFIX = "__key."; public PulsarColumnMetadata(String name, Type type, String comment, String extraInfo, boolean hidden, boolean isInternal, - String[] fieldNames, Integer[] positionIndices) { + String[] fieldNames, Integer[] positionIndices, + PulsarColumnHandle.HandleKeyValueType handleKeyValueType) { super(name, type, comment, extraInfo, hidden); this.nameWithCase = name; this.isInternal = isInternal; this.fieldNames = fieldNames; this.positionIndices = positionIndices; + this.handleKeyValueType = handleKeyValueType; } public String getNameWithCase() { @@ -59,6 +64,25 @@ public Integer[] getPositionIndices() { return positionIndices; } + public PulsarColumnHandle.HandleKeyValueType getHandleKeyValueType() { + return handleKeyValueType; + } + + public boolean isKey() { + return Objects.equals(handleKeyValueType, PulsarColumnHandle.HandleKeyValueType.KEY); + } + + public boolean isValue() { + return Objects.equals(handleKeyValueType, PulsarColumnHandle.HandleKeyValueType.VALUE); + } + + public static String getColumnName(PulsarColumnHandle.HandleKeyValueType handleKeyValueType, String name) { + if (Objects.equals(PulsarColumnHandle.HandleKeyValueType.KEY, handleKeyValueType)) { + return KEY_SCHEMA_COLUMN_PREFIX + name; + } + return name; + } + @Override public String toString() { return "PulsarColumnMetadata{" @@ -66,6 +90,7 @@ public String toString() { + ", nameWithCase='" + nameWithCase + '\'' + ", fieldNames=" + Arrays.toString(fieldNames) + ", positionIndices=" + Arrays.toString(positionIndices) + + ", handleKeyValueType=" + handleKeyValueType + '}'; } @@ -92,7 +117,10 @@ public boolean equals(Object o) { if (!Arrays.deepEquals(fieldNames, that.fieldNames)) { return false; } - return Arrays.deepEquals(positionIndices, that.positionIndices); + if (!Arrays.deepEquals(positionIndices, that.positionIndices)) { + return false; + } + return Objects.equals(handleKeyValueType, that.handleKeyValueType); } @Override @@ -102,6 +130,7 @@ public int hashCode() { result = 31 * result + (nameWithCase != null ? nameWithCase.hashCode() : 0); result = 31 * result + Arrays.hashCode(fieldNames); result = 31 * result + Arrays.hashCode(positionIndices); + result = 31 * result + (handleKeyValueType != null ? handleKeyValueType.hashCode() : 0); return result; } } diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarConnectorCache.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarConnectorCache.java index 1af86a2225a06..a29aabd155879 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarConnectorCache.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarConnectorCache.java @@ -25,6 +25,9 @@ import io.airlift.log.Logger; import java.io.IOException; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.conf.ClientConfiguration; import org.apache.bookkeeper.mledger.LedgerOffloader; @@ -37,8 +40,11 @@ import org.apache.bookkeeper.mledger.offload.OffloaderUtils; import org.apache.bookkeeper.mledger.offload.Offloaders; import org.apache.bookkeeper.stats.StatsProvider; +import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.pulsar.PulsarVersion; +import org.apache.pulsar.common.naming.NamespaceName; +import org.apache.pulsar.common.policies.data.OffloadPolicies; /** * Implementation of a cache for the Pulsar connector. @@ -55,7 +61,8 @@ public class PulsarConnectorCache { private final StatsProvider statsProvider; private OrderedScheduler offloaderScheduler; private Offloaders offloaderManager; - private LedgerOffloader offloader; + private LedgerOffloader defaultOffloader; + private Map offloaderMap = new ConcurrentHashMap<>(); private static final String OFFLOADERS_DIRECTOR = "offloadersDirectory"; private static final String MANAGED_LEDGER_OFFLOAD_DRIVER = "managedLedgerOffloadDriver"; @@ -74,7 +81,9 @@ private PulsarConnectorCache(PulsarConnectorConfig pulsarConnectorConfig) throws this.statsProvider.start(clientConfiguration); - this.offloader = initManagedLedgerOffloader(pulsarConnectorConfig); + OffloadPolicies offloadPolicies = new OffloadPolicies(); + BeanUtils.copyProperties(offloadPolicies, pulsarConnectorConfig); + this.defaultOffloader = initManagedLedgerOffloader(offloadPolicies); } public static PulsarConnectorCache getConnectorCache(PulsarConnectorConfig pulsarConnectorConfig) throws Exception { @@ -108,46 +117,55 @@ private static ManagedLedgerFactory initManagedLedgerFactory(PulsarConnectorConf return new ManagedLedgerFactoryImpl(bkClientConfiguration, managedLedgerFactoryConfig); } - public ManagedLedgerConfig getManagedLedgerConfig() { - - return new ManagedLedgerConfig() - .setLedgerOffloader(this.offloader); + public ManagedLedgerConfig getManagedLedgerConfig(NamespaceName namespaceName, OffloadPolicies offloadPolicies) { + ManagedLedgerConfig managedLedgerConfig = new ManagedLedgerConfig(); + if (offloadPolicies == null) { + managedLedgerConfig.setLedgerOffloader(this.defaultOffloader); + } else { + LedgerOffloader ledgerOffloader = offloaderMap.compute(namespaceName, + (ns, offloader) -> { + if (offloader != null && Objects.equals(offloader.getOffloadPolicies(), offloadPolicies)) { + return offloader; + } else { + if (offloader != null) { + offloader.close(); + } + return initManagedLedgerOffloader(offloadPolicies); + } + }); + managedLedgerConfig.setLedgerOffloader(ledgerOffloader); + } + return managedLedgerConfig; } - private synchronized OrderedScheduler getOffloaderScheduler(PulsarConnectorConfig pulsarConnectorConfig) { + private synchronized OrderedScheduler getOffloaderScheduler(OffloadPolicies offloadPolicies) { if (this.offloaderScheduler == null) { this.offloaderScheduler = OrderedScheduler.newSchedulerBuilder() - .numThreads(pulsarConnectorConfig.getManagedLedgerOffloadMaxThreads()) + .numThreads(offloadPolicies.getManagedLedgerOffloadMaxThreads()) .name("pulsar-offloader").build(); } return this.offloaderScheduler; } - private LedgerOffloader initManagedLedgerOffloader(PulsarConnectorConfig conf) { + private LedgerOffloader initManagedLedgerOffloader(OffloadPolicies offloadPolicies) { try { - if (StringUtils.isNotBlank(conf.getManagedLedgerOffloadDriver())) { - checkNotNull(conf.getOffloadersDirectory(), + if (StringUtils.isNotBlank(offloadPolicies.getManagedLedgerOffloadDriver())) { + checkNotNull(offloadPolicies.getOffloadersDirectory(), "Offloader driver is configured to be '%s' but no offloaders directory is configured.", - conf.getManagedLedgerOffloadDriver()); - this.offloaderManager = OffloaderUtils.searchForOffloaders(conf.getOffloadersDirectory()); + offloadPolicies.getManagedLedgerOffloadDriver()); + this.offloaderManager = OffloaderUtils.searchForOffloaders(offloadPolicies.getOffloadersDirectory()); LedgerOffloaderFactory offloaderFactory = this.offloaderManager.getOffloaderFactory( - conf.getManagedLedgerOffloadDriver()); - - Map offloaderProperties = conf.getOffloaderProperties(); - offloaderProperties.put(OFFLOADERS_DIRECTOR, conf.getOffloadersDirectory()); - offloaderProperties.put(MANAGED_LEDGER_OFFLOAD_DRIVER, conf.getManagedLedgerOffloadDriver()); - offloaderProperties - .put(MANAGED_LEDGER_OFFLOAD_MAX_THREADS, String.valueOf(conf.getManagedLedgerOffloadMaxThreads())); + offloadPolicies.getManagedLedgerOffloadDriver()); try { return offloaderFactory.create( - PulsarConnectorUtils.getProperties(offloaderProperties), + offloadPolicies, ImmutableMap.of( LedgerOffloader.METADATA_SOFTWARE_VERSION_KEY.toLowerCase(), PulsarVersion.getVersion(), LedgerOffloader.METADATA_SOFTWARE_GITSHA_KEY.toLowerCase(), PulsarVersion.getGitSha() ), - getOffloaderScheduler(conf)); + getOffloaderScheduler(offloadPolicies)); } catch (IOException ioe) { log.error("Failed to create offloader: ", ioe); throw new RuntimeException(ioe.getMessage(), ioe.getCause()); diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarInternalColumn.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarInternalColumn.java index 71107f86b3d64..4f2587bf0210f 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarInternalColumn.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarInternalColumn.java @@ -218,11 +218,12 @@ PulsarColumnHandle getColumnHandle(String connectorId, boolean hidden) { getName(), getType(), hidden, - true, null, null); + true, null, null, PulsarColumnHandle.HandleKeyValueType.NONE); } PulsarColumnMetadata getColumnMetadata(boolean hidden) { - return new PulsarColumnMetadata(name, type, comment, null, hidden, true, null, null); + return new PulsarColumnMetadata(name, type, comment, null, hidden, true, null, null, + PulsarColumnHandle.HandleKeyValueType.NONE); } public static Set getInternalFields() { diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarMetadata.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarMetadata.java index dc9a479aa3503..420232ba0e439 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarMetadata.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarMetadata.java @@ -80,7 +80,9 @@ import org.apache.pulsar.client.admin.PulsarAdmin; import org.apache.pulsar.client.admin.PulsarAdminException; import org.apache.pulsar.client.api.PulsarClientException; +import org.apache.pulsar.client.impl.schema.KeyValueSchemaInfo; import org.apache.pulsar.common.naming.TopicName; +import org.apache.pulsar.common.schema.KeyValue; import org.apache.pulsar.common.schema.SchemaInfo; import org.apache.pulsar.common.schema.SchemaType; @@ -222,7 +224,8 @@ public Map getColumnHandles(ConnectorSession session, Conn pulsarColumnMetadata.isHidden(), pulsarColumnMetadata.isInternal(), pulsarColumnMetadata.getFieldNames(), - pulsarColumnMetadata.getPositionIndices()); + pulsarColumnMetadata.getPositionIndices(), + pulsarColumnMetadata.getHandleKeyValueType()); columnHandles.put( columnMetadata.getName(), @@ -324,7 +327,7 @@ private ConnectorTableMetadata getTableMetadata(SchemaTableName schemaTableName, } } List handles = getPulsarColumns( - topicName, schemaInfo, withInternalColumns + topicName, schemaInfo, withInternalColumns, PulsarColumnHandle.HandleKeyValueType.NONE ); @@ -336,12 +339,15 @@ private ConnectorTableMetadata getTableMetadata(SchemaTableName schemaTableName, */ static List getPulsarColumns(TopicName topicName, SchemaInfo schemaInfo, - boolean withInternalColumns) { + boolean withInternalColumns, + PulsarColumnHandle.HandleKeyValueType handleKeyValueType) { SchemaType schemaType = schemaInfo.getType(); if (schemaType.isStruct()) { - return getPulsarColumnsFromStructSchema(topicName, schemaInfo, withInternalColumns); + return getPulsarColumnsFromStructSchema(topicName, schemaInfo, withInternalColumns, handleKeyValueType); } else if (schemaType.isPrimitive()) { - return getPulsarColumnsFromPrimitiveSchema(topicName, schemaInfo, withInternalColumns); + return getPulsarColumnsFromPrimitiveSchema(topicName, schemaInfo, withInternalColumns, handleKeyValueType); + } else if (schemaType.equals(SchemaType.KEY_VALUE)) { + return getPulsarColumnsFromKeyValueSchema(topicName, schemaInfo, withInternalColumns); } else { throw new IllegalArgumentException("Unsupported schema : " + schemaInfo); } @@ -349,15 +355,16 @@ static List getPulsarColumns(TopicName topicName, static List getPulsarColumnsFromPrimitiveSchema(TopicName topicName, SchemaInfo schemaInfo, - boolean withInternalColumns) { + boolean withInternalColumns, + PulsarColumnHandle.HandleKeyValueType handleKeyValueType) { ImmutableList.Builder builder = ImmutableList.builder(); ColumnMetadata valueColumn = new PulsarColumnMetadata( - "__value__", + PulsarColumnMetadata.getColumnName(handleKeyValueType, "__value__"), convertPulsarType(schemaInfo.getType()), "The value of the message with primitive type schema", null, false, false, new String[0], - new Integer[0]); + new Integer[0], handleKeyValueType); builder.add(valueColumn); @@ -372,8 +379,8 @@ static List getPulsarColumnsFromPrimitiveSchema(TopicName topicN static List getPulsarColumnsFromStructSchema(TopicName topicName, SchemaInfo schemaInfo, - boolean withInternalColumns) { - + boolean withInternalColumns, + PulsarColumnHandle.HandleKeyValueType handleKeyValueType) { String schemaJson = new String(schemaInfo.getSchema()); if (StringUtils.isBlank(schemaJson)) { throw new PrestoException(NOT_SUPPORTED, "Topic " + topicName.toString() @@ -389,7 +396,7 @@ static List getPulsarColumnsFromStructSchema(TopicName topicName ImmutableList.Builder builder = ImmutableList.builder(); - builder.addAll(getColumns(null, schema, new HashSet<>(), new Stack<>(), new Stack<>())); + builder.addAll(getColumns(null, schema, new HashSet<>(), new Stack<>(), new Stack<>(), handleKeyValueType)); if (withInternalColumns) { PulsarInternalColumn.getInternalFields() @@ -398,6 +405,29 @@ static List getPulsarColumnsFromStructSchema(TopicName topicName } return builder.build(); } + + static List getPulsarColumnsFromKeyValueSchema(TopicName topicName, + SchemaInfo schemaInfo, + boolean withInternalColumns) { + ImmutableList.Builder builder = ImmutableList.builder(); + KeyValue kvSchemaInfo = KeyValueSchemaInfo.decodeKeyValueSchemaInfo(schemaInfo); + SchemaInfo keySchemaInfo = kvSchemaInfo.getKey(); + List keyColumnMetadataList = getPulsarColumns(topicName, keySchemaInfo, false, + PulsarColumnHandle.HandleKeyValueType.KEY); + builder.addAll(keyColumnMetadataList); + + SchemaInfo valueSchemaInfo = kvSchemaInfo.getValue(); + List valueColumnMetadataList = getPulsarColumns(topicName, valueSchemaInfo, false, + PulsarColumnHandle.HandleKeyValueType.VALUE); + builder.addAll(valueColumnMetadataList); + + if (withInternalColumns) { + PulsarInternalColumn.getInternalFields() + .forEach(pulsarInternalColumn -> builder.add(pulsarInternalColumn.getColumnMetadata(false))); + } + return builder.build(); + } + @VisibleForTesting static Type convertPulsarType(SchemaType pulsarType) { switch (pulsarType) { @@ -435,18 +465,20 @@ static Type convertPulsarType(SchemaType pulsarType) { @VisibleForTesting static List getColumns(String fieldName, Schema fieldSchema, - Set fieldTypes, - Stack fieldNames, - Stack positionIndices) { + Set fieldTypes, + Stack fieldNames, + Stack positionIndices, + PulsarColumnHandle.HandleKeyValueType handleKeyValueType) { List columnMetadataList = new LinkedList<>(); if (isPrimitiveType(fieldSchema.getType())) { - columnMetadataList.add(new PulsarColumnMetadata(fieldName, + columnMetadataList.add(new PulsarColumnMetadata( + PulsarColumnMetadata.getColumnName(handleKeyValueType, fieldName), convertType(fieldSchema.getType(), fieldSchema.getLogicalType()), null, null, false, false, fieldNames.toArray(new String[fieldNames.size()]), - positionIndices.toArray(new Integer[positionIndices.size()]))); + positionIndices.toArray(new Integer[positionIndices.size()]), handleKeyValueType)); } else if (fieldSchema.getType() == Schema.Type.UNION) { boolean canBeNull = false; for (Schema type : fieldSchema.getTypes()) { @@ -454,17 +486,19 @@ static List getColumns(String fieldName, Schema fieldSchem PulsarColumnMetadata columnMetadata; if (type.getType() != Schema.Type.NULL) { if (!canBeNull) { - columnMetadata = new PulsarColumnMetadata(fieldName, + columnMetadata = new PulsarColumnMetadata( + PulsarColumnMetadata.getColumnName(handleKeyValueType, fieldName), convertType(type.getType(), type.getLogicalType()), null, null, false, false, fieldNames.toArray(new String[fieldNames.size()]), - positionIndices.toArray(new Integer[positionIndices.size()])); + positionIndices.toArray(new Integer[positionIndices.size()]), handleKeyValueType); } else { - columnMetadata = new PulsarColumnMetadata(fieldName, + columnMetadata = new PulsarColumnMetadata( + PulsarColumnMetadata.getColumnName(handleKeyValueType, fieldName), convertType(type.getType(), type.getLogicalType()), "field can be null", null, false, false, fieldNames.toArray(new String[fieldNames.size()]), - positionIndices.toArray(new Integer[positionIndices.size()])); + positionIndices.toArray(new Integer[positionIndices.size()]), handleKeyValueType); } columnMetadataList.add(columnMetadata); } else { @@ -472,7 +506,7 @@ static List getColumns(String fieldName, Schema fieldSchem } } else { List columns = getColumns(fieldName, type, fieldTypes, fieldNames, - positionIndices); + positionIndices, handleKeyValueType); columnMetadataList.addAll(columns); } } @@ -488,10 +522,11 @@ static List getColumns(String fieldName, Schema fieldSchem positionIndices.push(i); List columns; if (fieldName == null) { - columns = getColumns(field.name(), field.schema(), fieldTypes, fieldNames, positionIndices); + columns = getColumns(field.name(), field.schema(), fieldTypes, fieldNames, positionIndices, + handleKeyValueType); } else { columns = getColumns(String.format("%s.%s", fieldName, field.name()), field.schema(), - fieldTypes, fieldNames, positionIndices); + fieldTypes, fieldNames, positionIndices, handleKeyValueType); } positionIndices.pop(); @@ -507,11 +542,12 @@ static List getColumns(String fieldName, Schema fieldSchem } else if (fieldSchema.getType() == Schema.Type.MAP) { } else if (fieldSchema.getType() == Schema.Type.ENUM) { - PulsarColumnMetadata columnMetadata = new PulsarColumnMetadata(fieldName, + PulsarColumnMetadata columnMetadata = new PulsarColumnMetadata( + PulsarColumnMetadata.getColumnName(handleKeyValueType, fieldName), convertType(fieldSchema.getType(), fieldSchema.getLogicalType()), null, null, false, false, fieldNames.toArray(new String[fieldNames.size()]), - positionIndices.toArray(new Integer[positionIndices.size()])); + positionIndices.toArray(new Integer[positionIndices.size()]), handleKeyValueType); columnMetadataList.add(columnMetadata); } else if (fieldSchema.getType() == Schema.Type.FIXED) { diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarPrimitiveSchemaHandler.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarPrimitiveSchemaHandler.java index 28980a9f13be4..f26587bfcbf80 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarPrimitiveSchemaHandler.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarPrimitiveSchemaHandler.java @@ -56,6 +56,11 @@ public Object deserialize(ByteBuf byteBuf) { } } + @Override + public Object deserialize(ByteBuf keyPayload, ByteBuf dataPayload) { + return null; + } + @Override public Object extractField(int index, Object currentRecord) { return currentRecord; diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarRecordCursor.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarRecordCursor.java index 4d5a25db836d6..6899f97bbd1c2 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarRecordCursor.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarRecordCursor.java @@ -40,6 +40,7 @@ import io.airlift.log.Logger; import io.airlift.slice.Slice; import io.airlift.slice.Slices; +import io.netty.buffer.ByteBuf; import java.io.IOException; import java.util.List; import java.util.Map; @@ -57,6 +58,7 @@ import org.apache.pulsar.common.api.raw.RawMessage; import org.apache.pulsar.common.naming.NamespaceName; import org.apache.pulsar.common.naming.TopicName; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.apache.pulsar.sql.presto.PulsarInternalColumn.PartitionColumn; import org.jctools.queues.MessagePassingQueue; import org.jctools.queues.SpscArrayQueue; @@ -110,8 +112,18 @@ public PulsarRecordCursor(List columnHandles, PulsarSplit pu close(); throw new RuntimeException(e); } + + OffloadPolicies offloadPolicies = pulsarSplit.getOffloadPolicies(); + if (offloadPolicies != null) { + offloadPolicies.setOffloadersDirectory(pulsarConnectorConfig.getOffloadersDirectory()); + offloadPolicies.setManagedLedgerOffloadMaxThreads( + pulsarConnectorConfig.getManagedLedgerOffloadMaxThreads()); + } initialize(columnHandles, pulsarSplit, pulsarConnectorConfig, - pulsarConnectorCache.getManagedLedgerFactory(), pulsarConnectorCache.getManagedLedgerConfig(), + pulsarConnectorCache.getManagedLedgerFactory(), + pulsarConnectorCache.getManagedLedgerConfig( + TopicName.get("persistent", NamespaceName.get(pulsarSplit.getSchemaName()), + pulsarSplit.getTableName()).getNamespaceObject(), offloadPolicies), new PulsarConnectorMetricsTracker(pulsarConnectorCache.getStatsProvider())); } @@ -407,7 +419,15 @@ public boolean advanceNextPosition() { //start time for deseralizing record metricsTracker.start_RECORD_DESERIALIZE_TIME(); - currentRecord = this.schemaHandler.deserialize(this.currentMessage.getData()); + if (this.schemaHandler instanceof KeyValueSchemaHandler) { + ByteBuf keyByteBuf = null; + if (this.currentMessage.getKeyBytes().isPresent()) { + keyByteBuf = this.currentMessage.getKeyBytes().get(); + } + currentRecord = this.schemaHandler.deserialize(keyByteBuf, this.currentMessage.getData()); + } else { + currentRecord = this.schemaHandler.deserialize(this.currentMessage.getData()); + } metricsTracker.incr_NUM_RECORD_DESERIALIZED(); // stats for time spend deserializing diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSchemaHandlers.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSchemaHandlers.java index c0282df14007e..3b091ad74c77a 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSchemaHandlers.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSchemaHandlers.java @@ -23,9 +23,9 @@ import com.facebook.presto.spi.PrestoException; import java.util.List; - import org.apache.pulsar.client.api.Schema; import org.apache.pulsar.common.schema.SchemaInfo; +import org.apache.pulsar.common.schema.SchemaType; class PulsarSchemaHandlers { @@ -45,6 +45,8 @@ static SchemaHandler newPulsarSchemaHandler(SchemaInfo schemaInfo, throw new PrestoException(NOT_SUPPORTED, "Not supported schema type: " + schemaInfo.getType()); } + } else if (schemaInfo.getType().equals(SchemaType.KEY_VALUE)) { + return new KeyValueSchemaHandler(schemaInfo, columnHandles); } else { throw new PrestoException( NOT_SUPPORTED, diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplit.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplit.java index eeebbd174dd3f..9893d8ffab0ea 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplit.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplit.java @@ -26,10 +26,14 @@ import com.facebook.presto.spi.predicate.TupleDomain; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; +import io.airlift.log.Logger; +import java.io.IOException; import java.util.List; import java.util.Map; import org.apache.bookkeeper.mledger.impl.PositionImpl; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.apache.pulsar.common.schema.SchemaInfo; import org.apache.pulsar.common.schema.SchemaType; @@ -38,9 +42,12 @@ */ public class PulsarSplit implements ConnectorSplit { + private static final Logger log = Logger.get(PulsarSplit.class); + private final long splitId; private final String connectorId; private final String schemaName; + private final String originSchemaName; private final String tableName; private final long splitSize; private final String schema; @@ -54,12 +61,16 @@ public class PulsarSplit implements ConnectorSplit { private final PositionImpl startPosition; private final PositionImpl endPosition; + private final String schemaInfoProperties; + + private final OffloadPolicies offloadPolicies; @JsonCreator public PulsarSplit( @JsonProperty("splitId") long splitId, @JsonProperty("connectorId") String connectorId, @JsonProperty("schemaName") String schemaName, + @JsonProperty("originSchemaName") String originSchemaName, @JsonProperty("tableName") String tableName, @JsonProperty("splitSize") long splitSize, @JsonProperty("schema") String schema, @@ -69,15 +80,11 @@ public PulsarSplit( @JsonProperty("startPositionLedgerId") long startPositionLedgerId, @JsonProperty("endPositionLedgerId") long endPositionLedgerId, @JsonProperty("tupleDomain") TupleDomain tupleDomain, - @JsonProperty("properties") Map schemaInfoProperties) { + @JsonProperty("schemaInfoProperties") String schemaInfoProperties, + @JsonProperty("offloadPolicies") OffloadPolicies offloadPolicies) throws IOException { this.splitId = splitId; requireNonNull(schemaName, "schema name is null"); - this.schemaInfo = SchemaInfo.builder() - .type(schemaType) - .name(schemaName) - .schema(schema.getBytes()) - .properties(schemaInfoProperties) - .build(); + this.originSchemaName = originSchemaName; this.schemaName = requireNonNull(schemaName, "schema name is null"); this.connectorId = requireNonNull(connectorId, "connector id is null"); this.tableName = requireNonNull(tableName, "table name is null"); @@ -91,6 +98,16 @@ public PulsarSplit( this.tupleDomain = requireNonNull(tupleDomain, "tupleDomain is null"); this.startPosition = PositionImpl.get(startPositionLedgerId, startPositionEntryId); this.endPosition = PositionImpl.get(endPositionLedgerId, endPositionEntryId); + this.schemaInfoProperties = schemaInfoProperties; + this.offloadPolicies = offloadPolicies; + + ObjectMapper objectMapper = new ObjectMapper(); + this.schemaInfo = SchemaInfo.builder() + .name(originSchemaName) + .type(schemaType) + .schema(schema.getBytes("ISO8859-1")) + .properties(objectMapper.readValue(schemaInfoProperties, Map.class)) + .build(); } @JsonProperty @@ -123,6 +140,11 @@ public long getSplitSize() { return splitSize; } + @JsonProperty + public String getOriginSchemaName() { + return originSchemaName; + } + @JsonProperty public String getSchema() { return schema; @@ -161,6 +183,16 @@ public PositionImpl getEndPosition() { return endPosition; } + @JsonProperty + public String getSchemaInfoProperties() { + return schemaInfoProperties; + } + + @JsonProperty + public OffloadPolicies getOffloadPolicies() { + return offloadPolicies; + } + @Override public boolean isRemotelyAccessible() { return true; @@ -181,6 +213,7 @@ public String toString() { return "PulsarSplit{" + "splitId=" + splitId + ", connectorId='" + connectorId + '\'' + + ", originSchemaName='" + originSchemaName + '\'' + ", schemaName='" + schemaName + '\'' + ", tableName='" + tableName + '\'' + ", splitSize=" + splitSize @@ -190,6 +223,8 @@ public String toString() { + ", endPositionEntryId=" + endPositionEntryId + ", startPositionLedgerId=" + startPositionLedgerId + ", endPositionLedgerId=" + endPositionLedgerId + + ", schemaInfoProperties=" + schemaInfoProperties + + (offloadPolicies == null ? "" : offloadPolicies.toString()) + '}'; } diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplitManager.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplitManager.java index 06bc1200146f7..cce071ae15f12 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplitManager.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/PulsarSplitManager.java @@ -35,9 +35,11 @@ import com.facebook.presto.spi.predicate.Domain; import com.facebook.presto.spi.predicate.Range; import com.facebook.presto.spi.predicate.TupleDomain; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; import io.airlift.log.Logger; +import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; @@ -59,6 +61,7 @@ import org.apache.pulsar.client.impl.MessageImpl; import org.apache.pulsar.common.naming.NamespaceName; import org.apache.pulsar.common.naming.TopicName; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.apache.pulsar.common.schema.SchemaInfo; /** @@ -74,6 +77,8 @@ public class PulsarSplitManager implements ConnectorSplitManager { private static final Logger log = Logger.get(PulsarSplitManager.class); + private ObjectMapper objectMapper = new ObjectMapper(); + @Inject public PulsarSplitManager(PulsarConnectorId connectorId, PulsarConnectorConfig pulsarConnectorConfig) { this.connectorId = requireNonNull(connectorId, "connectorId is null").toString(); @@ -122,11 +127,15 @@ public ConnectorSplitSource getSplits(ConnectorTransactionHandle transactionHand Collection splits; try { + OffloadPolicies offloadPolicies = this.pulsarAdmin.namespaces() + .getOffloadPolicies(topicName.getNamespace()); if (!PulsarConnectorUtils.isPartitionedTopic(topicName, this.pulsarAdmin)) { - splits = getSplitsNonPartitionedTopic(numSplits, topicName, tableHandle, schemaInfo, tupleDomain); + splits = getSplitsNonPartitionedTopic( + numSplits, topicName, tableHandle, schemaInfo, tupleDomain, offloadPolicies); log.debug("Splits for non-partitioned topic %s: %s", topicName, splits); } else { - splits = getSplitsPartitionedTopic(numSplits, topicName, tableHandle, schemaInfo, tupleDomain); + splits = getSplitsPartitionedTopic( + numSplits, topicName, tableHandle, schemaInfo, tupleDomain, offloadPolicies); log.debug("Splits for partitioned topic %s: %s", topicName, splits); } } catch (Exception e) { @@ -138,7 +147,8 @@ public ConnectorSplitSource getSplits(ConnectorTransactionHandle transactionHand @VisibleForTesting Collection getSplitsPartitionedTopic(int numSplits, TopicName topicName, PulsarTableHandle - tableHandle, SchemaInfo schemaInfo, TupleDomain tupleDomain) throws Exception { + tableHandle, SchemaInfo schemaInfo, TupleDomain tupleDomain, + OffloadPolicies offloadPolicies) throws Exception { List predicatedPartitions = getPredicatedPartitions(topicName, tupleDomain); if (log.isDebugEnabled()) { @@ -165,7 +175,8 @@ Collection getSplitsPartitionedTopic(int numSplits, TopicName topic tableHandle, schemaInfo, topicName.getPartition(predicatedPartitions.get(i)).getLocalName(), - tupleDomain)); + tupleDomain, + offloadPolicies)); } return splits; } @@ -219,8 +230,8 @@ private List getPredicatedPartitions(TopicName topicName, TupleDomain getSplitsNonPartitionedTopic(int numSplits, TopicName topicName, - PulsarTableHandle tableHandle, SchemaInfo schemaInfo, TupleDomain tupleDomain) - throws Exception { + PulsarTableHandle tableHandle, SchemaInfo schemaInfo, TupleDomain tupleDomain, + OffloadPolicies offloadPolicies) throws Exception { ManagedLedgerFactory managedLedgerFactory = PulsarConnectorCache.getConnectorCache(pulsarConnectorConfig) .getManagedLedgerFactory(); @@ -230,7 +241,9 @@ Collection getSplitsNonPartitionedTopic(int numSplits, TopicName to numSplits, tableHandle, schemaInfo, - tableHandle.getTableName(), tupleDomain); + tableHandle.getTableName(), + tupleDomain, + offloadPolicies); } @VisibleForTesting @@ -239,8 +252,9 @@ Collection getSplitsForTopic(String topicNamePersistenceEncoding, int numSplits, PulsarTableHandle tableHandle, SchemaInfo schemaInfo, String tableName, - TupleDomain tupleDomain) - throws ManagedLedgerException, InterruptedException { + TupleDomain tupleDomain, + OffloadPolicies offloadPolicies) + throws ManagedLedgerException, InterruptedException, IOException { ReadOnlyCursor readOnlyCursor = null; try { @@ -285,18 +299,21 @@ Collection getSplitsForTopic(String topicNamePersistenceEncoding, readOnlyCursor.skipEntries(Math.toIntExact(entriesForSplit)); PositionImpl endPosition = (PositionImpl) readOnlyCursor.getReadPosition(); - splits.add(new PulsarSplit(i, this.connectorId, + PulsarSplit pulsarSplit = new PulsarSplit(i, this.connectorId, restoreNamespaceDelimiterIfNeeded(tableHandle.getSchemaName(), pulsarConnectorConfig), + schemaInfo.getName(), tableName, entriesForSplit, - new String(schemaInfo.getSchema()), + new String(schemaInfo.getSchema(), "ISO8859-1"), schemaInfo.getType(), startPosition.getEntryId(), endPosition.getEntryId(), startPosition.getLedgerId(), endPosition.getLedgerId(), tupleDomain, - schemaInfo.getProperties())); + objectMapper.writeValueAsString(schemaInfo.getProperties()), + offloadPolicies); + splits.add(pulsarSplit); } return splits; } finally { diff --git a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/SchemaHandler.java b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/SchemaHandler.java index 37fce04329555..7529034accea6 100644 --- a/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/SchemaHandler.java +++ b/pulsar-sql/presto-pulsar/src/main/java/org/apache/pulsar/sql/presto/SchemaHandler.java @@ -27,6 +27,8 @@ public interface SchemaHandler { Object deserialize(ByteBuf payload); + Object deserialize(ByteBuf keyPayload, ByteBuf dataPayload); + Object extractField(int index, Object currentRecord); } diff --git a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarConnector.java b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarConnector.java index cd81442db6b8b..adeababe481e8 100644 --- a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarConnector.java +++ b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarConnector.java @@ -26,6 +26,7 @@ import com.facebook.presto.spi.type.RealType; import com.facebook.presto.spi.type.Type; import com.facebook.presto.spi.type.VarcharType; +import com.fasterxml.jackson.databind.ObjectMapper; import io.airlift.log.Logger; import io.netty.buffer.ByteBuf; import org.apache.bookkeeper.mledger.AsyncCallbacks; @@ -118,6 +119,7 @@ public abstract class TestPulsarConnector { protected static Map topicsToSchemas; protected static Map topicsToNumEntries; + private final static ObjectMapper objectMapper = new ObjectMapper(); protected static final NamespaceName NAMESPACE_NAME_1 = NamespaceName.get("tenant-1", "ns-1"); protected static final NamespaceName NAMESPACE_NAME_2 = NamespaceName.get("tenant-1", "ns-2"); @@ -299,7 +301,7 @@ public static class Boo { false, false, fooFieldNames.get("field1"), - fooPositionIndices.get("field1"))); + fooPositionIndices.get("field1"), null)); String[] fieldNames2 = {"field2"}; @@ -312,7 +314,7 @@ public static class Boo { false, false, fieldNames2, - positionIndices2)); + positionIndices2, null)); String[] fieldNames3 = {"field3"}; Integer[] positionIndices3 = {2}; @@ -324,7 +326,7 @@ public static class Boo { false, false, fieldNames3, - positionIndices3)); + positionIndices3, null)); String[] fieldNames4 = {"field4"}; Integer[] positionIndices4 = {3}; @@ -336,7 +338,7 @@ public static class Boo { false, false, fieldNames4, - positionIndices4)); + positionIndices4, null)); String[] fieldNames5 = {"field5"}; @@ -349,7 +351,7 @@ public static class Boo { false, false, fieldNames5, - positionIndices5)); + positionIndices5, null)); String[] fieldNames6 = {"field6"}; Integer[] positionIndices6 = {5}; @@ -361,7 +363,7 @@ public static class Boo { false, false, fieldNames6, - positionIndices6)); + positionIndices6, null)); String[] fieldNames7 = {"timestamp"}; Integer[] positionIndices7 = {6}; @@ -373,7 +375,7 @@ public static class Boo { false, false, fieldNames7, - positionIndices7)); + positionIndices7, null)); String[] fieldNames8 = {"time"}; Integer[] positionIndices8 = {7}; @@ -385,7 +387,7 @@ public static class Boo { false, false, fieldNames8, - positionIndices8)); + positionIndices8, null)); String[] fieldNames9 = {"date"}; Integer[] positionIndices9 = {8}; @@ -397,7 +399,7 @@ public static class Boo { false, false, fieldNames9, - positionIndices9)); + positionIndices9, null)); String[] bar_fieldNames1 = {"bar", "field1"}; Integer[] bar_positionIndices1 = {9, 0}; @@ -409,7 +411,7 @@ public static class Boo { false, false, bar_fieldNames1, - bar_positionIndices1)); + bar_positionIndices1, null)); String[] bar_fieldNames2 = {"bar", "field2"}; Integer[] bar_positionIndices2 = {9, 1}; @@ -421,7 +423,7 @@ public static class Boo { false, false, bar_fieldNames2, - bar_positionIndices2)); + bar_positionIndices2, null)); String[] bar_test_fieldNames4 = {"bar", "test", "field4"}; Integer[] bar_test_positionIndices4 = {9, 2, 0}; @@ -433,7 +435,7 @@ public static class Boo { false, false, bar_test_fieldNames4, - bar_test_positionIndices4)); + bar_test_positionIndices4, null)); String[] bar_test_fieldNames5 = {"bar", "test", "field5"}; Integer[] bar_test_positionIndices5 = {9, 2, 1}; @@ -445,7 +447,7 @@ public static class Boo { false, false, bar_test_fieldNames5, - bar_test_positionIndices5)); + bar_test_positionIndices5, null)); String[] bar_test_fieldNames6 = {"bar", "test", "field6"}; Integer[] bar_test_positionIndices6 = {9, 2, 2}; @@ -457,7 +459,7 @@ public static class Boo { false, false, bar_test_fieldNames6, - bar_test_positionIndices6)); + bar_test_positionIndices6, null)); String[] bar_test_foobar_fieldNames1 = {"bar", "test", "foobar", "field1"}; Integer[] bar_test_foobar_positionIndices1 = {9, 2, 6, 0}; @@ -469,7 +471,7 @@ public static class Boo { false, false, bar_test_foobar_fieldNames1, - bar_test_foobar_positionIndices1)); + bar_test_foobar_positionIndices1, null)); String[] bar_field3 = {"bar", "field3"}; Integer[] bar_positionIndices3 = {9, 3}; @@ -481,7 +483,7 @@ public static class Boo { false, false, bar_field3, - bar_positionIndices3)); + bar_positionIndices3, null)); String[] bar_test2_fieldNames4 = {"bar", "test2", "field4"}; Integer[] bar_test2_positionIndices4 = {9, 4, 0}; @@ -493,7 +495,7 @@ public static class Boo { false, false, bar_test2_fieldNames4, - bar_test2_positionIndices4)); + bar_test2_positionIndices4, null)); String[] bar_test2_fieldNames5 = {"bar", "test2", "field5"}; Integer[] bar_test2_positionIndices5 = {9, 4, 1}; @@ -505,7 +507,7 @@ public static class Boo { false, false, bar_test2_fieldNames5, - bar_test2_positionIndices5)); + bar_test2_positionIndices5, null)); String[] bar_test2_fieldNames6 = {"bar", "test2", "field6"}; Integer[] bar_test2_positionIndices6 = {9, 4, 2}; @@ -517,7 +519,7 @@ public static class Boo { false, false, bar_test2_fieldNames6, - bar_test2_positionIndices6)); + bar_test2_positionIndices6, null)); String[] bar_test2_foobar_fieldNames1 = {"bar", "test2", "foobar", "field1"}; Integer[] bar_test2_foobar_positionIndices1 = {9, 4, 6, 0}; @@ -529,7 +531,7 @@ public static class Boo { false, false, bar_test2_foobar_fieldNames1, - bar_test2_foobar_positionIndices1)); + bar_test2_foobar_positionIndices1, null)); String[] fieldNames10 = {"field7"}; Integer[] positionIndices10 = {10}; @@ -541,7 +543,7 @@ public static class Boo { false, false, fieldNames10, - positionIndices10)); + positionIndices10, null)); fooColumnHandles.addAll(PulsarInternalColumn.getInternalFields().stream() .map(pulsarInternalColumn -> pulsarInternalColumn.getColumnHandle(pulsarConnectorId.toString(), false)) @@ -556,12 +558,14 @@ public static class Boo { for (TopicName topicName : allTopics) { if (topicsToSchemas.containsKey(topicName.getSchemaName())) { splits.put(topicName, new PulsarSplit(0, pulsarConnectorId.toString(), - topicName.getNamespace(), topicName.getLocalName(), + topicName.getNamespace(), topicName.getLocalName(), topicName.getLocalName(), topicsToNumEntries.get(topicName.getSchemaName()), new String(topicsToSchemas.get(topicName.getSchemaName()).getSchema()), topicsToSchemas.get(topicName.getSchemaName()).getType(), 0, topicsToNumEntries.get(topicName.getSchemaName()), - 0, 0, TupleDomain.all(), new HashMap<>())); + 0, 0, TupleDomain.all(), + objectMapper.writeValueAsString( + topicsToSchemas.get(topicName.getSchemaName()).getProperties()), null)); } } @@ -601,7 +605,6 @@ public static class Boo { } catch (Throwable e) { System.out.println("Error: " + e); System.out.println("Stacktrace: " + Arrays.asList(e.getStackTrace())); - throw e; } } diff --git a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarKeyValueSchemaHandler.java b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarKeyValueSchemaHandler.java new file mode 100644 index 0000000000000..193f68e00b069 --- /dev/null +++ b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarKeyValueSchemaHandler.java @@ -0,0 +1,331 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pulsar.sql.presto; + +import com.facebook.presto.spi.ColumnMetadata; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.client.impl.schema.KeyValueSchema; +import org.apache.pulsar.client.impl.schema.KeyValueSchemaInfo; +import org.apache.pulsar.common.api.raw.RawMessage; +import org.apache.pulsar.common.api.raw.RawMessageImpl; +import org.apache.pulsar.common.naming.TopicName; +import org.apache.pulsar.common.schema.KeyValue; +import org.apache.pulsar.common.schema.KeyValueEncodingType; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + + +/** + * Unit test for KeyValueSchemaHandler + */ +@Slf4j +public class TestPulsarKeyValueSchemaHandler { + + private final static ObjectMapper objectMapper = new ObjectMapper(); + + private Schema> schema1 = + Schema.KeyValue(Schema.STRING, Schema.INT32, KeyValueEncodingType.INLINE); + + private Schema> schema2 = + Schema.KeyValue(Schema.STRING, Schema.JSON(Foo.class), KeyValueEncodingType.INLINE); + + private Schema> schema3 = + Schema.KeyValue(Schema.AVRO(Boo.class), Schema.INT64, KeyValueEncodingType.SEPARATED); + + private Schema> schema4 = + Schema.KeyValue(Schema.JSON(Boo.class), Schema.AVRO(Foo.class), KeyValueEncodingType.SEPARATED); + + private final static TopicName topicName = TopicName.get("persistent://public/default/kv-test"); + + private final static Foo foo; + + private final static Boo boo; + + private final Integer KEY_FIELD_NAME_PREFIX_LENGTH = PulsarColumnMetadata.KEY_SCHEMA_COLUMN_PREFIX.length(); + + static { + foo = new Foo(); + foo.field1 = "field1-value"; + foo.field2 = 20; + + boo = new Boo(); + boo.field1 = "field1-value"; + boo.field2 = true; + boo.field3 = 10.2; + } + + + @Test + public void testSchema1() throws IOException { + final String keyData = "test-key"; + final Integer valueData = 10; + List columnMetadataList = + PulsarMetadata.getPulsarColumns(topicName, schema1.getSchemaInfo(), + true, null); + int keyCount = 0; + int valueCount = 0; + for (ColumnMetadata columnMetadata : columnMetadataList) { + PulsarColumnMetadata pulsarColumnMetadata = (PulsarColumnMetadata) columnMetadata; + if (pulsarColumnMetadata.isKey()) { + keyCount++; + } else if (pulsarColumnMetadata.isValue()) { + valueCount++; + } + } + Assert.assertEquals(keyCount, 1); + Assert.assertEquals(valueCount, 1); + + List columnHandleList = getColumnHandlerList(columnMetadataList); + + KeyValueSchemaHandler keyValueSchemaHandler = + new KeyValueSchemaHandler(schema1.getSchemaInfo(), columnHandleList); + + RawMessageImpl message = Mockito.mock(RawMessageImpl.class); + Mockito.when(message.getData()).thenReturn( + Unpooled.wrappedBuffer(schema1.encode(new KeyValue<>(keyData, valueData))) + ); + + KeyValue byteBufKeyValue = getKeyValueByteBuf(message, schema1); + Object object = keyValueSchemaHandler.deserialize(byteBufKeyValue.getKey(), byteBufKeyValue.getValue()); + Assert.assertEquals(keyValueSchemaHandler.extractField(0, object), keyData); + Assert.assertEquals(keyValueSchemaHandler.extractField(1, object), valueData); + } + + @Test + public void testSchema2() throws IOException { + final String keyData = "test-key"; + + List columnMetadataList = + PulsarMetadata.getPulsarColumns(topicName, schema2.getSchemaInfo(), + true, null); + int keyCount = 0; + int valueCount = 0; + for (ColumnMetadata columnMetadata : columnMetadataList) { + PulsarColumnMetadata pulsarColumnMetadata = (PulsarColumnMetadata) columnMetadata; + if (pulsarColumnMetadata.isKey()) { + keyCount++; + } else if (pulsarColumnMetadata.isValue()) { + valueCount++; + } + } + Assert.assertEquals(keyCount, 1); + Assert.assertEquals(valueCount, 2); + + List columnHandleList = getColumnHandlerList(columnMetadataList); + + RawMessage message = Mockito.mock(RawMessage.class); + Mockito.when(message.getData()).thenReturn( + Unpooled.wrappedBuffer(schema2.encode(new KeyValue<>(keyData, foo))) + ); + + + KeyValueSchemaHandler keyValueSchemaHandler = + new KeyValueSchemaHandler(schema2.getSchemaInfo(), columnHandleList); + + KeyValue byteBufKeyValue = getKeyValueByteBuf(message, schema2); + Object object = keyValueSchemaHandler.deserialize(byteBufKeyValue.getKey(), byteBufKeyValue.getValue()); + Assert.assertEquals(keyValueSchemaHandler.extractField(0, object), keyData); + Assert.assertEquals(keyValueSchemaHandler.extractField(1, object), + foo.getValue(columnHandleList.get(1).getName())); + Assert.assertEquals(keyValueSchemaHandler.extractField(2, object), + foo.getValue(columnHandleList.get(2).getName())); + } + + @Test + public void testSchema3() throws IOException { + final Boo boo = new Boo(); + boo.field1 = "field1-value"; + boo.field2 = true; + boo.field3 = 10.2; + final Long valueData = 999999L; + + List columnMetadataList = + PulsarMetadata.getPulsarColumns(topicName, schema3.getSchemaInfo(), + true, null); + int keyCount = 0; + int valueCount = 0; + for (ColumnMetadata columnMetadata : columnMetadataList) { + PulsarColumnMetadata pulsarColumnMetadata = (PulsarColumnMetadata) columnMetadata; + if (pulsarColumnMetadata.isKey()) { + keyCount++; + } else if (pulsarColumnMetadata.isValue()) { + valueCount++; + } + } + Assert.assertEquals(keyCount, 3); + Assert.assertEquals(valueCount, 1); + + List columnHandleList = getColumnHandlerList(columnMetadataList); + + KeyValueSchemaHandler keyValueSchemaHandler = + new KeyValueSchemaHandler(schema3.getSchemaInfo(), columnHandleList); + + RawMessage message = Mockito.mock(RawMessage.class); + Mockito.when(message.getKeyBytes()).thenReturn( + Optional.of(Unpooled.wrappedBuffer( + ((KeyValueSchema) schema3).getKeySchema().encode(boo) + )) + ); + Mockito.when(message.getData()).thenReturn( + Unpooled.wrappedBuffer(schema3.encode(new KeyValue<>(boo, valueData))) + ); + + KeyValue byteBufKeyValue = getKeyValueByteBuf(message, schema3); + Object object = keyValueSchemaHandler.deserialize(byteBufKeyValue.getKey(), byteBufKeyValue.getValue()); + Assert.assertEquals(keyValueSchemaHandler.extractField(0, object).toString(), + boo.getValue(columnHandleList.get(0).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(1, object), + boo.getValue(columnHandleList.get(1).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(2, object), + boo.getValue(columnHandleList.get(2).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(3, object), valueData); + } + + @Test + public void testSchema4() throws IOException { + List columnMetadataList = + PulsarMetadata.getPulsarColumns(topicName, schema4.getSchemaInfo(), + true, null); + int keyCount = 0; + int valueCount = 0; + for (ColumnMetadata columnMetadata : columnMetadataList) { + PulsarColumnMetadata pulsarColumnMetadata = (PulsarColumnMetadata) columnMetadata; + if (pulsarColumnMetadata.isKey()) { + keyCount++; + } else if (pulsarColumnMetadata.isValue()) { + valueCount++; + } + } + Assert.assertEquals(keyCount, 3); + Assert.assertEquals(valueCount, 2); + + List columnHandleList = getColumnHandlerList(columnMetadataList); + + KeyValueSchemaHandler keyValueSchemaHandler = + new KeyValueSchemaHandler(schema4.getSchemaInfo(), columnHandleList); + + RawMessage message = Mockito.mock(RawMessage.class); + Mockito.when(message.getKeyBytes()).thenReturn( + Optional.of(Unpooled.wrappedBuffer( + ((KeyValueSchema) schema4).getKeySchema().encode(boo) + )) + ); + Mockito.when(message.getData()).thenReturn( + Unpooled.wrappedBuffer(schema4.encode(new KeyValue<>(boo, foo))) + ); + + KeyValue byteBufKeyValue = getKeyValueByteBuf(message, schema4); + Object object = keyValueSchemaHandler.deserialize(byteBufKeyValue.getKey(), byteBufKeyValue.getValue()); + Assert.assertEquals(keyValueSchemaHandler.extractField(0, object).toString(), + boo.getValue(columnHandleList.get(0).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(1, object), + boo.getValue(columnHandleList.get(1).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(2, object), + boo.getValue(columnHandleList.get(2).getName().substring(KEY_FIELD_NAME_PREFIX_LENGTH))); + Assert.assertEquals(keyValueSchemaHandler.extractField(3, object).toString(), + foo.getValue(columnHandleList.get(3).getName())); + Assert.assertEquals(keyValueSchemaHandler.extractField(4, object).toString(), + foo.getValue(columnHandleList.get(4).getName()) + ""); + } + + private List getColumnHandlerList(List columnMetadataList) { + List columnHandleList = new LinkedList<>(); + + columnMetadataList.forEach(columnMetadata -> { + PulsarColumnMetadata pulsarColumnMetadata = (PulsarColumnMetadata) columnMetadata; + PulsarColumnHandle pulsarColumnHandle = new PulsarColumnHandle( + "connectorId", + pulsarColumnMetadata.getNameWithCase(), + pulsarColumnMetadata.getType(), + pulsarColumnMetadata.isHidden(), + pulsarColumnMetadata.isInternal(), + pulsarColumnMetadata.getFieldNames(), + pulsarColumnMetadata.getPositionIndices(), + pulsarColumnMetadata.getHandleKeyValueType()); + columnHandleList.add(pulsarColumnHandle); + }); + + return columnHandleList; + } + + public KeyValue getKeyValueByteBuf(RawMessage message, Schema schema) { + KeyValueEncodingType encodingType = KeyValueSchemaInfo.decodeKeyValueEncodingType(schema.getSchemaInfo()); + ByteBuf keyByteBuf = null; + if (Objects.equals(KeyValueEncodingType.SEPARATED, encodingType)) { + if (message.getKeyBytes().isPresent()) { + keyByteBuf = message.getKeyBytes().get(); + } else { + keyByteBuf = null; + } + } else { + keyByteBuf = null; + } + return new KeyValue<>(keyByteBuf, Unpooled.wrappedBuffer(message.getData())); + } + + @Data + static class Foo { + private String field1; + private Integer field2; + + public Object getValue(String fieldName) { + switch (fieldName) { + case "field1": + return field1; + case "field2": + return field2 == null ? null : new Long(field2); + default: + return null; + } + } + } + + @Data + static class Boo { + private String field1; + private Boolean field2; + private Double field3; + + public Object getValue(String fieldName) { + switch (fieldName) { + case "field1": + return field1; + case "field2": + return field2; + case "field3": + return field3 == null ? null : field3.doubleValue(); + default: + return null; + } + } + + } + +} diff --git a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarPrimitiveSchemaHandler.java b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarPrimitiveSchemaHandler.java index 87e148db84f1c..ac2e1f4bd3815 100644 --- a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarPrimitiveSchemaHandler.java +++ b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarPrimitiveSchemaHandler.java @@ -153,7 +153,7 @@ public void testNewPulsarPrimitiveSchemaHandler() { @Test public void testNewColumnMetadata() { List columnMetadataList = PulsarMetadata.getPulsarColumns(stringTopicName, - StringSchema.utf8().getSchemaInfo(), false); + StringSchema.utf8().getSchemaInfo(), false, null); Assert.assertEquals(columnMetadataList.size(), 1); ColumnMetadata columnMetadata = columnMetadataList.get(0); Assert.assertEquals("__value__", columnMetadata.getName()); diff --git a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarSplitManager.java b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarSplitManager.java index ef361487a054f..5ecbffc307764 100644 --- a/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarSplitManager.java +++ b/pulsar-sql/presto-pulsar/src/test/java/org/apache/pulsar/sql/presto/TestPulsarSplitManager.java @@ -87,7 +87,7 @@ public void testTopic(String delimiter) throws Exception { PulsarTableLayoutHandle pulsarTableLayoutHandle = new PulsarTableLayoutHandle(pulsarTableHandle, TupleDomain.all()); final ResultCaptor> resultCaptor = new ResultCaptor<>(); - doAnswer(resultCaptor).when(this.pulsarSplitManager).getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any()); + doAnswer(resultCaptor).when(this.pulsarSplitManager).getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); ConnectorSplitSource connectorSplitSource = this.pulsarSplitManager.getSplits( @@ -95,7 +95,7 @@ public void testTopic(String delimiter) throws Exception { pulsarTableLayoutHandle, null); verify(this.pulsarSplitManager, times(1)) - .getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any()); + .getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); int totalSize = 0; for (PulsarSplit pulsarSplit : resultCaptor.getResult()) { @@ -134,13 +134,13 @@ public void testPartitionedTopic(String delimiter) throws Exception { PulsarTableLayoutHandle pulsarTableLayoutHandle = new PulsarTableLayoutHandle(pulsarTableHandle, TupleDomain.all()); final ResultCaptor> resultCaptor = new ResultCaptor<>(); - doAnswer(resultCaptor).when(this.pulsarSplitManager).getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any()); + doAnswer(resultCaptor).when(this.pulsarSplitManager).getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); this.pulsarSplitManager.getSplits(mock(ConnectorTransactionHandle.class), mock(ConnectorSession.class), pulsarTableLayoutHandle, null); verify(this.pulsarSplitManager, times(1)) - .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any()); + .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); int partitions = partitionedTopicsToPartitions.get(topicName.toString()); @@ -200,8 +200,8 @@ public void testPublishTimePredicatePushdown(String delimiter) throws Exception PulsarTableLayoutHandle pulsarTableLayoutHandle = new PulsarTableLayoutHandle(pulsarTableHandle, tupleDomain); final ResultCaptor> resultCaptor = new ResultCaptor<>(); - doAnswer(resultCaptor).when(this.pulsarSplitManager).getSplitsNonPartitionedTopic(anyInt(), any(), any(), any - (), any()); + doAnswer(resultCaptor).when(this.pulsarSplitManager) + .getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); ConnectorSplitSource connectorSplitSource = this.pulsarSplitManager.getSplits( mock(ConnectorTransactionHandle.class), mock(ConnectorSession.class), @@ -209,7 +209,7 @@ public void testPublishTimePredicatePushdown(String delimiter) throws Exception verify(this.pulsarSplitManager, times(1)) - .getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any()); + .getSplitsNonPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); int totalSize = 0; int initalStart = 1; @@ -258,7 +258,7 @@ public void testPublishTimePredicatePushdownPartitionedTopic(String delimiter) t final ResultCaptor> resultCaptor = new ResultCaptor<>(); doAnswer(resultCaptor).when(this.pulsarSplitManager) - .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any()); + .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); ConnectorSplitSource connectorSplitSource = this.pulsarSplitManager.getSplits( mock(ConnectorTransactionHandle.class), mock(ConnectorSession.class), @@ -266,7 +266,7 @@ public void testPublishTimePredicatePushdownPartitionedTopic(String delimiter) t verify(this.pulsarSplitManager, times(1)) - .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any()); + .getSplitsPartitionedTopic(anyInt(), any(), any(), any(), any(), any()); int partitions = partitionedTopicsToPartitions.get(topicName.toString()); @@ -315,7 +315,7 @@ public void testPartitionFilter(String delimiter) throws Exception { domainMap.put(PulsarInternalColumn.PARTITION.getColumnHandle(pulsarConnectorId.toString(), false), domain); TupleDomain tupleDomain = TupleDomain.withColumnDomains(domainMap); Collection splits = this.pulsarSplitManager.getSplitsPartitionedTopic(2, topicName, pulsarTableHandle, - schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain); + schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain, null); if (topicsToNumEntries.get(topicName.getSchemaName()) > 1) { Assert.assertEquals(splits.size(), 2); } @@ -332,7 +332,7 @@ public void testPartitionFilter(String delimiter) throws Exception { domainMap.put(PulsarInternalColumn.PARTITION.getColumnHandle(pulsarConnectorId.toString(), false), domain); tupleDomain = TupleDomain.withColumnDomains(domainMap); splits = this.pulsarSplitManager.getSplitsPartitionedTopic(1, topicName, pulsarTableHandle, - schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain); + schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain, null); if (topicsToNumEntries.get(topicName.getSchemaName()) > 1) { Assert.assertEquals(splits.size(), 2); } @@ -348,7 +348,7 @@ public void testPartitionFilter(String delimiter) throws Exception { domainMap.put(PulsarInternalColumn.PARTITION.getColumnHandle(pulsarConnectorId.toString(), false), domain); tupleDomain = TupleDomain.withColumnDomains(domainMap); splits = this.pulsarSplitManager.getSplitsPartitionedTopic(2, topicName, pulsarTableHandle, - schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain); + schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain, null); if (topicsToNumEntries.get(topicName.getSchemaName()) > 1) { Assert.assertEquals(splits.size(), 3); } @@ -367,7 +367,7 @@ public void testPartitionFilter(String delimiter) throws Exception { domainMap.put(PulsarInternalColumn.PARTITION.getColumnHandle(pulsarConnectorId.toString(), false), domain); tupleDomain = TupleDomain.withColumnDomains(domainMap); splits = this.pulsarSplitManager.getSplitsPartitionedTopic(2, topicName, pulsarTableHandle, - schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain); + schemas.getSchemaInfo(topicName.getSchemaName()), tupleDomain, null); if (topicsToNumEntries.get(topicName.getSchemaName()) > 1) { Assert.assertEquals(splits.size(), 4); } diff --git a/pulsar-storm/src/main/java/org/apache/pulsar/storm/MessageToValuesMapper.java b/pulsar-storm/src/main/java/org/apache/pulsar/storm/MessageToValuesMapper.java index 4291fa8fd97ea..92e127c2d5e65 100644 --- a/pulsar-storm/src/main/java/org/apache/pulsar/storm/MessageToValuesMapper.java +++ b/pulsar-storm/src/main/java/org/apache/pulsar/storm/MessageToValuesMapper.java @@ -33,12 +33,12 @@ public interface MessageToValuesMapper extends Serializable { * @param msg * @return */ - public Values toValues(Message msg); + Values toValues(Message msg); /** * Declare the output schema for the spout. * * @param declarer */ - public void declareOutputFields(OutputFieldsDeclarer declarer); + void declareOutputFields(OutputFieldsDeclarer declarer); } diff --git a/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarSpout.java b/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarSpout.java index 773cb2b44c0da..63887a4f4309b 100644 --- a/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarSpout.java +++ b/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarSpout.java @@ -326,7 +326,11 @@ private boolean mapToValueAndEmit(Message msg) { } ack(msg); } else { - collector.emit(values, msg); + if (values instanceof PulsarTuple) { + collector.emit(((PulsarTuple) values).getOutputStream(), values, msg); + } else { + collector.emit(values, msg); + } ++messagesEmitted; if (LOG.isDebugEnabled()) { LOG.debug("[{}] Emitted message {} to the collector", spoutId, msg.getMessageId()); diff --git a/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarTuple.java b/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarTuple.java new file mode 100644 index 0000000000000..b000827d8c4eb --- /dev/null +++ b/pulsar-storm/src/main/java/org/apache/pulsar/storm/PulsarTuple.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pulsar.storm; + + +import org.apache.storm.tuple.Values; + +/** + * Returned by MessageToValuesMapper, this specifies the Values + * for an output tuple and the stream it should be sent to. + */ +public class PulsarTuple extends Values { + + protected final String outputStream; + + public PulsarTuple(String outStream, Object ... values) { + super(values); + outputStream = outStream; + } + + /** + * Return stream the tuple should be emitted on. + * + * @return String + */ + public String getOutputStream() { + return outputStream; + } +} diff --git a/pulsar-storm/src/test/java/org/apache/pulsar/storm/PulsarSpoutTest.java b/pulsar-storm/src/test/java/org/apache/pulsar/storm/PulsarSpoutTest.java index f8a1aa009b7a5..ba6705190a3bc 100644 --- a/pulsar-storm/src/test/java/org/apache/pulsar/storm/PulsarSpoutTest.java +++ b/pulsar-storm/src/test/java/org/apache/pulsar/storm/PulsarSpoutTest.java @@ -22,10 +22,13 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.lang.reflect.Field; @@ -47,6 +50,7 @@ import org.apache.storm.task.TopologyContext; import org.apache.storm.topology.OutputFieldsDeclarer; import org.apache.storm.tuple.Values; +import org.mockito.ArgumentCaptor; import org.testng.annotations.Test; import com.google.common.collect.Maps; @@ -92,8 +96,17 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) { verify(consumer, atLeast(1)).receive(anyInt(), any()); } + @Test + public void testPulsarTuple() throws Exception { + testPulsarSpout(true); + } + @Test public void testPulsarSpout() throws Exception { + testPulsarSpout(false); + } + + public void testPulsarSpout(boolean pulsarTuple) throws Exception { PulsarSpoutConfiguration conf = new PulsarSpoutConfiguration(); conf.setServiceUrl("http://localhost:8080"); conf.setSubscriptionName("sub1"); @@ -105,7 +118,15 @@ public void testPulsarSpout() throws Exception { @Override public Values toValues(Message msg) { called.set(true); - return new Values("test"); + if ("message to be dropped".equals(new String(msg.getData()))) { + return null; + } + String val = new String(msg.getData()); + if (val.startsWith("stream:")) { + String stream = val.split(":")[1]; + return new PulsarTuple(stream, val); + } + return new Values(val); } @Override @@ -114,6 +135,8 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) { }); + String msgContent = pulsarTuple ? "stream:pstream" : "test"; + ClientBuilder builder = spy(new ClientBuilderImpl()); PulsarSpout spout = spy(new PulsarSpout(conf, builder)); TopologyContext context = mock(TopologyContext.class); @@ -131,7 +154,7 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) { when(client.getSharedConsumer(any())).thenReturn(consumer); instances.put(componentId, client); - Message msg = new MessageImpl<>(conf.getTopic(), "1:1", Maps.newHashMap(), "test".getBytes(), Schema.BYTES); + Message msg = new MessageImpl<>(conf.getTopic(), "1:1", Maps.newHashMap(), msgContent.getBytes(), Schema.BYTES); when(consumer.receive(anyInt(), any())).thenReturn(msg); spout.open(config, context, collector); @@ -139,6 +162,14 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) { assertTrue(called.get()); verify(consumer, atLeast(1)).receive(anyInt(), any()); + ArgumentCaptor capt = ArgumentCaptor.forClass(Values.class); + if (pulsarTuple) { + verify(collector, times(1)).emit(eq("pstream"), capt.capture(), eq(msg)); + } else { + verify(collector, times(1)).emit(capt.capture(), eq(msg)); + } + Values vals = capt.getValue(); + assertEquals(msgContent, vals.get(0)); } } diff --git a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/WebSocketService.java b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/WebSocketService.java index 79cd4366e9fdf..062af7efb82ed 100644 --- a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/WebSocketService.java +++ b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/WebSocketService.java @@ -30,6 +30,7 @@ import javax.servlet.ServletException; import javax.websocket.DeploymentException; +import lombok.Setter; import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.pulsar.broker.PulsarServerException; import org.apache.pulsar.broker.ServiceConfiguration; @@ -77,6 +78,7 @@ public class WebSocketService implements Closeable { private ServiceConfiguration config; private ConfigurationCacheService configurationCacheService; + @Setter private ClusterData localCluster; private final ConcurrentOpenHashMap> topicProducerMap; private final ConcurrentOpenHashMap> topicConsumerMap; diff --git a/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/LocalBookkeeperEnsemble.java b/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/LocalBookkeeperEnsemble.java index 0a743c8cf2b26..2658035a1ba26 100644 --- a/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/LocalBookkeeperEnsemble.java +++ b/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/LocalBookkeeperEnsemble.java @@ -310,6 +310,9 @@ public void runStreamStorage(CompositeConfiguration conf) throws Exception { // stream storage port conf.setProperty("storageserver.grpc.port", streamStoragePort); + // storage server settings + conf.setProperty("storage.range.store.dirs", bkDataDirName + "/ranges/data"); + // initialize the stream storage metadata ClusterInitializer initializer = new ZkClusterInitializer(zkServers); initializer.initializeCluster(metadataServiceUri, 2); diff --git a/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/ZooKeeperCache.java b/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/ZooKeeperCache.java index d69a161b9c3f8..5052605f1685d 100644 --- a/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/ZooKeeperCache.java +++ b/pulsar-zookeeper-utils/src/main/java/org/apache/pulsar/zookeeper/ZooKeeperCache.java @@ -28,6 +28,7 @@ import java.nio.file.Paths; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collections; +import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; @@ -458,7 +459,12 @@ public CompletableFuture> getChildrenAsync(String path, Watcher watc @SuppressWarnings("unchecked") public T getDataIfPresent(String path) { - return (T) dataCache.getIfPresent(path); + CompletableFuture> f = dataCache.getIfPresent(path); + if (f != null && f.isDone() && !f.isCompletedExceptionally()) { + return (T) f.join().getKey(); + } else { + return null; + } } public Set getChildrenIfPresent(String path) { diff --git a/pulsar-zookeeper-utils/src/test/java/org/apache/pulsar/zookeeper/ZookeeperCacheTest.java b/pulsar-zookeeper-utils/src/test/java/org/apache/pulsar/zookeeper/ZookeeperCacheTest.java index a83b3705ec085..0b4100a4d6bb6 100644 --- a/pulsar-zookeeper-utils/src/test/java/org/apache/pulsar/zookeeper/ZookeeperCacheTest.java +++ b/pulsar-zookeeper-utils/src/test/java/org/apache/pulsar/zookeeper/ZookeeperCacheTest.java @@ -109,6 +109,7 @@ public String deserialize(String key, byte[] content) throws Exception { zkClient.create("/my_test", value.getBytes(), null, null); assertEquals(zkCache.get("/my_test").get(), value); + assertEquals(zkCache.getDataIfPresent("/my_test"), value); String newValue = "test2"; @@ -501,11 +502,11 @@ public String deserialize(String key, byte[] content) throws Exception { assertEquals(zkCache.getAsync(key1).get().get(), value); zkExecutor.shutdown(); } - + /** * This tests verifies that {{@link ZooKeeperDataCache} invalidates the cache if the get-operation time-out on that * path. - * + * * @throws Exception */ @Test diff --git a/site2/docs/adaptors-kafka.md b/site2/docs/adaptors-kafka.md index 8a5310622402c..4ec0f15ae0ec6 100644 --- a/site2/docs/adaptors-kafka.md +++ b/site2/docs/adaptors-kafka.md @@ -250,6 +250,7 @@ You can configure Pulsar authentication provider directly from the Kafka propert | [`pulsar.producer.max.pending.messages.across.partitions`](http://pulsar.apache.org/api/client/org/apache/pulsar/client/api/ProducerConfiguration.html#setMaxPendingMessagesAcrossPartitions-int-) | `50000` | Set the maximum number of pending messages across all the partitions. | | [`pulsar.producer.batching.enabled`](http://pulsar.apache.org/api/client/org/apache/pulsar/client/api/ProducerConfiguration.html#setBatchingEnabled-boolean-) | `true` | Control whether automatic batching of messages is enabled for the producer. | | [`pulsar.producer.batching.max.messages`](http://pulsar.apache.org/api/client/org/apache/pulsar/client/api/ProducerConfiguration.html#setBatchingMaxMessages-int-) | `1000` | The maximum number of messages in a batch. | +| [`pulsar.block.if.producer.queue.full`](http://pulsar.apache.org/api/client/org/apache/pulsar/client/api/ProducerConfiguration.html#setBlockIfQueueFull-boolean-) | | Specify the block producer if queue is full. | ### Pulsar consumer Properties diff --git a/site2/docs/admin-api-non-partitioned-topics.md b/site2/docs/admin-api-non-partitioned-topics.md index 61f7409b47938..a8cfe0fc8d183 100644 --- a/site2/docs/admin-api-non-partitioned-topics.md +++ b/site2/docs/admin-api-non-partitioned-topics.md @@ -101,3 +101,59 @@ persistent://tenant/namespace/topic2 ```java admin.topics().getList(namespace); ``` + +### Stats + +It shows current statistics of a given topic. Here's an example payload: + +The following stats are available: + +|Stat|Description| +|----|-----------| +|msgRateIn|The sum of all local and replication publishers’ publish rates in messages per second| +|msgThroughputIn|Same as msgRateIn but in bytes per second instead of messages per second| +|msgRateOut|The sum of all local and replication consumers’ dispatch rates in messages per second| +|msgThroughputOut|Same as msgRateOut but in bytes per second instead of messages per second| +|averageMsgSize|Average message size, in bytes, from this publisher within the last interval| +|storageSize|The sum of the ledgers’ storage size for this topic| +|publishers|The list of all local publishers into the topic. There can be anywhere from zero to thousands.| +|producerId|Internal identifier for this producer on this topic| +|producerName|Internal identifier for this producer, generated by the client library| +|address|IP address and source port for the connection of this producer| +|connectedSince|Timestamp this producer was created or last reconnected| +|subscriptions|The list of all local subscriptions to the topic| +|my-subscription|The name of this subscription (client defined)| +|msgBacklog|The count of messages in backlog for this subscription| +|msgBacklogNoDelayed|The count of messages in backlog without delayed messages for this subscription| +|type|This subscription type| +|msgRateExpired|The rate at which messages were discarded instead of dispatched from this subscription due to TTL| +|consumers|The list of connected consumers for this subscription| +|consumerName|Internal identifier for this consumer, generated by the client library| +|availablePermits|The number of messages this consumer has space for in the client library’s listen queue. A value of 0 means the client library’s queue is full and receive() isn’t being called. A nonzero value means this consumer is ready to be dispatched messages.| +|replication|This section gives the stats for cross-colo replication of this topic| +|replicationBacklog|The outbound replication backlog in messages| +|connected|Whether the outbound replicator is connected| +|replicationDelayInSeconds|How long the oldest message has been waiting to be sent through the connection, if connected is true| +|inboundConnection|The IP and port of the broker in the remote cluster’s publisher connection to this broker| +|inboundConnectedSince|The TCP connection being used to publish messages to the remote cluster. If there are no local publishers connected, this connection is automatically closed after a minute.| + +#### pulsar-admin + +The stats for the topic and its connected producers and consumers can be fetched by using the +[`stats`](reference-pulsar-admin.md#stats) command, specifying the topic by name: + +```shell +$ pulsar-admin topics stats \ + persistent://test-tenant/namespace/topic \ + --get-precise-backlog +``` + +#### REST API + +{@inject: endpoint|GET|/admin/v2/persistent/:tenant/:namespace/:topic/stats|operation/getStats} + +#### Java + +```java +admin.topics().getStats(persistentTopic, false /* is precise backlog */); +``` diff --git a/site2/docs/admin-api-partitioned-topics.md b/site2/docs/admin-api-partitioned-topics.md index a6507e073b759..d413425c282f1 100644 --- a/site2/docs/admin-api-partitioned-topics.md +++ b/site2/docs/admin-api-partitioned-topics.md @@ -254,6 +254,7 @@ The following stats are available: |subscriptions|The list of all local subscriptions to the topic| |my-subscription|The name of this subscription (client defined)| |msgBacklog|The count of messages in backlog for this subscription| +|msgBacklogNoDelayed|The count of messages in backlog without delayed messages for this subscription| |type|This subscription type| |msgRateExpired|The rate at which messages were discarded instead of dispatched from this subscription due to TTL| |consumers|The list of connected consumers for this subscription| @@ -274,7 +275,7 @@ The stats for the partitioned topic and its connected producers and consumers ca ```shell $ pulsar-admin topics partitioned-stats \ persistent://test-tenant/namespace/topic \ - --per-partition + --per-partition ``` #### REST API @@ -284,7 +285,7 @@ $ pulsar-admin topics partitioned-stats \ #### Java ```java -admin.persistentTopics().getStats(persistentTopic); +admin.topics().getPartitionedStats(persistentTopic, true /* per partition */, false /* is precise backlog */); ``` ### Internal stats diff --git a/site2/docs/admin-api-persistent-topics.md b/site2/docs/admin-api-persistent-topics.md index 1f688c547021e..b09040d098016 100644 --- a/site2/docs/admin-api-persistent-topics.md +++ b/site2/docs/admin-api-persistent-topics.md @@ -222,6 +222,14 @@ It shows current statistics of a given non-partitioned topic. - **type**: This subscription type - **msgRateExpired**: The rate at which messages were discarded instead of dispatched from this subscription due to TTL + + - **lastExpireTimestamp**: The last message expire execution timestamp + + - **lastConsumedFlowTimestamp**: The last flow command received timestamp + + - **lastConsumedTimestamp**: The latest timestamp of all the consumed timestamp of the consumers + + - **lastAckedTimestamp**: The latest timestamp of all the acked timestamp of the consumers - **consumers**: The list of connected consumers for this subscription @@ -236,6 +244,10 @@ It shows current statistics of a given non-partitioned topic. - **unackedMessages**: Number of unacknowledged messages for the consumer - **blockedConsumerOnUnackedMsgs**: Flag to verify if the consumer is blocked due to reaching threshold of unacked messages + + - **lastConsumedTimestamp**: The timestamp of the consumer last consume a message + + - **lastAckedTimestamp**: The timestamp of the consumer last ack a message - **replication**: This section gives the stats for cross-colo replication of this topic diff --git a/site2/docs/administration-pulsar-manager.md b/site2/docs/administration-pulsar-manager.md index af89091b4a5dd..54267e600c319 100644 --- a/site2/docs/administration-pulsar-manager.md +++ b/site2/docs/administration-pulsar-manager.md @@ -6,6 +6,9 @@ sidebar_label: Pulsar Manager Pulsar Manager is a web-based GUI management and monitoring tool that helps administrators and users manage and monitor tenants, namespaces, topics, subscriptions, brokers, clusters, and so on, and supports dynamic configuration of multiple environments. +> Note +> If you monitor your current stats with [Pulsar dashboard](administration-dashboard.md), you can try to use Pulsar Manager instead. Pulsar dashboard is deprecated. + ## Install The easiest way to use the Pulsar Manager is to run it inside a [Docker](https://www.docker.com/products/docker) container. diff --git a/site2/docs/assets/pulsar-functions-word-count.png b/site2/docs/assets/pulsar-functions-word-count.png index ad0c280938aad..db1e4970b42df 100644 Binary files a/site2/docs/assets/pulsar-functions-word-count.png and b/site2/docs/assets/pulsar-functions-word-count.png differ diff --git a/site2/docs/client-libraries-cpp.md b/site2/docs/client-libraries-cpp.md index 6656122355266..ddfba789399c3 100644 --- a/site2/docs/client-libraries-cpp.md +++ b/site2/docs/client-libraries-cpp.md @@ -4,10 +4,16 @@ title: Pulsar C++ client sidebar_label: C++ --- +You can use Pulsar C++ client to create Pulsar producers and consumers in C++. + +All the methods in producer, consumer, and reader of a C++ client are thread-safe. + ## Supported platforms Pulsar C++ client is supported on **Linux** and **MacOS** platforms. +[Doxygen](http://www.doxygen.nl/)-generated API docs for the C++ client are available [here](/api/cpp). + ## Linux > Since 2.1.0 release, Pulsar ships pre-built RPM and Debian packages. You can download and install those packages directly. diff --git a/site2/docs/client-libraries-go.md b/site2/docs/client-libraries-go.md index 716efed9638c9..bdeb032b30d6e 100644 --- a/site2/docs/client-libraries-go.md +++ b/site2/docs/client-libraries-go.md @@ -1,10 +1,19 @@ --- id: client-libraries-go -title: The Pulsar Go client +title: Pulsar Go client sidebar_label: Go --- -The Pulsar Go client can be used to create Pulsar [producers](#producers), [consumers](#consumers), and [readers](#readers) in Go (aka Golang). +You can use Pulsar Go client to create Pulsar [producers](#producers), [consumers](#consumers), and [readers](#readers) in Go (aka Golang). + +All the methods in [producers](#producers), [consumers](#consumers), and [readers](#readers) of a Go client are thread-safe. + +Currently, the following Go clients are maintained in two repositories. + +| Language | Project | Maintainer | License | Description | +|----------|---------|------------|---------|-------------| +| CGo | [pulsar-client-go](https://github.com/apache/pulsar/tree/master/pulsar-client-go) | [Apache Pulsar](https://github.com/apache/pulsar) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | CGo client that depends on C++ client library | +| Go | [pulsar-client-go](https://github.com/apache/pulsar-client-go) | [Apache Pulsar](https://github.com/apache/pulsar) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | A native golang client | > #### API docs available as well > For standard API docs, consult the [Godoc](https://godoc.org/github.com/apache/pulsar/pulsar-client-go/pulsar). @@ -15,10 +24,9 @@ The Pulsar Go client can be used to create Pulsar [producers](#producers), [cons ### Requirements Pulsar Go client library is based on the C++ client library. Follow -the instructions for [C++ library](client-libraries-cpp.md) for installing the binaries -through [RPM](client-libraries-cpp.md#rpm), [Deb](client-libraries-cpp.md#deb) or [Homebrew packages](client-libraries-cpp.md#macos). +the instructions for [C++ library](client-libraries-cpp.md) for installing the binaries through [RPM](client-libraries-cpp.md#rpm), [Deb](client-libraries-cpp.md#deb) or [Homebrew packages](client-libraries-cpp.md#macos). -### Installing go package +### Install go package > #### Compatibility Warning > The version number of the Go client **must match** the version number of the Pulsar C++ client library. @@ -63,7 +71,7 @@ If you're using [TLS](security-tls-authentication.md) authentication, the URL wi pulsar+ssl://pulsar.us-west.example.com:6651 ``` -## Creating a client +## Create a client In order to interact with Pulsar, you'll first need a `Client` object. You can create a client object using the `NewClient` function, passing in a `ClientOptions` object (more on configuration [below](#client-configuration)). Here's an example: @@ -433,8 +441,8 @@ reader, err := client.CreateReader(pulsar.ReaderOptions{ Parameter | Description | Default :---------|:------------|:------- -`Topic` | The Pulsar [topic](reference-terminology.md#topic) on which the reader will establish a subscription and listen for messages | -`Name` | The name of the reader | +`Topic` | The Pulsar [topic](reference-terminology.md#topic) on which the reader will establish a subscription and listen for messages +`Name` | The name of the reader `StartMessageID` | THe initial reader position, i.e. the message at which the reader begins processing messages. The options are `pulsar.EarliestMessage` (the earliest available message on the topic), `pulsar.LatestMessage` (the latest available message on the topic), or a `MessageID` object for a position that isn't earliest or latest. | `MessageChannel` | The Go channel used by the reader. Messages that arrive from the Pulsar topic(s) will be passed to this channel. | `ReceiverQueueSize` | Sets the size of the reader's receiver queue, i.e. the number of messages that can be accumulated by the reader before the application calls `Next`. A value higher than the default of 1000 could increase reader throughput, though at the expense of more memory utilization. | 1000 diff --git a/site2/docs/client-libraries-java.md b/site2/docs/client-libraries-java.md index e9bddcbbefe60..dabe0344b9334 100644 --- a/site2/docs/client-libraries-java.md +++ b/site2/docs/client-libraries-java.md @@ -4,7 +4,9 @@ title: Pulsar Java client sidebar_label: Java --- -You can use Pulsar Java client to create Java producers, consumers, and [readers](#reader-interface) of messages and to perform [administrative tasks](admin-api-overview.md). The current version of the Java client is **{{pulsar:version}}**. +You can use Pulsar Java client to create Java [producer](#producer), [consumer](#consumer), and [readers](#reader-interface) of messages and to perform [administrative tasks](admin-api-overview.md). The current version of the Java client is **{{pulsar:version}}**. + +All the methods in [producer](#producer), [consumer](#consumer), and [reader](#reader) of a Java client are thread-safe. Javadoc for the Pulsar client is divided into two domains by package as follows. diff --git a/site2/docs/client-libraries-node.md b/site2/docs/client-libraries-node.md index 130f7a058d667..ed7e3803ca024 100644 --- a/site2/docs/client-libraries-node.md +++ b/site2/docs/client-libraries-node.md @@ -6,6 +6,8 @@ sidebar_label: Node.js The Pulsar Node.js client can be used to create Pulsar [producers](#producers), [consumers](#consumers), and [readers](#readers) in Node.js. +All the methods in [producers](#producers), [consumers](#consumers), and [readers](#readers) of a Node.js client are thread-safe. + ## Installation You can install the [`pusar-client`](https://www.npmjs.com/package/pulsar-client) library via [npm](https://www.npmjs.com/). @@ -284,7 +286,7 @@ Pulsar Node.js readers have the following methods available: | :----- | :---------- | :---------- | | `readNext()` | Receives the next message on the topic (analogous to the `receive` method for [consumers](#consumer-operations)). When the message is available, the Promise object run executor function and get message object. | `Promise` | | `readNext(Number)` | Receives a single message from the topic with specific timeout in milliseconds. | `Promise` | -| `hasNext()` | Return whether Proker has next message in target topic. | `Boolean` | +| `hasNext()` | Return whether the broker has next message in target topic. | `Boolean` | | `close()` | Closes the reader, disabling its ability to receive messages from the broker. | `Promise` | ### Reader configuration diff --git a/site2/docs/client-libraries-python.md b/site2/docs/client-libraries-python.md index 2e93e96a753a9..4d5c0b0c495d2 100644 --- a/site2/docs/client-libraries-python.md +++ b/site2/docs/client-libraries-python.md @@ -1,16 +1,20 @@ --- id: client-libraries-python -title: The Pulsar Python client +title: Pulsar Python client sidebar_label: Python --- -The Pulsar Python client library is a wrapper over the existing [C++ client library](client-libraries-cpp.md) and exposes all of the [same features](/api/cpp). You can find the code in the [`python` subdirectory](https://github.com/apache/pulsar/tree/master/pulsar-client-cpp/python) of the C++ client code. +Pulsar Python client library is a wrapper over the existing [C++ client library](client-libraries-cpp.md) and exposes all of the [same features](/api/cpp). You can find the code in the [`python` subdirectory](https://github.com/apache/pulsar/tree/master/pulsar-client-cpp/python) of the C++ client code. -## Installation +All the methods in producer, consumer, and reader of a Python client are thread-safe. + +[pdoc](https://github.com/BurntSushi/pdoc)-generated API docs for the Python client are available [here](/api/python). + +## Install You can install the [`pulsar-client`](https://pypi.python.org/pypi/pulsar-client) library either via [PyPi](https://pypi.python.org/pypi), using [pip](#installation-using-pip), or by building the library from source. -### Installation using pip +### Install using pip To install the `pulsar-client` library as a pre-built package using the [pip](https://pip.pypa.io/en/stable/) package manager: @@ -25,9 +29,9 @@ Platform | Supported Python versions MacOS
10.13 (High Sierra), 10.14 (Mojave)
| 2.7, 3.7 Linux | 2.7, 3.4, 3.5, 3.6, 3.7 -### Installing from source +### Install from source -To install the `pulsar-client` library by building from source, follow [these instructions](client-libraries-cpp.md#compilation) and compile the Pulsar C++ client library. That will also build the Python binding for the library. +To install the `pulsar-client` library by building from source, follow [instructions](client-libraries-cpp.md#compilation) and compile the Pulsar C++ client library. That builds the Python binding for the library. To install the built Python bindings: @@ -43,11 +47,11 @@ The complete Python API reference is available at [api/python](/api/python). ## Examples -Below you'll find a variety of Python code examples for the `pulsar-client` library. +You can find a variety of Python code examples for the `pulsar-client` library. ### Producer example -This creates a Python producer for the `my-topic` topic and send 10 messages on that topic: +The following example creates a Python producer for the `my-topic` topic and sends 10 messages on that topic: ```python import pulsar @@ -64,7 +68,7 @@ client.close() ### Consumer example -This creates a consumer with the `my-subscription` subscription on the `my-topic` topic, listen for incoming messages, print the content and ID of messages that arrive, and acknowledge each message to the Pulsar broker: +The following example creates a consumer with the `my-subscription` subscription name on the `my-topic` topic, receives incoming messages, prints the content and ID of messages that arrive, and acknowledges each message to the Pulsar broker. ```python consumer = client.subscribe('my-topic', 'my-subscription') @@ -97,13 +101,32 @@ while True: print("Received message '{}' id='{}'".format(msg.data(), msg.message_id())) # No acknowledgment ``` +### Multi-topic subscriptions + +In addition to subscribing a consumer to a single Pulsar topic, you can also subscribe to multiple topics simultaneously. To use multi-topic subscriptions, you can supply a regular expression (regex) or a `List` of topics. If you select topics via regex, all topics must be within the same Pulsar namespace. + +The following is an example. +```python +import re +consumer = client.subscribe(re.compile('persistent://public/default/topic-*'), 'my-subscription') +while True: + msg = consumer.receive() + try: + print("Received message '{}' id='{}'".format(msg.data(), msg.message_id())) + # Acknowledge successful processing of the message + consumer.acknowledge(msg) + except: + # Message failed to be processed + consumer.negative_acknowledge(msg) +client.close() +``` ## Schema -### Declaring and validating schema +### Declare and validate schema -A schema can be declared by passing a class that inherits +You can declare a schema by passing a class that inherits from `pulsar.schema.Record` and defines the fields as class variables. For example: @@ -116,8 +139,7 @@ class Example(Record): c = Boolean() ``` -With this simple schema definition we can then create producers, -consumers and readers instances that will be referring to that. +With this simple schema definition, you can create producers, consumers and readers instances that refer to that. ```python producer = client.create_producer( @@ -127,13 +149,9 @@ producer = client.create_producer( producer.send(Example(a='Hello', b=1)) ``` -When the producer is created, the Pulsar broker will validate that -the existing topic schema is indeed of "Avro" type and that the -format is compatible with the schema definition of the `Example` -class. +After creating the producer, the Pulsar broker validates that the existing topic schema is indeed of "Avro" type and that the format is compatible with the schema definition of the `Example` class. -If there is a mismatch, the producer creation will raise an -exception. +If there is a mismatch, an exception occurs in the producer creation. Once a producer is created with a certain schema definition, it will only accept objects that are instances of the declared @@ -163,8 +181,7 @@ while True: ### Supported schema types -There are different builtin schema types that can be used in Pulsar. -All the definitions are in the `pulsar.schema` package. +You can use different builtin schema types in Pulsar. All the definitions are in the `pulsar.schema` package. | Schema | Notes | | ------ | ----- | @@ -175,12 +192,11 @@ All the definitions are in the `pulsar.schema` package. ### Schema definition reference -The schema definition is done through a class that inherits from -`pulsar.schema.Record`. +The schema definition is done through a class that inherits from `pulsar.schema.Record`. -This class can have a number of fields which can be of either -`pulsar.schema.Field` type or even another nested `Record`. All the -fields are also specified in the `pulsar.schema` package. The fields +This class has a number of fields which can be of either +`pulsar.schema.Field` type or another nested `Record`. All the +fields are specified in the `pulsar.schema` package. The fields are matching the AVRO fields types. | Field Type | Python Type | Notes | @@ -192,20 +208,19 @@ are matching the AVRO fields types. | `Double` | `float` | | | `Bytes` | `bytes` | | | `String` | `str` | | -| `Array` | `list` | Need to specify record type for items | -| `Map` | `dict` | Key is always `String`. Need to specify value type | +| `Array` | `list` | Need to specify record type for items. | +| `Map` | `dict` | Key is always `String`. Need to specify value type. | -Additionally, any Python `Enum` type can be used as a valid field -type +Additionally, any Python `Enum` type can be used as a valid field type. #### Fields parameters -When adding a field these parameters can be used in the constructor: +When adding a field, you can use these parameters in the constructor. | Argument | Default | Notes | | ---------- | --------| ----- | | `default` | `None` | Set a default value for the field. Eg: `a = Integer(default=5)` | -| `required` | `False` | Mark the field as "required". This will set it in the schema accordingly. | +| `required` | `False` | Mark the field as "required". It is set in the schema accordingly. | #### Schema definition examples diff --git a/site2/docs/concepts-messaging.md b/site2/docs/concepts-messaging.md index 0ab09e7984acf..4d2edcd75531d 100644 --- a/site2/docs/concepts-messaging.md +++ b/site2/docs/concepts-messaging.md @@ -49,12 +49,21 @@ Messages published by producers can be compressed during transportation in order ### Batching -If batching is enabled, the producer will accumulate and send a batch of messages in a single request. Batching size is defined by the maximum number of messages and maximum publish latency. +If batching is enabled, the producer will accumulate and send a batch of messages in a single request. Batch size is defined by the maximum number of messages and maximum publish latency. + +Batches are tracked and stored by Pulsar as batches rather than as individual messages. Under the hood the consumer unbundles these batches into individual messages. Since the messages are stored as batches, the backlog size will also represent the total number of batches rather than the total number of messages. + +Scheduled messages (using `deliverAt` or `deliverAfter`) are always sent as individual messages even when batching is enabled. + +> Note +> Since batches are tracked as single units, a batch will only be considered acknowledged when all its messages are acknowledged by the consumer. This means unexpected failures, negative acknowledgements, and acknowledgement timeouts can result in redelivery of all messages in the batch, even if some of the messages have already been acknowledged. ## Consumers A consumer is a process that attaches to a topic via a subscription and then receives messages. +A consumer sends a [flow permit request](developing-binary-protocol.md#flow-control) to a broker to get messages. There is a queue at the consumer side to receive messages pushed from the broker. The queue size is configurable by [`receiverQueueSize`](client-libraries-java.md#configure-consumer) (default: 1000). Each time `consumer.receive()` is called, a message is dequeued from the buffer. + ### Receive modes Messages can be received from [brokers](reference-terminology.md#broker) either synchronously (sync) or asynchronously (async). @@ -70,7 +79,7 @@ Client libraries provide listener implementation for consumers. For example, the ### Acknowledgement -When a consumer has consumed a message successfully, the consumer sends an acknowledgement request to the broker, so that the broker will discard the message. Otherwise, it [stores](concepts-architecture-overview.md#persistent-storage) the message. +When a consumer has consumed a message successfully, the consumer sends an acknowledgement request to the broker. This message is permanently [stored](concepts-architecture-overview.md#persistent-storage) and then it is deleted only after all the subscriptions have acknowledged it. If you want to store the message that has been acknowledged by a consumer, you need to configure the [message retention policy](concepts-messaging.md#message-retention-and-expiry). Messages can be acknowledged either one by one or cumulatively. With cumulative acknowledgement, the consumer only needs to acknowledge the last message it received. All messages in the stream up to (and including) the provided message will not be re-delivered to that consumer. @@ -89,12 +98,18 @@ In the exclusive and failover subscription modes, consumers only negatively ackn In the shared and Key_Shared subscription modes, you can negatively acknowledge messages individually. +> Note +> If batching is enabled, other messages in the same batch may be redelivered to the consumer as well as the negatively acknowledged messages. + ### Acknowledgement timeout When a message is not consumed successfully, and you want to trigger the broker to redeliver the message automatically, you can adopt the unacknowledged message automatic re-delivery mechanism. Client will track the unacknowledged messages within the entire `acktimeout` time range, and send a `redeliver unacknowledged messages` request to the broker automatically when the acknowledgement timeout is specified. +> Note +> If batching is enabled, other messages in the same batch may be redelivered to the consumer as well as the unacknowledged messages. + > Note -> Use negative acknowledgement prior to acknowledgement timeout. Negative acknowledgement controls re-delivery of individual messages with more precise, and avoids invalid redeliveries when the message processing time exceeds the acknowledgement timeout. +> Prefer negative acknowledgements over acknowledgement timeout. Negative acknowledgement controls the re-delivery of individual messages with more precision, and avoids invalid redeliveries when the message processing time exceeds the acknowledgement timeout. ### Dead letter topic @@ -161,12 +176,18 @@ Topic name component | Description A namespace is a logical nomenclature within a tenant. A tenant can create multiple namespaces via the [admin API](admin-api-namespaces.md#create). For instance, a tenant with different applications can create a separate namespace for each application. A namespace allows the application to create and manage a hierarchy of topics. The topic `my-tenant/app1` is a namespace for the application `app1` for `my-tenant`. You can create any number of [topics](#topics) under the namespace. -## Subscription modes +## Subscriptions -A subscription is a named configuration rule that determines how messages are delivered to consumers. Four subscription modes are available in Pulsar: [exclusive](#exclusive), [shared](#shared), [failover](#failover) and [Key_Shared](#key_shared). These modes are illustrated in the figure below. +A subscription is a named configuration rule that determines how messages are delivered to consumers. There are four available subscription modes in Pulsar: [exclusive](#exclusive), [shared](#shared), [failover](#failover), and [key_shared](#key_shared). These modes are illustrated in the figure below. ![Subscription modes](assets/pulsar-subscription-modes.png) +> #### Pub-Sub, Queuing, or Both +> There is a lot of flexibility in how to combine subscriptions: +> * If you want to achieve traditional "fan-out pub-sub messaging" among consumers, you can make each consumer have a unique subscription name (exclusive) +> * If you want to achieve "message queuing" among consumers, you can make multiple consumers have the same subscription name (shared, failover, key_shared) +> * If you want to do both simultaneously, you can have some consumers with exclusive subscriptions while others do not + ### Exclusive In *exclusive* mode, only a single consumer is allowed to attach to the subscription. If more than one consumer attempts to subscribe to a topic using the same subscription, the consumer receives an error. @@ -224,8 +245,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: @@ -268,7 +289,7 @@ The diagram below illustrates this: Here, the topic **Topic1** has five partitions (**P0** through **P4**) split across three brokers. Because there are more partitions than brokers, two brokers handle two partitions a piece, while the third handles only one (again, Pulsar handles this distribution of partitions automatically). -Messages for this topic are broadcast to two consumers. The [routing mode](#routing-modes) determines both which broker handles each partition, while the [subscription mode](#subscription-modes) determines which messages go to which consumers. +Messages for this topic are broadcast to two consumers. The [routing mode](#routing-modes) determines each message should be published to which partition, while the [subscription mode](#subscription-modes) determines which messages go to which consumers. Decisions about routing and subscription modes can be made separately in most cases. In general, throughput concerns should guide partitioning/routing decisions while subscription decisions should be guided by application semantics. @@ -439,4 +460,4 @@ The following is an example of delayed message delivery for a producer in Java: ```java // message to be delivered at the configured delay interval producer.newMessage().deliverAfter(3L, TimeUnit.Minute).value("Hello Pulsar!").send(); -``` \ No newline at end of file +``` diff --git a/site2/docs/cookbooks-partitioned.md b/site2/docs/cookbooks-partitioned.md index 76b6ed657b6a8..555d3c8824351 100644 --- a/site2/docs/cookbooks-partitioned.md +++ b/site2/docs/cookbooks-partitioned.md @@ -4,27 +4,25 @@ title: Partitioned topics sidebar_label: Partitioned Topics --- -By default, Pulsar topics are served by a single broker. Using only a single broker, however, limits a topic's maximum throughput. *Partitioned topics* are a special type of topic that can span multiple brokers and thus allow for much higher throughput. For an explanation of how partitioned topics work, see the [Partitioned Topics](concepts-messaging.md#partitioned-topics) concepts. +By default, Pulsar topics are served by a single broker. Using only a single broker limits a topic's maximum throughput. *Partitioned topics* are a special type of topic that can span multiple brokers and thus allow for much higher throughput. For an explanation of how partitioned topics work, see the [Partitioned Topics](concepts-messaging.md#partitioned-topics) concepts. -You can [publish](#publishing-to-partitioned-topics) to partitioned topics using Pulsar's client libraries and you can [create and manage](#managing-partitioned-topics) partitioned topics using Pulsar's [admin API](admin-api-overview.md). +You can publish to partitioned topics using Pulsar client libraries and you can [create and manage](#managing-partitioned-topics) partitioned topics using Pulsar [admin API](admin-api-overview.md). -## Publishing to partitioned topics +## Publish to partitioned topics -When publishing to partitioned topics, the only difference from non-partitioned topics is that you need to specify a [routing mode](concepts-messaging.md#routing-modes) when you create a new [producer](reference-terminology.md#producer). Examples for [Java](#java) are below. - -### Java +When publishing to partitioned topics, you do not need to explicitly specify a [routing mode](concepts-messaging.md#routing-modes) when you create a new producer. If you do not specify a routing mode, the round robin route mode is used. Take [Java](#java) as an example. Publishing messages to partitioned topics in the Java client works much like [publishing to normal topics](client-libraries-java.md#using-producers). The difference is that you need to specify either one of the currently available message routers or a custom router. -#### Routing mode +### Routing mode -You can specify the routing mode in the ProducerConfiguration object that you use to configure your producer. You have three options: +You can specify the routing mode in the ProducerConfiguration object that you use to configure your producer. Three options are available: * `SinglePartition` * `RoundRobinPartition` * `CustomPartition` -Here's an example: +The following is an example: ```java String pulsarBrokerRootUrl = "pulsar://localhost:6650"; @@ -38,7 +36,7 @@ Producer producer = pulsarClient.newProducer() producer.send("Partitioned topic message".getBytes()); ``` -#### Custom message router +### Custom message router To use a custom message router, you need to provide an implementation of the {@inject: javadoc:MessageRouter:/client/org/apache/pulsar/client/api/MessageRouter} interface, which has just one `choosePartition` method: @@ -48,7 +46,7 @@ public interface MessageRouter extends Serializable { } ``` -Here's a (not very useful!) router that routes every message to partition 10: +The following router routes every message to partition 10: ```java public class AlwaysTenRouter implements MessageRouter { @@ -72,6 +70,23 @@ Producer producer = pulsarClient.newProducer() producer.send("Partitioned topic message".getBytes()); ``` -## Managing partitioned topics +### How to choose partitions when using a key +If a message has a key, it supersedes the round robin routing policy. The following example illustrates how to choose partition when you use a key. -You can use Pulsar's [admin API](admin-api-overview.md) to create and manage [partitioned topics](admin-api-partitioned-topics.md). +```java +// If the message has a key, it supersedes the round robin routing policy + if (msg.hasKey()) { + return signSafeMod(hash.makeHash(msg.getKey()), topicMetadata.numPartitions()); + } + + if (isBatchingEnabled) { // if batching is enabled, choose partition on `partitionSwitchMs` boundary. + long currentMs = clock.millis(); + return signSafeMod(currentMs / partitionSwitchMs + startPtnIdx, topicMetadata.numPartitions()); + } else { + return signSafeMod(PARTITION_INDEX_UPDATER.getAndIncrement(this), topicMetadata.numPartitions()); + } +``` + +## Manage partitioned topics + +You can use Pulsar [admin API](admin-api-overview.md) to create and manage [partitioned topics](admin-api-partitioned-topics.md). \ No newline at end of file diff --git a/site2/docs/developing-binary-protocol.md b/site2/docs/developing-binary-protocol.md index e9e5252e45d31..6238be94e8497 100644 --- a/site2/docs/developing-binary-protocol.md +++ b/site2/docs/developing-binary-protocol.md @@ -288,8 +288,11 @@ A `Flow` command gives additional *permits* to send messages to the consumer. A typical consumer implementation will use a queue to accumulate these messages before the application is ready to consume them. -After the application has dequeued a number of message, the consumer will -send additional number of permits to allow the broker to push more messages. +After the application has dequeued half of the messages in the queue, the consumer +sends permits to the broker to ask for more messages (equals to half of the messages in the queue). + +For example, if the queue size is 1000 and the consumer consumes 500 messages in the queue. +Then the consumer sends permits to the broker to ask for 500 messages. ##### Command Subscribe diff --git a/site2/docs/functions-debug.md b/site2/docs/functions-debug.md index fc1c2be54bc82..931c73761659b 100644 --- a/site2/docs/functions-debug.md +++ b/site2/docs/functions-debug.md @@ -37,6 +37,9 @@ public class JavaNativeExclamationFunction implements Function { You can write a simple unit test to test Pulsar Function. +> #### Tip +> Pulsar uses testng for testing. + ```java @Test public void testJavaNativeExclamationFunction() { @@ -62,6 +65,9 @@ public class ExclamationFunction implements Function { In this situation, you can write a unit test for this function as well. Remember to mock the `Context` parameter. The following is an example. +> #### Tip +> Pulsar uses testng for testing. + ```java @Test public void testExclamationFunction() { diff --git a/site2/docs/functions-develop.md b/site2/docs/functions-develop.md index 162010655632e..43f43a87fe710 100644 --- a/site2/docs/functions-develop.md +++ b/site2/docs/functions-develop.md @@ -468,7 +468,7 @@ For complete code, see [here](https://github.com/apache/pulsar/blob/master/pulsa ### User config -When you run or update Pulsar Functions created using SDK, you can pass arbitrary key/values to them with the command line with the `--userConfig` flag. Key/values must be specified as JSON. The following function creation command passes a user configured key/value to a function. +When you run or update Pulsar Functions created using SDK, you can pass arbitrary key/values to them with the command line with the `--user-config` flag. Key/values must be specified as JSON. The following function creation command passes a user configured key/value to a function. ```bash $ bin/pulsar-admin functions create \ diff --git a/site2/docs/functions-overview.md b/site2/docs/functions-overview.md index 37869c0279ff5..dcdb97a1029cd 100644 --- a/site2/docs/functions-overview.md +++ b/site2/docs/functions-overview.md @@ -12,7 +12,7 @@ sidebar_label: Overview ## Goals -With Pulsar Functions, you can create complex processing logic without deploying a separate neighboring system (such as [Apache Storm](http://storm.apache.org/), [Apache Heron](https://apache.github.io/incubator-heron), [Apache Flink](https://flink.apache.org/)). Pulsar Functions are computing infrastructure of Pulsar messaging system. The core goal is tied to a series of other goals: +With Pulsar Functions, you can create complex processing logic without deploying a separate neighboring system (such as [Apache Storm](http://storm.apache.org/), [Apache Heron](https://heron.incubator.apache.org/), [Apache Flink](https://flink.apache.org/)). Pulsar Functions are computing infrastructure of Pulsar messaging system. The core goal is tied to a series of other goals: * Developer productivity (language-native vs Pulsar Functions SDK functions) * Easy troubleshooting @@ -164,29 +164,21 @@ Delivery semantics | Description ### Apply processing guarantees to a function -You can set the processing guarantees for a Pulsar Function when you create the Function. The [`pulsar-function create`](reference-pulsar-admin.md#create-1) command applies effectively-once guarantees to the Function. +You can set the processing guarantees for a Pulsar Function when you create the Function. The following [`pulsar-function create`](reference-pulsar-admin.md#create-1) command creates a function with effectively-once guarantees applied. ```bash $ bin/pulsar-admin functions create \ + --name my-effectively-once-function \ --processing-guarantees EFFECTIVELY_ONCE \ # Other function configs ``` -The available options are: +The available options for `--processing-guarantees` are: * `ATMOST_ONCE` * `ATLEAST_ONCE` * `EFFECTIVELY_ONCE` -The following command runs a function in the cluster mode with effectively-once guarantees applied. - -```bash -$ bin/pulsar-admin functions create \ - --name my-effectively-once-function \ - --processing-guarantees EFFECTIVELY_ONCE \ - # Other function configs -``` - > By default, Pulsar Functions provide at-least-once delivery guarantees. So if you create a function without supplying a value for the `--processingGuarantees` flag, the function provides at-least-once guarantees. ### Update the processing guarantees of a function @@ -196,4 +188,4 @@ You can change the processing guarantees applied to a function using the [`updat $ bin/pulsar-admin functions update \ --processing-guarantees ATMOST_ONCE \ # Other function configs -``` \ No newline at end of file +``` diff --git a/site2/docs/getting-started-clients.md b/site2/docs/getting-started-clients.md index 3eb6fe5211186..b7c2d550315ce 100644 --- a/site2/docs/getting-started-clients.md +++ b/site2/docs/getting-started-clients.md @@ -6,46 +6,19 @@ sidebar_label: Use Pulsar with client libraries Pulsar supports the following client libraries: -- [Java client](#java-client) -- [Go client](#go-client) -- [Python client](#python-client) -- [C++ client](#c-client) - -## Java client - -For instructions on how to use the Pulsar Java client to produce and consume messages, see [Pulsar Java client](client-libraries-java.md). - -Two independent sets of Javadoc API docs are available. - -Library | Purpose -:-------|:------- -[`org.apache.pulsar.client.api`](/api/client) | The [Pulsar Java client](client-libraries-java.md) is used to produce and consume messages on Pulsar topics. -[`org.apache.pulsar.client.admin`](/api/admin) | The Java client for the [Pulsar admin interface](admin-api-overview.md). - - -## Go client - -For a tutorial on using the Pulsar Go client, see [Pulsar Go client](client-libraries-go.md). - - -## Python client - -For a tutorial on using the Pulsar Python client, see [Pulsar Python client](client-libraries-python.md). - -There are also [pdoc](https://github.com/BurntSushi/pdoc)-generated API docs for the Python client [here](/api/python). - -## C++ client - -For a tutorial on using the Pulsar C++ clent, see [Pulsar C++ client](client-libraries-cpp.md). - -There are also [Doxygen](http://www.doxygen.nl/)-generated API docs for the C++ client [here](/api/cpp). - -## Feature Matrix +- [Java client](client-libraries-java.md) +- [Go client](client-libraries-go.md) +- [Python client](client-libraries-python.md) +- [C++ client](client-libraries-cpp.md) +- [Node.js client](client-libraries-node.md) +- [WebSocket client](client-libraries-websocket.md) + +## Feature matrix Pulsar client feature matrix for different languages is listed on [Client Features Matrix](https://github.com/apache/pulsar/wiki/Client-Features-Matrix) page. -## Thirdparty Clients +## Third-party clients -Besides the official released clients, there are also multiple projects on developing a Pulsar client in different languages. +Besides the official released clients, multiple projects on developing Pulsar clients are available in different languages. > If you have developed a new Pulsar client, feel free to submit a pull request and add your client to the list below. @@ -55,4 +28,4 @@ Besides the official released clients, there are also multiple projects on devel | Go | [go-pulsar](https://github.com/t2y/go-pulsar) | [t2y](https://github.com/t2y) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | | | Scala | [pulsar4s](https://github.com/sksamuel/pulsar4s) | [sksamuel](https://github.com/sksamuel) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | Idomatic, typesafe, and reactive Scala client for Apache Pulsar | | Rust | [pulsar-rs](https://github.com/wyyerd/pulsar-rs) | [Wyyerd Group](https://github.com/wyyerd) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | Future-based Rust bindings for Apache Pulsar | -| .NET | [pulsar-client-dotnet](https://github.com/fsharplang-ru/pulsar-client-dotnet) | [Lanayx](https://github.com/Lanayx) | [![GitHub](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) | Native .NET client for C#/F#/VB | +| .NET | [pulsar-client-dotnet](https://github.com/fsharplang-ru/pulsar-client-dotnet) | [Lanayx](https://github.com/Lanayx) | [![GitHub](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) | Native .NET client for C#/F#/VB | \ No newline at end of file diff --git a/site2/docs/io-connectors.md b/site2/docs/io-connectors.md index c2e7f580d6a81..a34cf540d0759 100644 --- a/site2/docs/io-connectors.md +++ b/site2/docs/io-connectors.md @@ -37,6 +37,14 @@ Pulsar has various source connectors, which are sorted alphabetically as below. * [Java class](https://github.com/apache/pulsar/blob/master/pulsar-io/debezium/postgres/src/main/java/org/apache/pulsar/io/debezium/postgres/DebeziumPostgresSource.java) +### Debezium MongoDB + +* [Configuration](io-debezium-source.md#configuration) + +* [Example](io-debezium-source.md#example-of-mongodb) + +* [Java class](https://github.com/apache/pulsar/blob/master/pulsar-io/debezium/mongodb/src/main/java/org/apache/pulsar/io/debezium/mongodb/DebeziumMongoDbSource.java) + ### File @@ -120,7 +128,7 @@ Pulsar has various sink connectors, which are sorted alphabetically as below. ### HBase -* [Configuration](io-hbase.md) +* [Configuration](io-hbase-sink.md#configuration) * [Java class](https://github.com/apache/pulsar/blob/master/pulsar-io/hbase/src/main/java/org/apache/pulsar/io/hbase/HbaseAbstractConfig.java) diff --git a/site2/docs/io-debezium-source.md b/site2/docs/io-debezium-source.md index 7bdb1e9dc90e7..29526e4e2ca7e 100644 --- a/site2/docs/io-debezium-source.md +++ b/site2/docs/io-debezium-source.md @@ -4,11 +4,12 @@ title: Debezium source onnector sidebar_label: Debezium source connector --- -The Debezium source connector pulls messages from MySQL or PostgreSQL to Pulsar topics. +The Debezium source connector pulls messages from MySQL or PostgreSQL +and persists the messages to Pulsar topics. ## Configuration -The configuration of the Debezium source connector has the following properties. +The configuration of Debezium source connector has the following properties. | Name | Required | Default | Description | |------|----------|---------|-------------| @@ -27,6 +28,16 @@ The configuration of the Debezium source connector has the following properties. | `database.history.pulsar.service.url` | true | null | Pulsar cluster service URL for history topic. | | `pulsar.service.url` | true | null | Pulsar cluster service URL. | | `offset.storage.topic` | true | null | Record the last committed offsets that the connector successfully completes. | +### MongoDB Configuration +| Name | Required | Default | Description | +|------|----------|---------|-------------| +| `mongodb.hosts` | true | null | The comma-separated list of hostname and port pairs (in the form 'host' or 'host:port') of the MongoDB servers in the replica set. The list contains a single hostname and a port pair. If mongodb.members.auto.discover is set to false, the host and port pair are prefixed with the replica set name (e.g., rs0/localhost:27017). | +| `mongodb.name` | true | null | A unique name that identifies the connector and/or MongoDB replica set or shared cluster that this connector monitors. Each server should be monitored by at most one Debezium connector, since this server name prefixes all persisted Kafka topics emanating from the MongoDB replica set or cluster. | +| `mongodb.user` | true | null | Name of the database user to be used when connecting to MongoDB. This is required only when MongoDB is configured to use authentication. | +| `mongodb.password` | true | null | Password to be used when connecting to MongoDB. This is required only when MongoDB is configured to use authentication. | +| `mongodb.task.id` | true | null | The taskId of the MongoDB connector that attempts to use a separate task for each replica set. | + + ## Example of MySQL @@ -270,7 +281,7 @@ This example shows how to change the data of a PostgreSQL table using the Pulsar $ docker exec -it pulsar-postgresql /bin/bash ``` -6. A MySQL client pops out. +6. A PostgreSQL client pops out. Use the following commands to change the data of the table _products_. @@ -304,7 +315,124 @@ This example shows how to change the data of a PostgreSQL table using the Pulsar ----- got message ----- {"schema":{"type":"struct","fields":[{"type":"int32","optional":false,"field":"id"}],"optional":false,"name":"dbserver1.inventory.products.Key"},"payload":{"id":107}}�{"schema":{"type":"struct","fields":[{"type":"struct","fields":[{"type":"int32","optional":false,"field":"id"},{"type":"string","optional":false,"field":"name"},{"type":"string","optional":true,"field":"description"},{"type":"double","optional":true,"field":"weight"}],"optional":true,"name":"dbserver1.inventory.products.Value","field":"before"},{"type":"struct","fields":[{"type":"int32","optional":false,"field":"id"},{"type":"string","optional":false,"field":"name"},{"type":"string","optional":true,"field":"description"},{"type":"double","optional":true,"field":"weight"}],"optional":true,"name":"dbserver1.inventory.products.Value","field":"after"},{"type":"struct","fields":[{"type":"string","optional":true,"field":"version"},{"type":"string","optional":true,"field":"connector"},{"type":"string","optional":false,"field":"name"},{"type":"string","optional":false,"field":"db"},{"type":"int64","optional":true,"field":"ts_usec"},{"type":"int64","optional":true,"field":"txId"},{"type":"int64","optional":true,"field":"lsn"},{"type":"string","optional":true,"field":"schema"},{"type":"string","optional":true,"field":"table"},{"type":"boolean","optional":true,"default":false,"field":"snapshot"},{"type":"boolean","optional":true,"field":"last_snapshot_record"}],"optional":false,"name":"io.debezium.connector.postgresql.Source","field":"source"},{"type":"string","optional":false,"field":"op"},{"type":"int64","optional":true,"field":"ts_ms"}],"optional":false,"name":"dbserver1.inventory.products.Envelope"},"payload":{"before":{"id":107,"name":"rocks","description":"box of assorted rocks","weight":5.3},"after":{"id":107,"name":"1111111111","description":"box of assorted rocks","weight":5.3},"source":{"version":"0.9.2.Final","connector":"postgresql","name":"dbserver1","db":"postgres","ts_usec":1559208957661080,"txId":577,"lsn":23862872,"schema":"inventory","table":"products","snapshot":false,"last_snapshot_record":null},"op":"u","ts_ms":1559208957692}} ``` +## Example of MongoDB + +You need to create a configuration file before using the Pulsar Debezium connector. + +* JSON + + ```json + { + "mongodb.hosts": "rs0/mongodb:27017", + "mongodb.name": "dbserver1", + "mongodb.user": "debezium", + "mongodb.password": "dbz", + "mongodb.task.id": "1", + "database.whitelist": "inventory", + "pulsar.service.url": "pulsar://127.0.0.1:6650" + } + ``` + +* YAML + + You can create a `debezium-mongodb-source-config.yaml` file and copy the [contents](https://github.com/apache/pulsar/blob/master/pulsar-io/debezium/mongodb/src/main/resources/debezium-mongodb-source-config.yaml) below to the `debezium-mongodb-source-config.yaml` file. + + ```yaml + tenant: "public" + namespace: "default" + name: "debezium-mongodb-source" + topicName: "debezium-mongodb-topic" + archive: "connectors/pulsar-io-debezium-mongodb-{{pulsar:version}}.nar" + parallelism: 1 + + configs: + + ## config for pg, docker image: debezium/example-mongodb:0.10 + mongodb.hosts: "rs0/mongodb:27017", + mongodb.name: "dbserver1", + mongodb.user: "debezium", + mongodb.password: "dbz", + mongodb.task.id: "1", + database.whitelist: "inventory", + + ## PULSAR_SERVICE_URL_CONFIG + pulsar.service.url: "pulsar://127.0.0.1:6650" + ``` + +### Usage + +This example shows how to change the data of a MongoDB table using the Pulsar Debezium connector. + + +1. Start a MongoDB server with a database from which Debezium can capture changes. + + ```bash + $ docker pull debezium/example-mongodb:0.10 + $ docker run -d -it --rm --name pulsar-mongodb -e MONGODB_USER=mongodb -e MONGODB_PASSWORD=mongodb -p 27017:27017 debezium/example-mongodb:0.10 + ``` + Use the following commands to initialize the data. + + ``` bash + ./usr/local/bin/init-inventory.sh + ``` + If the local host cannot access the container network, you can update the file ```/etc/hosts``` and add a rule ```127.0.0.1 6 f114527a95f```. f114527a95f is container id, you can try to get by ```docker ps -a``` + + +2. Start a Pulsar service locally in standalone mode. + + ```bash + $ bin/pulsar standalone + ``` + +3. Start the Pulsar Debezium connector in local run mode using one of the following methods. + + * Use the **JSON** configuration file as shown previously. + + Make sure the nar file is available at `connectors/pulsar-io-mongodb-{{pulsar:version}}.nar`. + + ```bash + $ bin/pulsar-admin source localrun \ + --archive connectors/pulsar-io-debezium-mongodb-{{pulsar:version}}.nar \ + --name debezium-mongodb-source \ + --destination-topic-name debezium-mongodb-topic \ + --tenant public \ + --namespace default \ + --source-config '{"mongodb.hosts": "rs0/mongodb:27017","mongodb.name": "dbserver1","mongodb.user": "debezium","mongodb.password": "dbz","mongodb.task.id": "1","database.whitelist": "inventory","pulsar.service.url": "pulsar://127.0.0.1:6650"}' + ``` + + * Use the **YAML** configuration file as shown previously. + + ```bash + $ bin/pulsar-admin source localrun \ + --source-config-file debezium-mongodb-source-config.yaml + ``` + +4. Subscribe the topic _sub-products_ for the _inventory.products_ table. + + ``` + $ bin/pulsar-client consume -s "sub-products" public/default/dbserver1.inventory.products -n 0 + ``` + +5. Start a MongoDB client in docker. + + ```bash + $ docker exec -it pulsar-mongodb /bin/bash + ``` +6. A MongoDB client pops out. + + ```bash + mongo -u debezium -p dbz --authenticationDatabase admin localhost:27017/inventory + db.products.update({"_id":NumberLong(104)},{$set:{weight:1.25}}) + ``` + + In the terminal window of subscribing topic, you can receive the following messages. + + ```bash + ----- got message ----- + {"schema":{"type":"struct","fields":[{"type":"string","optional":false,"field":"id"}],"optional":false,"name":"dbserver1.inventory.products.Key"},"payload":{"id":"104"}}, value = {"schema":{"type":"struct","fields":[{"type":"string","optional":true,"name":"io.debezium.data.Json","version":1,"field":"after"},{"type":"string","optional":true,"name":"io.debezium.data.Json","version":1,"field":"patch"},{"type":"struct","fields":[{"type":"string","optional":false,"field":"version"},{"type":"string","optional":false,"field":"connector"},{"type":"string","optional":false,"field":"name"},{"type":"int64","optional":false,"field":"ts_ms"},{"type":"string","optional":true,"name":"io.debezium.data.Enum","version":1,"parameters":{"allowed":"true,last,false"},"default":"false","field":"snapshot"},{"type":"string","optional":false,"field":"db"},{"type":"string","optional":false,"field":"rs"},{"type":"string","optional":false,"field":"collection"},{"type":"int32","optional":false,"field":"ord"},{"type":"int64","optional":true,"field":"h"}],"optional":false,"name":"io.debezium.connector.mongo.Source","field":"source"},{"type":"string","optional":true,"field":"op"},{"type":"int64","optional":true,"field":"ts_ms"}],"optional":false,"name":"dbserver1.inventory.products.Envelope"},"payload":{"after":"{\"_id\": {\"$numberLong\": \"104\"},\"name\": \"hammer\",\"description\": \"12oz carpenter's hammer\",\"weight\": 1.25,\"quantity\": 4}","patch":null,"source":{"version":"0.10.0.Final","connector":"mongodb","name":"dbserver1","ts_ms":1573541905000,"snapshot":"true","db":"inventory","rs":"rs0","collection":"products","ord":1,"h":4983083486544392763},"op":"r","ts_ms":1573541909761}}. + ``` + ## FAQ ### Debezium postgres connector will hang when create snap diff --git a/site2/docs/io-develop.md b/site2/docs/io-develop.md index 27aa6770f0bf6..9c082d12f3c44 100644 --- a/site2/docs/io-develop.md +++ b/site2/docs/io-develop.md @@ -24,9 +24,9 @@ You can develop Pulsar source connectors and sink connectors. ### Source Developing a source connector is to implement the {@inject: github:`Source`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java} -interface, which means you need to implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java#L33} method and the {@inject: github:`record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java#L28} method. +interface, which means you need to implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java} method and the {@inject: github:`read`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java} method. -1. Implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java#L33} method. +1. Implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java} method. ```java /** @@ -48,7 +48,7 @@ interface, which means you need to implement the {@inject: github:`open`:/pulsar Besides, Pulsar runtime also provides a `SourceContext` for the connector to access runtime resources for tasks like collecting metrics. The implementation can save the `SourceContext` for future use. -2. Implement the {@inject: github:`read`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java#L41} method. +2. Implement the {@inject: github:`read`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Source.java} method. ```java /** @@ -62,9 +62,9 @@ interface, which means you need to implement the {@inject: github:`open`:/pulsar If nothing to return, the implementation should be blocking rather than returning `null`. - The returned {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java#L28} should encapsulate the following information, which is needed by Pulsar IO runtime. + The returned {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java} should encapsulate the following information, which is needed by Pulsar IO runtime. - * {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java#L28} should provide the following variables: + * {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java} should provide the following variables: |Variable|Required|Description |---|---|--- @@ -78,7 +78,7 @@ interface, which means you need to implement the {@inject: github:`open`:/pulsar `DestinationTopic`|No|Topic to which message should be written. `Message`|No|A class which carries data sent by users.

For more information, see [Message.java](https://github.com/apache/pulsar/blob/master/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Message.java).| - * {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java#L28} should provide the following methods: + * {@inject: github:`Record`:/pulsar-functions/api-java/src/main/java/org/apache/pulsar/functions/api/Record.java} should provide the following methods: Method|Description |---|--- @@ -91,9 +91,9 @@ interface, which means you need to implement the {@inject: github:`open`:/pulsar ### Sink -Developing a sink connector **is similar to** developing a source connector, that is, you need to implement the {@inject: github:`Sink`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} interface, which means implementing the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java#L36} method and the {@inject: github:`write`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java#L44} method. +Developing a sink connector **is similar to** developing a source connector, that is, you need to implement the {@inject: github:`Sink`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} interface, which means implementing the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} method and the {@inject: github:`write`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} method. -1. Implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java#L36} method. +1. Implement the {@inject: github:`open`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} method. ```java /** @@ -106,7 +106,7 @@ Developing a sink connector **is similar to** developing a source connector, tha void open(final Map config, SinkContext sinkContext) throws Exception; ``` -2. Implement the {@inject: github:`write`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java#L44} method. +2. Implement the {@inject: github:`write`:/pulsar-io/core/src/main/java/org/apache/pulsar/io/core/Sink.java} method. ```java /** @@ -196,7 +196,7 @@ All you need to do is to include this [nifi-nar-maven-plugin](https://mvnreposit > #### Tip > -> For more information about an **how to use NAR for Pulsar connectors**, see {@inject: github:`TwitterFirehose`:/pulsar-io/twitter/pom.xml#L79}. +> For more information about an **how to use NAR for Pulsar connectors**, see {@inject: github:`TwitterFirehose`:/pulsar-io/twitter/pom.xml}. ### Uber JAR diff --git a/site2/docs/io-elasticsearch-sink.md b/site2/docs/io-elasticsearch-sink.md index d3312e5d82e47..a7c4d4cf762cc 100644 --- a/site2/docs/io-elasticsearch-sink.md +++ b/site2/docs/io-elasticsearch-sink.md @@ -16,21 +16,26 @@ The configuration of the ElasticSearch sink connector has the following properti |------|----------|----------|---------|-------------| | `elasticSearchUrl` | String| true |" " (empty string)| The URL of elastic search cluster to which the connector connects. | | `indexName` | String| true |" " (empty string)| The index name to which the connector writes messages. | +| `typeName` | String | false | "_doc" | The type name to which the connector writes messages to.

The value should be set explicitly to a valid type name other than "_doc" for Elasticsearch version before 6.2, and left to default otherwise. | | `indexNumberOfShards` | int| false |1| The number of shards of the index. | | `indexNumberOfReplicas` | int| false |1 | The number of replicas of the index. | | `username` | String| false |" " (empty string)| The username used by the connector to connect to the elastic search cluster.

If `username` is set, then `password` should also be provided. | | `password` | String| false | " " (empty string)|The password used by the connector to connect to the elastic search cluster.

If `username` is set, then `password` should also be provided. | -### Example +## Example Before using the ElasticSearch sink connector, you need to create a configuration file through one of the following methods. +### Configuration + +#### For Elasticsearch After 6.2 + * JSON ```json { - "elasticSearchUrl": "http://localhost:90902", - "indexName": "myIndex", + "elasticSearchUrl": "http://localhost:9200", + "indexName": "my_index", "username": "scooby", "password": "doobie" } @@ -40,10 +45,95 @@ Before using the ElasticSearch sink connector, you need to create a configuratio ```yaml configs: - elasticSearchUrl: "http://localhost:90902" - indexName: "myIndex" + elasticSearchUrl: "http://localhost:9200" + indexName: "my_index" username: "scooby" password: "doobie" ``` +#### For Elasticsearch Before 6.2 + +* JSON + + ```json + { + "elasticSearchUrl": "http://localhost:9200", + "indexName": "my_index", + "typeName": "doc", + "username": "scooby", + "password": "doobie" + } + ``` + +* YAML + + ```yaml + configs: + elasticSearchUrl: "http://localhost:9200" + indexName: "my_index" + typeName: "doc" + username: "scooby" + password: "doobie" + ``` + +### Usage + +1. Start a single node Elasticsearch cluster. + + ```bash + $ docker run -p 9200:9200 -p 9300:9300 \ + -e "discovery.type=single-node" \ + docker.elastic.co/elasticsearch/elasticsearch:7.5.1 + ``` + +2. Start a Pulsar service locally in standalone mode. + ```bash + $ bin/pulsar standalone + ``` + Make sure the nar file is available at `connectors/pulsar-io-elastic-search-{{pulsar:version}}.nar`. + +3. Start the Pulsar Elasticsearch connector in local run mode using one of the following methods. + * Use the **JSON** configuration as shown previously. + ```bash + $ bin/pulsar-admin sinks localrun \ + --archive connectors/pulsar-io-elastic-search-{{pulsar:version}}.nar \ + --tenant public \ + --namespace default \ + --name elasticsearch-test-sink \ + --sink-type elastic_search \ + --sink-config '{"elasticSearchUrl":"http://localhost:9200","indexName": "my_index","username": "scooby","password": "doobie"}' \ + --inputs elasticsearch_test + ``` + * Use the **YAML** configuration file as shown previously. + + ```bash + $ bin/pulsar-admin sinks localrun \ + --archive connectors/pulsar-io-elastic-search-{{pulsar:version}}.nar \ + --tenant public \ + --namespace default \ + --name elasticsearch-test-sink \ + --sink-type elastic_search \ + --sink-config-file elasticsearch-sink.yml \ + --inputs elasticsearch_test + ``` + +4. Publish records to the topic. + + ```bash + $ bin/pulsar-client produce elasticsearch_test --messages "{\"a\":1}" + ``` +5. Check documents in Elasticsearch. + + * refresh the index + ```bash + $ curl -s http://localhost:9200/my_index/_refresh + ``` + * search documents + ```bash + $ curl -s http://localhost:9200/my_index/_search + ``` + You can see the record that published earlier has been successfully written into Elasticsearch. + ```json + {"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"my_index","_type":"_doc","_id":"FSxemm8BLjG_iC0EeTYJ","_score":1.0,"_source":{"a":1}}]}} + ``` diff --git a/site2/docs/io-hbase-sink.md b/site2/docs/io-hbase-sink.md index 4e30f0807b574..0e0a996971416 100644 --- a/site2/docs/io-hbase-sink.md +++ b/site2/docs/io-hbase-sink.md @@ -14,7 +14,7 @@ The configuration of the HBase sink connector has the following properties. ### Property | Name | Type|Default | Required | Description | -|------|---------|----------|-------------| +|------|---------|----------|-------------|--- | `hbaseConfigResources` | String|None | false | HBase system configuration `hbase-site.xml` file. | | `zookeeperQuorum` | String|None | true | HBase system configuration about `hbase.zookeeper.quorum` value. | | `zookeeperClientPort` | String|2181 | false | HBase system configuration about `hbase.zookeeper.property.clientPort` value. | diff --git a/site2/docs/io-kafka-sink.md b/site2/docs/io-kafka-sink.md index c8838fe343687..d5f46c7cea399 100644 --- a/site2/docs/io-kafka-sink.md +++ b/site2/docs/io-kafka-sink.md @@ -18,7 +18,7 @@ The configuration of the Kafka sink connector has the following parameters. | Name | Type| Required | Default | Description |------|----------|---------|-------------|-------------| | `bootstrapServers` |String| true | " " (empty string) | A comma-separated list of host and port pairs for establishing the initial connection to the Kafka cluster. | -|`ack`|String|true|" " (empty string) |The number of acknowledgments that the producer requires the leader to receive before a request completes.
This controls the durability of the sent records. +|`acks`|String|true|" " (empty string) |The number of acknowledgments that the producer requires the leader to receive before a request completes.
This controls the durability of the sent records. |`batchsize`|long|false|16384L|The batch size that a Kafka producer attempts to batch records together before sending them to brokers. |`maxRequestSize`|long|false|1048576L|The maximum size of a Kafka request in bytes. |`topic`|String|true|" " (empty string) |The Kafka topic which receives messages from Pulsar. diff --git a/site2/docs/io-quickstart.md b/site2/docs/io-quickstart.md index 6fe87b03ed05d..f34a1374d436d 100644 --- a/site2/docs/io-quickstart.md +++ b/site2/docs/io-quickstart.md @@ -25,7 +25,7 @@ At the end of this tutorial, you are able to: Before connecting Pulsar to a database, you need to install Pulsar and the desired built-in connector. -For more information about **how to install a standalone Pulsar and built-in connectors**, see [here](standalone/#installing-pulsar). +For more information about **how to install a standalone Pulsar and built-in connectors**, see [here](getting-started-standalone.md/#installing-pulsar). ## Start Pulsar standalone @@ -52,7 +52,7 @@ For more information about **how to install a standalone Pulsar and built-in con ``` **Example output** - ```shell + ```json [{"workerId":"c-standalone-fw-localhost-6750","workerHostname":"localhost","port":6750}] ``` @@ -62,8 +62,8 @@ For more information about **how to install a standalone Pulsar and built-in con curl -s http://localhost:8080/admin/v2/namespaces/public ``` - **Example outoupt** - ```shell + **Example output** + ```json ["public/default","public/functions"] ``` @@ -184,7 +184,7 @@ You can create a configuration file through one of the following methods. * YAML - ``` + ```yaml configs: roots: "localhost:9042" keyspace: "pulsar_test_keyspace" @@ -233,7 +233,7 @@ to monitor a connector and perform other operations on it. **Example output** - ```bash + ```json { "tenant": "public", "namespace": "default", @@ -270,7 +270,7 @@ to monitor a connector and perform other operations on it. **Example output** - ```shell + ```json { "numInstances" : 1, "numRunning" : 1, @@ -314,7 +314,7 @@ to monitor a connector and perform other operations on it. **Example output** - ```shell + ```json { "numInstances" : 1, "numRunning" : 1, @@ -482,7 +482,7 @@ In this section, you need to configure a JDBC sink connector. Create a _pulsar-mysql-jdbc-sink.yaml_ file, copy the following contents to this file, and place the file in the `pulsar/connectors` folder. - ```bash + ```yaml configs: userName: "root" password: "jdbc" @@ -494,7 +494,7 @@ In this section, you need to configure a JDBC sink connector. Create a _avro-schema_ file, copy the following contents to this file, and place the file in the `pulsar/connectors` folder. - ```bash + ```json { "type": "AVRO", "schema": "{\"type\":\"record\",\"name\":\"Test\",\"fields\":[{\"name\":\"id\",\"type\":[\"null\",\"int\"]},{\"name\":\"name\",\"type\":[\"null\",\"string\"]}]}", @@ -523,7 +523,7 @@ In this section, you need to configure a JDBC sink connector. The schema has been uploaded successfully if the following message appears. - ```bash + ```json {"name":"pulsar-mysql-jdbc-sink-topic","schema":"{\"type\":\"record\",\"name\":\"Test\",\"fields\":[{\"name\":\"id\",\"type\":[\"null\",\"int\"]},{\"name\":\"name\",\"type\":[\"null\",\"string\"]}]}","type":"AVRO","properties":{}} ``` @@ -586,7 +586,7 @@ to monitor a connector and perform other operations on it. The result shows that only the _mysql-jdbc-sink_ sink is running. - ```bash + ```json [ "pulsar-mysql-jdbc-sink" ] @@ -607,7 +607,7 @@ to monitor a connector and perform other operations on it. The result shows the information of the sink connector, including tenant, namespace, topic and so on. - ```bash + ```json { "tenant": "public", "namespace": "default", @@ -646,7 +646,7 @@ to monitor a connector and perform other operations on it. The result shows the current status of sink connector, including the number of instance, running status, worker ID and so on. - ```bash + ```json { "numInstances" : 1, "numRunning" : 1, @@ -757,7 +757,7 @@ $ bin/pulsar-admin sinks get \ The result shows that the parallelism is 2. -```text +```json { "tenant": "public", "namespace": "default", @@ -788,7 +788,7 @@ to delete a connector and perform other operations on it. This example deletes the _pulsar-mysql-jdbc-sink_ sink connector. -```text +```bash $ bin/pulsar-admin sinks delete \ --tenant public \ --namespace default \ @@ -807,7 +807,7 @@ The sink connector has been deleted successfully if the following message appear This example double-checks the status of the sink connector. -```text +```bash $ bin/pulsar-admin sinks get \ --tenant public \ --namespace default \ diff --git a/site2/docs/io-use.md b/site2/docs/io-use.md index 4cbf69d61ba30..c623f337e691c 100644 --- a/site2/docs/io-use.md +++ b/site2/docs/io-use.md @@ -118,7 +118,7 @@ Use the `reload` subcommand. $ pulsar-admin sources reload ``` -For more information, see [`here`](reference-connector-admin/#reload). +For more information, see [`here`](io-cli/#reload). #### Sink @@ -128,7 +128,7 @@ Use the `reload` subcommand. $ pulsar-admin sinks reload ``` -For more information, see [`here`](reference-connector-admin/#reload-1). +For more information, see [`here`](io-cli/#reload-1). ### `available` @@ -178,7 +178,7 @@ Use the `create` subcommand. $ pulsar-admin sources create options ``` -For more information, see [here](reference-connector-admin.md#create). +For more information, see [here](io-cli.md#create). @@ -255,7 +255,7 @@ Use the `create` subcommand. $ pulsar-admin sinks create options ``` -For more information, see [here](reference-connector-admin.md#create-1). +For more information, see [here](io-cli.md#create-1). @@ -336,7 +336,7 @@ Use the `start` subcommand. $ pulsar-admin sources start options ``` -For more information, see [here](reference-connector-admin.md#start). +For more information, see [here](io-cli.md#start). @@ -364,7 +364,7 @@ Use the `start` subcommand. $ pulsar-admin sinks start options ``` -For more information, see [here](reference-connector-admin.md#start-1). +For more information, see [here](io-cli.md#start-1). @@ -396,7 +396,7 @@ Use the `localrun` subcommand. $ pulsar-admin sources localrun options ``` -For more information, see [here](reference-connector-admin.md#localrun). +For more information, see [here](io-cli.md#localrun). @@ -414,7 +414,7 @@ Use the `localrun` subcommand. $ pulsar-admin sinks localrun options ``` -For more information, see [here](reference-connector-admin.md#localrun-1). +For more information, see [here](io-cli.md#localrun-1). @@ -446,7 +446,7 @@ Use the `get` subcommand. $ pulsar-admin sources get options ``` -For more information, see [here](reference-connector-admin.md#get). +For more information, see [here](io-cli.md#get). @@ -543,7 +543,7 @@ Use the `get` subcommand. $ pulsar-admin sinks get options ``` -For more information, see [here](reference-connector-admin.md#get-1). +For more information, see [here](io-cli.md#get-1). @@ -637,7 +637,7 @@ Use the `list` subcommand. $ pulsar-admin sources list options ``` -For more information, see [here](reference-connector-admin.md#list). +For more information, see [here](io-cli.md#list). @@ -682,7 +682,7 @@ Use the `list` subcommand. $ pulsar-admin sinks list options ``` -For more information, see [here](reference-connector-admin.md#list-1). +For more information, see [here](io-cli.md#list-1). @@ -731,7 +731,7 @@ Use the `status` subcommand. $ pulsar-admin sources status options ``` -For more information, see [here](reference-connector-admin.md#status). +For more information, see [here](io-cli.md#status). @@ -813,7 +813,7 @@ Use the `status` subcommand. $ pulsar-admin sinks status options ``` -For more information, see [here](reference-connector-admin.md#status-1). +For more information, see [here](io-cli.md#status-1). @@ -901,7 +901,7 @@ Use the `update` subcommand. $ pulsar-admin sources update options ``` -For more information, see [here](reference-connector-admin.md#update). +For more information, see [here](io-cli.md#update). @@ -982,7 +982,7 @@ Use the `update` subcommand. $ pulsar-admin sinks update options ``` -For more information, see [here](reference-connector-admin.md#update-1). +For more information, see [here](io-cli.md#update-1). @@ -1069,7 +1069,7 @@ Use the `stop` subcommand. $ pulsar-admin sources stop options ``` -For more information, see [here](reference-connector-admin.md#stop). +For more information, see [here](io-cli.md#stop). @@ -1151,7 +1151,7 @@ Use the `stop` subcommand. $ pulsar-admin sinks stop options ``` -For more information, see [here](reference-connector-admin.md#stop-1). +For more information, see [here](io-cli.md#stop-1). @@ -1239,7 +1239,7 @@ Use the `restart` subcommand. $ pulsar-admin sources restart options ``` -For more information, see [here](reference-connector-admin.md#restart). +For more information, see [here](io-cli.md#restart). @@ -1321,7 +1321,7 @@ Use the `restart` subcommand. $ pulsar-admin sinks restart options ``` -For more information, see [here](reference-connector-admin.md#restart-1). +For more information, see [here](io-cli.md#restart-1). @@ -1409,7 +1409,7 @@ Use the `delete` subcommand. $ pulsar-admin sources delete options ``` -For more information, see [here](reference-connector-admin.md#delete). +For more information, see [here](io-cli.md#delete). @@ -1463,7 +1463,7 @@ Use the `delete` subcommand. $ pulsar-admin sinks delete options ``` -For more information, see [here](reference-connector-admin.md#delete-1). +For more information, see [here](io-cli.md#delete-1). diff --git a/site2/docs/reference-configuration.md b/site2/docs/reference-configuration.md index 3f31893284d45..cdfe0d419a70d 100644 --- a/site2/docs/reference-configuration.md +++ b/site2/docs/reference-configuration.md @@ -11,7 +11,7 @@ sidebar_label: Pulsar configuration -Pulsar configuration can be managed either via a series of configuration files contained in the [`conf`](https://github.com/apache/pulsar/tree/master/conf) directory of a Pulsar [installation](getting-started-standalone.md) +Pulsar configuration can be managed via a series of configuration files contained in the [`conf`](https://github.com/apache/pulsar/tree/master/conf) directory of a Pulsar [installation](getting-started-standalone.md) - [BookKeeper](#bookkeeper) - [Broker](#broker) @@ -165,6 +165,7 @@ Pulsar brokers are responsible for handling incoming messages from producers, di |brokerClientAuthenticationPlugin| Authentication settings of the broker itself. Used when the broker connects to other brokers, either in same or other clusters || |brokerClientAuthenticationParameters||| |athenzDomainNames| Supported Athenz provider domain names(comma separated) for authentication || +|exposePreciseBacklogInPrometheus| Enable expose the precise backlog stats, set false to use published counter and consumed counter to calculate, this would be more efficient but may be inaccurate. |false| |bookkeeperClientAuthenticationPlugin| Authentication plugin to use when connecting to bookies || |bookkeeperClientAuthenticationParametersName| BookKeeper auth plugin implementatation specifics parameters name and values || |bookkeeperClientAuthenticationParameters||| @@ -225,6 +226,8 @@ Pulsar brokers are responsible for handling incoming messages from producers, di |defaultRetentionSizeInMB| Default retention size |0| |keepAliveIntervalSeconds| How often to check whether the connections are still alive |30| |loadManagerClassName| Name of load manager to use |org.apache.pulsar.broker.loadbalance.impl.SimpleLoadManagerImpl| +|supportedNamespaceBundleSplitAlgorithms| Supported algorithms name for namespace bundle split |[range_equally_divide,topic_count_equally_divide]| +|defaultNamespaceBundleSplitAlgorithm| Default algorithm name for namespace bundle split |range_equally_divide| |managedLedgerOffloadDriver| Driver to use to offload old data to long term storage (Possible values: S3) || |managedLedgerOffloadMaxThreads| Maximum number of thread pool threads for ledger offloading |2| |managedLedgerUnackedRangesOpenCacheSetEnabled| Use Open Range-Set to cache unacknowledged messages |true| @@ -351,6 +354,7 @@ The [`pulsar-client`](reference-cli-tools.md#pulsar-client) CLI tool can be used |brokerClientAuthenticationPlugin| The authentication settings of the broker itself. Used when the broker connects to other brokers either in the same cluster or from other clusters. || |brokerClientAuthenticationParameters| The parameters that go along with the plugin specified using brokerClientAuthenticationPlugin. || |athenzDomainNames| Supported Athenz authentication provider domain names as a comma-separated list. || +|exposePreciseBacklogInPrometheus| Enable expose the precise backlog stats, set false to use published counter and consumed counter to calculate, this would be more efficient but may be inaccurate. |false| |bookkeeperClientAuthenticationPlugin| Authentication plugin to be used when connecting to bookies (BookKeeper servers). || |bookkeeperClientAuthenticationParametersName| BookKeeper authentication plugin implementation parameters and values. || |bookkeeperClientAuthenticationParameters| Parameters associated with the bookkeeperClientAuthenticationParametersName || diff --git a/site2/docs/reference-pulsar-admin.md b/site2/docs/reference-pulsar-admin.md index af8c88b50ad69..c52f87bad31f3 100644 --- a/site2/docs/reference-pulsar-admin.md +++ b/site2/docs/reference-pulsar-admin.md @@ -860,6 +860,8 @@ Subcommands * `clear-backlog` * `unsubscribe` * `set-encryption-required` +* `set-delayed-delivery` +* `get-delayed-delivery` * `set-subscription-auth-mode` * `get-max-producers-per-topic` * `set-max-producers-per-topic` @@ -867,6 +869,10 @@ Subcommands * `set-max-consumers-per-topic` * `get-max-consumers-per-subscription` * `set-max-consumers-per-subscription` +* `get-max-unacked-messages-per-subscription` +* `set-max-unacked-messages-per-subscription` +* `get-max-unacked-messages-per-consumer` +* `set-max-unacked-messages-per-consumer` * `get-compaction-threshold` * `set-compaction-threshold` * `get-offload-threshold` @@ -876,6 +882,8 @@ Subcommands * `clear-offload-deletion-lag` * `get-schema-autoupdate-strategy` * `set-schema-autoupdate-strategy` +* `set-offload-policies` +* `get-offload-policies` ### `list` @@ -1338,6 +1346,38 @@ Options |`-d`, `--disable`|Disable message encryption required|false| |`-e`, `--enable`|Enable message encryption required|false| +### `set-delayed-delivery` +Set the delayed delivery policy on a namespace + +Usage +```bash +$ pulsar-admin namespaces set-delayed-delivery tenant/namespace options +``` + +Options + +|Flag|Description|Default| +|----|---|---| +|`-d`, `--disable`|Disable delayed delivery messages|false| +|`-e`, `--enable`|Enable delayed delivery messages|false| +|`-t`, `--time`|The tick time for when retrying on delayed delivery messages|1s| + + +### `get-delayed-delivery` +Get the delayed delivery policy on a namespace + +Usage +```bash +$ pulsar-admin namespaces get-delayed-delivery-time tenant/namespace +``` + +Options + +|Flag|Description|Default| +|----|---|---| +|`-t`, `--time`|The tick time for when retrying on delayed delivery messages|1s| + + ### `set-subscription-auth-mode` Set subscription auth mode on a namespace @@ -1414,6 +1454,50 @@ Options |----|---|---| |`-c`, `--max-consumers-per-subscription`|maxConsumersPerSubscription for a namespace|0| +### `get-max-unacked-messages-per-subscription` +Get maxUnackedMessagesPerSubscription for a namespace + +Usage +```bash +$ pulsar-admin namespaces get-max-unacked-messages-per-subscription tenant/namespace +``` + +### `set-max-unacked-messages-per-subscription` +Set maxUnackedMessagesPerSubscription for a namespace + +Usage +```bash +$ pulsar-admin namespaces set-max-unacked-messages-per-subscription tenant/namespace options +``` + +Options + +|Flag|Description|Default| +|----|---|---| +|`-c`, `--max-unacked-messages-per-subscription`|maxUnackedMessagesPerSubscription for a namespace|-1| + +### `get-max-unacked-messages-per-consumer` +Get maxUnackedMessagesPerConsumer for a namespace + +Usage +```bash +$ pulsar-admin namespaces get-max-unacked-messages-per-consumer tenant/namespace +``` + +### `set-max-unacked-messages-per-consumer` +Set maxUnackedMessagesPerConsumer for a namespace + +Usage +```bash +$ pulsar-admin namespaces set-max-unacked-messages-per-consumer tenant/namespace options +``` + +Options + +|Flag|Description|Default| +|----|---|---| +|`-c`, `--max-unacked-messages-per-consumer`|maxUnackedMessagesPerConsumer for a namespace|-1| + ### `get-compaction-threshold` Get compactionThreshold for a namespace @@ -2210,3 +2294,28 @@ Options |`-t`, `--type`|The type of the schema (avro or json)|| +### `get-offload-policies` +Get the offload policy for a namespace + +Usage +```bash +$ pulsar-admin namespaces get-offload-policies tenant/namespace +``` + +### `set-offload-policies` +Set the offload policy for a namespace + +Usage +```bash +$ pulsar-admin namespaces set-offload-policies tenant/namespace +``` + +Options +|Flag|Description|Default| +|----|---|---| +|`-d`, `--driver`|Driver to use to offload old data to long term storage,(Possible values: S3, aws-s3, google-cloud-storage)|| +|`-r`, `--region`|The long term storage region|| +|`-b`, `--bucket`|Bucket to place offloaded ledger into|| +|`-e`, `--endpoint`|Alternative endpoint to connect to|| +|`-mbs`, `--maxBlockSize`|Max block size|64MB| +|`-rbs`, `--readBufferSize`|Read buffer size|1MB| diff --git a/site2/docs/schema-get-started.md b/site2/docs/schema-get-started.md index 68b7b6bfb3559..c94cd4aa0e534 100644 --- a/site2/docs/schema-get-started.md +++ b/site2/docs/schema-get-started.md @@ -82,7 +82,7 @@ Producer producer = client.newProducer(JSONSchema.of(User.class)) .topic(topic) .create(); User user = new User(“Tom”, 28); -producer.send(User); +producer.send(user); ``` ### Summary diff --git a/site2/docs/schema-understand.md b/site2/docs/schema-understand.md index d0859b8578589..86bacc9e3aca8 100644 --- a/site2/docs/schema-understand.md +++ b/site2/docs/schema-understand.md @@ -110,7 +110,7 @@ Possible properties might be the Git hash associated with the schema, an environ This is the `SchemaInfo` of a string. -```text +```json { "name": "test-string-schema", "type": "STRING", @@ -171,15 +171,15 @@ This example demonstrates how to use a string schema. 1. Create a producer with a string schema and send messages. - ```text + ```java Producer producer = client.newProducer(Schema.STRING).create(); producer.newMessage().value("Hello Pulsar!").send(); ``` 2. Create a consumer with a string schema and receive messages. - ```text - Consumer consumer = client.newConsumer(Schema.STRING).create(); + ```java + Consumer consumer = client.newConsumer(Schema.STRING).subscribe(); consumer.receive(); ``` @@ -305,7 +305,7 @@ Pulsar gets the schema definition from the predefined `struct` using an Avro lib 1. Create the _User_ class to define the messages sent to Pulsar topics. - ```text + ```java public class User { String name; int age; @@ -314,15 +314,15 @@ Pulsar gets the schema definition from the predefined `struct` using an Avro lib 2. Create a producer with a `struct` schema and send messages. - ```text + ```java Producer producer = client.newProducer(Schema.AVRO(User.class)).create(); producer.newMessage().value(User.builder().userName("pulsar-user").userId(1L).build()).send(); ``` 3. Create a consumer with a `struct` schema and receive messages - ```text - Consumer consumer = client.newConsumer(Schema.AVRO(User.class)).create(); + ```java + Consumer consumer = client.newConsumer(Schema.AVRO(User.class)).subscribe(); User user = consumer.receive(); ``` @@ -336,7 +336,7 @@ You can define the `struct` schema using the `GenericSchemaBuilder`, generate a 1. Use `RecordSchemaBuilder` to build a schema. - ```text + ```java RecordSchemaBuilder recordSchemaBuilder = SchemaBuilder.record("schemaName"); recordSchemaBuilder.field("intField").type(SchemaType.INT32); SchemaInfo schemaInfo = recordSchemaBuilder.build(SchemaType.AVRO); @@ -346,7 +346,7 @@ You can define the `struct` schema using the `GenericSchemaBuilder`, generate a 2. Use `RecordBuilder` to build the struct records. - ```text + ```java producer.newMessage().value(schema.newRecordBuilder() .set("intField", 32) .build()).send(); @@ -377,7 +377,7 @@ Suppose that: In this case, you can use `AUTO_PRODUCE` to verify whether the bytes produced by _K_ can be sent to _P_ or not. -```text +```java Produce pulsarProducer = client.newProducer(Schema.AUTO_PRODUCE()) … .create(); @@ -405,14 +405,13 @@ Suppose that: In this case, you can use `AUTO_CONSUME` to verify whether the bytes produced by _P_ can be sent to MySQL or not. -```text +```java Consumer pulsarConsumer = client.newConsumer(Schema.AUTO_CONSUME()) … .subscribe(); Message msg = consumer.receive() ; GenericRecord record = msg.getValue(); -… ``` ## Schema version @@ -431,7 +430,7 @@ The following example illustrates how the schema version works. Suppose that a Pulsar [Java client](client-libraries-java.md) created using the code below attempts to connect to Pulsar and begins to send messages: -```text +```java PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://localhost:6650") .build(); diff --git a/site2/tools/build-site.sh b/site2/tools/build-site.sh index 3fe1f42f20240..cd09d2cc882ba 100755 --- a/site2/tools/build-site.sh +++ b/site2/tools/build-site.sh @@ -41,6 +41,9 @@ cp versioned_docs/version-2.5.0/io-overview.md translated_docs/ja/version-2.5.0/ cp versioned_docs/version-2.4.2/functions-develop.md translated_docs/fr/version-2.4.2/functions-develop.md cp versioned_docs/version-2.5.0/functions-develop.md translated_docs/fr/version-2.5.0/functions-develop.md cp versioned_docs/version-2.5.0/io-overview.md translated_docs/fr/version-2.5.0/io-overview.md +cp versioned_docs/version-2.4.2/functions-develop.md translated_docs/zh-TW/version-2.4.2/functions-develop.md +cp versioned_docs/version-2.5.0/functions-develop.md translated_docs/zh-TW/version-2.5.0/functions-develop.md +cp versioned_docs/version-2.5.0/io-overview.md translated_docs/zh-TW/version-2.5.0/io-overview.md yarn build @@ -54,3 +57,4 @@ rm -rf ${ROOT_DIR}/generated-site/content mkdir -p ${ROOT_DIR}/generated-site/content cp -R ${ROOT_DIR}/generated-site/api ${ROOT_DIR}/generated-site/content cp -R ./build/pulsar/* ${ROOT_DIR}/generated-site/content +cp -R ${ROOT_DIR}/generated-site/tools ${ROOT_DIR}/generated-site/content diff --git a/site2/website/languages.js b/site2/website/languages.js index 8629453f2cb6b..a5686f9b74e83 100644 --- a/site2/website/languages.js +++ b/site2/website/languages.js @@ -176,6 +176,14 @@ const languages = [ name: '中文', tag: 'zh-CN', }, - {enabled: false, name: '繁體中文', tag: 'zh-TW'}, + { + enabled: true, + name: '繁體中文', + tag: 'zh-TW' + }, + { + enabled: true, + name: '한국어', + tag: 'ko-KR'}, ]; module.exports = languages; diff --git a/site2/website/pages/en/download.js b/site2/website/pages/en/download.js index 42b7842746a49..003f7e849f8d8 100644 --- a/site2/website/pages/en/download.js +++ b/site2/website/pages/en/download.js @@ -251,12 +251,12 @@ class Download extends React.Component { {info.version} - apache-pulsar-{info.version}-bin-tar.gz   + apache-pulsar-{info.version}-bin.tar.gz   (asc{`${sha}`}) - apache-pulsar-{info.version}-src-tar.gz + apache-pulsar-{info.version}-src.tar.gz   (asc{`${sha}`}) diff --git a/site2/website/pages/en/pulsar-admin-cli.js b/site2/website/pages/en/pulsar-admin-cli.js new file mode 100644 index 0000000000000..42108c1f645d7 --- /dev/null +++ b/site2/website/pages/en/pulsar-admin-cli.js @@ -0,0 +1,23 @@ +const React = require('react'); +const CompLibrary = require('../../core/CompLibrary.js'); + +const Container = CompLibrary.Container; +const CWD = process.cwd(); +const releases = require(`${CWD}/releases.json`); + +class PulsarAdminCli extends React.Component { + render() { + const latestVersion = releases[0]; + const url = "../js/getCliByVersion.js?latestVersion=" + latestVersion; + return ( +
+ + {latestVersion} + + +
+ ); + } +} + +module.exports = PulsarAdminCli; diff --git a/site2/website/release-notes.md b/site2/website/release-notes.md index 0143abca79fda..89cf365c73826 100644 --- a/site2/website/release-notes.md +++ b/site2/website/release-notes.md @@ -46,6 +46,7 @@ - [CLI] Add a broker tool for operations of a specific broker [#5768](https://github.com/apache/pulsar/pull/5768) - [CLI] Validate topic name before creating partition/non partition topic via admin cli [#5148](https://github.com/apache/pulsar/pull/5148) - [CLI] Make PulsarClusterMetadataSetup idempotent [#5879](https://github.com/apache/pulsar/pull/5879) +- [CLI] Allow for topic deletions with regex consumers [#5230](https://github.com/apache/pulsar/pull/5230) #### Stats & Monitoring @@ -207,7 +208,6 @@ https://github.com/apache/pulsar/releases/tag/v2.5.0 * Close previous dispatcher when subscription type changes [#5288](https://github.com/apache/pulsar/pull/5288) * Improve error handling logic for effectively once [#5271](https://github.com/apache/pulsar/pull/5271) * Upgrade dependencies for security fixes [#5232](https://github.com/apache/pulsar/pull/5232) -* Allow for topic deletions with regex consumers [#5230](https://github.com/apache/pulsar/pull/5230) * Ensure consumer background tasks are cancelled after subscribe failures [#5204](https://github.com/apache/pulsar/pull/5204) * Added missing enum value KeyShared on the python wrapper [#5196](https://github.com/apache/pulsar/pull/5196) * Make some member variables of Dispatcher volatile [#5193](https://github.com/apache/pulsar/pull/5193) diff --git a/site2/website/scripts/replace.js b/site2/website/scripts/replace.js index a701af7acf395..ac1a23b9b3b2f 100644 --- a/site2/website/scripts/replace.js +++ b/site2/website/scripts/replace.js @@ -85,6 +85,17 @@ function debDistUrl(version, type) { } } +function clientVersionUrl(version, type) { + var versions = version.split('.') + var majorVersion = parseInt(versions[0]) + var minorVersion = parseInt(versions[1]) + if (majorVersion === 2 && minorVersion < 5) { + return `/api/` + type + `/` + version; + } else if (majorVersion === 2 && minorVersion >= 5) { + return `/api/` + type + `/` + version + `-SNAPSHOT` + } +} + function doReplace(options) { replace(options) .then(changes => { @@ -122,7 +133,13 @@ const from = [ /{{pulsar:dist_rpm:client-debuginfo}}/g, /{{pulsar:dist_rpm:client-devel}}/g, /{{pulsar:dist_deb:client}}/g, - /{{pulsar:dist_deb:client-devel}}/g + /{{pulsar:dist_deb:client-devel}}/g, + + /\/api\/python/g, + /\/api\/cpp/g, + /\/api\/pulsar-functions/g, + /\/api\/client/g, + /\/api\/admin/g, ]; const options = { @@ -151,7 +168,13 @@ const options = { rpmDistUrl(`${latestVersion}`, "-debuginfo"), rpmDistUrl(`${latestVersion}`, "-devel"), debDistUrl(`${latestVersion}`, ""), - debDistUrl(`${latestVersion}`, "-dev") + debDistUrl(`${latestVersion}`, "-dev"), + + clientVersionUrl(`${latestVersion}`, "python"), + clientVersionUrl(`${latestVersion}`, "cpp"), + clientVersionUrl(`${latestVersion}`, "pulsar-functions"), + clientVersionUrl(`${latestVersion}`, "client"), + clientVersionUrl(`${latestVersion}`, "admin") ], dry: false }; @@ -190,7 +213,12 @@ for (v of versions) { rpmDistUrl(`${v}`, "-debuginfo"), rpmDistUrl(`${v}`, "-devel"), debDistUrl(`${v}`, ""), - debDistUrl(`${v}`, "-dev") + debDistUrl(`${v}`, "-dev"), + clientVersionUrl(`${v}`, "python"), + clientVersionUrl(`${v}`, "cpp"), + clientVersionUrl(`${v}`, "pulsar-functions"), + clientVersionUrl(`${v}`, "client"), + clientVersionUrl(`${v}`, "admin") ], dry: false }; diff --git a/site2/website/sidebars.json b/site2/website/sidebars.json index 6206ff1af7716..828f5ea155ab5 100644 --- a/site2/website/sidebars.json +++ b/site2/website/sidebars.json @@ -15,8 +15,7 @@ "concepts-multi-tenancy", "concepts-authentication", "concepts-topic-compaction", - "concepts-tiered-storage", - "concepts-schema-registry" + "concepts-tiered-storage" ], "Pulsar Schema": [ "schema-get-started", @@ -64,7 +63,6 @@ "Administration": [ "administration-zk-bk", "administration-geo", - "administration-dashboard", "administration-pulsar-manager", "administration-stats", "administration-load-balance", @@ -130,8 +128,6 @@ "Reference": [ "reference-terminology", "reference-cli-tools", - "pulsar-admin", - "reference-connector-admin", "reference-configuration", "reference-metrics" ] diff --git a/site2/website/siteConfig.js b/site2/website/siteConfig.js index f31394645b38f..1a646d52b311c 100644 --- a/site2/website/siteConfig.js +++ b/site2/website/siteConfig.js @@ -28,7 +28,7 @@ const createVariableInjectionPlugin = variables => { } else if (keyparts[0] == 'source') { return renderUrl(initializedPlugin, sourceApiUrl + "#", keyparts); } else if (keyparts[0] == 'sink') { - return renderUrl(initializedPlugin, sinkApiUrl + "#", keyparts); + return renderUrl(initializedPlugin, sinkApiUrl + "#", keyparts); } else { keyparts = key.split("|"); // endpoint api: endpoint| @@ -105,6 +105,7 @@ const siteConfig = { {page: 'download', label: 'Download'}, {doc: 'client-libraries', label: 'Clients'}, {href: '#restapis', label: 'REST APIs'}, + {href: '#cli', label: 'Cli'}, {blog: true, label: 'Blog'}, {href: '#community', label: 'Community'}, {href: '#apache', label: 'Apache'}, diff --git a/site2/website/static/css/custom.css b/site2/website/static/css/custom.css index 791a24ab1cab0..fe0bed9eda23b 100644 --- a/site2/website/static/css/custom.css +++ b/site2/website/static/css/custom.css @@ -64,6 +64,7 @@ #community-dropdown.hide, #apache-dropdown.hide, #restapis-dropdown.hide, +#cli-dropdown.hide, #languages-dropdown.hide { display: none; } @@ -72,6 +73,7 @@ #community-dropdown.visible, #apache-dropdown.visible, #restapis-dropdown.visible, +#cli-dropdown.visible, #languages-dropdown.visible { display: flex; } @@ -79,6 +81,7 @@ #community-dropdown, #apache-dropdown, #restapis-dropdown, +#cli-dropdown, #languages-dropdown { pointer-events: none; position: absolute; @@ -88,6 +91,7 @@ #community-dropdown-items, #apache-dropdown-items, #restapis-dropdown-items, +#cli-dropdown-items, #languages-dropdown-items { background-color: white; display: flex; diff --git a/site2/website/static/js/custom.js b/site2/website/static/js/custom.js index 6b4ac94df0964..9ef83e5d55b9c 100644 --- a/site2/website/static/js/custom.js +++ b/site2/website/static/js/custom.js @@ -100,6 +100,31 @@ window.addEventListener('load', function() { } }); + // setup cli menu items in nav bar + const cli = document.querySelector("a[href='#cli']").parentNode; + const cliMenu = + '
  • ' + + 'Cli  ▼' + + '
    ' + + '' + + '
    ' + + '
  • '; + + cli.innerHTML = cliMenu; + + const cliMenuItem = document.getElementById("cli-menu"); + const cliDropDown = document.getElementById("cli-dropdown"); + cliMenuItem.addEventListener("click", function(event) { + event.preventDefault(); + + if (cliDropDown.className == 'hide') { + cliDropDown.className = 'visible'; + } else { + cliDropDown.className = 'hide'; + } + }); function button(label, ariaLabel, icon, className) { const btn = document.createElement('button'); diff --git a/site2/website/static/js/getCliByVersion.js b/site2/website/static/js/getCliByVersion.js new file mode 100644 index 0000000000000..f817a91ba2580 --- /dev/null +++ b/site2/website/static/js/getCliByVersion.js @@ -0,0 +1,34 @@ +function getCliByVersion(){ + var params = window.location.search + var latestVersion = document.getElementById("latestVersion").textContent + params = params.replace('?', '') + const paramsList = params.split('&') + var version = 'master' + for (var i in paramsList) { + var param = paramsList[i].split('=') + if (param[0] === 'version') { + version = param[1] + } + } + + if (version === "master") { + var latestVersionSplit = latestVersion.split('.') + version = parseInt(latestVersionSplit[0]) + "." + (parseInt(latestVersionSplit[1]) + 1) + ".0" + } + var versions = version.split('.') + var majorVersion = parseInt(versions[0]) + var minorVersion = parseInt(versions[1]) + if ((majorVersion == 2 && minorVersion <= 5) || majorVersion === 1) { + if (version === latestVersion) { + window.location.href = "/docs/en/pulsar-admin" + return + } else { + window.location.href = "/docs/en/" + version + "/pulsar-admin" + return + } + } else { + window.location.href = "http://pulsar.apache.org/tools/pulsar-admin/" + version + "-SNAPSHOT" + return + } +} +window.onload=getCliByVersion diff --git a/site2/website/static/swagger/2.5.0/swagger.json b/site2/website/static/swagger/2.5.0/swagger.json new file mode 100644 index 0000000000000..e5269497b3783 --- /dev/null +++ b/site2/website/static/swagger/2.5.0/swagger.json @@ -0,0 +1,10285 @@ +{ + "swagger" : "2.0", + "info" : { + "description" : "This provides the REST API for admin operations", + "version" : "v2", + "title" : "Pulsar Admin REST API", + "license" : { + "name" : "Apache 2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath" : "/admin/v2", + "tags" : [ { + "name" : "bookies" + }, { + "name" : "broker-stats" + }, { + "name" : "brokers" + }, { + "name" : "clusters" + }, { + "name" : "namespaces" + }, { + "name" : "non-persistent topic" + }, { + "name" : "persistent topic" + }, { + "name" : "resource-quotas" + }, { + "name" : "schemas" + }, { + "name" : "tenants" + } ], + "schemes" : [ "http", "https" ], + "paths" : { + "/bookies/racks-info" : { + "get" : { + "tags" : [ "bookies" ], + "summary" : "Gets the rack placement information for all the bookies in the cluster", + "description" : "", + "operationId" : "getBookiesRackInfo", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/BookieInfo" + } + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/bookies/racks-info/{bookie}" : { + "get" : { + "tags" : [ "bookies" ], + "summary" : "Gets the rack placement information for a specific bookie in the cluster", + "description" : "", + "operationId" : "getBookieRackInfo", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "bookie", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/BookieInfo" + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + }, + "post" : { + "tags" : [ "bookies" ], + "summary" : "Updates the rack placement information for a specific bookie in the cluster", + "description" : "", + "operationId" : "updateBookieRackInfo", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "bookie", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "group", + "in" : "query", + "required" : false, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + }, + "delete" : { + "tags" : [ "bookies" ], + "summary" : "Removed the rack placement information for a specific bookie in the cluster", + "description" : "", + "operationId" : "deleteBookieRackInfo", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "bookie", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/allocator-stats/{allocator}" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Get the stats for the Netty allocator. Available allocators are 'default' and 'ml-cache'", + "description" : "", + "operationId" : "getAllocatorStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "allocator", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/AllocatorStats" + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/bookieops" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Get pending bookie client op stats by namesapce", + "description" : "", + "operationId" : "getPendingBookieOpsStats", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/PendingBookieOpsStats" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/broker-resource-availability/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Broker availability report", + "description" : "This API gives the current broker availability in percent, each resource percentage usage is calculated and thensum of all of the resource usage percent is called broker-resource-availability

    THIS API IS ONLY FOR USE BY TESTING FOR CONFIRMING NAMESPACE ALLOCATION ALGORITHM", + "operationId" : "getBrokerResourceAvailability", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/ResourceUnit" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "409" : { + "description" : "Load-manager doesn't support operation" + } + } + } + }, + "/broker-stats/load-report" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Get Load for this broker", + "description" : "consists of topics stats & systemResourceUsage", + "operationId" : "getLoadReport", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/LoadReport" + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/mbeans" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Get all the mbean details of this broker JVM", + "description" : "", + "operationId" : "getMBeans", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Metrics" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/metrics" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Gets the metrics for Monitoring", + "description" : "Requested should be executed by Monitoring agent on each broker to fetch the metrics", + "operationId" : "getMetrics", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/Metrics" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/broker-stats/topics" : { + "get" : { + "tags" : [ "broker-stats" ], + "summary" : "Get all the topic stats by namespace", + "description" : "", + "operationId" : "getTopics2", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/OutputStream" + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/brokers/configuration" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get all updatable dynamic configurations's name", + "description" : "", + "operationId" : "getDynamicConfigurationName", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "object" + } + } + } + } + } + }, + "/brokers/configuration/runtime" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get all runtime configurations. This operation requires Pulsar super-user privileges.", + "description" : "", + "operationId" : "getRuntimeConfiguration", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/brokers/configuration/values" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get value of all dynamic configurations' value overridden on local config", + "description" : "", + "operationId" : "getAllDynamicConfigurations", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "404" : { + "description" : "Configuration not found" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/brokers/configuration/{configName}" : { + "delete" : { + "tags" : [ "brokers" ], + "summary" : "Delete dynamic serviceconfiguration into zk only. This operation requires Pulsar super-user privileges.", + "description" : "", + "operationId" : "deleteDynamicConfiguration", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "configName", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "204" : { + "description" : "Service configuration updated successfully" + }, + "403" : { + "description" : "You don't have admin permission to update service-configuration" + }, + "412" : { + "description" : "Invalid dynamic-config value" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/brokers/configuration/{configName}/{configValue}" : { + "post" : { + "tags" : [ "brokers" ], + "summary" : "Update dynamic serviceconfiguration into zk only. This operation requires Pulsar super-user privileges.", + "description" : "", + "operationId" : "updateDynamicConfiguration", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "configName", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "configValue", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "204" : { + "description" : "Service configuration updated successfully" + }, + "403" : { + "description" : "You don't have admin permission to update service-configuration" + }, + "404" : { + "description" : "Configuration not found" + }, + "412" : { + "description" : "Invalid dynamic-config value" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/brokers/health" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Run a healthcheck against the broker", + "description" : "", + "operationId" : "healthcheck", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "Everything is OK" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Cluster doesn't exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/brokers/internal-configuration" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get the internal configuration data", + "description" : "", + "operationId" : "getInternalConfigurationData", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/InternalConfigurationData" + } + } + } + } + }, + "/brokers/{clusterName}/{broker-webserviceurl}/ownedNamespaces" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get the list of namespaces served by the specific broker", + "description" : "", + "operationId" : "getOwnedNamespaces", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "clusterName", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "broker-webserviceurl", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/NamespaceOwnershipStatus" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Cluster doesn't exist" + } + } + } + }, + "/brokers/{cluster}" : { + "get" : { + "tags" : [ "brokers" ], + "summary" : "Get the list of active brokers (web service addresses) in the cluster.If authorization is not enabled, any cluster name is valid.", + "description" : "", + "operationId" : "getActiveBrokers", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Authentication required" + }, + "403" : { + "description" : "This operation requires super-user access" + }, + "404" : { + "description" : "Cluster does not exist: cluster={clustername}" + } + } + } + }, + "/clusters" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the list of all the Pulsar clusters.", + "description" : "", + "operationId" : "getClusters", + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "Return a list of clusters.", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the configuration for the specified cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getCluster", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "Return the cluster data.", + "schema" : { + "$ref" : "#/definitions/ClusterData" + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "post" : { + "tags" : [ "clusters" ], + "summary" : "Update the configuration for a cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "updateCluster", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The cluster data", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ClusterData" + }, + "x-examples" : { + "application/json" : "{\n 'serviceUrl': 'http://pulsar.example.com:8080',\n 'brokerServiceUrl': 'pulsar://pulsar.example.com:6651'\n}" + } + } ], + "responses" : { + "204" : { + "description" : "Cluster has been updated." + }, + "403" : { + "description" : "Don't have admin permission or policies are read-only." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "put" : { + "tags" : [ "clusters" ], + "summary" : "Create a new cluster.", + "description" : "This operation requires Pulsar superuser privileges, and the name cannot contain the '/' characters.", + "operationId" : "createCluster", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The cluster data", + "required" : true, + "schema" : { + "$ref" : "#/definitions/ClusterData" + }, + "x-examples" : { + "application/json" : "{\n 'serviceUrl': 'http://pulsar.example.com:8080',\n 'brokerServiceUrl': 'pulsar://pulsar.example.com:6651',\n}" + } + } ], + "responses" : { + "204" : { + "description" : "Cluster has been created." + }, + "403" : { + "description" : "You don't have admin permission to create the cluster." + }, + "409" : { + "description" : "Cluster already exists." + }, + "412" : { + "description" : "Cluster name is not valid." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "delete" : { + "tags" : [ "clusters" ], + "summary" : "Delete an existing cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "deleteCluster", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "204" : { + "description" : "Cluster has been deleted." + }, + "403" : { + "description" : "Don't have admin permission or policies are read-only." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "412" : { + "description" : "Cluster is not empty." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}/failureDomains" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the cluster failure domains.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getFailureDomains", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/FailureDomain" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/clusters/{cluster}/failureDomains/{domainName}" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get a domain in a cluster", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getDomain", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "domainName", + "in" : "path", + "description" : "The failure domain name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FailureDomain" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "FailureDomain doesn't exist" + }, + "412" : { + "description" : "Cluster doesn't exist" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "post" : { + "tags" : [ "clusters" ], + "summary" : "Set the failure domain of the cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "setFailureDomain", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "domainName", + "in" : "path", + "description" : "The failure domain name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The configuration data of a failure domain", + "required" : true, + "schema" : { + "$ref" : "#/definitions/FailureDomain" + } + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Failure domain doesn't exist." + }, + "409" : { + "description" : "Broker already exists in another domain." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "delete" : { + "tags" : [ "clusters" ], + "summary" : "Delete the failure domain of the cluster", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "deleteFailureDomain", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "domainName", + "in" : "path", + "description" : "The failure domain name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission or policy is read only" + }, + "404" : { + "description" : "FailureDomain doesn't exist" + }, + "412" : { + "description" : "Cluster doesn't exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/clusters/{cluster}/namespaceIsolationPolicies" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the namespace isolation policies assigned to the cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getNamespaceIsolationPolicies", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/NamespaceIsolationData" + } + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}/namespaceIsolationPolicies/brokers" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get list of brokers with namespace-isolation policies attached to them.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getBrokersWithNamespaceIsolationPolicy", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/BrokerNamespaceIsolationData" + } + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Namespace-isolation policies not found." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}/namespaceIsolationPolicies/brokers/{broker}" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get a broker with namespace-isolation policies attached to it.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getBrokerWithNamespaceIsolationPolicy", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "broker", + "in" : "path", + "description" : "The broker name (:)", + "required" : true, + "type" : "string", + "x-example" : "broker1:8080" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/BrokerNamespaceIsolationData" + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Namespace-isolation policies/ Broker not found." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}/namespaceIsolationPolicies/{policyName}" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the single namespace isolation policy assigned to the cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getNamespaceIsolationPolicy", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "policyName", + "in" : "path", + "description" : "The name of the namespace isolation policy", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/NamespaceIsolationData" + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Policy doesn't exist." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "post" : { + "tags" : [ "clusters" ], + "summary" : "Set namespace isolation policy.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "setNamespaceIsolationPolicy", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "policyName", + "in" : "path", + "description" : "The namespace isolation policy name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The namespace isolation policy data", + "required" : true, + "schema" : { + "$ref" : "#/definitions/NamespaceIsolationData" + } + } ], + "responses" : { + "400" : { + "description" : "Namespace isolation policy data is invalid." + }, + "403" : { + "description" : "Don't have admin permission or policies are read-only." + }, + "404" : { + "description" : "Namespace isolation policy doesn't exist." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "delete" : { + "tags" : [ "clusters" ], + "summary" : "Delete namespace isolation policy.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "deleteNamespaceIsolationPolicy", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "name" : "policyName", + "in" : "path", + "description" : "The namespace isolation policy name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission or policies are read only." + }, + "404" : { + "description" : "Namespace isolation policy doesn't exist." + }, + "412" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/clusters/{cluster}/peers" : { + "get" : { + "tags" : [ "clusters" ], + "summary" : "Get the peer-cluster data for the specified cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "getPeerCluster", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + }, + "post" : { + "tags" : [ "clusters" ], + "summary" : "Update peer-cluster-list for a cluster.", + "description" : "This operation requires Pulsar superuser privileges.", + "operationId" : "setPeerClusterNames", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "description" : "The cluster name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The list of peer cluster names", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "x-examples" : { + "application/json" : "[\n 'cluster-a',\n 'cluster-b'\n]" + } + } ], + "responses" : { + "204" : { + "description" : "Cluster has been updated." + }, + "403" : { + "description" : "Don't have admin permission or policies are read-only." + }, + "404" : { + "description" : "Cluster doesn't exist." + }, + "412" : { + "description" : "Peer cluster doesn't exist." + }, + "500" : { + "description" : "Internal server error." + } + } + } + }, + "/namespaces/{cluster}/antiAffinity/{group}" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get all namespaces that are grouped by given anti-affinity group in a given cluster. api can be only accessed by admin of any of the existing tenant", + "description" : "", + "operationId" : "getAntiAffinityNamespaces", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "cluster", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "group", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "tenant", + "in" : "query", + "required" : false, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "object" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "412" : { + "description" : "Cluster not exist/Anti-affinity group can't be empty." + } + } + } + }, + "/namespaces/{property}/{namespace}/persistence/bookieAffinity" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the bookie-affinity-group from namespace-local policy.", + "description" : "", + "operationId" : "getBookieAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "property", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/BookieAffinityGroupData" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Delete the bookie-affinity-group from namespace-local policy.", + "description" : "", + "operationId" : "deleteBookieAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "property", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the list of all the namespaces for a certain tenant.", + "description" : "", + "operationId" : "getTenantNamespaces", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant doesn't exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the dump all the policies specified for a namespace.", + "description" : "", + "operationId" : "getPolicies", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/Policies" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Creates a new namespace with the specified policies", + "description" : "", + "operationId" : "createNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster doesn't exist" + }, + "409" : { + "description" : "Namespace already exists" + }, + "412" : { + "description" : "Namespace name is not valid" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Delete a namespace and all the topics under it.", + "description" : "", + "operationId" : "deleteNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Namespace is not empty" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/antiAffinity" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get anti-affinity group of a namespace.", + "description" : "", + "operationId" : "getNamespaceAntiAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set anti-affinity group for a namespace", + "description" : "", + "operationId" : "setNamespaceAntiAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "412" : { + "description" : "Invalid antiAffinityGroup" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Remove anti-affinity group of a namespace.", + "description" : "", + "operationId" : "removeNamespaceAntiAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/backlogQuota" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set a backlog quota for all the topics on a namespace.", + "description" : "", + "operationId" : "setBacklogQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "backlogQuotaType", + "in" : "query", + "required" : false, + "type" : "string", + "enum" : [ "destination_storage" ] + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Specified backlog quota exceeds retention quota. Increase retention quota and retry request" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Remove a backlog quota policy from a namespace.", + "description" : "", + "operationId" : "removeBacklogQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "backlogQuotaType", + "in" : "query", + "required" : false, + "type" : "string", + "enum" : [ "destination_storage" ] + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/backlogQuotaMap" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get backlog quota map on a namespace.", + "description" : "", + "operationId" : "getBacklogQuotaMap", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/bundles" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the bundles split data.", + "description" : "", + "operationId" : "getBundlesData", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/BundlesData" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "412" : { + "description" : "Namespace is not setup to split in bundles" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/clearBacklog" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Clear backlog for all topics on a namespace.", + "description" : "", + "operationId" : "clearNamespaceBacklog", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/clearBacklog/{subscription}" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Clear backlog for a given subscription on all topics on a namespace.", + "description" : "", + "operationId" : "clearNamespaceBacklogForSubscription", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "subscription", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/compactionThreshold" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Maximum number of uncompacted bytes in topics before compaction is triggered.", + "description" : "The backlog size is compared to the threshold periodically. A threshold of 0 disabled automatic compaction", + "operationId" : "getCompactionThreshold", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Set maximum number of uncompacted bytes in a topic before compaction is triggered.", + "description" : "The backlog size is compared to the threshold periodically. A threshold of 0 disabled automatic compaction", + "operationId" : "setCompactionThreshold", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "compactionThreshold value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/deduplication" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Enable or disable broker side deduplication for all topics in a namespace", + "description" : "", + "operationId" : "modifyDeduplication", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/dispatchRate" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get dispatch-rate configured for the namespace, -1 represents not configured yet", + "description" : "", + "operationId" : "getDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set dispatch-rate throttling for all topics of the namespace", + "description" : "", + "operationId" : "setDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/encryptionRequired" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Message encryption is required or not for all topics in a namespace", + "description" : "", + "operationId" : "modifyEncryptionRequired", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/isAllowAutoUpdateSchema" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "The flag of whether allow auto update schema", + "description" : "", + "operationId" : "getIsAllowAutoUpdateSchema", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "boolean" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Update flag of whether allow auto update schema", + "description" : "", + "operationId" : "setIsAllowAutoUpdateSchema", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/maxConsumersPerSubscription" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get maxConsumersPerSubscription config on a namespace.", + "description" : "", + "operationId" : "getMaxConsumersPerSubscription", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set maxConsumersPerSubscription configuration on a namespace.", + "description" : "", + "operationId" : "setMaxConsumersPerSubscription", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "maxConsumersPerSubscription value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/maxConsumersPerTopic" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get maxConsumersPerTopic config on a namespace.", + "description" : "", + "operationId" : "getMaxConsumersPerTopic", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set maxConsumersPerTopic configuration on a namespace.", + "description" : "", + "operationId" : "setMaxConsumersPerTopic", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "maxConsumersPerTopic value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/maxProducersPerTopic" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get maxProducersPerTopic config on a namespace.", + "description" : "", + "operationId" : "getMaxProducersPerTopic", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set maxProducersPerTopic configuration on a namespace.", + "description" : "", + "operationId" : "setMaxProducersPerTopic", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "maxProducersPerTopic value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/messageTTL" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the message TTL for the namespace", + "description" : "", + "operationId" : "getNamespaceMessageTTL", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set message TTL in seconds for namespace", + "description" : "", + "operationId" : "setNamespaceMessageTTL", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "412" : { + "description" : "Invalid TTL" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/offloadDeletionLagMs" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Number of milliseconds to wait before deleting a ledger segment which has been offloaded from the Pulsar cluster's local storage (i.e. BookKeeper)", + "description" : "A negative value denotes that deletion has been completely disabled. 'null' denotes that the topics in the namespace will fall back to the broker default for deletion lag.", + "operationId" : "getOffloadDeletionLag", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Set number of milliseconds to wait before deleting a ledger segment which has been offloaded from the Pulsar cluster's local storage (i.e. BookKeeper)", + "description" : "A negative value disables the deletion completely.", + "operationId" : "setOffloadDeletionLag", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "offloadDeletionLagMs value is not valid" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Clear the namespace configured offload deletion lag. The topics in the namespace will fallback to using the default configured deletion lag for the broker", + "description" : "", + "operationId" : "clearOffloadDeletionLag", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/offloadThreshold" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Maximum number of bytes stored on the pulsar cluster for a topic, before the broker will start offloading to longterm storage", + "description" : "A negative value disables automatic offloading", + "operationId" : "getOffloadThreshold", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Set maximum number of bytes stored on the pulsar cluster for a topic, before the broker will start offloading to longterm storage", + "description" : "-1 will revert to using the cluster default. A negative value disables automatic offloading. ", + "operationId" : "setOffloadThreshold", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "offloadThreshold value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/permissions" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Retrieve the permissions for a namespace.", + "description" : "", + "operationId" : "getPermissions", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "cluster", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Namespace is not empty" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/permissions/{role}" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Grant a new permission to a role on a namespace.", + "description" : "", + "operationId" : "grantPermissionOnNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "501" : { + "description" : "Authorization is not enabled" + } + } + }, + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Revoke all permissions to a role on a namespace.", + "description" : "", + "operationId" : "revokePermissionsOnNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/persistence" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the persistence configuration for a namespace.", + "description" : "", + "operationId" : "getPersistence", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PersistencePolicies" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set the persistence configuration for all the topics on a namespace.", + "description" : "", + "operationId" : "setPersistence", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid persistence policies" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/persistence/bookieAffinity" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set the bookie-affinity-group to namespace-persistent policy.", + "description" : "", + "operationId" : "setBookieAffinityGroup", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/replication" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the replication clusters for a namespace.", + "description" : "", + "operationId" : "getNamespaceReplicationClusters", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "412" : { + "description" : "Namespace is not global" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set the replication clusters for a namespace.", + "description" : "", + "operationId" : "setNamespaceReplicationClusters", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Peer-cluster can't be part of replication-cluster" + }, + "412" : { + "description" : "Namespace is not global or invalid cluster ids" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/replicatorDispatchRate" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get replicator dispatch-rate configured for the namespace, -1 represents not configured yet", + "description" : "", + "operationId" : "getReplicatorDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set replicator dispatch-rate throttling for all topics of the namespace", + "description" : "", + "operationId" : "setReplicatorDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/retention" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get retention config on a namespace.", + "description" : "", + "operationId" : "getRetention", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/RetentionPolicies" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set retention configuration on a namespace.", + "description" : "", + "operationId" : "setRetention", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Retention Quota must exceed backlog quota" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/schemaAutoUpdateCompatibilityStrategy" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "The strategy used to check the compatibility of new schemas, provided by producers, before automatically updating the schema", + "description" : "The value AutoUpdateDisabled prevents producers from updating the schema. If set to AutoUpdateDisabled, schemas must be updated through the REST api", + "operationId" : "getSchemaAutoUpdateCompatibilityStrategy", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string", + "enum" : [ "AutoUpdateDisabled", "Backward", "Forward", "Full", "AlwaysCompatible", "BackwardTransitive", "ForwardTransitive", "FullTransitive" ] + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Update the strategy used to check the compatibility of new schemas, provided by producers, before automatically updating the schema", + "description" : "The value AutoUpdateDisabled prevents producers from updating the schema. If set to AutoUpdateDisabled, schemas must be updated through the REST api", + "operationId" : "setSchemaAutoUpdateCompatibilityStrategy", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/schemaCompatibilityStrategy" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "The strategy of the namespace schema compatibility ", + "description" : "", + "operationId" : "getSchemaCompatibilityStrategy", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string", + "enum" : [ "UNDEFINED", "ALWAYS_INCOMPATIBLE", "ALWAYS_COMPATIBLE", "BACKWARD", "FORWARD", "FULL", "BACKWARD_TRANSITIVE", "FORWARD_TRANSITIVE", "FULL_TRANSITIVE" ] + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Update the strategy used to check the compatibility of new schema", + "description" : "", + "operationId" : "setSchemaCompatibilityStrategy", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/schemaValidationEnforced" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get schema validation enforced flag for namespace.", + "description" : "If the flag is set to true, when a producer without a schema attempts to produce to a topic with schema in this namespace, the producer will be failed to connect. PLEASE be carefully on using this, since non-java clients don't support schema.if you enable this setting, it will cause non-java clients failed to produce.", + "operationId" : "getSchemaValidtionEnforced", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "boolean" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenants or Namespace doesn't exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set schema validation enforced flag on namespace.", + "description" : "If the flag is set to true, when a producer without a schema attempts to produce to a topic with schema in this namespace, the producer will be failed to connect. PLEASE be carefully on using this, since non-java clients don't support schema.if you enable this setting, it will cause non-java clients failed to produce.", + "operationId" : "setSchemaValidtionEnforced", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or Namespace doesn't exist" + }, + "412" : { + "description" : "schemaValidationEnforced value is not valid" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/subscribeRate" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get subscribe-rate configured for the namespace", + "description" : "", + "operationId" : "getSubscribeRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SubscribeRate" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set subscribe-rate throttling for all topics of the namespace", + "description" : "", + "operationId" : "setSubscribeRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/subscriptionAuthMode" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : " Set a subscription auth mode for all the topics on a namespace.", + "description" : "", + "operationId" : "setSubscriptionAuthMode", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/subscriptionDispatchRate" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get Subscription dispatch-rate configured for the namespace, -1 represents not configured yet", + "description" : "", + "operationId" : "getSubscriptionDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Set Subscription dispatch-rate throttling for all topics of the namespace", + "description" : "", + "operationId" : "setSubscriptionDispatchRate", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/topics" : { + "get" : { + "tags" : [ "namespaces" ], + "summary" : "Get the list of all the topics under a certain namespace.", + "description" : "", + "operationId" : "getTopics", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "mode", + "in" : "query", + "required" : false, + "type" : "string", + "default" : "PERSISTENT", + "enum" : [ "PERSISTENT", "NON_PERSISTENT", "ALL" ] + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/unload" : { + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Unload namespace", + "description" : "Unload an active namespace from the current broker serving it. Performing this operation will let the brokerremoves all producers, consumers, and connections using this namespace, and close all topics (includingtheir persistent store). During that operation, the namespace is marked as tentatively unavailable until thebroker completes the unloading action. This operation requires strictly super user privileges, since it wouldresult in non-persistent message loss and unexpected connection closure to the clients.", + "operationId" : "unloadNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or namespace doesn't exist" + }, + "412" : { + "description" : "Namespace is already unloaded or Namespace has bundles activated" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/unsubscribe/{subscription}" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Unsubscribes the given subscription on all topics on a namespace.", + "description" : "", + "operationId" : "unsubscribeNamespace", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "cluster", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "subscription", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}" : { + "delete" : { + "tags" : [ "namespaces" ], + "summary" : "Delete a namespace bundle and all the topics under it.", + "description" : "", + "operationId" : "deleteNamespaceBundle", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant or cluster or namespace doesn't exist" + }, + "409" : { + "description" : "Namespace bundle is not empty" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}/clearBacklog" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Clear backlog for all topics on a namespace bundle.", + "description" : "", + "operationId" : "clearNamespaceBundleBacklog", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}/clearBacklog/{subscription}" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Clear backlog for a given subscription on all topics on a namespace bundle.", + "description" : "", + "operationId" : "clearNamespaceBundleBacklogForSubscription", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "subscription", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}/split" : { + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Split a namespace bundle", + "description" : "", + "operationId" : "splitNamespaceBundle", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "unload", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}/unload" : { + "put" : { + "tags" : [ "namespaces" ], + "summary" : "Unload a namespace bundle", + "description" : "", + "operationId" : "unloadNamespaceBundle", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/namespaces/{tenant}/{namespace}/{bundle}/unsubscribe/{subscription}" : { + "post" : { + "tags" : [ "namespaces" ], + "summary" : "Unsubscribes the given subscription on all topics on a namespace bundle.", + "description" : "", + "operationId" : "unsubscribeNamespaceBundle", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "subscription", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the list of non-persistent topics under a namespace.", + "description" : "", + "operationId" : "getList", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace does not exist" + }, + "412" : { + "description" : "Namespace name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/partitioned" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the list of partitioned topics under a namespace.", + "description" : "", + "operationId" : "getPartitionedTopicList", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Namespace name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{bundle}" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the list of non-persistent topics under a namespace bundle.", + "description" : "", + "operationId" : "getListFromBundle", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "description" : "Bundle range of a topic", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace doesn't exist" + }, + "412" : { + "description" : "Namespace name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}" : { + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Create a non-partitioned topic.", + "description" : "This is the only REST endpoint from which non-partitioned topics could be created.", + "operationId" : "createNonPartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "409" : { + "description" : "Partitioned topic already exist" + }, + "412" : { + "description" : "Failed Reason : Name is invalid or Namespace does not have any clusters configured" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "delete" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Delete a topic.", + "description" : "The topic cannot be deleted if delete is not forcefully and there's any active subscription or producer connected to the it. Force delete ignores connected clients and deletes topic by explicitly closing them.", + "operationId" : "deleteTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "force", + "in" : "query", + "description" : "Stop all producer/consumer/replicator and delete topic forcefully", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic has active producers/subscriptions" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/all_subscription/expireMessages/{expireTimeInSeconds}" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Expiry messages on all subscriptions of topic.", + "description" : "", + "operationId" : "expireMessagesForAllSubscriptions", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "expireTimeInSeconds", + "in" : "path", + "description" : "Expires beyond the specified number of seconds", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Expiry messages on a non-persistent topic is not allowed" + }, + "412" : { + "description" : "Can't find owner for topic" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/backlog" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get estimated backlog for offline topic.", + "description" : "", + "operationId" : "getBacklog", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PersistentOfflineTopicStats" + } + }, + "404" : { + "description" : "Namespace does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/compaction" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the status of a compaction operation for a topic.", + "description" : "", + "operationId" : "compactionStatus", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/LongRunningProcessStatus" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist, or compaction hasn't run" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Trigger a compaction operation on a topic.", + "description" : "", + "operationId" : "compact", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "409" : { + "description" : "Compaction already running" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/internal-info" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the internal stats for the topic.", + "description" : "", + "operationId" : "getManagedLedgerInfo", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/internalStats" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the internal stats for the topic.", + "description" : "", + "operationId" : "getInternalStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PersistentTopicInternalStats" + } + }, + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace/topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/lastMessageId" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Return the last commit message id of topic", + "description" : "", + "operationId" : "getLastMessageId", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/MessageId" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/offload" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Offload a prefix of a topic to long term storage", + "description" : "", + "operationId" : "offloadStatus", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/OffloadProcessStatus" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Offload a prefix of a topic to long term storage", + "description" : "", + "operationId" : "triggerOffload", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "409" : { + "description" : "Offload already running" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/partitioned-stats" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the stats for the partitioned topic.", + "description" : "", + "operationId" : "getPartitionedStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "perPartition", + "in" : "query", + "description" : "Get per partition stats", + "required" : false, + "type" : "boolean", + "default" : true + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/partitions" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get partitioned topic metadata.", + "description" : "", + "operationId" : "getPartitionedMetadata", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "checkAllowAutoCreation", + "in" : "query", + "description" : "Is check configuration required to automatically create topic", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PartitionedTopicMetadata" + } + }, + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace/topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate cluster configuration" + } + } + }, + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Increment partitons of an existing partitioned topic.", + "description" : "It only increments partitions of existing non-global partitioned-topic", + "operationId" : "updatePartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "updateLocalTopicOnly", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "in" : "body", + "name" : "body", + "description" : "The number of partitions for the topic", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to adminisActions to be grantedtrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "406" : { + "description" : "The number of partitions should be more than 0" + }, + "409" : { + "description" : "Partitioned topic does not exist" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Create a partitioned topic.", + "description" : "It needs to be called before creating a producer on a partitioned topic.", + "operationId" : "createPartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The number of partitions for the topic", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace does not exist" + }, + "409" : { + "description" : "Partitioned topic already exists" + }, + "412" : { + "description" : "Failed Reason : Name is invalid or Namespace does not have any clusters configured" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "delete" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Delete a partitioned topic.", + "description" : "It will also delete all the partitions of the topic if it exists.", + "operationId" : "deletePartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "force", + "in" : "query", + "description" : "Stop all producer/consumer/replicator and delete topic forcefully", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Partitioned topic does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/permissions" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get permissions on a topic.", + "description" : "Retrieve the effective permissions for a topic. These permissions are defined by the permissions set at thenamespace level combined (union) with any eventual specific permission set on the topic.", + "operationId" : "getPermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/permissions/{role}" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Grant a new permission to a role on a single topic.", + "description" : "", + "operationId" : "grantPermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "description" : "Client role to which grant permissions", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "Actions to be granted (produce,functions,consume)", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "produce", "consume", "functions" ] + } + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "delete" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Revoke permissions on a topic.", + "description" : "Revoke permissions to a role on a single topic. If the permission was not set at the topiclevel, but rather at the namespace level, this operation will return an error (HTTP status code 412).", + "operationId" : "revokePermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "description" : "Client role to which grant permissions", + "required" : true, + "type" : "string" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Permissions are not set at the topic level" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/stats" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the stats for the topic.", + "description" : "", + "operationId" : "getStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/TopicStats" + } + }, + "401" : { + "description" : "Don't have permission to manage resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace/topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}" : { + "delete" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Delete a subscription.", + "description" : "There should not be any active consumers on the subscription.", + "operationId" : "deleteSubscription", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to be deleted", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Subscription has active consumers" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/expireMessages/{expireTimeInSeconds}" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Expiry messages on a topic subscription.", + "description" : "", + "operationId" : "expireTopicMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to be Expiry messages on", + "required" : true, + "type" : "string" + }, { + "name" : "expireTimeInSeconds", + "in" : "path", + "description" : "Expires beyond the specified number of seconds", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Expiry messages on a non-persistent topic is not allowed" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/position/{messagePosition}" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Peek nth message on a topic subscription.", + "description" : "", + "operationId" : "peekNthMessage", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscribed message expired", + "required" : true, + "type" : "string" + }, { + "name" : "messagePosition", + "in" : "path", + "description" : "The number of messages (default 1)", + "required" : true, + "type" : "integer", + "default" : 1, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic, subscription or the message position does not exist" + }, + "405" : { + "description" : "Skipping messages on a non-persistent topic is not allowed" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Reset subscription to message position closest to given position.", + "description" : "It fence cursor and disconnects all active consumers before reseting cursor.", + "operationId" : "resetCursorOnPosition", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to reset position on", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "in" : "body", + "name" : "messageId", + "description" : "messageId to reset back to (ledgerId:entryId)", + "required" : false, + "schema" : { + "$ref" : "#/definitions/MessageIdImpl" + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Not supported for partitioned topics" + }, + "412" : { + "description" : "Unable to find position for position specified" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor/{timestamp}" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Reset subscription to message position closest to absolute timestamp (in ms).", + "description" : "It fence cursor and disconnects all active consumers before reseting cursor.", + "operationId" : "resetCursor", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to reset position on", + "required" : true, + "type" : "string" + }, { + "name" : "timestamp", + "in" : "path", + "description" : "time in minutes to reset back to (or minutes, hours,days,weeks eg:100m, 3h, 2d, 5w)", + "required" : true, + "type" : "integer", + "format" : "int64" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Method Not Allowed" + }, + "412" : { + "description" : "Failed to reset cursor on subscription or Unable to find position for timestamp specified" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/skip/{numMessages}" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Skipping messages on a topic subscription.", + "description" : "", + "operationId" : "skipMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Name of subscription", + "required" : true, + "type" : "string" + }, { + "name" : "numMessages", + "in" : "path", + "description" : "The number of messages to skip", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Skipping messages on a partitioned topic is not allowed" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/skip_all" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Skip all messages on a topic subscription.", + "description" : "Completely clears the backlog on the subscription.", + "operationId" : "skipAllMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Name of subscription", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Operation not allowed on non-persistent topic" + }, + "412" : { + "description" : "Can't find owner for topic" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscription/{subscriptionName}" : { + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Reset subscription to message position closest to given position.", + "description" : "Creates a subscription on the topic at the specified message id", + "operationId" : "createSubscription", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subscriptionName", + "in" : "path", + "description" : "Subscription to create position on", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "messageId where to create the subscription. It can be 'latest', 'earliest' or (ledgerId:entryId)", + "required" : false, + "type" : "boolean", + "default" : "latest", + "enum" : [ "latest", "earliest", "ledgerId:entryId" ] + }, { + "name" : "replicated", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Not supported for partitioned topics" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/subscriptions" : { + "get" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Get the list of persistent subscriptions for a given topic.", + "description" : "", + "operationId" : "getSubscriptions", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/terminate" : { + "post" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Terminate a topic. A topic that is terminated will not accept any more messages to be published and will let consumer to drain existing messages in backlog", + "description" : "", + "operationId" : "terminate", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/MessageId" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Termination of a partitioned topic is not allowed" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/non-persistent/{tenant}/{namespace}/{topic}/unload" : { + "put" : { + "tags" : [ "non-persistent topic" ], + "summary" : "Unload a topic", + "description" : "", + "operationId" : "unloadTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "This operation requires super-user access" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "The tenant/namespace/topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the list of topics under a namespace.", + "description" : "", + "operationId" : "getList", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Namespace name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/partitioned" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the list of partitioned topics under a namespace.", + "description" : "", + "operationId" : "getPartitionedTopicList", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Namespace name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}" : { + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Create a non-partitioned topic.", + "description" : "This is the only REST endpoint from which non-partitioned topics could be created.", + "operationId" : "createNonPartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "409" : { + "description" : "Partitioned topic already exist" + }, + "412" : { + "description" : "Failed Reason : Name is invalid or Namespace does not have any clusters configured" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "delete" : { + "tags" : [ "persistent topic" ], + "summary" : "Delete a topic.", + "description" : "The topic cannot be deleted if delete is not forcefully and there's any active subscription or producer connected to the it. Force delete ignores connected clients and deletes topic by explicitly closing them.", + "operationId" : "deleteTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "force", + "in" : "query", + "description" : "Stop all producer/consumer/replicator and delete topic forcefully", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic has active producers/subscriptions" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/all_subscription/expireMessages/{expireTimeInSeconds}" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Expiry messages on all subscriptions of topic.", + "description" : "", + "operationId" : "expireMessagesForAllSubscriptions", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "expireTimeInSeconds", + "in" : "path", + "description" : "Expires beyond the specified number of seconds", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Expiry messages on a non-persistent topic is not allowed" + }, + "412" : { + "description" : "Can't find owner for topic" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/backlog" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get estimated backlog for offline topic.", + "description" : "", + "operationId" : "getBacklog", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PersistentOfflineTopicStats" + } + }, + "404" : { + "description" : "Namespace does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/compaction" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the status of a compaction operation for a topic.", + "description" : "", + "operationId" : "compactionStatus", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/LongRunningProcessStatus" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist, or compaction hasn't run" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Trigger a compaction operation on a topic.", + "description" : "", + "operationId" : "compact", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "409" : { + "description" : "Compaction already running" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/internal-info" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the internal stats for the topic.", + "description" : "", + "operationId" : "getManagedLedgerInfo", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/internalStats" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the internal stats for the topic.", + "description" : "", + "operationId" : "getInternalStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PersistentTopicInternalStats" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/lastMessageId" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Return the last commit message id of topic", + "description" : "", + "operationId" : "getLastMessageId", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/MessageId" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/offload" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Offload a prefix of a topic to long term storage", + "description" : "", + "operationId" : "offloadStatus", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/OffloadProcessStatus" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Offload a prefix of a topic to long term storage", + "description" : "", + "operationId" : "triggerOffload", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Operation is not allowed on the persistent topic" + }, + "409" : { + "description" : "Offload already running" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/partitioned-stats" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the stats for the partitioned topic.", + "description" : "", + "operationId" : "getPartitionedStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "perPartition", + "in" : "query", + "description" : "Get per partition stats", + "required" : false, + "type" : "boolean", + "default" : true + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/partitions" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get partitioned topic metadata.", + "description" : "", + "operationId" : "getPartitionedMetadata", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "checkAllowAutoCreation", + "in" : "query", + "description" : "Is check configuration required to automatically create topic", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PartitionedTopicMetadata" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Increment partitons of an existing partitioned topic.", + "description" : "It only increments partitions of existing non-global partitioned-topic", + "operationId" : "updatePartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "updateLocalTopicOnly", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "in" : "body", + "name" : "body", + "description" : "The number of partitions for the topic", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to adminisActions to be grantedtrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "406" : { + "description" : "The number of partitions should be more than 0" + }, + "409" : { + "description" : "Partitioned topic does not exist" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Create a partitioned topic.", + "description" : "It needs to be called before creating a producer on a partitioned topic.", + "operationId" : "createPartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The number of partitions for the topic", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "409" : { + "description" : "Partitioned topic already exist" + }, + "412" : { + "description" : "Failed Reason : Name is invalid or Namespace does not have any clusters configured" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + }, + "delete" : { + "tags" : [ "persistent topic" ], + "summary" : "Delete a partitioned topic.", + "description" : "It will also delete all the partitions of the topic if it exists.", + "operationId" : "deletePartitionedTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "force", + "in" : "query", + "description" : "Stop all producer/consumer/replicator and delete topic forcefully", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Partitioned topic does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Partitioned topic name is invalid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/permissions" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get permissions on a topic.", + "description" : "Retrieve the effective permissions for a topic. These permissions are defined by the permissions set at thenamespace level combined (union) with any eventual specific permission set on the topic.", + "operationId" : "getPermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/permissions/{role}" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Grant a new permission to a role on a single topic.", + "description" : "", + "operationId" : "grantPermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "description" : "Client role to which grant permissions", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "Actions to be granted (produce,functions,consume)", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "produce", "consume", "functions" ] + } + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "delete" : { + "tags" : [ "persistent topic" ], + "summary" : "Revoke permissions on a topic.", + "description" : "Revoke permissions to a role on a single topic. If the permission was not set at the topiclevel, but rather at the namespace level, this operation will return an error (HTTP status code 412).", + "operationId" : "revokePermissionsOnTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "role", + "in" : "path", + "description" : "Client role to which grant permissions", + "required" : true, + "type" : "string" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "tenant/namespace/topic doesn't exit" + }, + "412" : { + "description" : "Permissions are not set at the topic level" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/stats" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the stats for the topic.", + "description" : "", + "operationId" : "getStats", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/TopicStats" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}" : { + "delete" : { + "tags" : [ "persistent topic" ], + "summary" : "Delete a subscription.", + "description" : "There should not be any active consumers on the subscription.", + "operationId" : "deleteSubscription", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to be deleted", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Subscription has active consumers" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/expireMessages/{expireTimeInSeconds}" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Expiry messages on a topic subscription.", + "description" : "", + "operationId" : "expireTopicMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to be Expiry messages on", + "required" : true, + "type" : "string" + }, { + "name" : "expireTimeInSeconds", + "in" : "path", + "description" : "Expires beyond the specified number of seconds", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Expiry messages on a non-persistent topic is not allowed" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/position/{messagePosition}" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Peek nth message on a topic subscription.", + "description" : "", + "operationId" : "peekNthMessage", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscribed message expired", + "required" : true, + "type" : "string" + }, { + "name" : "messagePosition", + "in" : "path", + "description" : "The number of messages (default 1)", + "required" : true, + "type" : "integer", + "default" : 1, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic, subscription or the message position does not exist" + }, + "405" : { + "description" : "Skipping messages on a non-persistent topic is not allowed" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Reset subscription to message position closest to given position.", + "description" : "It fence cursor and disconnects all active consumers before reseting cursor.", + "operationId" : "resetCursorOnPosition", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to reset position on", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + }, { + "in" : "body", + "name" : "messageId", + "description" : "messageId to reset back to (ledgerId:entryId)", + "required" : false, + "schema" : { + "$ref" : "#/definitions/MessageIdImpl" + } + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Not supported for partitioned topics" + }, + "412" : { + "description" : "Unable to find position for position specified" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor/{timestamp}" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Reset subscription to message position closest to absolute timestamp (in ms).", + "description" : "It fence cursor and disconnects all active consumers before reseting cursor.", + "operationId" : "resetCursor", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Subscription to reset position on", + "required" : true, + "type" : "string" + }, { + "name" : "timestamp", + "in" : "path", + "description" : "time in minutes to reset back to (or minutes, hours,days,weeks eg:100m, 3h, 2d, 5w)", + "required" : true, + "type" : "integer", + "format" : "int64" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Method Not Allowed" + }, + "412" : { + "description" : "Failed to reset cursor on subscription or Unable to find position for timestamp specified" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/skip/{numMessages}" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Skipping messages on a topic subscription.", + "description" : "", + "operationId" : "skipMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Name of subscription", + "required" : true, + "type" : "string" + }, { + "name" : "numMessages", + "in" : "path", + "description" : "The number of messages to skip", + "required" : true, + "type" : "integer", + "default" : 0, + "format" : "int32" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Skipping messages on a partitioned topic is not allowed" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subName}/skip_all" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Skip all messages on a topic subscription.", + "description" : "Completely clears the backlog on the subscription.", + "operationId" : "skipAllMessages", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subName", + "in" : "path", + "description" : "Name of subscription", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic or subscription does not exist" + }, + "405" : { + "description" : "Operation not allowed on non-persistent topic" + }, + "412" : { + "description" : "Can't find owner for topic" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscription/{subscriptionName}" : { + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Reset subscription to message position closest to given position.", + "description" : "Creates a subscription on the topic at the specified message id", + "operationId" : "createSubscription", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "subscriptionName", + "in" : "path", + "description" : "Subscription to create position on", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "messageId where to create the subscription. It can be 'latest', 'earliest' or (ledgerId:entryId)", + "required" : false, + "type" : "boolean", + "default" : "latest", + "enum" : [ "latest", "earliest", "ledgerId:entryId" ] + }, { + "name" : "replicated", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean" + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic/Subscription does not exist" + }, + "405" : { + "description" : "Not supported for partitioned topics" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/subscriptions" : { + "get" : { + "tags" : [ "persistent topic" ], + "summary" : "Get the list of persistent subscriptions for a given topic.", + "description" : "", + "operationId" : "getSubscriptions", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/terminate" : { + "post" : { + "tags" : [ "persistent topic" ], + "summary" : "Terminate a topic. A topic that is terminated will not accept any more messages to be published and will let consumer to drain existing messages in backlog", + "description" : "", + "operationId" : "terminate", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/MessageId" + } + }, + "401" : { + "description" : "Don't have permission to administrate resources on this tenant orsubscriber is not authorized to access this operation" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "405" : { + "description" : "Termination of a partitioned topic is not allowed" + }, + "412" : { + "description" : "Topic name is not valid" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/persistent/{tenant}/{namespace}/{topic}/unload" : { + "put" : { + "tags" : [ "persistent topic" ], + "summary" : "Unload a topic", + "description" : "", + "operationId" : "unloadTopic", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "Specify the tenant", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "Specify the namespace", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "description" : "Specify topic name", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "description" : "Is authentication required to perform this operation", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "401" : { + "description" : "Don't have permission to administrate resources on this tenant" + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Topic does not exist" + }, + "409" : { + "description" : "Concurrent modification" + }, + "412" : { + "description" : "Topic name is not valid or can't find owner for topic" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Failed to validate global cluster configuration" + } + } + } + }, + "/resource-quotas" : { + "get" : { + "tags" : [ "resource-quotas" ], + "summary" : "Get the default quota", + "description" : "", + "operationId" : "getDefaultResourceQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + }, + "post" : { + "tags" : [ "resource-quotas" ], + "summary" : "Set the default quota", + "description" : "", + "operationId" : "setDefaultResourceQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "Don't have admin permission" + } + } + } + }, + "/resource-quotas/{tenant}/{namespace}/{bundle}" : { + "get" : { + "tags" : [ "resource-quotas" ], + "summary" : "Get resource quota of a namespace bundle.", + "description" : "", + "operationId" : "getNamespaceBundleResourceQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/ResourceQuota" + } + }, + "403" : { + "description" : "Don't have admin permission" + }, + "404" : { + "description" : "Namespace does not exist" + } + } + }, + "post" : { + "tags" : [ "resource-quotas" ], + "summary" : "Set resource quota on a namespace.", + "description" : "", + "operationId" : "setNamespaceBundleResourceQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "409" : { + "description" : "Concurrent modification" + } + } + }, + "delete" : { + "tags" : [ "resource-quotas" ], + "summary" : "Remove resource quota for a namespace.", + "description" : "", + "operationId" : "removeNamespaceBundleResourceQuota", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "bundle", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "Don't have admin permission" + }, + "409" : { + "description" : "Concurrent modification" + } + } + } + }, + "/schemas/{tenant}/{namespace}/{topic}/compatibility" : { + "post" : { + "tags" : [ "schemas" ], + "summary" : "test the schema compatibility", + "description" : "", + "operationId" : "testCompatibility", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting a schema playload. An example of the expected schema can be found down here.", + "required" : false, + "schema" : { + "$ref" : "#/definitions/PostSchemaPayload" + }, + "x-examples" : { + "application/json" : "{\"type\": \"STRING\", \"schema\": \"\", \"properties\": { \"key1\" : \"value1\" + } }" + } + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/IsCompatibilityResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "500" : { + "description" : "Internal Server Error" + } + } + } + }, + "/schemas/{tenant}/{namespace}/{topic}/schema" : { + "get" : { + "tags" : [ "schemas" ], + "summary" : "Get the schema of a topic", + "description" : "", + "operationId" : "getSchema", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/GetSchemaResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist; or Schema is not found for this topic" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "500" : { + "description" : "Internal Server Error" + } + } + }, + "post" : { + "tags" : [ "schemas" ], + "summary" : "Update the schema of a topic", + "description" : "", + "operationId" : "postSchema", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting a schema playload. An example of the expected schema can be found down here.", + "required" : false, + "schema" : { + "$ref" : "#/definitions/PostSchemaPayload" + }, + "x-examples" : { + "application/json" : "{\"type\": \"STRING\", \"schema\": \"\", \"properties\": { \"key1\" : \"value1\" + } }" + } + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/PostSchemaResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist" + }, + "409" : { + "description" : "Incompatible schema" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "422" : { + "description" : "Invalid schema data" + }, + "500" : { + "description" : "Internal Server Error" + } + } + }, + "delete" : { + "tags" : [ "schemas" ], + "summary" : "Delete the schema of a topic", + "description" : "", + "operationId" : "deleteSchema", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/DeleteSchemaResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "500" : { + "description" : "Internal Server Error" + } + } + } + }, + "/schemas/{tenant}/{namespace}/{topic}/schema/{version}" : { + "get" : { + "tags" : [ "schemas" ], + "summary" : "Get the schema of a topic at a given version", + "description" : "", + "operationId" : "getSchema", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "version", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/GetSchemaResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist; or Schema is not found for this topic" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "500" : { + "description" : "Internal Server Error" + } + } + } + }, + "/schemas/{tenant}/{namespace}/{topic}/schemas" : { + "get" : { + "tags" : [ "schemas" ], + "summary" : "Get the all schemas of a topic", + "description" : "", + "operationId" : "getAllSchemas", + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/GetAllVersionsSchemaResponse" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist; or Schema is not found for this topic" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "500" : { + "description" : "Internal Server Error" + } + } + } + }, + "/schemas/{tenant}/{namespace}/{topic}/version" : { + "post" : { + "tags" : [ "schemas" ], + "summary" : "get the version of the schema", + "description" : "", + "operationId" : "getVersionBySchema", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "topic", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting a schema playload. An example of the expected schema can be found down here.", + "required" : false, + "schema" : { + "$ref" : "#/definitions/PostSchemaPayload" + }, + "x-examples" : { + "application/json" : "{\"type\": \"STRING\", \"schema\": \"\", \"properties\": { \"key1\" : \"value1\" + } }" + } + }, { + "name" : "authoritative", + "in" : "query", + "required" : false, + "type" : "boolean", + "default" : false + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/LongSchemaVersion" + } + }, + "307" : { + "description" : "Current broker doesn't serve the namespace of this topic" + }, + "401" : { + "description" : "Client is not authorized or Don't have admin permission" + }, + "403" : { + "description" : "Client is not authenticated" + }, + "404" : { + "description" : "Tenant or Namespace or Topic doesn't exist" + }, + "412" : { + "description" : "Failed to find the ownership for the topic" + }, + "422" : { + "description" : "Invalid schema data" + }, + "500" : { + "description" : "Internal Server Error" + } + } + } + }, + "/tenants" : { + "get" : { + "tags" : [ "tenants" ], + "summary" : "Get the list of existing tenants.", + "description" : "", + "operationId" : "getTenants", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "Tenant doesn't exist" + } + } + } + }, + "/tenants/{tenant}" : { + "get" : { + "tags" : [ "tenants" ], + "summary" : "Get the admin configuration for a given tenant.", + "description" : "", + "operationId" : "getTenantAdmin", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/TenantInfo" + } + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "Tenant does not exist" + } + } + }, + "post" : { + "tags" : [ "tenants" ], + "summary" : "Update the admins for a tenant.", + "description" : "This operation requires Pulsar super-user privileges.", + "operationId" : "updateTenant", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "TenantInfo", + "required" : false, + "schema" : { + "$ref" : "#/definitions/TenantInfo" + } + } ], + "responses" : { + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "409" : { + "description" : "Tenant already exists" + }, + "412" : { + "description" : "Clusters do not exist" + } + } + }, + "put" : { + "tags" : [ "tenants" ], + "summary" : "Create a new tenant.", + "description" : "This operation requires Pulsar super-user privileges.", + "operationId" : "createTenant", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant name", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "TenantInfo", + "required" : false, + "schema" : { + "$ref" : "#/definitions/TenantInfo" + } + } ], + "responses" : { + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "409" : { + "description" : "Tenant already exists" + }, + "412" : { + "description" : "Clusters do not exist" + } + } + }, + "delete" : { + "tags" : [ "tenants" ], + "summary" : "Delete a tenant and all namespaces and topics under it.", + "description" : "", + "operationId" : "deleteTenant", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant name", + "required" : true, + "type" : "string" + } ], + "responses" : { + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "Tenant does not exist" + }, + "409" : { + "description" : "The tenant still has active namespaces" + } + } + } + } + }, + "definitions" : { + "AllocatorStats" : { + "type" : "object", + "properties" : { + "numDirectArenas" : { + "type" : "integer", + "format" : "int32" + }, + "numHeapArenas" : { + "type" : "integer", + "format" : "int32" + }, + "numThreadLocalCaches" : { + "type" : "integer", + "format" : "int32" + }, + "normalCacheSize" : { + "type" : "integer", + "format" : "int32" + }, + "smallCacheSize" : { + "type" : "integer", + "format" : "int32" + }, + "tinyCacheSize" : { + "type" : "integer", + "format" : "int32" + }, + "directArenas" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolArenaStats" + } + }, + "heapArenas" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolArenaStats" + } + } + } + }, + "AuthPolicies" : { + "type" : "object", + "properties" : { + "namespace_auth" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string", + "enum" : [ "produce", "consume", "functions" ] + } + } + }, + "destination_auth" : { + "type" : "object", + "additionalProperties" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string", + "enum" : [ "produce", "consume", "functions" ] + } + } + } + }, + "subscription_auth_roles" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + } + } + }, + "AutoFailoverPolicyData" : { + "type" : "object", + "properties" : { + "policy_type" : { + "type" : "string", + "description" : "The auto failover policy type", + "enum" : [ "min_available" ] + }, + "parameters" : { + "type" : "object", + "example" : "{\n \"min_limit\": 3,\n \"usage_threshold\": 80\n}\n", + "description" : "The parameters applied to the auto failover policy specified by `policy_type`.\nThe parameters for 'min_available' are :\n - 'min_limit': the limit of minimal number of available brokers in primary group before auto failover\n - 'usage_threshold': the resource usage threshold. If the usage of a broker is beyond this value, it would be marked as unavailable\n", + "additionalProperties" : { + "type" : "string" + } + } + }, + "description" : "The auto failover policy configuration data" + }, + "BacklogQuota" : { + "type" : "object", + "properties" : { + "limit" : { + "type" : "integer", + "format" : "int64" + }, + "policy" : { + "type" : "string", + "enum" : [ "producer_request_hold", "producer_exception", "consumer_backlog_eviction" ] + } + } + }, + "BookieAffinityGroupData" : { + "type" : "object", + "properties" : { + "bookkeeperAffinityGroupPrimary" : { + "type" : "string" + }, + "bookkeeperAffinityGroupSecondary" : { + "type" : "string" + } + } + }, + "BookieInfo" : { + "type" : "object", + "properties" : { + "rack" : { + "type" : "string" + }, + "hostname" : { + "type" : "string" + } + } + }, + "BrokerNamespaceIsolationData" : { + "type" : "object", + "properties" : { + "brokerName" : { + "type" : "string", + "example" : "broker1:8080", + "description" : "The broker name" + }, + "policyName" : { + "type" : "string", + "example" : "my-policy", + "description" : "Policy name" + }, + "isPrimary" : { + "type" : "boolean", + "example" : false, + "description" : "Is Primary broker" + }, + "namespaceRegex" : { + "type" : "array", + "description" : "The namespace-isolation policies attached to this broker", + "items" : { + "type" : "string" + } + } + }, + "description" : "The namespace isolation data for a given broker" + }, + "BundlesData" : { + "type" : "object", + "properties" : { + "boundaries" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "numBundles" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ClusterData" : { + "type" : "object", + "properties" : { + "serviceUrl" : { + "type" : "string", + "example" : "http://pulsar.example.com:8080", + "description" : "The HTTP rest service URL (for admin operations)" + }, + "serviceUrlTls" : { + "type" : "string", + "example" : "https://pulsar.example.com:8443", + "description" : "The HTTPS rest service URL (for admin operations)" + }, + "brokerServiceUrl" : { + "type" : "string", + "example" : "pulsar://pulsar.example.com:6650", + "description" : "The broker service url (for produce and consume operations)" + }, + "brokerServiceUrlTls" : { + "type" : "string", + "example" : "pulsar+ssl://pulsar.example.com:6651", + "description" : "The secured broker service url (for produce and consume operations)" + }, + "peerClusterNames" : { + "type" : "array", + "description" : "A set of peer cluster names", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "description" : "The configuration data for a cluster" + }, + "ConsumerStats" : { + "type" : "object", + "properties" : { + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "msgRateRedeliver" : { + "type" : "number", + "format" : "double" + }, + "consumerName" : { + "type" : "string" + }, + "availablePermits" : { + "type" : "integer", + "format" : "int32" + }, + "unackedMessages" : { + "type" : "integer", + "format" : "int32" + }, + "blockedConsumerOnUnackedMsgs" : { + "type" : "boolean" + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "address" : { + "type" : "string" + }, + "connectedSince" : { + "type" : "string" + }, + "clientVersion" : { + "type" : "string" + } + } + }, + "CursorDetails" : { + "type" : "object", + "properties" : { + "cursorBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerId" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "CursorStats" : { + "type" : "object", + "properties" : { + "markDeletePosition" : { + "type" : "string" + }, + "readPosition" : { + "type" : "string" + }, + "waitingReadOp" : { + "type" : "boolean" + }, + "pendingReadOps" : { + "type" : "integer", + "format" : "int32" + }, + "messagesConsumedCounter" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedger" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerLastEntry" : { + "type" : "integer", + "format" : "int64" + }, + "individuallyDeletedMessages" : { + "type" : "string" + }, + "lastLedgerSwitchTimestamp" : { + "type" : "string" + }, + "state" : { + "type" : "string" + }, + "numberOfEntriesSinceFirstNotAckedMessage" : { + "type" : "integer", + "format" : "int64" + }, + "totalNonContiguousDeletedMessagesRange" : { + "type" : "integer", + "format" : "int32" + }, + "properties" : { + "type" : "object", + "additionalProperties" : { + "type" : "integer", + "format" : "int64" + } + } + } + }, + "DeleteSchemaResponse" : { + "type" : "object", + "properties" : { + "version" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "DispatchRate" : { + "type" : "object", + "properties" : { + "dispatchThrottlingRateInMsg" : { + "type" : "integer", + "format" : "int32" + }, + "dispatchThrottlingRateInByte" : { + "type" : "integer", + "format" : "int64" + }, + "relativeToPublishRate" : { + "type" : "boolean" + }, + "ratePeriodInSecond" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "FailureDomain" : { + "type" : "object", + "properties" : { + "brokers" : { + "type" : "array", + "example" : "[ 'broker-1', 'broker-2' ]", + "description" : "The collection of brokers in the same failure domain", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "description" : "The data of a failure domain configuration in a cluster" + }, + "GetAllVersionsSchemaResponse" : { + "type" : "object", + "properties" : { + "getSchemaResponses" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/GetSchemaResponse" + } + } + } + }, + "GetSchemaResponse" : { + "type" : "object", + "properties" : { + "version" : { + "type" : "integer", + "format" : "int64" + }, + "type" : { + "type" : "string", + "enum" : [ "NONE", "STRING", "JSON", "PROTOBUF", "AVRO", "BOOLEAN", "INT8", "INT16", "INT32", "INT64", "FLOAT", "DOUBLE", "DATE", "TIME", "TIMESTAMP", "KEY_VALUE", "BYTES", "AUTO", "AUTO_CONSUME", "AUTO_PUBLISH" ] + }, + "timestamp" : { + "type" : "integer", + "format" : "int64" + }, + "data" : { + "type" : "string" + }, + "properties" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "InternalConfigurationData" : { + "type" : "object", + "properties" : { + "zookeeperServers" : { + "type" : "string" + }, + "configurationStoreServers" : { + "type" : "string" + }, + "ledgersRootPath" : { + "type" : "string" + }, + "stateStorageServiceUrl" : { + "type" : "string" + } + } + }, + "IsCompatibilityResponse" : { + "type" : "object", + "properties" : { + "schemaCompatibilityStrategy" : { + "type" : "string" + }, + "compatibility" : { + "type" : "boolean" + } + } + }, + "LedgerDetails" : { + "type" : "object", + "properties" : { + "entries" : { + "type" : "integer", + "format" : "int64" + }, + "timestamp" : { + "type" : "integer", + "format" : "int64" + }, + "size" : { + "type" : "integer", + "format" : "int64" + }, + "ledgerId" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "LedgerInfo" : { + "type" : "object", + "properties" : { + "ledgerId" : { + "type" : "integer", + "format" : "int64" + }, + "entries" : { + "type" : "integer", + "format" : "int64" + }, + "size" : { + "type" : "integer", + "format" : "int64" + }, + "offloaded" : { + "type" : "boolean" + } + } + }, + "LoadReport" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "brokerVersionString" : { + "type" : "string" + }, + "webServiceUrl" : { + "type" : "string" + }, + "webServiceUrlTls" : { + "type" : "string" + }, + "pulsarServiceUrl" : { + "type" : "string" + }, + "pulsarServiceUrlTls" : { + "type" : "string" + }, + "persistentTopicsEnabled" : { + "type" : "boolean" + }, + "nonPersistentTopicsEnabled" : { + "type" : "boolean" + }, + "timestamp" : { + "type" : "integer", + "format" : "int64" + }, + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "numTopics" : { + "type" : "integer", + "format" : "int32" + }, + "numConsumers" : { + "type" : "integer", + "format" : "int32" + }, + "numProducers" : { + "type" : "integer", + "format" : "int32" + }, + "numBundles" : { + "type" : "integer", + "format" : "int32" + }, + "protocols" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "systemResourceUsage" : { + "$ref" : "#/definitions/SystemResourceUsage" + }, + "bundleStats" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/NamespaceBundleStats" + } + }, + "bundleGains" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + }, + "bundleLosses" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + }, + "allocatedCPU" : { + "type" : "number", + "format" : "double" + }, + "allocatedMemory" : { + "type" : "number", + "format" : "double" + }, + "allocatedBandwidthIn" : { + "type" : "number", + "format" : "double" + }, + "allocatedBandwidthOut" : { + "type" : "number", + "format" : "double" + }, + "allocatedMsgRateIn" : { + "type" : "number", + "format" : "double" + }, + "allocatedMsgRateOut" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedCPU" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedMemory" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedBandwidthIn" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedBandwidthOut" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedMsgRateIn" : { + "type" : "number", + "format" : "double" + }, + "preAllocatedMsgRateOut" : { + "type" : "number", + "format" : "double" + }, + "cpu" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "memory" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "directMemory" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "bandwidthIn" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "bandwidthOut" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "lastUpdate" : { + "type" : "integer", + "format" : "int64" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "underLoaded" : { + "type" : "boolean" + }, + "overLoaded" : { + "type" : "boolean" + }, + "loadReportType" : { + "type" : "string" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + } + } + }, + "LongRunningProcessStatus" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "NOT_RUN", "RUNNING", "SUCCESS", "ERROR" ] + }, + "lastError" : { + "type" : "string" + } + } + }, + "LongSchemaVersion" : { + "type" : "object", + "properties" : { + "version" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "MessageId" : { + "type" : "object" + }, + "MessageIdImpl" : { + "type" : "object", + "properties" : { + "ledgerId" : { + "type" : "integer", + "format" : "int64" + }, + "entryId" : { + "type" : "integer", + "format" : "int64" + }, + "partitionIndex" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "Metrics" : { + "type" : "object", + "properties" : { + "metrics" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "dimensions" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "NamespaceBundleStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "consumerCount" : { + "type" : "integer", + "format" : "int32" + }, + "producerCount" : { + "type" : "integer", + "format" : "int32" + }, + "topics" : { + "type" : "integer", + "format" : "int64" + }, + "cacheSize" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "NamespaceIsolationData" : { + "type" : "object", + "properties" : { + "namespaces" : { + "type" : "array", + "description" : "The list of namespaces to apply this namespace isolation data", + "items" : { + "type" : "string" + } + }, + "primary" : { + "type" : "array", + "description" : "The list of secondary brokers for serving the list of namespaces in this isolation policy", + "items" : { + "type" : "string" + } + }, + "auto_failover_policy" : { + "example" : "{ \"policy_type\": \"min_available\" \"parameters\": { \"\": \"\" }}", + "description" : "The data of auto-failover policy configuration", + "$ref" : "#/definitions/AutoFailoverPolicyData" + } + }, + "description" : "The data of namespace isolation configuration" + }, + "NamespaceOwnershipStatus" : { + "type" : "object", + "properties" : { + "broker_assignment" : { + "type" : "string", + "enum" : [ "primary", "secondary", "shared" ] + }, + "is_controlled" : { + "type" : "boolean" + }, + "is_active" : { + "type" : "boolean" + } + } + }, + "NonPersistentPublisherStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "averageMsgSize" : { + "type" : "number", + "format" : "double" + }, + "producerId" : { + "type" : "integer", + "format" : "int64" + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "msgDropRate" : { + "type" : "number", + "format" : "double" + }, + "address" : { + "type" : "string" + }, + "producerName" : { + "type" : "string" + }, + "connectedSince" : { + "type" : "string" + }, + "clientVersion" : { + "type" : "string" + } + } + }, + "NonPersistentReplicatorStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "msgRateExpired" : { + "type" : "number", + "format" : "double" + }, + "replicationBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "connected" : { + "type" : "boolean" + }, + "replicationDelayInSeconds" : { + "type" : "integer", + "format" : "int64" + }, + "inboundConnection" : { + "type" : "string" + }, + "inboundConnectedSince" : { + "type" : "string" + }, + "outboundConnection" : { + "type" : "string" + }, + "outboundConnectedSince" : { + "type" : "string" + }, + "msgDropRate" : { + "type" : "number", + "format" : "double" + } + } + }, + "NonPersistentSubscriptionStats" : { + "type" : "object", + "properties" : { + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "msgRateRedeliver" : { + "type" : "number", + "format" : "double" + }, + "msgBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "blockedSubscriptionOnUnackedMsgs" : { + "type" : "boolean" + }, + "msgDelayed" : { + "type" : "integer", + "format" : "int64" + }, + "unackedMessages" : { + "type" : "integer", + "format" : "int64" + }, + "type" : { + "type" : "string", + "enum" : [ "Exclusive", "Shared", "Failover", "Key_Shared" ] + }, + "activeConsumerName" : { + "type" : "string" + }, + "msgRateExpired" : { + "type" : "number", + "format" : "double" + }, + "lastExpireTimestamp" : { + "type" : "integer", + "format" : "int64" + }, + "consumers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ConsumerStats" + } + }, + "isReplicated" : { + "type" : "boolean" + }, + "msgDropRate" : { + "type" : "number", + "format" : "double" + } + } + }, + "NonPersistentTopicStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "averageMsgSize" : { + "type" : "number", + "format" : "double" + }, + "storageSize" : { + "type" : "integer", + "format" : "int64" + }, + "backlogSize" : { + "type" : "integer", + "format" : "int64" + }, + "publishers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/NonPersistentPublisherStats" + } + }, + "subscriptions" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/NonPersistentSubscriptionStats" + } + }, + "replication" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/NonPersistentReplicatorStats" + } + }, + "deduplicationStatus" : { + "type" : "string" + }, + "bytesInCounter" : { + "type" : "integer", + "format" : "int64" + }, + "msgInCounter" : { + "type" : "integer", + "format" : "int64" + }, + "msgDropRate" : { + "type" : "number", + "format" : "double" + } + } + }, + "OffloadProcessStatus" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "NOT_RUN", "RUNNING", "SUCCESS", "ERROR" ] + }, + "lastError" : { + "type" : "string" + }, + "firstUnoffloadedMessage" : { + "$ref" : "#/definitions/MessageIdImpl" + } + } + }, + "OutputStream" : { + "type" : "object" + }, + "PartitionedTopicMetadata" : { + "type" : "object", + "properties" : { + "partitions" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "PendingBookieOpsStats" : { + "type" : "object", + "properties" : { + "dataLedgerOpenOp" : { + "type" : "integer", + "format" : "int64" + }, + "dataLedgerCloseOp" : { + "type" : "integer", + "format" : "int64" + }, + "dataLedgerCreateOp" : { + "type" : "integer", + "format" : "int64" + }, + "dataLedgerDeleteOp" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerOpenOp" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerCloseOp" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerCreateOp" : { + "type" : "integer", + "format" : "int64" + }, + "cursorLedgerDeleteOp" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "PersistencePolicies" : { + "type" : "object", + "properties" : { + "bookkeeperEnsemble" : { + "type" : "integer", + "format" : "int32" + }, + "bookkeeperWriteQuorum" : { + "type" : "integer", + "format" : "int32" + }, + "bookkeeperAckQuorum" : { + "type" : "integer", + "format" : "int32" + }, + "managedLedgerMaxMarkDeleteRate" : { + "type" : "number", + "format" : "double" + } + } + }, + "PersistentOfflineTopicStats" : { + "type" : "object", + "properties" : { + "storageSize" : { + "type" : "integer", + "format" : "int64" + }, + "totalMessages" : { + "type" : "integer", + "format" : "int64" + }, + "messageBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "brokerName" : { + "type" : "string" + }, + "topicName" : { + "type" : "string" + }, + "dataLedgerDetails" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/LedgerDetails" + } + }, + "cursorDetails" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/CursorDetails" + } + }, + "statGeneratedAt" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "PersistentTopicInternalStats" : { + "type" : "object", + "properties" : { + "entriesAddedCounter" : { + "type" : "integer", + "format" : "int64" + }, + "numberOfEntries" : { + "type" : "integer", + "format" : "int64" + }, + "totalSize" : { + "type" : "integer", + "format" : "int64" + }, + "currentLedgerEntries" : { + "type" : "integer", + "format" : "int64" + }, + "currentLedgerSize" : { + "type" : "integer", + "format" : "int64" + }, + "lastLedgerCreatedTimestamp" : { + "type" : "string" + }, + "lastLedgerCreationFailureTimestamp" : { + "type" : "string" + }, + "waitingCursorsCount" : { + "type" : "integer", + "format" : "int32" + }, + "pendingAddEntriesCount" : { + "type" : "integer", + "format" : "int32" + }, + "lastConfirmedEntry" : { + "type" : "string" + }, + "state" : { + "type" : "string" + }, + "ledgers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/LedgerInfo" + } + }, + "cursors" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/CursorStats" + } + } + } + }, + "Policies" : { + "type" : "object", + "properties" : { + "auth_policies" : { + "$ref" : "#/definitions/AuthPolicies" + }, + "replication_clusters" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + }, + "bundles" : { + "$ref" : "#/definitions/BundlesData" + }, + "backlog_quota_map" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/BacklogQuota" + } + }, + "topicDispatchRate" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "subscriptionDispatchRate" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "replicatorDispatchRate" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/DispatchRate" + } + }, + "clusterSubscribeRate" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/SubscribeRate" + } + }, + "persistence" : { + "$ref" : "#/definitions/PersistencePolicies" + }, + "deduplicationEnabled" : { + "type" : "boolean" + }, + "publishMaxMessageRate" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/PublishRate" + } + }, + "latency_stats_sample_rate" : { + "type" : "object", + "additionalProperties" : { + "type" : "integer", + "format" : "int32" + } + }, + "message_ttl_in_seconds" : { + "type" : "integer", + "format" : "int32" + }, + "retention_policies" : { + "$ref" : "#/definitions/RetentionPolicies" + }, + "deleted" : { + "type" : "boolean" + }, + "antiAffinityGroup" : { + "type" : "string" + }, + "encryption_required" : { + "type" : "boolean" + }, + "subscription_auth_mode" : { + "type" : "string", + "enum" : [ "None", "Prefix" ] + }, + "max_producers_per_topic" : { + "type" : "integer", + "format" : "int32" + }, + "max_consumers_per_topic" : { + "type" : "integer", + "format" : "int32" + }, + "max_consumers_per_subscription" : { + "type" : "integer", + "format" : "int32" + }, + "compaction_threshold" : { + "type" : "integer", + "format" : "int64" + }, + "offload_threshold" : { + "type" : "integer", + "format" : "int64" + }, + "offload_deletion_lag_ms" : { + "type" : "integer", + "format" : "int64" + }, + "schema_auto_update_compatibility_strategy" : { + "type" : "string", + "enum" : [ "AutoUpdateDisabled", "Backward", "Forward", "Full", "AlwaysCompatible", "BackwardTransitive", "ForwardTransitive", "FullTransitive" ] + }, + "schema_compatibility_strategy" : { + "type" : "string", + "enum" : [ "UNDEFINED", "ALWAYS_INCOMPATIBLE", "ALWAYS_COMPATIBLE", "BACKWARD", "FORWARD", "FULL", "BACKWARD_TRANSITIVE", "FORWARD_TRANSITIVE", "FULL_TRANSITIVE" ] + }, + "is_allow_auto_update_schema" : { + "type" : "boolean" + }, + "schema_validation_enforced" : { + "type" : "boolean" + } + } + }, + "PoolArenaStats" : { + "type" : "object", + "properties" : { + "numTinySubpages" : { + "type" : "integer", + "format" : "int32" + }, + "numSmallSubpages" : { + "type" : "integer", + "format" : "int32" + }, + "numChunkLists" : { + "type" : "integer", + "format" : "int32" + }, + "tinySubpages" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolSubpageStats" + } + }, + "smallSubpages" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolSubpageStats" + } + }, + "chunkLists" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolChunkListStats" + } + }, + "numAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numTinyAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numSmallAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numNormalAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numHugeAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numDeallocations" : { + "type" : "integer", + "format" : "int64" + }, + "numTinyDeallocations" : { + "type" : "integer", + "format" : "int64" + }, + "numSmallDeallocations" : { + "type" : "integer", + "format" : "int64" + }, + "numNormalDeallocations" : { + "type" : "integer", + "format" : "int64" + }, + "numHugeDeallocations" : { + "type" : "integer", + "format" : "int64" + }, + "numActiveAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numActiveTinyAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numActiveSmallAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numActiveNormalAllocations" : { + "type" : "integer", + "format" : "int64" + }, + "numActiveHugeAllocations" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "PoolChunkListStats" : { + "type" : "object", + "properties" : { + "minUsage" : { + "type" : "integer", + "format" : "int32" + }, + "maxUsage" : { + "type" : "integer", + "format" : "int32" + }, + "chunks" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PoolChunkStats" + } + } + } + }, + "PoolChunkStats" : { + "type" : "object", + "properties" : { + "usage" : { + "type" : "integer", + "format" : "int32" + }, + "chunkSize" : { + "type" : "integer", + "format" : "int32" + }, + "freeBytes" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "PoolSubpageStats" : { + "type" : "object", + "properties" : { + "maxNumElements" : { + "type" : "integer", + "format" : "int32" + }, + "numAvailable" : { + "type" : "integer", + "format" : "int32" + }, + "elementSize" : { + "type" : "integer", + "format" : "int32" + }, + "pageSize" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "PostSchemaPayload" : { + "type" : "object", + "properties" : { + "type" : { + "type" : "string" + }, + "schema" : { + "type" : "string" + }, + "properties" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "PostSchemaResponse" : { + "type" : "object", + "properties" : { + "version" : { + "$ref" : "#/definitions/SchemaVersion" + } + } + }, + "PublishRate" : { + "type" : "object", + "properties" : { + "publishThrottlingRateInMsg" : { + "type" : "integer", + "format" : "int32" + }, + "publishThrottlingRateInByte" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "PublisherStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "averageMsgSize" : { + "type" : "number", + "format" : "double" + }, + "producerId" : { + "type" : "integer", + "format" : "int64" + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "address" : { + "type" : "string" + }, + "producerName" : { + "type" : "string" + }, + "connectedSince" : { + "type" : "string" + }, + "clientVersion" : { + "type" : "string" + } + } + }, + "ReplicatorStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "msgRateExpired" : { + "type" : "number", + "format" : "double" + }, + "replicationBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "connected" : { + "type" : "boolean" + }, + "replicationDelayInSeconds" : { + "type" : "integer", + "format" : "int64" + }, + "inboundConnection" : { + "type" : "string" + }, + "inboundConnectedSince" : { + "type" : "string" + }, + "outboundConnection" : { + "type" : "string" + }, + "outboundConnectedSince" : { + "type" : "string" + } + } + }, + "ResourceDescription" : { + "type" : "object", + "properties" : { + "usagePct" : { + "type" : "integer", + "format" : "int32" + }, + "resourceUsage" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/ResourceUsage" + } + } + } + }, + "ResourceQuota" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "bandwidthIn" : { + "type" : "number", + "format" : "double" + }, + "bandwidthOut" : { + "type" : "number", + "format" : "double" + }, + "memory" : { + "type" : "number", + "format" : "double" + }, + "dynamic" : { + "type" : "boolean" + } + } + }, + "ResourceUnit" : { + "type" : "object", + "properties" : { + "resourceId" : { + "type" : "string" + }, + "availableResource" : { + "$ref" : "#/definitions/ResourceDescription" + } + } + }, + "ResourceUsage" : { + "type" : "object", + "properties" : { + "usage" : { + "type" : "number", + "format" : "double" + }, + "limit" : { + "type" : "number", + "format" : "double" + } + } + }, + "RetentionPolicies" : { + "type" : "object", + "properties" : { + "retentionTimeInMinutes" : { + "type" : "integer", + "format" : "int32" + }, + "retentionSizeInMB" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "SchemaVersion" : { + "type" : "object" + }, + "SubscribeRate" : { + "type" : "object", + "properties" : { + "subscribeThrottlingRatePerConsumer" : { + "type" : "integer", + "format" : "int32" + }, + "ratePeriodInSecond" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "SubscriptionStats" : { + "type" : "object", + "properties" : { + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "msgRateRedeliver" : { + "type" : "number", + "format" : "double" + }, + "msgBacklog" : { + "type" : "integer", + "format" : "int64" + }, + "blockedSubscriptionOnUnackedMsgs" : { + "type" : "boolean" + }, + "msgDelayed" : { + "type" : "integer", + "format" : "int64" + }, + "unackedMessages" : { + "type" : "integer", + "format" : "int64" + }, + "type" : { + "type" : "string", + "enum" : [ "Exclusive", "Shared", "Failover", "Key_Shared" ] + }, + "activeConsumerName" : { + "type" : "string" + }, + "msgRateExpired" : { + "type" : "number", + "format" : "double" + }, + "lastExpireTimestamp" : { + "type" : "integer", + "format" : "int64" + }, + "consumers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ConsumerStats" + } + }, + "isReplicated" : { + "type" : "boolean" + } + } + }, + "SystemResourceUsage" : { + "type" : "object", + "properties" : { + "bandwidthIn" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "bandwidthOut" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "cpu" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "memory" : { + "$ref" : "#/definitions/ResourceUsage" + }, + "directMemory" : { + "$ref" : "#/definitions/ResourceUsage" + } + } + }, + "TenantInfo" : { + "type" : "object", + "properties" : { + "adminRoles" : { + "type" : "array", + "description" : "Comma separated list of auth principal allowed to administrate the tenant.", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + }, + "allowedClusters" : { + "type" : "array", + "description" : "Comma separated allowed clusters.", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + } + }, + "description" : "Information of adminRoles and allowedClusters for tenant" + }, + "TopicStats" : { + "type" : "object", + "properties" : { + "msgRateIn" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputIn" : { + "type" : "number", + "format" : "double" + }, + "msgRateOut" : { + "type" : "number", + "format" : "double" + }, + "msgThroughputOut" : { + "type" : "number", + "format" : "double" + }, + "averageMsgSize" : { + "type" : "number", + "format" : "double" + }, + "storageSize" : { + "type" : "integer", + "format" : "int64" + }, + "backlogSize" : { + "type" : "integer", + "format" : "int64" + }, + "publishers" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/PublisherStats" + } + }, + "subscriptions" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/SubscriptionStats" + } + }, + "replication" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/ReplicatorStats" + } + }, + "deduplicationStatus" : { + "type" : "string" + }, + "bytesInCounter" : { + "type" : "integer", + "format" : "int64" + }, + "msgInCounter" : { + "type" : "integer", + "format" : "int64" + } + } + } + } +} \ No newline at end of file diff --git a/site2/website/static/swagger/2.5.0/swaggerfunctions.json b/site2/website/static/swagger/2.5.0/swaggerfunctions.json new file mode 100644 index 0000000000000..8db0dc97fdf3d --- /dev/null +++ b/site2/website/static/swagger/2.5.0/swaggerfunctions.json @@ -0,0 +1,1499 @@ +{ + "swagger" : "2.0", + "info" : { + "description" : "This provides the REST API for Pulsar Functions operations", + "version" : "v3", + "title" : "Pulsar Functions REST API", + "license" : { + "name" : "Apache 2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath" : "/admin/v3", + "tags" : [ { + "name" : "functions" + } ], + "schemes" : [ "http", "https" ], + "paths" : { + "/functions/connectors" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Fetches a list of supported Pulsar IO connectors currently running in cluster mode", + "description" : "", + "operationId" : "getConnectorsList", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "object" + } + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "408" : { + "description" : "Request timeout" + } + }, + "deprecated" : true + } + }, + "/functions/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Lists all Pulsar Functions currently deployed in a given namespace", + "description" : "", + "operationId" : "listFunctions", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Fetches information about a Pulsar Function currently running in cluster mode", + "description" : "", + "operationId" : "getFunctionInfo", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionConfig" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + }, + "408" : { + "description" : "Request timeout" + } + } + }, + "post" : { + "tags" : [ "functions" ], + "summary" : "Creates a new Pulsar Function in cluster mode", + "description" : "", + "operationId" : "registerFunction", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting configuration payload of a Pulsar Function. An example of the expected Pulsar Function can be found here. \n- **autoAck** \n Whether or not the framework acknowledges messages automatically. \n- **runtime** \n What is the runtime of the Pulsar Function. Possible Values: [JAVA, PYTHON, GO] \n- **resources** \n The size of the system resources allowed by the Pulsar Function runtime. The resources include: cpu, ram, disk. \n- **className** \n The class name of a Pulsar Function. \n- **customSchemaInputs** \n The map of input topics to Schema class names (specified as a JSON object). \n- **customSerdeInputs** \n The map of input topics to SerDe class names (specified as a JSON object). \n- **deadLetterTopic** \n Messages that are not processed successfully are sent to `deadLetterTopic`. \n- **runtimeFlags** \n Any flags that you want to pass to the runtime. Note that in thread mode, these flags have no impact. \n- **fqfn** \n The Fully Qualified Function Name (FQFN) for the Pulsar Function. \n- **inputSpecs** \n The map of input topics to its consumer configuration, each configuration has schema of {\"schemaType\": \"type-x\", \"serdeClassName\": \"name-x\", \"isRegexPattern\": true, \"receiverQueueSize\": 5} \n- **inputs** \n The input topic or topics (multiple topics can be specified as a comma-separated list) of a Pulsar Function. \n- **jar** \n Path to the JAR file for the Pulsar Function (if the Pulsar Function is written in Java). It also supports URL path [http/https/file (file protocol assumes that file already exists on worker host)] from which worker can download the package. \n- **py** \n Path to the main Python file or Python wheel file for the Pulsar Function (if the Pulsar Function is written in Python). \n- **go** \n Path to the main Go executable binary for the Pulsar Function (if the Pulsar Function is written in Go). \n- **logTopic** \n The topic to which the logs of a Pulsar Function are produced. \n- **maxMessageRetries** \n How many times should we try to process a message before giving up. \n- **output** \n The output topic of a Pulsar Function (If none is specified, no output is written). \n- **outputSerdeClassName** \n The SerDe class to be used for messages output by the Pulsar Function. \n- **parallelism** \n The parallelism factor of a Pulsar Function (i.e. the number of a Pulsar Function instances to run). \n- **processingGuarantees** \n The processing guarantees (that is, delivery semantics) applied to the Pulsar Function. Possible Values: [ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE] \n- **retainOrdering** \n Function consumes and processes messages in order. \n- **outputSchemaType** \n Represents either a builtin schema type (for example: 'avro', 'json', ect) or the class name for a Schema implementation.- **subName** \n Pulsar source subscription name. User can specify a subscription-name for the input-topic consumer. \n- **windowConfig** \n The window configuration of a Pulsar Function. \n- **timeoutMs** \n The message timeout in milliseconds. \n- **topicsPattern** \n The topic pattern to consume from a list of topics under a namespace that match the pattern. [input] and [topic-pattern] are mutually exclusive. Add SerDe class name for a pattern in customSerdeInputs (supported for java fun only) \n- **userConfig** \n A map of user-defined configurations (specified as a JSON object). \n- **secrets** \n This is a map of secretName(that is how the secret is going to be accessed in the Pulsar Function via context) to an object that encapsulates how the secret is fetched by the underlying secrets provider. The type of an value here can be found by the SecretProviderConfigurator.getSecretObjectType() method. \n- **cleanupSubscription** \n Whether the subscriptions of a Pulsar Function created or used should be deleted when the Pulsar Function is deleted. \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/FunctionConfig" + }, + "x-examples" : { + "application/json" : "{\n \"inputs\": persistent://public/default/input-topic,\n \"parallelism\": 4\n \"output\": persistent://public/default/output-topic\n \"log-topic\": persistent://public/default/log-topic\n \"classname\": org.example.test.ExclamationFunction\n \"jar\": java-function-1.0-SNAPSHOT.jar\n}" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Function successfully created" + }, + "400" : { + "description" : "Invalid request (The Pulsar Function already exists, etc.)" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "408" : { + "description" : "Request timeout" + } + } + }, + "put" : { + "tags" : [ "functions" ], + "summary" : "Updates a Pulsar Function currently running in cluster mode", + "description" : "", + "operationId" : "updateFunction", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting configuration payload of a Pulsar Function. An example of the expected Pulsar Function can be found here. \n- **autoAck** \n Whether or not the framework acknowledges messages automatically. \n- **runtime** \n What is the runtime of the Pulsar Function. Possible Values: [JAVA, PYTHON, GO] \n- **resources** \n The size of the system resources allowed by the Pulsar Function runtime. The resources include: cpu, ram, disk. \n- **className** \n The class name of a Pulsar Function. \n- **customSchemaInputs** \n The map of input topics to Schema class names (specified as a JSON object). \n- **customSerdeInputs** \n The map of input topics to SerDe class names (specified as a JSON object). \n- **deadLetterTopic** \n Messages that are not processed successfully are sent to `deadLetterTopic`. \n- **runtimeFlags** \n Any flags that you want to pass to the runtime. Note that in thread mode, these flags have no impact. \n- **fqfn** \n The Fully Qualified Function Name (FQFN) for the Pulsar Function. \n- **inputSpecs** \n The map of input topics to its consumer configuration, each configuration has schema of {\"schemaType\": \"type-x\", \"serdeClassName\": \"name-x\", \"isRegexPattern\": true, \"receiverQueueSize\": 5} \n- **inputs** \n The input topic or topics (multiple topics can be specified as a comma-separated list) of a Pulsar Function. \n- **jar** \n Path to the JAR file for the Pulsar Function (if the Pulsar Function is written in Java). It also supports URL path [http/https/file (file protocol assumes that file already exists on worker host)] from which worker can download the package. \n- **py** \n Path to the main Python file or Python wheel file for the Pulsar Function (if the Pulsar Function is written in Python). \n- **go** \n Path to the main Go executable binary for the Pulsar Function (if the Pulsar Function is written in Go). \n- **logTopic** \n The topic to which the logs of a Pulsar Function are produced. \n- **maxMessageRetries** \n How many times should we try to process a message before giving up. \n- **output** \n The output topic of a Pulsar Function (If none is specified, no output is written). \n- **outputSerdeClassName** \n The SerDe class to be used for messages output by the Pulsar Function. \n- **parallelism** \n The parallelism factor of a Pulsar Function (i.e. the number of a Pulsar Function instances to run). \n- **processingGuarantees** \n The processing guarantees (that is, delivery semantics) applied to the Pulsar Function. Possible Values: [ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE] \n- **retainOrdering** \n Function consumes and processes messages in order. \n- **outputSchemaType** \n Represents either a builtin schema type (for example: 'avro', 'json', ect) or the class name for a Schema implementation.- **subName** \n Pulsar source subscription name. User can specify a subscription-name for the input-topic consumer. \n- **windowConfig** \n The window configuration of a Pulsar Function. \n- **timeoutMs** \n The message timeout in milliseconds. \n- **topicsPattern** \n The topic pattern to consume from a list of topics under a namespace that match the pattern. [input] and [topic-pattern] are mutually exclusive. Add SerDe class name for a pattern in customSerdeInputs (supported for java fun only) \n- **userConfig** \n A map of user-defined configurations (specified as a JSON object). \n- **secrets** \n This is a map of secretName(that is how the secret is going to be accessed in the Pulsar Function via context) to an object that encapsulates how the secret is fetched by the underlying secrets provider. The type of an value here can be found by the SecretProviderConfigurator.getSecretObjectType() method. \n- **cleanupSubscription** \n Whether the subscriptions of a Pulsar Function created or used should be deleted when the Pulsar Function is deleted. \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/FunctionConfig" + }, + "x-examples" : { + "application/json" : "{\n \"inputs\": persistent://public/default/input-topic,\n \"parallelism\": 4\n \"output\": persistent://public/default/output-topic\n \"log-topic\": persistent://public/default/log-topic\n \"classname\": org.example.test.ExclamationFunction\n \"jar\": java-function-1.0-SNAPSHOT.jar\n}" + } + }, { + "in" : "body", + "name" : "body", + "description" : "The update options is for the Pulsar Function that needs to be updated.", + "required" : false, + "schema" : { + "$ref" : "#/definitions/UpdateOptions" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Function successfully updated" + }, + "400" : { + "description" : "Invalid request (The Pulsar Function doesn't exist, etc.)" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + } + } + }, + "delete" : { + "tags" : [ "functions" ], + "summary" : "Deletes a Pulsar Function currently running in cluster mode", + "description" : "", + "operationId" : "deregisterFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "The Pulsar Function was successfully deleted" + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + }, + "408" : { + "description" : "Request timeout" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/restart" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Restart all instances of a Pulsar Function", + "description" : "", + "operationId" : "restartFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/start" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Start all instances of a Pulsar Function", + "description" : "", + "operationId" : "startFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/state/{key}" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Fetch the current state associated with a Pulsar Function", + "description" : "", + "operationId" : "getFunctionState", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "key", + "in" : "path", + "description" : "The stats key", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionState" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The key does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + }, + "post" : { + "tags" : [ "functions" ], + "summary" : "Put the state associated with a Pulsar Function", + "description" : "", + "operationId" : "putFunctionState", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "required" : true, + "type" : "string" + }, { + "name" : "key", + "in" : "path", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/stats" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Displays the stats of a Pulsar Function", + "description" : "", + "operationId" : "getFunctionStats", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionStats" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/status" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Displays the status of a Pulsar Function", + "description" : "", + "operationId" : "getFunctionStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionStatus" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/stop" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Stop all instances of a Pulsar Function", + "description" : "", + "operationId" : "stopFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/trigger" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Triggers a Pulsar Function with a user-specified value or file data", + "description" : "", + "operationId" : "triggerFunction", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "The value with which you want to trigger the Pulsar Function", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "in" : "body", + "name" : "body", + "description" : "The path to the file that contains the data with which you'd like to trigger the Pulsar Function", + "required" : false, + "schema" : { + "$ref" : "#/definitions/InputStream" + } + }, { + "in" : "body", + "name" : "body", + "description" : "The specific topic name that the Pulsar Function consumes from which you want to inject the data to", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/Message" + } + }, + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "408" : { + "description" : "Request timeout" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/{instanceId}/restart" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Restart an instance of a Pulsar Function", + "description" : "", + "operationId" : "restartFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Function (if instance-id is not provided, all instances are restarted", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/{instanceId}/start" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Start an instance of a Pulsar Function", + "description" : "", + "operationId" : "startFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Function (if instance-id is not provided, all instances sre started. ", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/{instanceId}/stats" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Displays the stats of a Pulsar Function instance", + "description" : "", + "operationId" : "getFunctionInstanceStats", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Function (if instance-id is not provided, the stats of all instances is returned", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionInstanceStatsData" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/{instanceId}/status" : { + "get" : { + "tags" : [ "functions" ], + "summary" : "Displays the status of a Pulsar Function instance", + "description" : "", + "operationId" : "getFunctionInstanceStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Function (if instance-id is not provided, the stats of all instances is returned", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/FunctionInstanceStatusData" + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "The Pulsar Function doesn't exist" + } + } + } + }, + "/functions/{tenant}/{namespace}/{functionName}/{instanceId}/stop" : { + "post" : { + "tags" : [ "functions" ], + "summary" : "Stop an instance of a Pulsar Function", + "description" : "", + "operationId" : "stopFunction", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "functionName", + "in" : "path", + "description" : "The name of a Pulsar Function", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Function (if instance-id is not provided, all instances are stopped. ", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Function does not exist" + }, + "500" : { + "description" : "Internal server error" + } + } + } + } + }, + "definitions" : { + "ConsumerConfig" : { + "type" : "object", + "properties" : { + "schemaType" : { + "type" : "string" + }, + "serdeClassName" : { + "type" : "string" + }, + "receiverQueueSize" : { + "type" : "integer", + "format" : "int32" + }, + "regexPattern" : { + "type" : "boolean" + } + } + }, + "EncryptionContext" : { + "type" : "object", + "properties" : { + "keys" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/EncryptionKey" + } + }, + "param" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "algorithm" : { + "type" : "string" + }, + "compressionType" : { + "type" : "string", + "enum" : [ "NONE", "LZ4", "ZLIB", "ZSTD", "SNAPPY" ] + }, + "uncompressedMessageSize" : { + "type" : "integer", + "format" : "int32" + }, + "batchSize" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "EncryptionKey" : { + "type" : "object", + "properties" : { + "keyValue" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "ExceptionInformation" : { + "type" : "object", + "properties" : { + "exceptionString" : { + "type" : "string" + }, + "timestampMs" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "FunctionConfig" : { + "type" : "object", + "properties" : { + "runtimeFlags" : { + "type" : "string" + }, + "tenant" : { + "type" : "string" + }, + "namespace" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "className" : { + "type" : "string" + }, + "inputs" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "customSerdeInputs" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "topicsPattern" : { + "type" : "string" + }, + "customSchemaInputs" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "inputSpecs" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/ConsumerConfig" + } + }, + "output" : { + "type" : "string" + }, + "outputSchemaType" : { + "type" : "string" + }, + "outputSerdeClassName" : { + "type" : "string" + }, + "logTopic" : { + "type" : "string" + }, + "processingGuarantees" : { + "type" : "string", + "enum" : [ "ATLEAST_ONCE", "ATMOST_ONCE", "EFFECTIVELY_ONCE" ] + }, + "retainOrdering" : { + "type" : "boolean" + }, + "userConfig" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "secrets" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "runtime" : { + "type" : "string", + "enum" : [ "JAVA", "PYTHON", "GO" ] + }, + "autoAck" : { + "type" : "boolean" + }, + "maxMessageRetries" : { + "type" : "integer", + "format" : "int32" + }, + "deadLetterTopic" : { + "type" : "string" + }, + "subName" : { + "type" : "string" + }, + "parallelism" : { + "type" : "integer", + "format" : "int32" + }, + "resources" : { + "$ref" : "#/definitions/Resources" + }, + "fqfn" : { + "type" : "string" + }, + "windowConfig" : { + "$ref" : "#/definitions/WindowConfig" + }, + "timeoutMs" : { + "type" : "integer", + "format" : "int64" + }, + "jar" : { + "type" : "string" + }, + "py" : { + "type" : "string" + }, + "go" : { + "type" : "string" + }, + "cleanupSubscription" : { + "type" : "boolean" + }, + "customRuntimeOptions" : { + "type" : "string" + } + } + }, + "FunctionInstanceStats" : { + "type" : "object", + "properties" : { + "instanceId" : { + "type" : "integer", + "format" : "int32" + }, + "metrics" : { + "$ref" : "#/definitions/FunctionInstanceStatsData" + } + } + }, + "FunctionInstanceStatsData" : { + "type" : "object", + "properties" : { + "receivedTotal" : { + "type" : "integer", + "format" : "int64" + }, + "processedSuccessfullyTotal" : { + "type" : "integer", + "format" : "int64" + }, + "systemExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "userExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "avgProcessLatency" : { + "type" : "number", + "format" : "double" + }, + "1min" : { + "$ref" : "#/definitions/FunctionInstanceStatsDataBase" + }, + "lastInvocation" : { + "type" : "integer", + "format" : "int64" + }, + "userMetrics" : { + "type" : "object", + "additionalProperties" : { + "type" : "number", + "format" : "double" + } + } + } + }, + "FunctionInstanceStatsDataBase" : { + "type" : "object", + "properties" : { + "receivedTotal" : { + "type" : "integer", + "format" : "int64" + }, + "processedSuccessfullyTotal" : { + "type" : "integer", + "format" : "int64" + }, + "systemExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "userExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "avgProcessLatency" : { + "type" : "number", + "format" : "double" + } + } + }, + "FunctionInstanceStatus" : { + "type" : "object", + "properties" : { + "instanceId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "$ref" : "#/definitions/FunctionInstanceStatusData" + } + } + }, + "FunctionInstanceStatusData" : { + "type" : "object", + "properties" : { + "running" : { + "type" : "boolean" + }, + "error" : { + "type" : "string" + }, + "numRestarts" : { + "type" : "integer", + "format" : "int64" + }, + "numReceived" : { + "type" : "integer", + "format" : "int64" + }, + "numSuccessfullyProcessed" : { + "type" : "integer", + "format" : "int64" + }, + "numUserExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestUserExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "numSystemExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestSystemExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "averageLatency" : { + "type" : "number", + "format" : "double" + }, + "lastInvocationTime" : { + "type" : "integer", + "format" : "int64" + }, + "workerId" : { + "type" : "string" + } + } + }, + "FunctionState" : { + "type" : "object", + "properties" : { + "key" : { + "type" : "string" + }, + "stringValue" : { + "type" : "string" + }, + "byteValue" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "numberValue" : { + "type" : "integer", + "format" : "int64" + }, + "version" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "FunctionStats" : { + "type" : "object", + "properties" : { + "receivedTotal" : { + "type" : "integer", + "format" : "int64" + }, + "processedSuccessfullyTotal" : { + "type" : "integer", + "format" : "int64" + }, + "systemExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "userExceptionsTotal" : { + "type" : "integer", + "format" : "int64" + }, + "avgProcessLatency" : { + "type" : "number", + "format" : "double" + }, + "1min" : { + "$ref" : "#/definitions/FunctionInstanceStatsDataBase" + }, + "lastInvocation" : { + "type" : "integer", + "format" : "int64" + }, + "instances" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/FunctionInstanceStats" + } + } + } + }, + "FunctionStatus" : { + "type" : "object", + "properties" : { + "numInstances" : { + "type" : "integer", + "format" : "int32" + }, + "numRunning" : { + "type" : "integer", + "format" : "int32" + }, + "instances" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/FunctionInstanceStatus" + } + } + } + }, + "InputStream" : { + "type" : "object" + }, + "Message" : { + "type" : "object", + "properties" : { + "data" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "replicatedFrom" : { + "type" : "string" + }, + "encryptionCtx" : { + "$ref" : "#/definitions/EncryptionContext" + }, + "schemaVersion" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "sequenceId" : { + "type" : "integer", + "format" : "int64" + }, + "orderingKey" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "keyBytes" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "messageId" : { + "$ref" : "#/definitions/MessageId" + }, + "redeliveryCount" : { + "type" : "integer", + "format" : "int32" + }, + "topicName" : { + "type" : "string" + }, + "producerName" : { + "type" : "string" + }, + "publishTime" : { + "type" : "integer", + "format" : "int64" + }, + "eventTime" : { + "type" : "integer", + "format" : "int64" + }, + "replicated" : { + "type" : "boolean" + }, + "value" : { + "type" : "object" + }, + "key" : { + "type" : "string" + }, + "properties" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "MessageId" : { + "type" : "object" + }, + "Resources" : { + "type" : "object", + "properties" : { + "cpu" : { + "type" : "number", + "format" : "double" + }, + "ram" : { + "type" : "integer", + "format" : "int64" + }, + "disk" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "UpdateOptions" : { + "type" : "object", + "properties" : { + "update-auth-data" : { + "type" : "boolean", + "description" : "Whether or not to update the auth data" + } + }, + "description" : "Options while updating the sink" + }, + "WindowConfig" : { + "type" : "object", + "properties" : { + "windowLengthCount" : { + "type" : "integer", + "format" : "int32" + }, + "windowLengthDurationMs" : { + "type" : "integer", + "format" : "int64" + }, + "slidingIntervalCount" : { + "type" : "integer", + "format" : "int32" + }, + "slidingIntervalDurationMs" : { + "type" : "integer", + "format" : "int64" + }, + "lateDataTopic" : { + "type" : "string" + }, + "maxLagMs" : { + "type" : "integer", + "format" : "int64" + }, + "watermarkEmitIntervalMs" : { + "type" : "integer", + "format" : "int64" + }, + "timestampExtractorClassName" : { + "type" : "string" + }, + "actualWindowFunctionClassName" : { + "type" : "string" + } + } + } + } +} \ No newline at end of file diff --git a/site2/website/static/swagger/2.5.0/swaggersink.json b/site2/website/static/swagger/2.5.0/swaggersink.json new file mode 100644 index 0000000000000..53f32b1663279 --- /dev/null +++ b/site2/website/static/swagger/2.5.0/swaggersink.json @@ -0,0 +1,955 @@ +{ + "swagger" : "2.0", + "info" : { + "description" : "This provides the REST API for Pulsar Sink operations", + "version" : "v3", + "title" : "Pulsar Sink REST API", + "license" : { + "name" : "Apache 2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath" : "/admin/v3", + "tags" : [ { + "name" : "sinks" + } ], + "schemes" : [ "http", "https" ], + "paths" : { + "/sinks/builtinsinks" : { + "get" : { + "tags" : [ "sinks" ], + "summary" : "Fetches a list of supported Pulsar IO sink connectors currently running in cluster mode", + "description" : "", + "operationId" : "getSinkList", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "Get builtin sinks successfully.", + "schema" : { + "type" : "array", + "items" : { + "type" : "object" + } + } + } + } + } + }, + "/sinks/reloadBuiltInSinks" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Reload the available built-in connectors, include Source and Sink", + "description" : "", + "operationId" : "reloadSinks", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "401" : { + "description" : "This operation requires super-user access" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "sinks" ], + "summary" : "Lists all Pulsar Sinks currently deployed in a given namespace", + "description" : "", + "operationId" : "listSinks", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string" + } + }, + "400" : { + "description" : "Invalid list request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "500" : { + "description" : "Internal server error (failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}" : { + "get" : { + "tags" : [ "sinks" ], + "summary" : "Fetches information about a Pulsar Sink currently running in cluster mode", + "description" : "", + "operationId" : "getSinkInfo", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SinkConfig" + } + }, + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "post" : { + "tags" : [ "sinks" ], + "summary" : "Creates a new Pulsar Sink in cluster mode", + "description" : "", + "operationId" : "registerSink", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting config payload of a Pulsar Sink. All available configuration options are: \n- **classname** \n The class name of a Pulsar Sink if archive is file-url-path (file://) \n- **sourceSubscriptionName** \n Pulsar source subscription name if user wants a specific \n subscription-name for input-topic consumer \n- **inputs** \n The input topic or topics of a Pulsar Sink (specified as a JSON array) \n- **topicsPattern** \n TopicsPattern to consume from list of topics under a namespace that match the pattern. [input] and [topicsPattern] are mutually exclusive. Add SerDe class name for a pattern in customSerdeInputs (supported for java fun only)- **topicToSerdeClassName** \n The map of input topics to SerDe class names (specified as a JSON object) \n- **topicToSchemaType** \n The map of input topics to Schema types or class names (specified as a JSON object) \n- **inputSpecs** \n The map of input topics to its consumer configuration, each configuration has schema of {\"schemaType\": \"type-x\", \"serdeClassName\": \"name-x\", \"isRegexPattern\": true, \"receiverQueueSize\": 5} \n- **configs** \n The map of configs (specified as a JSON object) \n- **secrets** \n a map of secretName(aka how the secret is going to be \n accessed in the function via context) to an object that \n encapsulates how the secret is fetched by the underlying \n secrets provider. The type of an value here can be found by the \n SecretProviderConfigurator.getSecretObjectType() method. (specified as a JSON object) \n- **parallelism** \n The parallelism factor of a Pulsar Sink (i.e. the number of a Pulsar Sink instances to run \n- **processingGuarantees** \n The processing guarantees (aka delivery semantics) applied to the Pulsar Sink. Possible Values: \"ATLEAST_ONCE\", \"ATMOST_ONCE\", \"EFFECTIVELY_ONCE\" \n- **retainOrdering** \n Boolean denotes whether the Pulsar Sink consumes and processes messages in order \n- **resources** \n {\"cpu\": 1, \"ram\": 2, \"disk\": 3} The CPU (in cores), RAM (in bytes) and disk (in bytes) that needs to be allocated per Pulsar Sink instance (applicable only to Docker runtime) \n- **autoAck** \n Boolean denotes whether or not the framework will automatically acknowledge messages \n- **timeoutMs** \n Long denotes the message timeout in milliseconds \n- **cleanupSubscription** \n Boolean denotes whether the subscriptions the functions created/used should be deleted when the functions is deleted \n- **runtimeFlags** \n Any flags that you want to pass to the runtime as a single string \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/SinkConfig" + }, + "x-examples" : { + "application/json" : "{ \n\t\"classname\": \"org.example.MySinkTest\",\n\t\"inputs\": [\"persistent://public/default/sink-input\"],\n\t\"processingGuarantees\": \"EFFECTIVELY_ONCE\",\n\t\"parallelism\": 10\n}" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Sink successfully created" + }, + "400" : { + "description" : "Invalid request (The Pulsar Sink already exists, etc.)" + }, + "401" : { + "description" : "Client is not authorized to perform operation" + }, + "500" : { + "description" : "Internal server error (failed to authorize, failed to get tenant data, failed to process package, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "put" : { + "tags" : [ "sinks" ], + "summary" : "Updates a Pulsar Sink currently running in cluster mode", + "description" : "", + "operationId" : "updateSink", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting config payload of a Pulsar Sink. All available configuration options are: \n- **classname** \n The class name of a Pulsar Sink if archive is file-url-path (file://) \n- **sourceSubscriptionName** \n Pulsar source subscription name if user wants a specific \n subscription-name for input-topic consumer \n- **inputs** \n The input topic or topics of a Pulsar Sink (specified as a JSON array) \n- **topicsPattern** \n TopicsPattern to consume from list of topics under a namespace that match the pattern. [input] and [topicsPattern] are mutually exclusive. Add SerDe class name for a pattern in customSerdeInputs (supported for java fun only)- **topicToSerdeClassName** \n The map of input topics to SerDe class names (specified as a JSON object) \n- **topicToSchemaType** \n The map of input topics to Schema types or class names (specified as a JSON object) \n- **inputSpecs** \n The map of input topics to its consumer configuration, each configuration has schema of {\"schemaType\": \"type-x\", \"serdeClassName\": \"name-x\", \"isRegexPattern\": true, \"receiverQueueSize\": 5} \n- **configs** \n The map of configs (specified as a JSON object) \n- **secrets** \n a map of secretName(aka how the secret is going to be \n accessed in the function via context) to an object that \n encapsulates how the secret is fetched by the underlying \n secrets provider. The type of an value here can be found by the \n SecretProviderConfigurator.getSecretObjectType() method. (specified as a JSON object) \n- **parallelism** \n The parallelism factor of a Pulsar Sink (i.e. the number of a Pulsar Sink instances to run \n- **processingGuarantees** \n The processing guarantees (aka delivery semantics) applied to the Pulsar Sink. Possible Values: \"ATLEAST_ONCE\", \"ATMOST_ONCE\", \"EFFECTIVELY_ONCE\" \n- **retainOrdering** \n Boolean denotes whether the Pulsar Sink consumes and processes messages in order \n- **resources** \n {\"cpu\": 1, \"ram\": 2, \"disk\": 3} The CPU (in cores), RAM (in bytes) and disk (in bytes) that needs to be allocated per Pulsar Sink instance (applicable only to Docker runtime) \n- **autoAck** \n Boolean denotes whether or not the framework will automatically acknowledge messages \n- **timeoutMs** \n Long denotes the message timeout in milliseconds \n- **cleanupSubscription** \n Boolean denotes whether the subscriptions the functions created/used should be deleted when the functions is deleted \n- **runtimeFlags** \n Any flags that you want to pass to the runtime as a single string \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/SinkConfig" + }, + "x-examples" : { + "application/json" : "{ \n\t\"classname\": \"org.example.SinkStressTest\", \n\t\"inputs\": [\"persistent://public/default/sink-input\"],\n\t\"processingGuarantees\": \"EFFECTIVELY_ONCE\",\n\t\"parallelism\": 5\n}" + } + }, { + "in" : "body", + "name" : "body", + "description" : "Update options for the Pulsar Sink", + "required" : false, + "schema" : { + "$ref" : "#/definitions/UpdateOptions" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Sink successfully updated" + }, + "400" : { + "description" : "Invalid request (The Pulsar Sink doesn't exist, update contains no change, etc.)" + }, + "401" : { + "description" : "Client is not authorized to perform operation" + }, + "404" : { + "description" : "The Pulsar Sink doesn't exist" + }, + "500" : { + "description" : "Internal server error (failed to authorize, failed to process package, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "delete" : { + "tags" : [ "sinks" ], + "summary" : "Deletes a Pulsar Sink currently running in cluster mode", + "description" : "", + "operationId" : "deregisterSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "The Pulsar Sink was successfully deleted" + }, + "400" : { + "description" : "Invalid deregister request" + }, + "401" : { + "description" : "Client is not authorized to perform operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "408" : { + "description" : "Got InterruptedException while deregistering the Pulsar Sink" + }, + "500" : { + "description" : "Internal server error (failed to authorize, failed to deregister, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/restart" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Restart all instances of a Pulsar Sink", + "description" : "", + "operationId" : "restartSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid restart request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal server error (failed to restart the Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/start" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Start all instances of a Pulsar Sink", + "description" : "", + "operationId" : "startSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid start request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal server error (failed to start the Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/status" : { + "get" : { + "tags" : [ "sinks" ], + "summary" : "Displays the status of a Pulsar Sink running in cluster mode", + "description" : "", + "operationId" : "getSinkStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SinkStatus" + } + }, + "400" : { + "description" : "Invalid get status request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/stop" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Stop all instances of a Pulsar Sink", + "description" : "", + "operationId" : "stopSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid stop request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal server error (failed to stop the Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/{instanceId}/restart" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Restart an instance of a Pulsar Sink", + "description" : "", + "operationId" : "restartSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid restart request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal server error (failed to restart the instance of a Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/{instanceId}/start" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Start an instance of a Pulsar Sink", + "description" : "", + "operationId" : "startSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid start request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal server error (failed to start the Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/{instanceId}/status" : { + "get" : { + "tags" : [ "sinks" ], + "summary" : "Displays the status of a Pulsar Sink instance", + "description" : "", + "operationId" : "getSinkInstanceStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SinkInstanceStatusData" + } + }, + "400" : { + "description" : "The Pulsar Sink instance does not exist" + }, + "404" : { + "description" : "The Pulsar Sink does not exist" + }, + "500" : { + "description" : "Internal Server Error (got exception while getting status, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sinks/{tenant}/{namespace}/{sinkName}/{instanceId}/stop" : { + "post" : { + "tags" : [ "sinks" ], + "summary" : "Stop an instance of a Pulsar Sink", + "description" : "", + "operationId" : "stopSink", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "sinkName", + "in" : "path", + "description" : "The name of a Pulsar Sink", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Sink", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid stop request" + }, + "401" : { + "description" : "The client is not authorized to perform this operation" + }, + "404" : { + "description" : "The Pulsar Sink instance does not exist" + }, + "500" : { + "description" : "Internal server error (failed to stop the Pulsar Sink, failed to authorize, etc.)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + } + }, + "definitions" : { + "ConsumerConfig" : { + "type" : "object", + "properties" : { + "schemaType" : { + "type" : "string" + }, + "serdeClassName" : { + "type" : "string" + }, + "receiverQueueSize" : { + "type" : "integer", + "format" : "int32" + }, + "regexPattern" : { + "type" : "boolean" + } + } + }, + "ExceptionInformation" : { + "type" : "object", + "properties" : { + "exceptionString" : { + "type" : "string" + }, + "timestampMs" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "Resources" : { + "type" : "object", + "properties" : { + "cpu" : { + "type" : "number", + "format" : "double" + }, + "ram" : { + "type" : "integer", + "format" : "int64" + }, + "disk" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "SinkConfig" : { + "type" : "object", + "properties" : { + "tenant" : { + "type" : "string" + }, + "namespace" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "className" : { + "type" : "string" + }, + "sourceSubscriptionName" : { + "type" : "string" + }, + "sourceSubscriptionPosition" : { + "type" : "string", + "enum" : [ "Latest", "Earliest" ] + }, + "inputs" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "topicToSerdeClassName" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "topicsPattern" : { + "type" : "string" + }, + "topicToSchemaType" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "inputSpecs" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/definitions/ConsumerConfig" + } + }, + "configs" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "secrets" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "parallelism" : { + "type" : "integer", + "format" : "int32" + }, + "processingGuarantees" : { + "type" : "string", + "enum" : [ "ATLEAST_ONCE", "ATMOST_ONCE", "EFFECTIVELY_ONCE" ] + }, + "retainOrdering" : { + "type" : "boolean" + }, + "resources" : { + "$ref" : "#/definitions/Resources" + }, + "autoAck" : { + "type" : "boolean" + }, + "timeoutMs" : { + "type" : "integer", + "format" : "int64" + }, + "archive" : { + "type" : "string" + }, + "cleanupSubscription" : { + "type" : "boolean" + }, + "runtimeFlags" : { + "type" : "string" + }, + "customRuntimeOptions" : { + "type" : "string" + } + } + }, + "SinkInstanceStatus" : { + "type" : "object", + "properties" : { + "instanceId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "$ref" : "#/definitions/SinkInstanceStatusData" + } + } + }, + "SinkInstanceStatusData" : { + "type" : "object", + "properties" : { + "running" : { + "type" : "boolean" + }, + "error" : { + "type" : "string" + }, + "numRestarts" : { + "type" : "integer", + "format" : "int64" + }, + "numReadFromPulsar" : { + "type" : "integer", + "format" : "int64" + }, + "numSystemExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestSystemExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "numSinkExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestSinkExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "numWrittenToSink" : { + "type" : "integer", + "format" : "int64" + }, + "lastReceivedTime" : { + "type" : "integer", + "format" : "int64" + }, + "workerId" : { + "type" : "string" + } + } + }, + "SinkStatus" : { + "type" : "object", + "properties" : { + "numInstances" : { + "type" : "integer", + "format" : "int32" + }, + "numRunning" : { + "type" : "integer", + "format" : "int32" + }, + "instances" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SinkInstanceStatus" + } + } + } + }, + "UpdateOptions" : { + "type" : "object", + "properties" : { + "update-auth-data" : { + "type" : "boolean", + "description" : "Whether or not to update the auth data" + } + }, + "description" : "Options while updating the sink" + } + } +} \ No newline at end of file diff --git a/site2/website/static/swagger/2.5.0/swaggersource.json b/site2/website/static/swagger/2.5.0/swaggersource.json new file mode 100644 index 0000000000000..5725806a19f6f --- /dev/null +++ b/site2/website/static/swagger/2.5.0/swaggersource.json @@ -0,0 +1,902 @@ +{ + "swagger" : "2.0", + "info" : { + "description" : "This provides the REST API for Pulsar Source operations", + "version" : "v3", + "title" : "Pulsar Source REST API", + "license" : { + "name" : "Apache 2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath" : "/admin/v3", + "tags" : [ { + "name" : "sources" + } ], + "schemes" : [ "http", "https" ], + "paths" : { + "/sources/builtinsources" : { + "get" : { + "tags" : [ "sources" ], + "summary" : "Fetches a list of supported Pulsar IO source connectors currently running in cluster mode", + "description" : "", + "operationId" : "getSourceList", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "array", + "items" : { + "type" : "object" + } + } + }, + "400" : { + "description" : "Invalid request" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "408" : { + "description" : "Request timeout" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/reloadBuiltInSources" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Reload the available built-in connectors, include Source and Sink", + "description" : "", + "operationId" : "reloadSources", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "responses" : { + "401" : { + "description" : "This operation requires super-user access" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}" : { + "get" : { + "tags" : [ "sources" ], + "summary" : "Lists all Pulsar Sources currently deployed in a given namespace", + "description" : "", + "operationId" : "listSources", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "type" : "string" + } + }, + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}" : { + "get" : { + "tags" : [ "sources" ], + "summary" : "Fetches information about a Pulsar Source currently running in cluster mode", + "description" : "", + "operationId" : "getSourceInfo", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SourceConfig" + } + }, + "400" : { + "description" : "Invalid request" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "post" : { + "tags" : [ "sources" ], + "summary" : "Creates a new Pulsar Source in cluster mode", + "description" : "", + "operationId" : "registerSource", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting configuration payload of a Pulsar Source. An example of the expected functions can be found here. \n- **classname** \n The class name of a Pulsar Source if archive is file-url-path (file://). \n- **topicName** \n The Pulsar topic to which data is sent. \n- **serdeClassName** \n The SerDe classname for the Pulsar Source. \n- **schemaType** \n The schema type (either a builtin schema like 'avro', 'json', etc.. or custom Schema class name to be used to encode messages emitted from the Pulsar Source \n- **configs** \n Source config key/values \n- **secrets** \n This is a map of secretName(that is how the secret is going to be accessed in the function via context) to an object that encapsulates how the secret is fetched by the underlying secrets provider. The type of an value here can be found by the SecretProviderConfigurator.getSecretObjectType() method. \n- **parallelism** \n The parallelism factor of a Pulsar Source (i.e. the number of a Pulsar Source instances to run). \n- **processingGuarantees** \n The processing guarantees (aka delivery semantics) applied to the Pulsar Source. Possible Values: [ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE] \n- **resources** \n The size of the system resources allowed by the Pulsar Source runtime. The resources include: cpu, ram, disk. \n- **archive** \n The path to the NAR archive for the Pulsar Source. It also supports url-path [http/https/file (file protocol assumes that file already exists on worker host)] from which worker can download the package. \n- **runtimeFlags** \n Any flags that you want to pass to the runtime. \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/SourceConfig" + }, + "x-examples" : { + "application/json" : "{\n \"tenant\": public\n \"namespace\": default\n \"name\": pulsar-io-mysql\n \"className\": TestSourceMysql\n \"topicName\": pulsar-io-mysql\n \"parallelism\": 1\n \"archive\": /connectors/pulsar-io-mysql-0.0.1.nar\n \"schemaType\": avro\n}" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Function successfully created" + }, + "400" : { + "description" : "Invalid request (Function already exists or Tenant, Namespace or Name is not provided, etc.)" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "put" : { + "tags" : [ "sources" ], + "summary" : "Updates a Pulsar Source currently running in cluster mode", + "description" : "", + "operationId" : "updateSource", + "consumes" : [ "multipart/form-data" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "in" : "body", + "name" : "body", + "description" : "A JSON value presenting configuration payload of a Pulsar Source. An example of the expected functions can be found here. \n- **classname** \n The class name of a Pulsar Source if archive is file-url-path (file://). \n- **topicName** \n The Pulsar topic to which data is sent. \n- **serdeClassName** \n The SerDe classname for the Pulsar Source. \n- **schemaType** \n The schema type (either a builtin schema like 'avro', 'json', etc.. or custom Schema class name to be used to encode messages emitted from the Pulsar Source \n- **configs** \n Pulsar Source config key/values \n- **secrets** \n This is a map of secretName(that is how the secret is going to be accessed in the function via context) to an object that encapsulates how the secret is fetched by the underlying secrets provider. The type of an value here can be found by the SecretProviderConfigurator.getSecretObjectType() method. \n- **parallelism** \n The parallelism factor of a Pulsar Source (i.e. the number of a Pulsar Source instances to run). \n- **processingGuarantees** \n The processing guarantees (aka delivery semantics) applied to the Pulsar Source. Possible Values: [ATLEAST_ONCE, ATMOST_ONCE, EFFECTIVELY_ONCE] \n- **resources** \n The size of the system resources allowed by the Pulsar Source runtime. The resources include: cpu, ram, disk. \n- **archive** \n The path to the NAR archive for the Pulsar Source. It also supports url-path [http/https/file (file protocol assumes that file already exists on worker host)] from which worker can download the package. \n- **runtimeFlags** \n Any flags that you want to pass to the runtime. \n", + "required" : false, + "schema" : { + "$ref" : "#/definitions/SourceConfig" + }, + "x-examples" : { + "application/json" : "{\n \"tenant\": public\n \"namespace\": default\n \"name\": pulsar-io-mysql\n \"className\": TestSourceMysql\n \"topicName\": pulsar-io-mysql\n \"parallelism\": 1\n \"archive\": /connectors/pulsar-io-mysql-0.0.1.nar\n \"schemaType\": avro\n}" + } + }, { + "in" : "body", + "name" : "body", + "description" : "Update options for Pulsar Source", + "required" : false, + "schema" : { + "$ref" : "#/definitions/UpdateOptions" + } + } ], + "responses" : { + "200" : { + "description" : "Pulsar Function successfully updated" + }, + "400" : { + "description" : "Invalid request (Function already exists or Tenant, Namespace or Name is not provided, etc.)" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "403" : { + "description" : "The requester doesn't have admin permissions" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + }, + "delete" : { + "tags" : [ "sources" ], + "summary" : "Deletes a Pulsar Source currently running in cluster mode", + "description" : "", + "operationId" : "deregisterSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "The function was successfully deleted" + }, + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "408" : { + "description" : "Request timeout" + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/restart" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Restart all instances of a Pulsar Source", + "description" : "", + "operationId" : "restartSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/start" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Start all instances of a Pulsar Source", + "description" : "", + "operationId" : "startSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/status" : { + "get" : { + "tags" : [ "sources" ], + "summary" : "Displays the status of a Pulsar Source running in cluster mode", + "description" : "", + "operationId" : "getSourceStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SourceStatus" + } + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/stop" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Stop all instances of a Pulsar Source", + "description" : "", + "operationId" : "stopSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/{instanceId}/restart" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Restart an instance of a Pulsar Source", + "description" : "", + "operationId" : "restartSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Source (if instance-id is not provided, the stats of all instances is returned).", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/{instanceId}/start" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Start an instance of a Pulsar Source", + "description" : "", + "operationId" : "startSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Source (if instance-id is not provided, the stats of all instances is returned).", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/{instanceId}/status" : { + "get" : { + "tags" : [ "sources" ], + "summary" : "Displays the status of a Pulsar Source instance", + "description" : "", + "operationId" : "getSourceInstanceStatus", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Source (if instance-id is not provided, the stats of all instances is returned).", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "schema" : { + "$ref" : "#/definitions/SourceInstanceStatusData" + } + }, + "500" : { + "description" : "Internal Server Error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + }, + "/sources/{tenant}/{namespace}/{sourceName}/{instanceId}/stop" : { + "post" : { + "tags" : [ "sources" ], + "summary" : "Stop instance of a Pulsar Source", + "description" : "", + "operationId" : "stopSource", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "tenant", + "in" : "path", + "description" : "The tenant of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "namespace", + "in" : "path", + "description" : "The namespace of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "sourceName", + "in" : "path", + "description" : "The name of a Pulsar Source", + "required" : true, + "type" : "string" + }, { + "name" : "instanceId", + "in" : "path", + "description" : "The instanceId of a Pulsar Source (if instance-id is not provided, the stats of all instances is returned).", + "required" : true, + "type" : "string" + } ], + "responses" : { + "400" : { + "description" : "Invalid request" + }, + "401" : { + "description" : "Client is not authorize to perform operation" + }, + "404" : { + "description" : "Not Found(The Pulsar Source doesn't exist)" + }, + "500" : { + "description" : "Internal server error" + }, + "503" : { + "description" : "Function worker service is now initializing. Please try again later." + } + } + } + } + }, + "definitions" : { + "ExceptionInformation" : { + "type" : "object", + "properties" : { + "exceptionString" : { + "type" : "string" + }, + "timestampMs" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "Resources" : { + "type" : "object", + "properties" : { + "cpu" : { + "type" : "number", + "format" : "double" + }, + "ram" : { + "type" : "integer", + "format" : "int64" + }, + "disk" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "SourceConfig" : { + "type" : "object", + "properties" : { + "tenant" : { + "type" : "string" + }, + "namespace" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "className" : { + "type" : "string" + }, + "topicName" : { + "type" : "string" + }, + "serdeClassName" : { + "type" : "string" + }, + "schemaType" : { + "type" : "string" + }, + "configs" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "secrets" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "parallelism" : { + "type" : "integer", + "format" : "int32" + }, + "processingGuarantees" : { + "type" : "string", + "enum" : [ "ATLEAST_ONCE", "ATMOST_ONCE", "EFFECTIVELY_ONCE" ] + }, + "resources" : { + "$ref" : "#/definitions/Resources" + }, + "archive" : { + "type" : "string" + }, + "runtimeFlags" : { + "type" : "string" + }, + "customRuntimeOptions" : { + "type" : "string" + } + } + }, + "SourceInstanceStatus" : { + "type" : "object", + "properties" : { + "instanceId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "$ref" : "#/definitions/SourceInstanceStatusData" + } + } + }, + "SourceInstanceStatusData" : { + "type" : "object", + "properties" : { + "running" : { + "type" : "boolean" + }, + "error" : { + "type" : "string" + }, + "numRestarts" : { + "type" : "integer", + "format" : "int64" + }, + "numReceivedFromSource" : { + "type" : "integer", + "format" : "int64" + }, + "numSystemExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestSystemExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "numSourceExceptions" : { + "type" : "integer", + "format" : "int64" + }, + "latestSourceExceptions" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/ExceptionInformation" + } + }, + "numWritten" : { + "type" : "integer", + "format" : "int64" + }, + "lastReceivedTime" : { + "type" : "integer", + "format" : "int64" + }, + "workerId" : { + "type" : "string" + } + } + }, + "SourceStatus" : { + "type" : "object", + "properties" : { + "numInstances" : { + "type" : "integer", + "format" : "int32" + }, + "numRunning" : { + "type" : "integer", + "format" : "int32" + }, + "instances" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/SourceInstanceStatus" + } + } + } + }, + "UpdateOptions" : { + "type" : "object", + "properties" : { + "update-auth-data" : { + "type" : "boolean", + "description" : "Whether or not to update the auth data" + } + }, + "description" : "Options while updating the sink" + } + } +} \ No newline at end of file diff --git a/site2/website/versioned_docs/version-2.1.0-incubating/concepts-messaging.md b/site2/website/versioned_docs/version-2.1.0-incubating/concepts-messaging.md index 9b0caf3ff5df6..1df8f92903a5f 100644 --- a/site2/website/versioned_docs/version-2.1.0-incubating/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.1.0-incubating/concepts-messaging.md @@ -150,8 +150,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.3.0/concepts-messaging.md b/site2/website/versioned_docs/version-2.3.0/concepts-messaging.md index 015d00c18e2f0..2ecc6ef142e4a 100644 --- a/site2/website/versioned_docs/version-2.3.0/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.3.0/concepts-messaging.md @@ -151,8 +151,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.3.1/concepts-messaging.md b/site2/website/versioned_docs/version-2.3.1/concepts-messaging.md index acf6a8afbd1ca..715ec199dd6b4 100644 --- a/site2/website/versioned_docs/version-2.3.1/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.3.1/concepts-messaging.md @@ -151,8 +151,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.3.2/concepts-messaging.md b/site2/website/versioned_docs/version-2.3.2/concepts-messaging.md index 6e5d4ba2e8eaa..4bef883caa410 100644 --- a/site2/website/versioned_docs/version-2.3.2/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.3.2/concepts-messaging.md @@ -206,8 +206,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.4.0/concepts-messaging.md b/site2/website/versioned_docs/version-2.4.0/concepts-messaging.md index 9b912351dd672..c110ccff686da 100644 --- a/site2/website/versioned_docs/version-2.4.0/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.4.0/concepts-messaging.md @@ -206,8 +206,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.4.0/io-quickstart.md b/site2/website/versioned_docs/version-2.4.0/io-quickstart.md index 81e4b7cf2a3e1..9daa157018cac 100644 --- a/site2/website/versioned_docs/version-2.4.0/io-quickstart.md +++ b/site2/website/versioned_docs/version-2.4.0/io-quickstart.md @@ -26,7 +26,7 @@ At the end of this tutorial, you will be able to: Before connecting Pulsar to a database, we need to install Pulsar and the desired builtin connector. -For more information about how to install a standalone Pulsar and builtin connectors, see [here](standalone/#installing-pulsar). +For more information about how to install a standalone Pulsar and builtin connectors, see [here](getting-started-standalone.md/#installing-pulsar). ## Start a standalone Pulsar diff --git a/site2/website/versioned_docs/version-2.4.1/concepts-messaging.md b/site2/website/versioned_docs/version-2.4.1/concepts-messaging.md index d76ade65f9bd4..b67ca931947fb 100644 --- a/site2/website/versioned_docs/version-2.4.1/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.4.1/concepts-messaging.md @@ -227,8 +227,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.4.1/functions-overview.md b/site2/website/versioned_docs/version-2.4.1/functions-overview.md index 97b59c3f5d6db..bd97ce70ff0d3 100644 --- a/site2/website/versioned_docs/version-2.4.1/functions-overview.md +++ b/site2/website/versioned_docs/version-2.4.1/functions-overview.md @@ -54,7 +54,7 @@ If you implement the classic word count example using Pulsar Functions, it looks ![Pulsar Functions word count example](assets/pulsar-functions-word-count.png) -To write the function in Java with [Pulsar Functions SDK for Java](functions-develop#available-apis), you can write the function as follows. +To write the function in Java with [Pulsar Functions SDK for Java](functions-develop.md#available-apis), you can write the function as follows. ```java package org.example.functions; diff --git a/site2/website/versioned_docs/version-2.4.2/concepts-messaging.md b/site2/website/versioned_docs/version-2.4.2/concepts-messaging.md index 3d6f0f7f4d3db..4758e9cb664a4 100644 --- a/site2/website/versioned_docs/version-2.4.2/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.4.2/concepts-messaging.md @@ -227,8 +227,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.4.2/functions-overview.md b/site2/website/versioned_docs/version-2.4.2/functions-overview.md index 0119b65c8eace..d2024cca14bcf 100644 --- a/site2/website/versioned_docs/version-2.4.2/functions-overview.md +++ b/site2/website/versioned_docs/version-2.4.2/functions-overview.md @@ -54,7 +54,7 @@ If you implement the classic word count example using Pulsar Functions, it looks ![Pulsar Functions word count example](assets/pulsar-functions-word-count.png) -To write the function in Java with [Pulsar Functions SDK for Java](functions-develop#available-apis), you can write the function as follows. +To write the function in Java with [Pulsar Functions SDK for Java](functions-develop.md#available-apis), you can write the function as follows. ```java package org.example.functions; diff --git a/site2/website/versioned_docs/version-2.4.2/getting-started-standalone.md b/site2/website/versioned_docs/version-2.4.2/getting-started-standalone.md index 33f72dc329f8b..3c58c88ec9e18 100644 --- a/site2/website/versioned_docs/version-2.4.2/getting-started-standalone.md +++ b/site2/website/versioned_docs/version-2.4.2/getting-started-standalone.md @@ -172,6 +172,8 @@ If you have started Pulsar successfully, you will see `INFO`-level log messages > #### Tip > +> * Before starting the Pulsar standalone, you may need to config the advertisedAddress to avoid startup errors. Actually, you can modify the default value of advertisedAddress in "conf/standalone.conf" or use command "bin/pulsar standalone -a 127.0.0.1" to assign the advertisedAddress. +> > * The service is running on your terminal, which is under your direct control. If you need to run other commands, open a new terminal window. You can also run the service as a background process using the `pulsar-daemon start standalone` command. For more information, see [pulsar-daemon](https://pulsar.apache.org/docs/en/reference-cli-tools/#pulsar-daemon). > diff --git a/site2/website/versioned_docs/version-2.5.0/administration-pulsar-manager.md b/site2/website/versioned_docs/version-2.5.0/administration-pulsar-manager.md index 3a7bcc21468c1..aca601678d7f5 100644 --- a/site2/website/versioned_docs/version-2.5.0/administration-pulsar-manager.md +++ b/site2/website/versioned_docs/version-2.5.0/administration-pulsar-manager.md @@ -7,6 +7,9 @@ original_id: administration-pulsar-manager Pulsar Manager is a web-based GUI management and monitoring tool that helps administrators and users manage and monitor tenants, namespaces, topics, subscriptions, brokers, clusters, and so on, and supports dynamic configuration of multiple environments. +> Note +> If you monitor your current stats with [Pulsar dashboard](administration-dashboard.md), you can try to use Pulsar Manager instead. Pulsar dashboard is deprecated. + ## Install The easiest way to use the Pulsar Manager is to run it inside a [Docker](https://www.docker.com/products/docker) container. diff --git a/site2/website/versioned_docs/version-2.5.0/concepts-messaging.md b/site2/website/versioned_docs/version-2.5.0/concepts-messaging.md index 86fae65d1b82f..0bbe5ecc09831 100644 --- a/site2/website/versioned_docs/version-2.5.0/concepts-messaging.md +++ b/site2/website/versioned_docs/version-2.5.0/concepts-messaging.md @@ -227,8 +227,8 @@ When a consumer subscribes to a Pulsar topic, by default it subscribes to one sp When subscribing to multiple topics, the Pulsar client will automatically make a call to the Pulsar API to discover the topics that match the regex pattern/list and then subscribe to all of them. If any of the topics don't currently exist, the consumer will auto-subscribe to them once the topics are created. -> #### No ordering guarantees -> When a consumer subscribes to multiple topics, all ordering guarantees normally provided by Pulsar on single topics do not hold. If your use case for Pulsar involves any strict ordering requirements, we would strongly recommend against using this feature. +> #### No ordering guarantees across multiple topics +> When a producer sends messages to a single topic, all messages are guaranteed to be read from that topic in the same order. However, these guarantees do not hold across multiple topics. So when a producer sends message to multiple topics, the order in which messages are read from those topics is not guaranteed to be the same. Here are some multi-topic subscription examples for Java: diff --git a/site2/website/versioned_docs/version-2.5.0/deploy-monitoring.md b/site2/website/versioned_docs/version-2.5.0/deploy-monitoring.md index 96ea306f4e7fe..d7f80621667a1 100644 --- a/site2/website/versioned_docs/version-2.5.0/deploy-monitoring.md +++ b/site2/website/versioned_docs/version-2.5.0/deploy-monitoring.md @@ -32,7 +32,7 @@ All the message rates are updated every 1min. The aggregated broker metrics are also exposed in the [Prometheus](https://prometheus.io) format at: ```shell -http://$BROKER_ADDRESS:8080/metrics +http://$BROKER_ADDRESS:8080/metrics/ ``` ### ZooKeeper stats diff --git a/site2/website/versioned_docs/version-2.5.0/io-kafka-sink.md b/site2/website/versioned_docs/version-2.5.0/io-kafka-sink.md index 5988b6aa65d7c..e458e0d093ba8 100644 --- a/site2/website/versioned_docs/version-2.5.0/io-kafka-sink.md +++ b/site2/website/versioned_docs/version-2.5.0/io-kafka-sink.md @@ -19,7 +19,7 @@ The configuration of the Kafka sink connector has the following parameters. | Name | Type| Required | Default | Description |------|----------|---------|-------------|-------------| | `bootstrapServers` |String| true | " " (empty string) | A comma-separated list of host and port pairs for establishing the initial connection to the Kafka cluster. | -|`ack`|String|true|" " (empty string) |The number of acknowledgments that the producer requires the leader to receive before a request completes.
    This controls the durability of the sent records. +|`acks`|String|true|" " (empty string) |The number of acknowledgments that the producer requires the leader to receive before a request completes.
    This controls the durability of the sent records. |`batchsize`|long|false|16384L|The batch size that a Kafka producer attempts to batch records together before sending them to brokers. |`maxRequestSize`|long|false|1048576L|The maximum size of a Kafka request in bytes. |`topic`|String|true|" " (empty string) |The Kafka topic which receives messages from Pulsar. diff --git a/site2/website/versioned_docs/version-2.5.0/io-quickstart.md b/site2/website/versioned_docs/version-2.5.0/io-quickstart.md index 26a9df3f6113c..dba9845a41a68 100644 --- a/site2/website/versioned_docs/version-2.5.0/io-quickstart.md +++ b/site2/website/versioned_docs/version-2.5.0/io-quickstart.md @@ -26,7 +26,7 @@ At the end of this tutorial, you are able to: Before connecting Pulsar to a database, you need to install Pulsar and the desired built-in connector. -For more information about **how to install a standalone Pulsar and built-in connectors**, see [here](standalone/#installing-pulsar). +For more information about **how to install a standalone Pulsar and built-in connectors**, see [here](getting-started-standalone.md/#installing-pulsar). ## Start Pulsar standalone diff --git a/site2/website/versioned_docs/version-2.5.0/io-use.md b/site2/website/versioned_docs/version-2.5.0/io-use.md new file mode 100644 index 0000000000000..4b19c2881ae04 --- /dev/null +++ b/site2/website/versioned_docs/version-2.5.0/io-use.md @@ -0,0 +1,1506 @@ +--- +id: version-2.5.0-io-use +title: How to use Pulsar connectors +sidebar_label: Use +original_id: io-use +--- + + +This guide describes how to use Pulsar connectors. + +## Install a connector + +Pulsar bundles several [builtin connectors](io-connectors.md) used to move data in and out of commonly used systems (such as database and messaging system). Optionally, you can create and use your desired non-builtin connectors. + +> #### Note +> +> When using a non-builtin connector, you need to specify the path of a archive file for the connector. + +To set up a builtin connector, follow +the instructions [here](getting-started-standalone.md#installing-builtin-connectors). + +After the setup, the builtin connector is automatically discovered by Pulsar brokers (or function-workers), so no additional installation steps are required. + +## Configure a connector + +You can configure the following information: + +* [Configure a default storage location for a connector](#configure-a-default-storage-location-for-a-connector) + +* [Configure a connector with a YAML file](#configure-a-connector-with-yaml-file) + +### Configure a default storage location for a connector + +To configure a default folder for builtin connectors, set the `connectorsDirectory` parameter in the `./conf/functions_worker.yml` configuration file. + +**Example** + +Set the `./connectors` folder as the default storage location for builtin connectors. + +``` +######################## +# Connectors +######################## + +connectorsDirectory: ./connectors +``` + +### Configure a connector with a YAML file + +To configure a connector, you need to provide a YAML configuration file when creating a connector. + +The YAML configuration file tells Pulsar where to locate connectors and how to connect connectors with Pulsar topics. + +**Example 1** + +Below is a YAML configuration file of a Cassandra sink, which tells Pulsar: + +* Which Cassandra cluster to connect + +* What is the `keyspace` and `columnFamily` to be used in Cassandra for collecting data + +* How to map Pulsar messages into Cassandra table key and columns + +```shell +tenant: public +namespace: default +name: cassandra-test-sink +... +# cassandra specific config +configs: + roots: "localhost:9042" + keyspace: "pulsar_test_keyspace" + columnFamily: "pulsar_test_table" + keyname: "key" + columnName: "col" +``` + +**Example 2** + +Below is a YAML configuration file of a Kafka source. + +```shell +configs: + bootstrapServers: "pulsar-kafka:9092" + groupId: "test-pulsar-io" + topic: "my-topic" + sessionTimeoutMs: "10000" + autoCommitEnabled: "false" +``` + +**Example 3** + +Below is a YAML configuration file of a MySQL JDBC sink. + +```shell +configs: + userName: "root" + password: "jdbc" + jdbcUrl: "jdbc:mysql://127.0.0.1:3306/test_jdbc" + tableName: "test_jdbc" +``` + +## Get available connectors + +Before starting using connectors, you can perform the following operations: + +* [Reload connectors](#reload) + +* [Get a list of available connectors](#get-available-connectors) + +### `reload` + +If you add or delete a nar file in a connector folder, reload the available builtin connector before using it. + +#### Source + +Use the `reload` subcommand. + +```shell +$ pulsar-admin sources reload +``` + +For more information, see [`here`](io-cli/#reload). + +#### Sink + +Use the `reload` subcommand. + +```shell +$ pulsar-admin sinks reload +``` + +For more information, see [`here`](io-cli/#reload-1). + +### `available` + +After reloading connectors (optional), you can get a list of available connectors. + +#### Source + +Use the `available-sources` subcommand. + +```shell +$ pulsar-admin sources available-sources +``` + +#### Sink + +Use the `available-sinks` subcommand. + +```shell +$ pulsar-admin sinks available-sinks +``` + +## Run a connector + +To run a connector, you can perform the following operations: + +* [Create a connector](#create) + +* [Start a connector](#start) + +* [Run a connector locally](#localrun) + +### `create` + +You can create a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Create a source connector. + + + + + +Use the `create` subcommand. + +``` +$ pulsar-admin sources create options +``` + +For more information, see [here](io-cli.md#create). + + + +Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName|operation/registerSource} + + + +* Create a source connector with a **local file**. + + ```java + void createSource(SourceConfig sourceConfig, + String fileName) + throws PulsarAdminException + ``` + + **Parameter** + + |Name|Description + |---|--- + `sourceConfig` | The source configuration object + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`createSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#createSource-SourceConfig-java.lang.String-). + +* Create a source connector using a **remote file** with a URL from which fun-pkg can be downloaded. + + ```java + void createSourceWithUrl(SourceConfig sourceConfig, + String pkgUrl) + throws PulsarAdminException + ``` + + Supported URLs are `http` and `file`. + + **Example** + + * HTTP: http://www.repo.com/fileName.jar + + * File: file:///dir/fileName.jar + + **Parameter** + + Parameter| Description + |---|--- + `sourceConfig` | The source configuration object + `pkgUrl` | URL from which pkg can be downloaded + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`createSourceWithUrl`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#createSourceWithUrl-SourceConfig-java.lang.String-). + + + +#### Sink + +Create a sink connector. + + + + + +Use the `create` subcommand. + +``` +$ pulsar-admin sinks create options +``` + +For more information, see [here](io-cli.md#create-1). + + + +Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sinks/:tenant/:namespace/:sinkName|operation/registerSink} + + + +* Create a sink connector with a **local file**. + + ```java + void createSink(SinkConfig sinkConfig, + String fileName) + throws PulsarAdminException + ``` + + **Parameter** + + |Name|Description + |---|--- + `sinkConfig` | The sink configuration object + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`createSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#createSink-SinkConfig-java.lang.String-). + +* Create a sink connector using a **remote file** with a URL from which fun-pkg can be downloaded. + + ```java + void createSinkWithUrl(SinkConfig sinkConfig, + String pkgUrl) + throws PulsarAdminException + ``` + + Supported URLs are `http` and `file`. + + **Example** + + * HTTP: http://www.repo.com/fileName.jar + + * File: file:///dir/fileName.jar + + **Parameter** + + Parameter| Description + |---|--- + `sinkConfig` | The sink configuration object + `pkgUrl` | URL from which pkg can be downloaded + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`createSinkWithUrl`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#createSinkWithUrl-SinkConfig-java.lang.String-). + + + +### `start` + +You can start a connector using **Admin CLI** or **REST API**. + +#### Source + +Start a source connector. + + + + + +Use the `start` subcommand. + +``` +$ pulsar-admin sources start options +``` + +For more information, see [here](io-cli.md#start). + + + +* Start **all** source connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName/start|operation/startSource} + +* Start a **specified** source connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName/:instanceId/start|operation/startSource} + + + +#### Sink + +Start a sink connector. + + + + + +Use the `start` subcommand. + +``` +$ pulsar-admin sinks start options +``` + +For more information, see [here](io-cli.md#start-1). + + + +* Start **all** sink connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sinkName/start|operation/startSink} + +* Start a **specified** sink connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sinks/:tenant/:namespace/:sourceName/:instanceId/start|operation/startSink} + + + +### `localrun` + +You can run a connector locally rather than deploying it on a Pulsar cluster using **Admin CLI**. + +#### Source + +Run a source connector locally. + + + + + +Use the `localrun` subcommand. + +``` +$ pulsar-admin sources localrun options +``` + +For more information, see [here](io-cli.md#localrun). + + + +#### Sink + +Run a sink connector locally. + + + + + +Use the `localrun` subcommand. + +``` +$ pulsar-admin sinks localrun options +``` + +For more information, see [here](io-cli.md#localrun-1). + + + +## Monitor a connector + +To monitor a connector, you can perform the following operations: + +* [Get the information of a connector](#get) + +* [Get the list of all running connectors](#list) + +* [Get the current status of a connector](#status) + +### `get` + +You can get the information of a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Get the information of a source connector. + + + + + +Use the `get` subcommand. + +``` +$ pulsar-admin sources get options +``` + +For more information, see [here](io-cli.md#get). + + + +Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sources/:tenant/:namespace/:sourceName|operation/getSourceInfo} + + + +```java +SourceConfig getSource(String tenant, + String namespace, + String source) + throws PulsarAdminException +``` + +**Example** + +This is a sourceConfig. + +```java +{ + "tenant": "tenantName", + "namespace": "namespaceName", + "name": "sourceName", + "className": "className", + "topicName": "topicName", + "configs": {}, + "parallelism": 1, + "processingGuarantees": "ATLEAST_ONCE", + "resources": { + "cpu": 1.0, + "ram": 1073741824, + "disk": 10737418240 + } +} +``` + +This is a sourceConfig example. + +``` +{ + "tenant": "public", + "namespace": "default", + "name": "debezium-mysql-source", + "className": "org.apache.pulsar.io.debezium.mysql.DebeziumMysqlSource", + "topicName": "debezium-mysql-topic", + "configs": { + "database.user": "debezium", + "database.server.id": "184054", + "database.server.name": "dbserver1", + "database.port": "3306", + "database.hostname": "localhost", + "database.password": "dbz", + "database.history.pulsar.service.url": "pulsar://127.0.0.1:6650", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "database.whitelist": "inventory", + "key.converter": "org.apache.kafka.connect.json.JsonConverter", + "database.history": "org.apache.pulsar.io.debezium.PulsarDatabaseHistory", + "pulsar.service.url": "pulsar://127.0.0.1:6650", + "database.history.pulsar.topic": "history-topic2" + }, + "parallelism": 1, + "processingGuarantees": "ATLEAST_ONCE", + "resources": { + "cpu": 1.0, + "ram": 1073741824, + "disk": 10737418240 + } +} +``` + +**Exception** + +Exception name | Description +|---|--- +`PulsarAdminException.NotAuthorizedException` | You don't have the admin permission +`PulsarAdminException.NotFoundException` | Cluster doesn't exist +`PulsarAdminException` | Unexpected error + +For more information, see [`getSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#getSource-java.lang.String-java.lang.String-java.lang.String-). + + + +#### Sink + +Get the information of a sink connector. + + + + + +Use the `get` subcommand. + +``` +$ pulsar-admin sinks get options +``` + +For more information, see [here](io-cli.md#get-1). + + + +Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sinks/:tenant/:namespace/:sinkName|operation/getSinkInfo} + + + +```java +SinkConfig getSink(String tenant, + String namespace, + String sink) + throws PulsarAdminException +``` + +**Example** + +This is a sinkConfig. + +``` +{ +"tenant": "tenantName", +"namespace": "namespaceName", +"name": "sinkName", +"className": "className", +"inputSpecs": { +"topicName": { + "isRegexPattern": false +} +}, +"configs": {}, +"parallelism": 1, +"processingGuarantees": "ATLEAST_ONCE", +"retainOrdering": false, +"autoAck": true +} +``` + +This is a sinkConfig example. + +``` +{ +"tenant": "public", +"namespace": "default", +"name": "pulsar-mysql-jdbc-sink", +"className": "org.apache.pulsar.io.jdbc.JdbcAutoSchemaSink", +"inputSpecs": { +"pulsar-mysql-jdbc-sink-topic": { + "isRegexPattern": false +} +}, +"configs": { +"password": "jdbc", +"jdbcUrl": "jdbc:mysql://127.0.0.1:3306/pulsar_mysql_jdbc_sink", +"userName": "root", +"tableName": "pulsar_mysql_jdbc_sink" +}, +"parallelism": 1, +"processingGuarantees": "ATLEAST_ONCE", +"retainOrdering": false, +"autoAck": true +} +``` + +**Parameter description** + +Name| Description +|---|--- +`tenant` | Tenant name +`namespace` | Namespace name +`sink` | Sink name + +For more information, see [`getSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#getSink-java.lang.String-java.lang.String-java.lang.String-). + + + +### `list` + +You can get the list of all running connectors using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Get the list of all running source connectors. + + + + + +Use the `list` subcommand. + +``` +$ pulsar-admin sources list options +``` + +For more information, see [here](io-cli.md#list). + + + +Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sources/:tenant/:namespace/|operation/listSources} + + + +```java +List listSources(String tenant, + String namespace) + throws PulsarAdminException +``` + +**Response example** + +```java +["f1", "f2", "f3"] +``` + +**Exception** + +Exception name | Description +|---|--- +`PulsarAdminException.NotAuthorizedException` | You don't have the admin permission +`PulsarAdminException` | Unexpected error + +For more information, see [`listSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#listSources-java.lang.String-java.lang.String-). + + + +#### Sink + +Get the list of all running sink connectors. + + + + + +Use the `list` subcommand. + +``` +$ pulsar-admin sinks list options +``` + +For more information, see [here](io-cli.md#list-1). + + + +Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sinks/:tenant/:namespace/|operation/listSinks} + + + +```java +List listSinks(String tenant, + String namespace) + throws PulsarAdminException +``` + +**Response example** + +```java +["f1", "f2", "f3"] +``` + +**Exception** + +Exception name | Description +|---|--- +`PulsarAdminException.NotAuthorizedException` | You don't have the admin permission +`PulsarAdminException` | Unexpected error + +For more information, see [`listSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#listSinks-java.lang.String-java.lang.String-). + + + +### `status` + +You can get the current status of a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Get the current status of a source connector. + + + + + +Use the `status` subcommand. + +``` +$ pulsar-admin sources status options +``` + +For more information, see [here](io-cli.md#status). + + + +* Get the current status of **all** source connectors. + + Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sources/:tenant/:namespace/:sourceName/status|operation/getSourceStatus} + +* Gets the current status of a **specified** source connector. + + Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sources/:tenant/:namespace/:sourceName/:instanceId/status|operation/getSourceStatus} + + + +* Get the current status of **all** source connectors. + + ```java + SourceStatus getSourceStatus(String tenant, + String namespace, + String source) + throws PulsarAdminException + ``` + + **Parameter** + + Parameter| Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `sink` | Source name + + **Exception** + + Name | Description + |---|--- + `PulsarAdminException` | Unexpected error + + For more information, see [`getSourceStatus`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#getSource-java.lang.String-java.lang.String-java.lang.String-). + +* Gets the current status of a **specified** source connector. + + ```java + SourceStatus.SourceInstanceStatus.SourceInstanceStatusData getSourceStatus(String tenant, + String namespace, + String source, + int id) + throws PulsarAdminException + ``` + + **Parameter** + + Parameter| Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `sink` | Source name + `id` | Source instanceID + + **Exception** + + Exception name | Description + |---|--- + `PulsarAdminException` | Unexpected error + + For more information, see [`getSourceStatus`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#getSourceStatus-java.lang.String-java.lang.String-java.lang.String-int-). + + + +#### Sink + +Get the current status of a Pulsar sink connector. + + + + + +Use the `status` subcommand. + +``` +$ pulsar-admin sinks status options +``` + +For more information, see [here](io-cli.md#status-1). + + + +* Get the current status of **all** sink connectors. + + Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sinks/:tenant/:namespace/:sinkName/status|operation/getSinkStatus} + +* Gets the current status of a **specified** sink connector. + + Send a `GET` request to this endpoint: {@inject: endpoint|GET|/admin/v3/sinks/:tenant/:namespace/:sourceName/:instanceId/status|operation/getSinkInstanceStatus} + + + +* Get the current status of **all** sink connectors. + + ```java + SinkStatus getSinkStatus(String tenant, + String namespace, + String sink) + throws PulsarAdminException + ``` + + **Parameter** + + Parameter| Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `sink` | Source name + + **Exception** + + Exception name | Description + |---|--- + `PulsarAdminException` | Unexpected error + + For more information, see [`getSinkStatus`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#getSinkStatus-java.lang.String-java.lang.String-java.lang.String-). + +* Gets the current status of a **specified** source connector. + + ```java + SinkStatus.SinkInstanceStatus.SinkInstanceStatusData getSinkStatus(String tenant, + String namespace, + String sink, + int id) + throws PulsarAdminException + ``` + + **Parameter** + + Parameter| Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `sink` | Source name + `id` | Sink instanceID + + **Exception** + + Exception name | Description + |---|--- + `PulsarAdminException` | Unexpected error + + For more information, see [`getSinkStatusWithInstanceID`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#getSinkStatus-java.lang.String-java.lang.String-java.lang.String-int-). + + + +## Update a connector + +### `update` + +You can update a running connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Update a running Pulsar source connector. + + + + + +Use the `update` subcommand. + +``` +$ pulsar-admin sources update options +``` + +For more information, see [here](io-cli.md#update). + + + +Send a `PUT` request to this endpoint: {@inject: endpoint|PUT|/admin/v3/sources/:tenant/:namespace/:sourceName|operation/updateSource} + + + +* Update a running source connector with a **local file**. + + ```java + void updateSource(SourceConfig sourceConfig, + String fileName) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + |`sourceConfig` | The source configuration object + + **Exception** + + |Name|Description| + |---|--- + |`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission + | `PulsarAdminException.NotFoundException` | Cluster doesn't exist + | `PulsarAdminException` | Unexpected error + + For more information, see [`updateSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#updateSource-SourceConfig-java.lang.String-). + +* Update a source connector using a **remote file** with a URL from which fun-pkg can be downloaded. + + ```java + void updateSourceWithUrl(SourceConfig sourceConfig, + String pkgUrl) + throws PulsarAdminException + ``` + + Supported URLs are `http` and `file`. + + **Example** + + * HTTP: http://www.repo.com/fileName.jar + + * File: file:///dir/fileName.jar + + **Parameter** + + | Name | Description + |---|--- + | `sourceConfig` | The source configuration object + | `pkgUrl` | URL from which pkg can be downloaded + + **Exception** + + |Name|Description| + |---|--- + |`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission + | `PulsarAdminException.NotFoundException` | Cluster doesn't exist + | `PulsarAdminException` | Unexpected error + +For more information, see [`createSourceWithUrl`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#updateSourceWithUrl-SourceConfig-java.lang.String-). + + + +#### Sink + +Update a running Pulsar sink connector. + + + + + +Use the `update` subcommand. + +``` +$ pulsar-admin sinks update options +``` + +For more information, see [here](io-cli.md#update-1). + + + +Send a `PUT` request to this endpoint: {@inject: endpoint|PUT|/admin/v3/sinks/:tenant/:namespace/:sinkName|operation/updateSink} + + + +* Update a running sink connector with a **local file**. + + ```java + void updateSink(SinkConfig sinkConfig, + String fileName) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + |`sinkConfig` | The sink configuration object + + **Exception** + + |Name|Description| + |---|--- + |`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission + | `PulsarAdminException.NotFoundException` | Cluster doesn't exist + | `PulsarAdminException` | Unexpected error + + For more information, see [`updateSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#updateSink-SinkConfig-java.lang.String-). + +* Update a sink connector using a **remote file** with a URL from which fun-pkg can be downloaded. + + ```java + void updateSinkWithUrl(SinkConfig sinkConfig, + String pkgUrl) + throws PulsarAdminException + ``` + + Supported URLs are `http` and `file`. + + **Example** + + * HTTP: http://www.repo.com/fileName.jar + + * File: file:///dir/fileName.jar + + **Parameter** + + | Name | Description + |---|--- + | `sinkConfig` | The sink configuration object + | `pkgUrl` | URL from which pkg can be downloaded + + **Exception** + + |Name|Description| + |---|--- + |`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission + |`PulsarAdminException.NotFoundException` | Cluster doesn't exist + |`PulsarAdminException` | Unexpected error + +For more information, see [`updateSinkWithUrl`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#updateSinkWithUrl-SinkConfig-java.lang.String-). + + + +## Stop a connector + +### `stop` + +You can stop a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Stop a source connector. + + + + + +Use the `stop` subcommand. + +``` +$ pulsar-admin sources stop options +``` + +For more information, see [here](io-cli.md#stop). + + + +* Stop **all** source connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName|operation/stopSource} + +* Stop a **specified** source connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName/:instanceId|operation/stopSource} + + + +* Stop **all** source connectors. + + ```java + void stopSource(String tenant, + String namespace, + String source) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`stopSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#stopSource-java.lang.String-java.lang.String-java.lang.String-). + +* Stop a **specified** source connector. + + ```java + void stopSource(String tenant, + String namespace, + String source, + int instanceId) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + `instanceId` | Source instanceID + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`stopSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#stopSource-java.lang.String-java.lang.String-java.lang.String-int-). + + + +#### Sink + +Stop a sink connector. + + + + + +Use the `stop` subcommand. + +``` +$ pulsar-admin sinks stop options +``` + +For more information, see [here](io-cli.md#stop-1). + + + +* Stop **all** sink connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sinks/:tenant/:namespace/:sinkName/stop|operation/stopSink} + +* Stop a **specified** sink connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sinkeName/:instanceId/stop|operation/stopSink} + + + +* Stop **all** sink connectors. + + ```java + void stopSink(String tenant, + String namespace, + String sink) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`stopSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#stopSink-java.lang.String-java.lang.String-java.lang.String-). + +* Stop a **specified** sink connector. + + ```java + void stopSink(String tenant, + String namespace, + String sink, + int instanceId) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + `instanceId` | Source instanceID + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`stopSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#stopSink-java.lang.String-java.lang.String-java.lang.String-int-). + + + +## Restart a connector + +### `restart` + +You can restart a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Restart a source connector. + + + + + +Use the `restart` subcommand. + +``` +$ pulsar-admin sources restart options +``` + +For more information, see [here](io-cli.md#restart). + + + +* Restart **all** source connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName/restart|operation/restartSource} + +* Restart a **specified** source connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sourceName/:instanceId/restart|operation/restartSource} + + + +* Restart **all** source connectors. + + ```java + void restartSource(String tenant, + String namespace, + String source) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`restartSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#restartSource-java.lang.String-java.lang.String-java.lang.String-). + +* Restart a **specified** source connector. + + ```java + void restartSource(String tenant, + String namespace, + String source, + int instanceId) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + `instanceId` | Source instanceID + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`restartSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#restartSource-java.lang.String-java.lang.String-java.lang.String-int-). + + + +#### Sink + +Restart a sink connector. + + + + + +Use the `restart` subcommand. + +``` +$ pulsar-admin sinks restart options +``` + +For more information, see [here](io-cli.md#restart-1). + + + +* Restart **all** sink connectors. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sinkName/restart|operation/restartSource} + +* Restart a **specified** sink connector. + + Send a `POST` request to this endpoint: {@inject: endpoint|POST|/admin/v3/sources/:tenant/:namespace/:sinkName/:instanceId/restart|operation/restartSource} + + + +* Restart all Pulsar sink connectors. + + ```java + void restartSink(String tenant, + String namespace, + String sink) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `sink` | Sink name + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`restartSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#restartSink-java.lang.String-java.lang.String-java.lang.String-). + +* Restart a **specified** sink connector. + + ```java + void restartSink(String tenant, + String namespace, + String sink, + int instanceId) + throws PulsarAdminException + ``` + + **Parameter** + + | Name | Description + |---|--- + `tenant` | Tenant name + `namespace` | Namespace name + `source` | Source name + `instanceId` | Sink instanceID + + **Exception** + + |Name|Description| + |---|--- + | `PulsarAdminException` | Unexpected error + + For more information, see [`restartSink`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#restartSink-java.lang.String-java.lang.String-java.lang.String-int-). + + + +## Delete a connector + +### `delete` + +You can delete a connector using **Admin CLI**, **REST API** or **JAVA admin API**. + +#### Source + +Delete a source connector. + + + + + +Use the `delete` subcommand. + +``` +$ pulsar-admin sources delete options +``` + +For more information, see [here](io-cli.md#delete). + + + +Delete al Pulsar source connector. + +Send a `DELETE` request to this endpoint: {@inject: endpoint|DELETE|/admin/v3/sources/:tenant/:namespace/:sourceName|operation/deregisterSource} + + + +Delete a source connector. + +```java +void deleteSource(String tenant, + String namespace, + String source) + throws PulsarAdminException +``` + +**Parameter** + +| Name | Description +|---|--- +`tenant` | Tenant name +`namespace` | Namespace name +`source` | Source name + +**Exception** + +|Name|Description| +|---|--- +|`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission +| `PulsarAdminException.NotFoundException` | Cluster doesn't exist +| `PulsarAdminException.PreconditionFailedException` | Cluster is not empty +| `PulsarAdminException` | Unexpected error + +For more information, see [`deleteSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Source.html#deleteSource-java.lang.String-java.lang.String-java.lang.String-). + + + +#### Sink + +Delete a sink connector. + + + + + +Use the `delete` subcommand. + +``` +$ pulsar-admin sinks delete options +``` + +For more information, see [here](io-cli.md#delete-1). + + + +Delete a sink connector. + +Send a `DELETE` request to this endpoint: {@inject: endpoint|DELETE|/admin/v3/sinks/:tenant/:namespace/:sinkName|operation/deregisterSink} + + + +Delete a Pulsar sink connector. + +```java +void deleteSink(String tenant, + String namespace, + String source) + throws PulsarAdminException +``` + +**Parameter** + +| Name | Description +|---|--- +`tenant` | Tenant name +`namespace` | Namespace name +`sink` | Sink name + +**Exception** + +|Name|Description| +|---|--- +|`PulsarAdminException.NotAuthorizedException`| You don't have the admin permission +| `PulsarAdminException.NotFoundException` | Cluster doesn't exist +| `PulsarAdminException.PreconditionFailedException` | Cluster is not empty +| `PulsarAdminException` | Unexpected error + +For more information, see [`deleteSource`](https://pulsar.apache.org/api/admin/org/apache/pulsar/client/admin/Sink.html#deleteSink-java.lang.String-java.lang.String-java.lang.String-). + + diff --git a/site2/website/versioned_sidebars/version-2.5.0-sidebars.json b/site2/website/versioned_sidebars/version-2.5.0-sidebars.json index f61588847baf4..c9850988bef59 100644 --- a/site2/website/versioned_sidebars/version-2.5.0-sidebars.json +++ b/site2/website/versioned_sidebars/version-2.5.0-sidebars.json @@ -64,7 +64,6 @@ "Administration": [ "version-2.5.0-administration-zk-bk", "version-2.5.0-administration-geo", - "version-2.5.0-administration-dashboard", "version-2.5.0-administration-pulsar-manager", "version-2.5.0-administration-stats", "version-2.5.0-administration-load-balance", @@ -131,7 +130,6 @@ "version-2.5.0-reference-terminology", "version-2.5.0-reference-cli-tools", "version-2.5.0-pulsar-admin", - "version-2.5.0-reference-connector-admin", "version-2.5.0-reference-configuration", "version-2.5.0-reference-metrics" ] diff --git a/tests/docker-images/latest-version-image/Dockerfile b/tests/docker-images/latest-version-image/Dockerfile index 35b757d7789a1..eaa279a42cd11 100644 --- a/tests/docker-images/latest-version-image/Dockerfile +++ b/tests/docker-images/latest-version-image/Dockerfile @@ -19,7 +19,9 @@ FROM apachepulsar/pulsar-all:latest -RUN apt-get update && apt-get install -y supervisor vim procps +RUN rm -rf /var/lib/apt/lists/* && apt update + +RUN apt-get clean && apt-get update && apt-get install -y supervisor vim procps curl git RUN mkdir -p /var/log/pulsar && mkdir -p /var/run/supervisor/ && mkdir -p /pulsar/ssl @@ -43,10 +45,33 @@ COPY scripts/init-cluster.sh scripts/run-global-zk.sh scripts/run-local-zk.sh \ RUN mkdir -p /pulsar/instances/deps +ENV GOLANG_VERSION 1.13.3 + +RUN curl -sSL https://storage.googleapis.com/golang/go$GOLANG_VERSION.linux-amd64.tar.gz \ + | tar -v -C /usr/local -xz + +# RUN wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz && tar -xvf go1.13.3.linux-amd64.tar.gz && mv go /usr/local +# RUN export GOROOT=/usr/local/go && export GOPATH=$HOME/go && export PATH=$GOPATH/bin:$GOROOT/bin:$PATH +# RUN echo "export GOROOT=/usr/local/go" >> ~/.profile && echo "export GOPATH=$HOME/go" >> ~/.profile && echo "export PATH=$GOPATH/bin:$GOROOT/bin:$PATH" >> ~/.profile + +ENV PATH /usr/local/go/bin:$PATH + +RUN mkdir -p /go/src /go/bin && chmod -R 777 /go +ENV GOROOT /usr/local/go +ENV GOPATH /go +ENV PATH /go/bin:$PATH + COPY python-examples/exclamation_lib.py /pulsar/instances/deps/ COPY python-examples/exclamation_with_extra_deps.py /pulsar/examples/python-examples/ COPY python-examples/exclamation.zip /pulsar/examples/python-examples/ COPY python-examples/producer_schema.py /pulsar/examples/python-examples/ COPY python-examples/consumer_schema.py /pulsar/examples/python-examples/ COPY python-examples/exception_function.py /pulsar/examples/python-examples/ + +COPY target/pulsar-function-go/ /go/src/github.com/apache/pulsar/pulsar-function-go +RUN cd /go/src/github.com/apache/pulsar/pulsar-function-go && go install ./... +RUN cd /go/src/github.com/apache/pulsar/pulsar-function-go/pf && go install +RUN cd /go/src/github.com/apache/pulsar/pulsar-function-go/examples && go install ./... + COPY target/java-test-functions.jar /pulsar/examples/ +RUN cp -a /go/bin/. /pulsar/examples/go-examples/ diff --git a/tests/docker-images/latest-version-image/pom.xml b/tests/docker-images/latest-version-image/pom.xml index b0c3f76a94306..5a961b016f2f7 100644 --- a/tests/docker-images/latest-version-image/pom.xml +++ b/tests/docker-images/latest-version-image/pom.xml @@ -79,6 +79,28 @@ + + maven-resources-plugin + + + copy-pulsar-function-go + generate-resources + + copy-resources + + + ${project.build.directory}/pulsar-function-go/ + true + + + ../../../pulsar-function-go/ + false + + + + + + com.spotify dockerfile-maven-plugin diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/PulsarTokenAuthenticationBaseSuite.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/PulsarTokenAuthenticationBaseSuite.java index 118cb3ba252a2..ad0fa83ce6c68 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/PulsarTokenAuthenticationBaseSuite.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/PulsarTokenAuthenticationBaseSuite.java @@ -24,11 +24,9 @@ import java.util.Collections; import java.util.EnumSet; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import lombok.Cleanup; -import lombok.extern.slf4j.Slf4j; - import org.apache.pulsar.client.admin.PulsarAdmin; import org.apache.pulsar.client.api.AuthenticationFactory; import org.apache.pulsar.client.api.Consumer; @@ -51,8 +49,12 @@ import org.testng.ITest; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeSuite; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import lombok.Cleanup; +import lombok.extern.slf4j.Slf4j; + @Slf4j public abstract class PulsarTokenAuthenticationBaseSuite extends PulsarClusterTestBase implements ITest { @@ -64,24 +66,27 @@ public abstract class PulsarTokenAuthenticationBaseSuite extends PulsarClusterTe protected abstract void configureBroker(BrokerContainer brokerContainer) throws Exception; protected abstract void configureProxy(ProxyContainer proxyContainer) throws Exception; + protected abstract String createClientTokenWithExpiry(long expiryTime, TimeUnit unit) throws Exception; + protected static final String SUPER_USER_ROLE = "super-user"; protected static final String PROXY_ROLE = "proxy"; protected static final String REGULAR_USER_ROLE = "client"; + protected ZKContainer cmdContainer; + @BeforeSuite @Override public void setupCluster() throws Exception { // Before starting the cluster, generate the secret key and the token // Use Zk container to have 1 container available before starting the cluster - try (ZKContainer zkContainer = new ZKContainer<>("cli-setup")) { - zkContainer + this.cmdContainer = new ZKContainer<>("cli-setup"); + cmdContainer .withNetwork(Network.newNetwork()) .withNetworkAliases(ZKContainer.NAME) .withEnv("zkServers", ZKContainer.NAME); - zkContainer.start(); + cmdContainer.start(); - createKeysAndTokens(zkContainer); - } + createKeysAndTokens(cmdContainer); final String clusterName = Stream.of(this.getClass().getSimpleName(), randomName(5)) .filter(s -> s != null && !s.isEmpty()) @@ -108,6 +113,8 @@ public void setupCluster() throws Exception { brokerContainer.withEnv("superUserRoles", SUPER_USER_ROLE + "," + PROXY_ROLE); brokerContainer.withEnv("brokerClientAuthenticationPlugin", AuthenticationToken.class.getName()); brokerContainer.withEnv("brokerClientAuthenticationParameters", "token:" + superUserAuthToken); + brokerContainer.withEnv("authenticationRefreshCheckSeconds", "1"); + brokerContainer.withEnv("authenticateOriginalAuthData", "true"); } ProxyContainer proxyContainer = pulsarCluster.getProxy(); @@ -118,6 +125,7 @@ public void setupCluster() throws Exception { proxyContainer.withEnv("authorizationEnabled", "true"); proxyContainer.withEnv("brokerClientAuthenticationPlugin", AuthenticationToken.class.getName()); proxyContainer.withEnv("brokerClientAuthenticationParameters", "token:" + proxyAuthToken); + proxyContainer.withEnv("forwardAuthorizationCredentials", "true"); pulsarCluster.start(); @@ -128,6 +136,7 @@ public void setupCluster() throws Exception { @Override public void tearDownCluster() { super.tearDownCluster(); + cmdContainer.close(); } @Override @@ -253,4 +262,71 @@ public void testProxyRedirectWithTokenAuth() throws Exception { admin.topics().getStats(topic); } } + + @DataProvider(name = "shouldRefreshToken") + public static Object[][] shouldRefreshToken() { + return new Object[][] { { Boolean.TRUE }, { Boolean.FALSE } }; + } + + @Test(dataProvider = "shouldRefreshToken") + public void testExpiringToken(boolean shouldRefreshToken) throws Exception { + final String tenant = "token-test-tenant" + randomName(4); + final String namespace = tenant + "/ns-1"; + final String topic = "persistent://" + namespace + "/topic-1"; + + @Cleanup + PulsarAdmin admin = PulsarAdmin.builder() + .serviceHttpUrl(pulsarCluster.getHttpServiceUrl()) + .authentication(AuthenticationFactory.token(superUserAuthToken)) + .build(); + + admin.tenants().createTenant(tenant, + new TenantInfo(Collections.singleton(REGULAR_USER_ROLE), + Collections.singleton(pulsarCluster.getClusterName()))); + + admin.namespaces().createNamespace(namespace, Collections.singleton(pulsarCluster.getClusterName())); + admin.namespaces().grantPermissionOnNamespace(namespace, REGULAR_USER_ROLE, EnumSet.allOf(AuthAction.class)); + + String initialToken = this.createClientTokenWithExpiry(5, TimeUnit.SECONDS); + + @Cleanup + PulsarClient client = PulsarClient.builder() + .serviceUrl(pulsarCluster.getPlainTextServiceUrl()) + .authentication(AuthenticationFactory.token(() -> { + if (shouldRefreshToken) { + try { + return createClientTokenWithExpiry(5, TimeUnit.SECONDS); + } catch (Exception e) { + return null; + } + } else { + return initialToken; + } + })) + .build(); + + @Cleanup + Producer producer = client.newProducer(Schema.STRING) + .topic(topic) + .sendTimeout(1, TimeUnit.SECONDS) + .create(); + + // Initially the token is valid and producer will be able to publish + producer.send("hello-1"); + + Thread.sleep(TimeUnit.SECONDS.toMillis(10)); + + if (shouldRefreshToken) { + // The token will have been refreshed, so the app won't see any error + producer.send("hello-2"); + } else { + // The token has expired, so this next message will be rejected + try { + producer.send("hello-2"); + fail("Publish should have failed"); + } catch (PulsarClientException e) { + // Expected + } + } + } } diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithPublicPrivateKeys.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithPublicPrivateKeys.java index 7f1a03aa9cd8b..f252e29f140d5 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithPublicPrivateKeys.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithPublicPrivateKeys.java @@ -21,6 +21,7 @@ import com.google.common.io.Files; import java.io.File; +import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @@ -87,4 +88,14 @@ protected void configureProxy(ProxyContainer proxyContainer) throws Exception { proxyContainer.withFileSystemBind(publicKeyFile.toString(), PUBLIC_KEY_PATH_INSIDE_CONTAINER); proxyContainer.withEnv("tokenPublicKey", "file://" + PUBLIC_KEY_PATH_INSIDE_CONTAINER); } + + @Override + protected String createClientTokenWithExpiry(long expiryTime, TimeUnit unit) throws Exception { + return cmdContainer + .execCmd(PulsarCluster.PULSAR_COMMAND_SCRIPT, "tokens", "create", + "--private-key", "file://" + PRIVATE_KEY_PATH_INSIDE_CONTAINER, + "--subject", REGULAR_USER_ROLE, + "--expiry-time", unit.toSeconds(expiryTime) + "s") + .getStdout().trim(); + } } diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithSymmetricKeys.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithSymmetricKeys.java index 96efa6c2ec13f..8a4a19da12fd6 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithSymmetricKeys.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/auth/token/TokenAuthWithSymmetricKeys.java @@ -18,13 +18,15 @@ */ package org.apache.pulsar.tests.integration.auth.token; -import lombok.extern.slf4j.Slf4j; +import java.util.concurrent.TimeUnit; import org.apache.pulsar.tests.integration.containers.BrokerContainer; import org.apache.pulsar.tests.integration.containers.ProxyContainer; import org.apache.pulsar.tests.integration.containers.PulsarContainer; import org.apache.pulsar.tests.integration.topologies.PulsarCluster; +import lombok.extern.slf4j.Slf4j; + @Slf4j public class TokenAuthWithSymmetricKeys extends PulsarTokenAuthenticationBaseSuite { @@ -70,4 +72,13 @@ protected void configureProxy(ProxyContainer proxyContainer) throws Exception { proxyContainer.withEnv("tokenSecretKey", "data:base64," + secretKey); } + @Override + protected String createClientTokenWithExpiry(long expiryTime, TimeUnit unit) throws Exception { + return cmdContainer + .execCmd(PulsarCluster.PULSAR_COMMAND_SCRIPT, "tokens", "create", + "--secret-key", "data:base64," + secretKey, + "--subject", REGULAR_USER_ROLE, + "--expiry-time", unit.toSeconds(expiryTime) + "s") + .getStdout().trim(); + } } diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/cli/FunctionsCLITest.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/cli/FunctionsCLITest.java index 181876911974a..74503b5306519 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/cli/FunctionsCLITest.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/cli/FunctionsCLITest.java @@ -58,7 +58,8 @@ public String uploadFunction() throws Exception { return bkPkgPath; } - @Test + // Flaky Test: https://github.com/apache/pulsar/issues/6179 + // @Test public void testUploadDownload() throws Exception { String bkPkgPath = uploadFunction(); String localPkgFile = "/tmp/checkdownload-" + randomName(16); diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTest.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTest.java index e112474f73593..e18a2ee320efe 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTest.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTest.java @@ -61,6 +61,8 @@ import org.testng.annotations.Test; import org.testng.collections.Maps; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.time.Duration; import java.util.Collections; import java.util.HashSet; @@ -842,6 +844,10 @@ protected void getSourceInfoNotFound(String tenant, String namespace, String sou public void testPythonFunctionLocalRun() throws Exception { testFunctionLocalRun(Runtime.PYTHON); } + @Test(groups = "function") + public void testGoFunctionLocalRun() throws Exception { + testFunctionLocalRun(Runtime.GO); + } @Test(groups = "function") public void testJavaFunctionLocalRun() throws Exception { @@ -862,19 +868,26 @@ public void testFunctionLocalRun(Runtime runtime) throws Exception { String outputTopicName = "test-function-local-run-" + runtime + "-output-" + randomName(8); final int numMessages = 10; - String cmd; + String cmd = ""; CommandGenerator commandGenerator = new CommandGenerator(); commandGenerator.setAdminUrl("pulsar://pulsar-broker-0:6650"); commandGenerator.setSourceTopic(inputTopicName); commandGenerator.setSinkTopic(outputTopicName); commandGenerator.setFunctionName("localRunTest"); commandGenerator.setRuntime(runtime); - if (runtime == Runtime.JAVA) { - commandGenerator.setFunctionClassName(EXCLAMATION_JAVA_CLASS); - cmd = commandGenerator.generateLocalRunCommand(null); - } else { - commandGenerator.setFunctionClassName(EXCLAMATION_PYTHON_CLASS); - cmd = commandGenerator.generateLocalRunCommand(EXCLAMATION_PYTHON_FILE); + switch (runtime) { + case JAVA: + commandGenerator.setFunctionClassName(EXCLAMATION_JAVA_CLASS); + cmd = commandGenerator.generateLocalRunCommand(null); + break; + case PYTHON: + commandGenerator.setFunctionClassName(EXCLAMATION_PYTHON_CLASS); + cmd = commandGenerator.generateLocalRunCommand(EXCLAMATION_PYTHON_FILE); + break; + case GO: + commandGenerator.setFunctionClassName(null); + cmd = commandGenerator.generateLocalRunCommand(EXCLAMATION_GO_FILE); + break; } log.info("cmd: {}", cmd); @@ -1316,6 +1329,11 @@ public void testJavaPublishFunction() throws Exception { testPublishFunction(Runtime.JAVA); } + @Test + public void testGoPublishFunction() throws Exception { + testPublishFunction(Runtime.GO); + } + private void testPublishFunction(Runtime runtime) throws Exception { if (functionRuntimeType == FunctionRuntimeType.THREAD) { return; @@ -1344,27 +1362,39 @@ private void testPublishFunction(Runtime runtime) throws Exception { final int numMessages = 10; // submit the exclamation function - - if (runtime == Runtime.PYTHON) { - submitFunction( - runtime, - inputTopicName, - outputTopicName, - functionName, - PUBLISH_FUNCTION_PYTHON_FILE, - PUBLISH_PYTHON_CLASS, - schema, - Collections.singletonMap("publish-topic", outputTopicName)); - } else { - submitFunction( - runtime, - inputTopicName, - outputTopicName, - functionName, - null, - PUBLISH_JAVA_CLASS, - schema, - Collections.singletonMap("publish-topic", outputTopicName)); + switch (runtime){ + case JAVA: + submitFunction( + runtime, + inputTopicName, + outputTopicName, + functionName, + null, + PUBLISH_JAVA_CLASS, + schema, + Collections.singletonMap("publish-topic", outputTopicName)); + break; + case PYTHON: + submitFunction( + runtime, + inputTopicName, + outputTopicName, + functionName, + PUBLISH_FUNCTION_PYTHON_FILE, + PUBLISH_PYTHON_CLASS, + schema, + Collections.singletonMap("publish-topic", outputTopicName)); + break; + case GO: + submitFunction( + runtime, + inputTopicName, + outputTopicName, + functionName, + PUBLISH_FUNCTION_GO_FILE, + null, + schema, + Collections.singletonMap("publish-topic", outputTopicName)); } // get function info @@ -1374,11 +1404,12 @@ private void testPublishFunction(Runtime runtime) throws Exception { getFunctionStatsEmpty(functionName); // publish and consume result + if (Runtime.JAVA == runtime) { // java supports schema publishAndConsumeMessages(inputTopicName, outputTopicName, numMessages); } else { - // python doesn't support schema + // python doesn't support schema. Does Go? Maybe we need a switch instead for the Go case. @Cleanup PulsarClient client = PulsarClient.builder() .serviceUrl(pulsarCluster.getPlainTextServiceUrl()) @@ -1673,14 +1704,19 @@ private static void submitFunction(Runtime runtime, if (userConfigs != null) { generator.setUserConfig(userConfigs); } - String command; - if (Runtime.JAVA == runtime) { - command = generator.generateCreateFunctionCommand(); - } else if (Runtime.PYTHON == runtime) { - generator.setRuntime(runtime); - command = generator.generateCreateFunctionCommand(functionFile); - } else { - throw new IllegalArgumentException("Unsupported runtime : " + runtime); + String command = ""; + + switch (runtime){ + case JAVA: + command = generator.generateCreateFunctionCommand(); + break; + case PYTHON: + case GO: + generator.setRuntime(runtime); + command = generator.generateCreateFunctionCommand(functionFile); + break; + default: + throw new IllegalArgumentException("Unsupported runtime : " + runtime); } log.info("---------- Function command: {}", command); diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTestBase.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTestBase.java index cb5291bed7bc3..94bffb3316c64 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTestBase.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/PulsarFunctionsTestBase.java @@ -103,6 +103,9 @@ public void teardownFunctionWorkers() { public static final String PUBLISH_FUNCTION_PYTHON_FILE = "typed_message_builder_publish.py"; public static final String EXCEPTION_FUNCTION_PYTHON_FILE = "exception_function.py"; + public static final String EXCLAMATION_GO_FILE = "exclamationFunc"; + public static final String PUBLISH_FUNCTION_GO_FILE = "exclamationFunc"; + protected static String getExclamationClass(Runtime runtime, boolean pyZip, boolean extraDeps) { @@ -125,7 +128,8 @@ protected static String getExclamationClass(Runtime runtime, public static Object[][] functionRuntimes() { return new Object[][] { new Object[] { Runtime.JAVA }, - new Object[] { Runtime.PYTHON } + new Object[] { Runtime.PYTHON }, + new Object[] { Runtime.GO } }; } diff --git a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/utils/CommandGenerator.java b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/utils/CommandGenerator.java index f18e0c8720c98..40458d59aecf2 100644 --- a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/utils/CommandGenerator.java +++ b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/functions/utils/CommandGenerator.java @@ -33,6 +33,7 @@ public class CommandGenerator { public enum Runtime { JAVA, PYTHON, + GO, }; private String functionName; private String tenant = "public"; @@ -56,6 +57,7 @@ public enum Runtime { private Map userConfig = new HashMap<>(); public static final String JAVAJAR = "/pulsar/examples/java-test-functions.jar"; public static final String PYTHONBASE = "/pulsar/examples/python-examples/"; + public static final String GOBASE = "/pulsar/examples/go-examples/"; public static CommandGenerator createDefaultGenerator(String sourceTopic, String functionClassName) { CommandGenerator generator = new CommandGenerator(); @@ -88,22 +90,33 @@ public String generateLocalRunCommand(String codeFile) { if (functionName != null) { commandBuilder.append(" --name " + functionName); } - commandBuilder.append(" --className " + functionClassName); + if(runtime != Runtime.GO){ + commandBuilder.append(" --className " + functionClassName); + } if (sourceTopic != null) { commandBuilder.append(" --inputs " + sourceTopic); } if (sinkTopic != null) { commandBuilder.append(" --output " + sinkTopic); } - - if (runtime == Runtime.JAVA) { - commandBuilder.append(" --jar " + JAVAJAR); - } else { - if (codeFile != null) { - commandBuilder.append(" --py " + PYTHONBASE + codeFile); - } else { - commandBuilder.append(" --py " + PYTHONBASE); - } + switch (runtime){ + case JAVA: + commandBuilder.append(" --jar " + JAVAJAR); + break; + case PYTHON: + if (codeFile != null) { + commandBuilder.append(" --py " + PYTHONBASE + codeFile); + } else { + commandBuilder.append(" --py " + PYTHONBASE); + } + break; + case GO: + if (codeFile != null) { + commandBuilder.append(" --go " + GOBASE + codeFile); + } else { + commandBuilder.append(" --go " + GOBASE); + } + break; } return commandBuilder.toString(); @@ -129,7 +142,9 @@ public String generateCreateFunctionCommand(String codeFile) { if (functionName != null) { commandBuilder.append(" --name " + functionName); } - commandBuilder.append(" --className " + functionClassName); + if (runtime != Runtime.GO){ + commandBuilder.append(" --className " + functionClassName); + } if (sourceTopic != null) { commandBuilder.append(" --inputs " + sourceTopic); } @@ -170,14 +185,24 @@ public String generateCreateFunctionCommand(String codeFile) { commandBuilder.append(" --slidingIntervalDurationMs " + slidingIntervalDurationMs); } - if (runtime == Runtime.JAVA) { - commandBuilder.append(" --jar " + JAVAJAR); - } else { - if (codeFile != null) { - commandBuilder.append(" --py " + PYTHONBASE + codeFile); - } else { - commandBuilder.append(" --py " + PYTHONBASE); - } + switch (runtime){ + case JAVA: + commandBuilder.append(" --jar " + JAVAJAR); + break; + case PYTHON: + if (codeFile != null) { + commandBuilder.append(" --py " + PYTHONBASE + codeFile); + } else { + commandBuilder.append(" --py " + PYTHONBASE); + } + break; + case GO: + if (codeFile != null) { + commandBuilder.append(" --go " + GOBASE + codeFile); + } else { + commandBuilder.append(" --go " + GOBASE); + } + break; } return commandBuilder.toString(); } @@ -243,14 +268,24 @@ public String generateUpdateFunctionCommand(String codeFile) { commandBuilder.append(" --slidingIntervalDurationMs " + slidingIntervalDurationMs); } - if (runtime == Runtime.JAVA) { - commandBuilder.append(" --jar " + JAVAJAR); - } else { - if (codeFile != null) { - commandBuilder.append(" --py " + PYTHONBASE + codeFile); - } else { - commandBuilder.append(" --py " + PYTHONBASE); - } + switch (runtime){ + case JAVA: + commandBuilder.append(" --jar " + JAVAJAR); + break; + case PYTHON: + if (codeFile != null) { + commandBuilder.append(" --py " + PYTHONBASE + codeFile); + } else { + commandBuilder.append(" --py " + PYTHONBASE); + } + break; + case GO: + if (codeFile != null) { + commandBuilder.append(" --go " + GOBASE + codeFile); + } else { + commandBuilder.append(" --go " + GOBASE); + } + break; } return commandBuilder.toString(); } diff --git a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemConfigurationData.java b/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemConfigurationData.java deleted file mode 100644 index 899887b6f8fbf..0000000000000 --- a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemConfigurationData.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.bookkeeper.mledger.offload.filesystem; - -import lombok.Data; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Properties; - -import static org.apache.pulsar.common.util.FieldParser.value; - -/** - * Configuration for file system. - */ -@Data -public class FileSystemConfigurationData implements Serializable, Cloneable { - - /**** --- Ledger Offloading --- ****/ - // Driver to use to offload old data to long term storage - private String managedLedgerOffloadDriver = null; - - private String fileSystemProfilePath = null; - - private String fileSystemURI = null; - - private int managedLedgerOffloadMaxThreads = 2; - - /** - * Create a tiered storage configuration from the provided properties. - * - * @param properties the configuration properties - * @return tiered storage configuration - */ - public static FileSystemConfigurationData create(Properties properties) { - FileSystemConfigurationData data = new FileSystemConfigurationData(); - Field[] fields = FileSystemConfigurationData.class.getDeclaredFields(); - Arrays.stream(fields).forEach(f -> { - if (properties.containsKey(f.getName())) { - try { - f.setAccessible(true); - f.set(data, value((String) properties.get(f.getName()), f)); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("failed to initialize %s field while setting value %s", - f.getName(), properties.get(f.getName())), e); - } - } - }); - return data; - } -} diff --git a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemLedgerOffloaderFactory.java b/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemLedgerOffloaderFactory.java index cd52197a59bb5..cf86f38e1bedb 100644 --- a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemLedgerOffloaderFactory.java +++ b/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/FileSystemLedgerOffloaderFactory.java @@ -21,6 +21,7 @@ import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.mledger.LedgerOffloaderFactory; import org.apache.bookkeeper.mledger.offload.filesystem.impl.FileSystemManagedLedgerOffloader; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import java.io.IOException; import java.util.Map; @@ -33,8 +34,8 @@ public boolean isDriverSupported(String driverName) { } @Override - public FileSystemManagedLedgerOffloader create(Properties properties, Map userMetadata, OrderedScheduler scheduler) throws IOException { - FileSystemConfigurationData data = FileSystemConfigurationData.create(properties); - return FileSystemManagedLedgerOffloader.create(data, scheduler); + public FileSystemManagedLedgerOffloader create(OffloadPolicies offloadPolicies, + Map userMetadata, OrderedScheduler scheduler) throws IOException { + return FileSystemManagedLedgerOffloader.create(offloadPolicies, scheduler); } } diff --git a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/impl/FileSystemManagedLedgerOffloader.java b/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/impl/FileSystemManagedLedgerOffloader.java index 309076c4c3605..eda747ded8712 100644 --- a/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/impl/FileSystemManagedLedgerOffloader.java +++ b/tiered-storage/file-system/src/main/java/org/apache/bookkeeper/mledger/offload/filesystem/impl/FileSystemManagedLedgerOffloader.java @@ -29,7 +29,6 @@ import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.mledger.LedgerOffloader; import org.apache.bookkeeper.mledger.offload.filesystem.FileSystemLedgerOffloaderFactory; -import org.apache.bookkeeper.mledger.offload.filesystem.FileSystemConfigurationData; import org.apache.bookkeeper.net.BookieSocketAddress; import org.apache.bookkeeper.proto.DataFormats; import org.apache.hadoop.conf.Configuration; @@ -39,6 +38,7 @@ import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.MapFile; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +69,8 @@ public class FileSystemManagedLedgerOffloader implements LedgerOffloader { private OrderedScheduler scheduler; private static final long ENTRIES_PER_READ = 100; private OrderedScheduler assignmentScheduler; + private OffloadPolicies offloadPolicies; + public static boolean driverSupported(String driver) { return DRIVER_NAMES.equals(driver); } @@ -77,11 +79,12 @@ public String getOffloadDriverName() { return driverName; } - public static FileSystemManagedLedgerOffloader create(FileSystemConfigurationData conf, OrderedScheduler scheduler) throws IOException { + public static FileSystemManagedLedgerOffloader create(OffloadPolicies conf, OrderedScheduler scheduler) throws IOException { return new FileSystemManagedLedgerOffloader(conf, scheduler); } - private FileSystemManagedLedgerOffloader(FileSystemConfigurationData conf, OrderedScheduler scheduler) throws IOException { + private FileSystemManagedLedgerOffloader(OffloadPolicies conf, OrderedScheduler scheduler) throws IOException { + this.offloadPolicies = conf; this.configuration = new Configuration(); if (conf.getFileSystemProfilePath() != null) { String[] paths = conf.getFileSystemProfilePath().split(","); @@ -110,7 +113,8 @@ private FileSystemManagedLedgerOffloader(FileSystemConfigurationData conf, Order .name("offload-assignment").build(); } @VisibleForTesting - public FileSystemManagedLedgerOffloader(FileSystemConfigurationData conf, OrderedScheduler scheduler, String testHDFSPath, String baseDir) throws IOException { + public FileSystemManagedLedgerOffloader(OffloadPolicies conf, OrderedScheduler scheduler, String testHDFSPath, String baseDir) throws IOException { + this.offloadPolicies = conf; this.configuration = new Configuration(); this.configuration.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem"); this.configuration.set("fs.defaultFS", testHDFSPath); @@ -328,4 +332,20 @@ private static byte[] buildLedgerMetadataFormat(LedgerMetadata metadata) { return builder.build().toByteArray(); } + + @Override + public OffloadPolicies getOffloadPolicies() { + return offloadPolicies; + } + + @Override + public void close() { + if (fileSystem != null) { + try { + fileSystem.close(); + } catch (Exception e) { + log.error("FileSystemManagedLedgerOffloader close failed!", e); + } + } + } } diff --git a/tiered-storage/file-system/src/test/java/org/apache/bookkeeper/mledger/offload/filesystem/FileStoreTestBase.java b/tiered-storage/file-system/src/test/java/org/apache/bookkeeper/mledger/offload/filesystem/FileStoreTestBase.java index 4abff16443090..b014fd23860b6 100644 --- a/tiered-storage/file-system/src/test/java/org/apache/bookkeeper/mledger/offload/filesystem/FileStoreTestBase.java +++ b/tiered-storage/file-system/src/test/java/org/apache/bookkeeper/mledger/offload/filesystem/FileStoreTestBase.java @@ -23,6 +23,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -48,7 +49,7 @@ public void start() throws Exception { hdfsURI = "hdfs://localhost:"+ hdfsCluster.getNameNodePort() + "/"; Properties properties = new Properties(); fileSystemManagedLedgerOffloader = new FileSystemManagedLedgerOffloader( - FileSystemConfigurationData.create(properties), + OffloadPolicies.create(properties), scheduler, hdfsURI, basePath); } diff --git a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/CredentialsUtil.java b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/CredentialsUtil.java new file mode 100644 index 0000000000000..4971a6d0e1b70 --- /dev/null +++ b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/CredentialsUtil.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bookkeeper.mledger.offload.jcloud; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.google.common.base.Strings; +import org.apache.pulsar.common.policies.data.OffloadPolicies; + +public class CredentialsUtil { + + /** + * Builds an AWS credential provider based on the offload options + * @return aws credential provider + */ + public static AWSCredentialsProvider getAWSCredentialProvider(OffloadPolicies offloadPolicies) { + if (Strings.isNullOrEmpty(offloadPolicies.getS3ManagedLedgerOffloadRole())) { + return DefaultAWSCredentialsProviderChain.getInstance(); + } else { + String roleName = offloadPolicies.getS3ManagedLedgerOffloadRole(); + String roleSessionName = offloadPolicies.getS3ManagedLedgerOffloadRoleSessionName(); + return new STSAssumeRoleSessionCredentialsProvider.Builder(roleName, roleSessionName).build(); + } + } + +} diff --git a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/JCloudLedgerOffloaderFactory.java b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/JCloudLedgerOffloaderFactory.java index dffe253f94a98..20fac68f1f822 100644 --- a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/JCloudLedgerOffloaderFactory.java +++ b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/JCloudLedgerOffloaderFactory.java @@ -20,10 +20,10 @@ import java.io.IOException; import java.util.Map; -import java.util.Properties; import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.mledger.LedgerOffloaderFactory; import org.apache.bookkeeper.mledger.offload.jcloud.impl.BlobStoreManagedLedgerOffloader; +import org.apache.pulsar.common.policies.data.OffloadPolicies; /** * A jcloud based offloader factory. @@ -42,10 +42,9 @@ public boolean isDriverSupported(String driverName) { } @Override - public BlobStoreManagedLedgerOffloader create(Properties properties, + public BlobStoreManagedLedgerOffloader create(OffloadPolicies offloadPolicies, Map userMetadata, OrderedScheduler scheduler) throws IOException { - TieredStorageConfigurationData data = TieredStorageConfigurationData.create(properties); - return BlobStoreManagedLedgerOffloader.create(data, userMetadata, scheduler); + return BlobStoreManagedLedgerOffloader.create(offloadPolicies, userMetadata, scheduler); } } diff --git a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/TieredStorageConfigurationData.java b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/TieredStorageConfigurationData.java deleted file mode 100644 index a4c5cf4fa8b2e..0000000000000 --- a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/TieredStorageConfigurationData.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.bookkeeper.mledger.offload.jcloud; - -import static org.apache.pulsar.common.util.FieldParser.value; - -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; -import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; -import com.google.common.base.Strings; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Properties; -import lombok.Data; - -/** - * Configuration for tiered storage. - */ -@Data -public class TieredStorageConfigurationData implements Serializable, Cloneable { - - /**** --- Ledger Offloading --- ****/ - // Driver to use to offload old data to long term storage - private String managedLedgerOffloadDriver = null; - - // Maximum number of thread pool threads for ledger offloading - private int managedLedgerOffloadMaxThreads = 2; - - // For Amazon S3 ledger offload, AWS region - private String s3ManagedLedgerOffloadRegion = null; - - // For Amazon S3 ledger offload, Bucket to place offloaded ledger into - private String s3ManagedLedgerOffloadBucket = null; - - // For Amazon S3 ledger offload, Alternative endpoint to connect to (useful for testing) - private String s3ManagedLedgerOffloadServiceEndpoint = null; - - // For Amazon S3 ledger offload, Max block size in bytes. - private int s3ManagedLedgerOffloadMaxBlockSizeInBytes = 64 * 1024 * 1024; // 64MB - - // For Amazon S3 ledger offload, Read buffer size in bytes. - private int s3ManagedLedgerOffloadReadBufferSizeInBytes = 1024 * 1024; // 1MB - - // For Amazon S3 ledger offload, provide a role to assume before writing to s3 - private String s3ManagedLedgerOffloadRole = null; - - // For Amazon S3 ledger offload, provide a role session name when using a role - private String s3ManagedLedgerOffloadRoleSessionName = "pulsar-s3-offload"; - - // For Google Cloud Storage ledger offload, region where offload bucket is located. - // reference this page for more details: https://cloud.google.com/storage/docs/bucket-locations - private String gcsManagedLedgerOffloadRegion = null; - - // For Google Cloud Storage ledger offload, Bucket to place offloaded ledger into - private String gcsManagedLedgerOffloadBucket = null; - - // For Google Cloud Storage ledger offload, Max block size in bytes. - private int gcsManagedLedgerOffloadMaxBlockSizeInBytes = 64 * 1024 * 1024; // 64MB - - // For Google Cloud Storage ledger offload, Read buffer size in bytes. - private int gcsManagedLedgerOffloadReadBufferSizeInBytes = 1024 * 1024; // 1MB - - // For Google Cloud Storage, path to json file containing service account credentials. - // For more details, see the "Service Accounts" section of https://support.google.com/googleapi/answer/6158849 - private String gcsManagedLedgerOffloadServiceAccountKeyFile = null; - - /** - * Builds an AWS credential provider based on the offload options - * @return aws credential provider - */ - public AWSCredentialsProvider getAWSCredentialProvider() { - if (Strings.isNullOrEmpty(this.getS3ManagedLedgerOffloadRole())) { - return DefaultAWSCredentialsProviderChain.getInstance(); - } else { - String roleName = this.getS3ManagedLedgerOffloadRole(); - String roleSessionName = this.getS3ManagedLedgerOffloadRoleSessionName(); - return new STSAssumeRoleSessionCredentialsProvider.Builder(roleName, roleSessionName).build(); - } - } - - /** - * Create a tiered storage configuration from the provided properties. - * - * @param properties the configuration properties - * @return tiered storage configuration - */ - public static TieredStorageConfigurationData create(Properties properties) { - TieredStorageConfigurationData data = new TieredStorageConfigurationData(); - Field[] fields = TieredStorageConfigurationData.class.getDeclaredFields(); - Arrays.stream(fields).forEach(f -> { - if (properties.containsKey(f.getName())) { - try { - f.setAccessible(true); - f.set(data, value((String) properties.get(f.getName()), f)); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("failed to initialize %s field while setting value %s", - f.getName(), properties.get(f.getName())), e); - } - } - }); - return data; - } - -} diff --git a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloader.java b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloader.java index 7f7acaf25ccd6..0c92b96695e72 100644 --- a/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloader.java +++ b/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloader.java @@ -44,10 +44,11 @@ import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.mledger.LedgerOffloader; import org.apache.bookkeeper.mledger.offload.jcloud.BlockAwareSegmentInputStream; +import org.apache.bookkeeper.mledger.offload.jcloud.CredentialsUtil; import org.apache.bookkeeper.mledger.offload.jcloud.OffloadIndexBlock; -import org.apache.bookkeeper.mledger.offload.jcloud.TieredStorageConfigurationData; import org.apache.bookkeeper.mledger.offload.jcloud.OffloadIndexBlockBuilder; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.apache.pulsar.jcloud.shade.com.google.common.base.Supplier; import org.jclouds.Constants; import org.jclouds.ContextBuilder; @@ -181,16 +182,19 @@ private static Pair createBlobStore(String driver, // offload driver metadata to be stored as part of the original ledger metadata private final String offloadDriverName; + private static OffloadPolicies offloadPolicies; + @VisibleForTesting - static BlobStoreManagedLedgerOffloader create(TieredStorageConfigurationData conf, + static BlobStoreManagedLedgerOffloader create(OffloadPolicies conf, OrderedScheduler scheduler) throws IOException { return create(conf, Maps.newHashMap(), scheduler); } - public static BlobStoreManagedLedgerOffloader create(TieredStorageConfigurationData conf, + public static BlobStoreManagedLedgerOffloader create(OffloadPolicies conf, Map userMetadata, OrderedScheduler scheduler) throws IOException { + offloadPolicies = conf; String driver = conf.getManagedLedgerOffloadDriver(); if (!driverSupported(driver)) { throw new IOException( @@ -232,7 +236,8 @@ public static BlobStoreManagedLedgerOffloader create(TieredStorageConfigurationD maxBlockSize, readBufferSize, endpoint, region, credentials, userMetadata); } - public static Supplier getCredentials(String driver, TieredStorageConfigurationData conf) throws IOException { + public static Supplier getCredentials(String driver, + OffloadPolicies conf) throws IOException { // credentials: // for s3, get by DefaultAWSCredentialsProviderChain. // for gcs, use downloaded file 'google_creds.json', which contains service account key by @@ -252,7 +257,7 @@ public static Supplier getCredentials(String driver, TieredStorageC throw new IOException(ioe); } } else if (isS3Driver(driver)) { - AWSCredentialsProvider credsChain = conf.getAWSCredentialProvider(); + AWSCredentialsProvider credsChain = CredentialsUtil.getAWSCredentialProvider(conf); // try and get creds before starting... if we can't fetch // creds on boot, we want to fail try { @@ -587,6 +592,23 @@ public CompletableFuture deleteOffloaded(long ledgerId, UUID uid, public interface VersionCheck { void check(String key, Blob blob) throws IOException; } + + @Override + public OffloadPolicies getOffloadPolicies() { + return offloadPolicies; + } + + @Override + public void close() { + if (writeBlobStore != null) { + writeBlobStore.getContext().close(); + } + for (BlobStore readBlobStore : readBlobStores.values()) { + if (readBlobStore != null) { + readBlobStore.getContext().close(); + } + } + } } diff --git a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreBackedInputStreamTest.java b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreBackedInputStreamTest.java index 16784a553be9e..baefb95a13b00 100644 --- a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreBackedInputStreamTest.java +++ b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreBackedInputStreamTest.java @@ -44,7 +44,7 @@ import org.testng.annotations.Test; @Slf4j -class BlobStoreBackedInputStreamTest extends BlobStoreTestBase { +public class BlobStoreBackedInputStreamTest extends BlobStoreTestBase { private static final Logger log = LoggerFactory.getLogger(BlobStoreBackedInputStreamTest.class); class RandomInputStream extends InputStream { diff --git a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreTestBase.java b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreTestBase.java index 183424177e9b7..2725c051d1269 100644 --- a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreTestBase.java +++ b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/BlobStoreTestBase.java @@ -21,11 +21,21 @@ import org.jclouds.ContextBuilder; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.BlobStoreContext; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.IObjectFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.ObjectFactory; + +@PrepareForTest({CredentialsUtil.class}) +@PowerMockIgnore({ + "org.apache.logging.log4j.*", + "org.apache.pulsar.jcloud.shade.com.google.common.*", + "org.jclouds.*"}) public class BlobStoreTestBase { private static final Logger log = LoggerFactory.getLogger(BlobStoreTestBase.class); @@ -79,4 +89,10 @@ public void tearDown() { } } + @ObjectFactory + // Necessary to make PowerMockito.mockStatic work with TestNG. + public IObjectFactory getObjectFactory() { + return new org.powermock.modules.testng.PowerMockObjectFactory(); + } + } diff --git a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloaderTest.java b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloaderTest.java index a3c4972365007..8aa977a0e8366 100644 --- a/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloaderTest.java +++ b/tiered-storage/jcloud/src/test/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlobStoreManagedLedgerOffloaderTest.java @@ -31,7 +31,6 @@ import com.google.common.util.concurrent.MoreExecutors; import java.io.File; import java.io.IOException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -53,8 +52,9 @@ import org.apache.bookkeeper.common.util.OrderedScheduler; import org.apache.bookkeeper.mledger.LedgerOffloader; import org.apache.bookkeeper.mledger.offload.jcloud.BlobStoreTestBase; -import org.apache.bookkeeper.mledger.offload.jcloud.TieredStorageConfigurationData; +import org.apache.bookkeeper.mledger.offload.jcloud.CredentialsUtil; import org.apache.bookkeeper.util.ZkUtils; +import org.apache.pulsar.common.policies.data.OffloadPolicies; import org.apache.pulsar.jcloud.shade.com.google.common.base.Supplier; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.MockZooKeeper; @@ -64,13 +64,14 @@ import org.jclouds.blobstore.options.CopyOptions; import org.jclouds.domain.Credentials; import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; import org.testng.collections.Maps; -class BlobStoreManagedLedgerOffloaderTest extends BlobStoreTestBase { +public class BlobStoreManagedLedgerOffloaderTest extends BlobStoreTestBase { private static final Logger log = LoggerFactory.getLogger(BlobStoreManagedLedgerOffloaderTest.class); private static MockZooKeeper createMockZooKeeper() throws Exception { @@ -90,7 +91,7 @@ private static MockZooKeeper createMockZooKeeper() throws Exception { final OrderedScheduler scheduler; final PulsarMockBookKeeper bk; - BlobStoreManagedLedgerOffloaderTest() throws Exception { + public BlobStoreManagedLedgerOffloaderTest() throws Exception { scheduler = OrderedScheduler.newSchedulerBuilder().numThreads(1).name("offloader").build(); bk = new PulsarMockBookKeeper(createMockZooKeeper(), scheduler.chooseThread(this)); } @@ -154,7 +155,7 @@ public void testBucketDoesNotExist() throws Exception { @Test public void testNoRegionConfigured() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("s3"); conf.setS3ManagedLedgerOffloadBucket(BUCKET); @@ -168,7 +169,7 @@ public void testNoRegionConfigured() throws Exception { @Test public void testNoBucketConfigured() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("s3"); conf.setS3ManagedLedgerOffloadRegion("eu-west-1"); @@ -182,7 +183,7 @@ public void testNoBucketConfigured() throws Exception { @Test public void testSmallBlockSizeConfigured() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("s3"); conf.setS3ManagedLedgerOffloadRegion("eu-west-1"); conf.setS3ManagedLedgerOffloadBucket(BUCKET); @@ -198,7 +199,7 @@ public void testSmallBlockSizeConfigured() throws Exception { @Test public void testGcsNoKeyPath() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("google-cloud-storage"); conf.setGcsManagedLedgerOffloadBucket(BUCKET); @@ -213,7 +214,7 @@ public void testGcsNoKeyPath() throws Exception { @Test public void testGcsNoBucketConfigured() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("google-cloud-storage"); File tmpKeyFile = File.createTempFile("gcsOffload", "json"); conf.setGcsManagedLedgerOffloadServiceAccountKeyFile(tmpKeyFile.getAbsolutePath()); @@ -229,7 +230,7 @@ public void testGcsNoBucketConfigured() throws Exception { @Test public void testGcsSmallBlockSizeConfigured() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData(); + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("google-cloud-storage"); File tmpKeyFile = File.createTempFile("gcsOffload", "json"); conf.setGcsManagedLedgerOffloadServiceAccountKeyFile(tmpKeyFile.getAbsolutePath()); @@ -247,37 +248,35 @@ public void testGcsSmallBlockSizeConfigured() throws Exception { @Test public void testS3DriverConfiguredWell() throws Exception { - TieredStorageConfigurationData conf = new TieredStorageConfigurationData() { + PowerMockito.mockStatic(CredentialsUtil.class); + PowerMockito.when(CredentialsUtil.getAWSCredentialProvider(any())).thenReturn(new AWSCredentialsProvider() { @Override - public AWSCredentialsProvider getAWSCredentialProvider() { - return new AWSCredentialsProvider() { + public AWSCredentials getCredentials() { + return new AWSSessionCredentials() { @Override - public AWSCredentials getCredentials() { - return new AWSSessionCredentials() { - @Override - public String getSessionToken() { - return "token"; - } - - @Override - public String getAWSAccessKeyId() { - return "access"; - } - - @Override - public String getAWSSecretKey() { - return "secret"; - } - }; + public String getSessionToken() { + return "token"; } @Override - public void refresh() { + public String getAWSAccessKeyId() { + return "access"; + } + @Override + public String getAWSSecretKey() { + return "secret"; } }; } - }; + + @Override + public void refresh() { + + } + }); + + OffloadPolicies conf = new OffloadPolicies(); conf.setManagedLedgerOffloadDriver("s3"); conf.setS3ManagedLedgerOffloadBucket(BUCKET); conf.setS3ManagedLedgerOffloadServiceEndpoint("http://fake.s3.end.point"); @@ -644,8 +643,8 @@ public void testReadUnknownIndexVersion() throws Exception { @Test public void testSessionCredentialSupplier() throws Exception { - TieredStorageConfigurationData mock = mock(TieredStorageConfigurationData.class); - Mockito.when(mock.getAWSCredentialProvider()).thenReturn(new AWSCredentialsProvider() { + PowerMockito.mockStatic(CredentialsUtil.class); + PowerMockito.when(CredentialsUtil.getAWSCredentialProvider(any())).thenReturn(new AWSCredentialsProvider() { @Override public AWSCredentials getCredentials() { return new AWSSessionCredentials() { @@ -672,7 +671,7 @@ public void refresh() { } }); - Supplier creds = BlobStoreManagedLedgerOffloader.getCredentials("aws-s3", mock); + Supplier creds = BlobStoreManagedLedgerOffloader.getCredentials("aws-s3", any()); Assert.assertTrue(creds.get() instanceof SessionCredentials); SessionCredentials sessCreds = (SessionCredentials) creds.get();