diff --git a/.github/actions/clean-disk/action.yml b/.github/actions/clean-disk/action.yml
index 8bcc5f1396802..d74c3f25fc64c 100644
--- a/.github/actions/clean-disk/action.yml
+++ b/.github/actions/clean-disk/action.yml
@@ -31,7 +31,7 @@ runs:
directories=(/usr/local/lib/android /opt/ghc)
if [[ "${{ inputs.mode }}" == "full" ]]; then
# remove these directories only when mode is 'full'
- directories+=(/usr/share/dotnet)
+ directories+=(/usr/share/dotnet /opt/hostedtoolcache/CodeQL)
fi
emptydir=/tmp/empty$$/
mkdir $emptydir
diff --git a/.github/workflows/pulsar-ci.yaml b/.github/workflows/pulsar-ci.yaml
index 64708d940a9fb..f4d258a5b4317 100644
--- a/.github/workflows/pulsar-ci.yaml
+++ b/.github/workflows/pulsar-ci.yaml
@@ -190,6 +190,8 @@ jobs:
group: BROKER_GROUP_2
- name: Brokers - Broker Group 3
group: BROKER_GROUP_3
+ - name: Brokers - Broker Group 4
+ group: BROKER_GROUP_4
- name: Brokers - Client Api
group: BROKER_CLIENT_API
- name: Brokers - Client Impl
@@ -747,6 +749,8 @@ jobs:
- name: Clean Disk
uses: ./.github/actions/clean-disk
+ with:
+ mode: full
- name: Cache local Maven repository
uses: actions/cache@v3
@@ -862,6 +866,7 @@ jobs:
- name: Pulsar IO
group: PULSAR_IO
+ clean_disk: true
- name: Sql
group: SQL
@@ -873,6 +878,10 @@ jobs:
- name: Tune Runner VM
uses: ./.github/actions/tune-runner-vm
+ - name: Clean Disk when needed
+ if: ${{ matrix.clean_disk }}
+ uses: ./.github/actions/clean-disk
+
- name: Setup ssh access to build runner VM
# ssh access is enabled for builds in own forks
if: ${{ github.repository != 'apache/pulsar' && github.event_name == 'pull_request' }}
@@ -1073,6 +1082,7 @@ jobs:
- name: Pulsar IO - Oracle
group: PULSAR_IO_ORA
+ clean_disk: true
steps:
- name: checkout
@@ -1081,6 +1091,10 @@ jobs:
- name: Tune Runner VM
uses: ./.github/actions/tune-runner-vm
+ - name: Clean Disk when needed
+ if: ${{ matrix.clean_disk }}
+ uses: ./.github/actions/clean-disk
+
- name: Setup ssh access to build runner VM
# ssh access is enabled for builds in own forks
if: ${{ github.repository != 'apache/pulsar' && github.event_name == 'pull_request' }}
diff --git a/bouncy-castle/bc/pom.xml b/bouncy-castle/bc/pom.xml
index f1d314a98a815..6058c28076814 100644
--- a/bouncy-castle/bc/pom.xml
+++ b/bouncy-castle/bc/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
bouncy-castle-parent
- 3.1.1
+ 3.1.2
..
diff --git a/bouncy-castle/bcfips-include-test/pom.xml b/bouncy-castle/bcfips-include-test/pom.xml
index 3ec9511a7b042..92641bdd2c49a 100644
--- a/bouncy-castle/bcfips-include-test/pom.xml
+++ b/bouncy-castle/bcfips-include-test/pom.xml
@@ -24,7 +24,7 @@
com.datastax.oss
bouncy-castle-parent
- 3.1.1
+ 3.1.2
..
diff --git a/bouncy-castle/bcfips/pom.xml b/bouncy-castle/bcfips/pom.xml
index 3f217731ea3e1..2280474524cee 100644
--- a/bouncy-castle/bcfips/pom.xml
+++ b/bouncy-castle/bcfips/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
bouncy-castle-parent
- 3.1.1
+ 3.1.2
..
diff --git a/bouncy-castle/pom.xml b/bouncy-castle/pom.xml
index de82aa6b30e6c..dfb955a354459 100644
--- a/bouncy-castle/pom.xml
+++ b/bouncy-castle/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
..
diff --git a/build/run_integration_group.sh b/build/run_integration_group.sh
index 9372d1ecb9317..ffe20390cf7fa 100755
--- a/build/run_integration_group.sh
+++ b/build/run_integration_group.sh
@@ -33,7 +33,7 @@ TESTNG_VERSION="7.3.0"
# returns a CSV value
mvn_list_modules() {
(
- mvn -B -ntp -Dscan=false "$@" initialize \
+ mvn -fae -B -ntp -Dscan=false "$@" initialize \
| grep -- "-< .* >-" \
| sed -E 's/.*-< (.*) >-.*/\1/' \
| tr '\n' ',' | sed 's/,$/\n/'
diff --git a/build/run_unit_group.sh b/build/run_unit_group.sh
index f6c212dcdd28f..8597848b69bf8 100755
--- a/build/run_unit_group.sh
+++ b/build/run_unit_group.sh
@@ -87,6 +87,10 @@ function test_group_broker_group_3() {
mvn_test -pl pulsar-broker -Dgroups='broker-admin'
}
+function test_group_broker_group_4() {
+ mvn_test -pl pulsar-broker -Dgroups='cluster-migration'
+}
+
function test_group_broker_client_api() {
mvn_test -pl pulsar-broker -Dgroups='broker-api'
}
diff --git a/buildtools/pom.xml b/buildtools/pom.xml
index c2f993387bd9a..393a69fd1edb5 100644
--- a/buildtools/pom.xml
+++ b/buildtools/pom.xml
@@ -31,12 +31,12 @@
com.datastax.oss
buildtools
- 3.1.1
+ 3.1.2
jar
Pulsar Build Tools
- 2023-09-27T08:29:08Z
+ 2023-11-30T15:05:59Z
1.8
1.8
3.1.0
@@ -47,7 +47,7 @@
4.1
8.37
3.1.2
- 4.1.94.Final
+ 4.1.100.Final
4.2.3
32.1.1-jre
1.10.12
diff --git a/conf/broker.conf b/conf/broker.conf
index 94c89414504f9..73a42171fc1d5 100644
--- a/conf/broker.conf
+++ b/conf/broker.conf
@@ -538,6 +538,9 @@ brokerServiceCompactionThresholdInBytes=0
# If the execution time of the compaction phase one loop exceeds this time, the compaction will not proceed.
brokerServiceCompactionPhaseOneLoopTimeInSeconds=30
+# Whether retain null-key message during topic compaction
+topicCompactionRetainNullKey=true
+
# Whether to enable the delayed delivery for messages.
# If disabled, messages will be immediately delivered and there will
# be no tracking overhead.
diff --git a/conf/schema_example.conf b/conf/schema_example.json
similarity index 100%
rename from conf/schema_example.conf
rename to conf/schema_example.json
diff --git a/conf/standalone.conf b/conf/standalone.conf
index 76223c5933e45..0b486bdaf0481 100644
--- a/conf/standalone.conf
+++ b/conf/standalone.conf
@@ -1277,4 +1277,7 @@ brokerInterceptorsDirectory=./interceptors
brokerInterceptors=
# Enable or disable the broker interceptor, which is only used for testing for now
-disableBrokerInterceptors=true
\ No newline at end of file
+disableBrokerInterceptors=true
+
+# Whether retain null-key message during topic compaction
+topicCompactionRetainNullKey=true
diff --git a/distribution/io/pom.xml b/distribution/io/pom.xml
index 74800f514e6fb..710373712427c 100644
--- a/distribution/io/pom.xml
+++ b/distribution/io/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
distribution
- 3.1.1
+ 3.1.2
..
diff --git a/distribution/offloaders/pom.xml b/distribution/offloaders/pom.xml
index 797d8324e4c6f..b6ea86580489e 100644
--- a/distribution/offloaders/pom.xml
+++ b/distribution/offloaders/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
distribution
- 3.1.1
+ 3.1.2
..
diff --git a/distribution/pom.xml b/distribution/pom.xml
index d839d51e80d98..01354a0eaac69 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
..
diff --git a/distribution/server/pom.xml b/distribution/server/pom.xml
index 4895467b2dd2d..6177782aad3bd 100644
--- a/distribution/server/pom.xml
+++ b/distribution/server/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
distribution
- 3.1.1
+ 3.1.2
..
@@ -155,6 +155,12 @@
io.dropwizard.metrics
metrics-graphite
+
+
+ amqp-client
+ com.rabbitmq
+
+
diff --git a/distribution/server/src/assemble/LICENSE.bin.txt b/distribution/server/src/assemble/LICENSE.bin.txt
index d32c44a6651c5..287fe8e680338 100644
--- a/distribution/server/src/assemble/LICENSE.bin.txt
+++ b/distribution/server/src/assemble/LICENSE.bin.txt
@@ -289,27 +289,26 @@ The Apache Software License, Version 2.0
- org.apache.commons-commons-lang3-3.11.jar
- org.apache.commons-commons-text-1.10.0.jar
* Netty
- - io.netty-netty-buffer-4.1.94.Final.jar
- - io.netty-netty-codec-4.1.94.Final.jar
- - io.netty-netty-codec-dns-4.1.94.Final.jar
- - io.netty-netty-codec-http-4.1.94.Final.jar
- - io.netty-netty-codec-http2-4.1.94.Final.jar
- - io.netty-netty-codec-socks-4.1.94.Final.jar
- - io.netty-netty-codec-haproxy-4.1.94.Final.jar
- - io.netty-netty-common-4.1.94.Final.jar
- - io.netty-netty-handler-4.1.94.Final.jar
- - io.netty-netty-handler-proxy-4.1.94.Final.jar
- - io.netty-netty-resolver-4.1.94.Final.jar
- - io.netty-netty-resolver-dns-4.1.94.Final.jar
- - io.netty-netty-resolver-dns-classes-macos-4.1.94.Final.jar
- - io.netty-netty-resolver-dns-native-macos-4.1.94.Final-osx-aarch_64.jar
- - io.netty-netty-resolver-dns-native-macos-4.1.94.Final-osx-x86_64.jar
- - io.netty-netty-transport-4.1.94.Final.jar
- - io.netty-netty-transport-classes-epoll-4.1.94.Final.jar
- - io.netty-netty-transport-native-epoll-4.1.94.Final-linux-x86_64.jar
- - io.netty-netty-transport-native-epoll-4.1.94.Final.jar
- - io.netty-netty-transport-native-unix-common-4.1.94.Final.jar
- - io.netty-netty-transport-native-unix-common-4.1.94.Final-linux-x86_64.jar
+ - io.netty-netty-buffer-4.1.100.Final.jar
+ - io.netty-netty-codec-4.1.100.Final.jar
+ - io.netty-netty-codec-dns-4.1.100.Final.jar
+ - io.netty-netty-codec-http-4.1.100.Final.jar
+ - io.netty-netty-codec-http2-4.1.100.Final.jar
+ - io.netty-netty-codec-socks-4.1.100.Final.jar
+ - io.netty-netty-codec-haproxy-4.1.100.Final.jar
+ - io.netty-netty-common-4.1.100.Final.jar
+ - io.netty-netty-handler-4.1.100.Final.jar
+ - io.netty-netty-handler-proxy-4.1.100.Final.jar
+ - io.netty-netty-resolver-4.1.100.Final.jar
+ - io.netty-netty-resolver-dns-4.1.100.Final.jar
+ - io.netty-netty-resolver-dns-classes-macos-4.1.100.Final.jar
+ - io.netty-netty-resolver-dns-native-macos-4.1.100.Final-osx-aarch_64.jar
+ - io.netty-netty-resolver-dns-native-macos-4.1.100.Final-osx-x86_64.jar
+ - io.netty-netty-transport-4.1.100.Final.jar
+ - io.netty-netty-transport-classes-epoll-4.1.100.Final.jar
+ - io.netty-netty-transport-native-epoll-4.1.100.Final-linux-x86_64.jar
+ - io.netty-netty-transport-native-unix-common-4.1.100.Final.jar
+ - io.netty-netty-transport-native-unix-common-4.1.100.Final-linux-x86_64.jar
- io.netty-netty-tcnative-boringssl-static-2.0.61.Final.jar
- io.netty-netty-tcnative-boringssl-static-2.0.61.Final-linux-aarch_64.jar
- io.netty-netty-tcnative-boringssl-static-2.0.61.Final-linux-x86_64.jar
@@ -383,25 +382,25 @@ The Apache Software License, Version 2.0
- org.asynchttpclient-async-http-client-2.12.1.jar
- org.asynchttpclient-async-http-client-netty-utils-2.12.1.jar
* Jetty
- - org.eclipse.jetty-jetty-client-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-continuation-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-http-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-io-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-proxy-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-security-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-server-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-servlet-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-servlets-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-util-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-util-ajax-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-javax-websocket-client-impl-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-websocket-api-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-websocket-client-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-websocket-common-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-websocket-server-9.4.51.v20230217.jar
- - org.eclipse.jetty.websocket-websocket-servlet-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-alpn-conscrypt-server-9.4.51.v20230217.jar
- - org.eclipse.jetty-jetty-alpn-server-9.4.51.v20230217.jar
+ - org.eclipse.jetty-jetty-client-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-continuation-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-http-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-io-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-proxy-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-security-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-server-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-servlet-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-servlets-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-util-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-util-ajax-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-javax-websocket-client-impl-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-websocket-api-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-websocket-client-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-websocket-common-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-websocket-server-9.4.53.v20231009.jar
+ - org.eclipse.jetty.websocket-websocket-servlet-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-alpn-conscrypt-server-9.4.53.v20231009.jar
+ - org.eclipse.jetty-jetty-alpn-server-9.4.53.v20231009.jar
* SnakeYaml -- org.yaml-snakeyaml-2.0.jar
* RocksDB - org.rocksdb-rocksdbjni-7.9.2.jar
* Google Error Prone Annotations - com.google.errorprone-error_prone_annotations-2.5.1.jar
@@ -447,8 +446,8 @@ The Apache Software License, Version 2.0
- net.jodah-typetools-0.5.0.jar
- net.jodah-failsafe-2.4.4.jar
* Apache Avro
- - org.apache.avro-avro-1.10.2.jar
- - org.apache.avro-avro-protobuf-1.10.2.jar
+ - org.apache.avro-avro-1.11.3.jar
+ - org.apache.avro-avro-protobuf-1.11.3.jar
* Apache Curator
- org.apache.curator-curator-client-5.1.0.jar
- org.apache.curator-curator-framework-5.1.0.jar
@@ -480,11 +479,11 @@ The Apache Software License, Version 2.0
- io.vertx-vertx-web-common-4.3.8.jar
- io.vertx-vertx-grpc-4.3.5.jar
* Apache ZooKeeper
- - org.apache.zookeeper-zookeeper-3.8.1.jar
- - org.apache.zookeeper-zookeeper-jute-3.8.1.jar
- - org.apache.zookeeper-zookeeper-prometheus-metrics-3.8.1.jar
+ - org.apache.zookeeper-zookeeper-3.9.1.jar
+ - org.apache.zookeeper-zookeeper-jute-3.9.1.jar
+ - org.apache.zookeeper-zookeeper-prometheus-metrics-3.9.1.jar
* Snappy Java
- - org.xerial.snappy-snappy-java-1.1.10.1.jar
+ - org.xerial.snappy-snappy-java-1.1.10.5.jar
* Google HTTP Client
- com.google.http-client-google-http-client-gson-1.41.0.jar
- com.google.http-client-google-http-client-1.41.0.jar
@@ -499,8 +498,6 @@ The Apache Software License, Version 2.0
- com.github.seancfoley-ipaddress-5.3.3.jar
* RxJava
- io.reactivex.rxjava3-rxjava-3.0.1.jar
- * RabbitMQ Java Client
- - com.rabbitmq-amqp-client-5.5.3.jar
* RoaringBitmap
- org.roaringbitmap-RoaringBitmap-0.9.44.jar
diff --git a/distribution/server/src/assemble/bin.xml b/distribution/server/src/assemble/bin.xml
index 41ac24d0582da..aafb559d67fb2 100644
--- a/distribution/server/src/assemble/bin.xml
+++ b/distribution/server/src/assemble/bin.xml
@@ -133,12 +133,12 @@
${artifact.groupId}-${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}
- org.apache.pulsar:pulsar-functions-runtime-all
+ com.datastax.oss:pulsar-functions-runtime-all
org.projectlombok:lombok
- org.apache.pulsar:pulsar-functions-api-examples
+ com.datastax.oss:pulsar-functions-api-examples
*:tar.gz
diff --git a/distribution/shell/pom.xml b/distribution/shell/pom.xml
index caff9367f32bd..c4e6118f8bdd2 100644
--- a/distribution/shell/pom.xml
+++ b/distribution/shell/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
distribution
- 3.1.1
+ 3.1.2
..
diff --git a/distribution/shell/src/assemble/LICENSE.bin.txt b/distribution/shell/src/assemble/LICENSE.bin.txt
index f92d95928298a..91324dc4a7175 100644
--- a/distribution/shell/src/assemble/LICENSE.bin.txt
+++ b/distribution/shell/src/assemble/LICENSE.bin.txt
@@ -344,22 +344,22 @@ The Apache Software License, Version 2.0
- commons-text-1.10.0.jar
- commons-compress-1.21.jar
* Netty
- - netty-buffer-4.1.94.Final.jar
- - netty-codec-4.1.94.Final.jar
- - netty-codec-dns-4.1.94.Final.jar
- - netty-codec-http-4.1.94.Final.jar
- - netty-codec-socks-4.1.94.Final.jar
- - netty-codec-haproxy-4.1.94.Final.jar
- - netty-common-4.1.94.Final.jar
- - netty-handler-4.1.94.Final.jar
- - netty-handler-proxy-4.1.94.Final.jar
- - netty-resolver-4.1.94.Final.jar
- - netty-resolver-dns-4.1.94.Final.jar
- - netty-transport-4.1.94.Final.jar
- - netty-transport-classes-epoll-4.1.94.Final.jar
- - netty-transport-native-epoll-4.1.94.Final-linux-x86_64.jar
- - netty-transport-native-unix-common-4.1.94.Final.jar
- - netty-transport-native-unix-common-4.1.94.Final-linux-x86_64.jar
+ - netty-buffer-4.1.100.Final.jar
+ - netty-codec-4.1.100.Final.jar
+ - netty-codec-dns-4.1.100.Final.jar
+ - netty-codec-http-4.1.100.Final.jar
+ - netty-codec-socks-4.1.100.Final.jar
+ - netty-codec-haproxy-4.1.100.Final.jar
+ - netty-common-4.1.100.Final.jar
+ - netty-handler-4.1.100.Final.jar
+ - netty-handler-proxy-4.1.100.Final.jar
+ - netty-resolver-4.1.100.Final.jar
+ - netty-resolver-dns-4.1.100.Final.jar
+ - netty-transport-4.1.100.Final.jar
+ - netty-transport-classes-epoll-4.1.100.Final.jar
+ - netty-transport-native-epoll-4.1.100.Final-linux-x86_64.jar
+ - netty-transport-native-unix-common-4.1.100.Final.jar
+ - netty-transport-native-unix-common-4.1.100.Final-linux-x86_64.jar
- netty-tcnative-boringssl-static-2.0.61.Final.jar
- netty-tcnative-boringssl-static-2.0.61.Final-linux-aarch_64.jar
- netty-tcnative-boringssl-static-2.0.61.Final-linux-x86_64.jar
@@ -370,9 +370,9 @@ The Apache Software License, Version 2.0
- netty-incubator-transport-classes-io_uring-0.0.21.Final.jar
- netty-incubator-transport-native-io_uring-0.0.21.Final-linux-aarch_64.jar
- netty-incubator-transport-native-io_uring-0.0.21.Final-linux-x86_64.jar
- - netty-resolver-dns-classes-macos-4.1.94.Final.jar
- - netty-resolver-dns-native-macos-4.1.94.Final-osx-aarch_64.jar
- - netty-resolver-dns-native-macos-4.1.94.Final-osx-x86_64.jar
+ - netty-resolver-dns-classes-macos-4.1.100.Final.jar
+ - netty-resolver-dns-native-macos-4.1.100.Final-osx-aarch_64.jar
+ - netty-resolver-dns-native-macos-4.1.100.Final-osx-x86_64.jar
* Prometheus client
- simpleclient-0.16.0.jar
- simpleclient_log4j2-0.16.0.jar
@@ -395,20 +395,20 @@ The Apache Software License, Version 2.0
- async-http-client-2.12.1.jar
- async-http-client-netty-utils-2.12.1.jar
* Jetty
- - jetty-client-9.4.51.v20230217.jar
- - jetty-http-9.4.51.v20230217.jar
- - jetty-io-9.4.51.v20230217.jar
- - jetty-util-9.4.51.v20230217.jar
- - javax-websocket-client-impl-9.4.51.v20230217.jar
- - websocket-api-9.4.51.v20230217.jar
- - websocket-client-9.4.51.v20230217.jar
- - websocket-common-9.4.51.v20230217.jar
+ - jetty-client-9.4.53.v20231009.jar
+ - jetty-http-9.4.53.v20231009.jar
+ - jetty-io-9.4.53.v20231009.jar
+ - jetty-util-9.4.53.v20231009.jar
+ - javax-websocket-client-impl-9.4.53.v20231009.jar
+ - websocket-api-9.4.53.v20231009.jar
+ - websocket-client-9.4.53.v20231009.jar
+ - websocket-common-9.4.53.v20231009.jar
* SnakeYaml -- snakeyaml-2.0.jar
* Google Error Prone Annotations - error_prone_annotations-2.5.1.jar
* Javassist -- javassist-3.25.0-GA.jar
* Apache Avro
- - avro-1.10.2.jar
- - avro-protobuf-1.10.2.jar
+ - avro-1.11.3.jar
+ - avro-protobuf-1.11.3.jar
BSD 3-clause "New" or "Revised" License
* JSR305 -- jsr305-3.0.2.jar -- ../licenses/LICENSE-JSR305.txt
diff --git a/docker/pom.xml b/docker/pom.xml
index a9fb7603863e9..477cfa3f221d6 100644
--- a/docker/pom.xml
+++ b/docker/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
docker-images
Apache Pulsar :: Docker Images
diff --git a/docker/pulsar-all/pom.xml b/docker/pulsar-all/pom.xml
index cfe6b2558efcd..977253c228028 100644
--- a/docker/pulsar-all/pom.xml
+++ b/docker/pulsar-all/pom.xml
@@ -23,7 +23,7 @@
com.datastax.oss
docker-images
- 3.1.1
+ 3.1.2
4.0.0
pulsar-all-docker-image
diff --git a/docker/pulsar-experimental/pom.xml b/docker/pulsar-experimental/pom.xml
index 89344e610a37d..c19a278607a9b 100644
--- a/docker/pulsar-experimental/pom.xml
+++ b/docker/pulsar-experimental/pom.xml
@@ -23,7 +23,7 @@
com.datastax.oss
docker-images
- 3.1.1
+ 3.1.2
4.0.0
pulsar-experimental-docker-image
diff --git a/docker/pulsar/Dockerfile b/docker/pulsar/Dockerfile
index 593401f57be8c..c8ff0ef4f189a 100644
--- a/docker/pulsar/Dockerfile
+++ b/docker/pulsar/Dockerfile
@@ -61,9 +61,9 @@ RUN sed -i -e "s|http://archive\.ubuntu\.com/ubuntu/|${UBUNTU_MIRROR:-mirror://m
&& echo 'Acquire::http::Timeout "30";\nAcquire::ftp::Timeout "30";\nAcquire::Retries "3";' > /etc/apt/apt.conf.d/99timeout_and_retries \
&& apt-get update \
&& apt-get -y dist-upgrade \
- && apt-get -y install --no-install-recommends netcat dnsutils less procps iputils-ping \
- python3 python3-kazoo python3-pip \
- curl ca-certificates wget apt-transport-https
+ && apt-get -y install netcat dnsutils less procps iputils-ping \
+ curl ca-certificates wget apt-transport-https \
+ && apt-get -y install --no-install-recommends python3 python3-kazoo python3-pip
# Install Eclipse Temurin Package
RUN mkdir -p /etc/apt/keyrings \
diff --git a/docker/pulsar/pom.xml b/docker/pulsar/pom.xml
index 3e7dd305ef055..9c55ff898d913 100644
--- a/docker/pulsar/pom.xml
+++ b/docker/pulsar/pom.xml
@@ -23,7 +23,7 @@
com.datastax.oss
docker-images
- 3.1.1
+ 3.1.2
4.0.0
pulsar-docker-image
diff --git a/jclouds-shaded/pom.xml b/jclouds-shaded/pom.xml
index 8c36e6caed14c..9d3f6961fe550 100644
--- a/jclouds-shaded/pom.xml
+++ b/jclouds-shaded/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
..
diff --git a/managed-ledger/pom.xml b/managed-ledger/pom.xml
index aab2fb407d4b5..fca2d04548f43 100644
--- a/managed-ledger/pom.xml
+++ b/managed-ledger/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
..
@@ -47,6 +47,12 @@
org.apache.bookkeeper.stats
codahale-metrics-provider
${bookkeeper.version}
+
+
+ amqp-client
+ com.rabbitmq
+
+
diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedger.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedger.java
index c7dd8ea9129b7..f91d9ec3f5a02 100644
--- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedger.java
+++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/ManagedLedger.java
@@ -682,8 +682,10 @@ default void skipNonRecoverableLedger(long ledgerId){}
/**
* Check current inactive ledger (based on {@link ManagedLedgerConfig#getInactiveLedgerRollOverTimeMs()} and
* roll over that ledger if inactive.
+ *
+ * @return true if ledger is considered for rolling over
*/
- void checkInactiveLedgerAndRollOver();
+ boolean checkInactiveLedgerAndRollOver();
/**
* Check if managed ledger should cache backlog reads.
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 ff8e0655d03be..ea013d2da7dd7 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
@@ -690,7 +690,7 @@ private void recoveredCursor(PositionImpl position, Map properties
position = ledger.getLastPosition();
}
log.info("[{}] Cursor {} recovered to position {}", ledger.getName(), name, position);
- this.cursorProperties = cursorProperties;
+ this.cursorProperties = cursorProperties == null ? Collections.emptyMap() : cursorProperties;
messagesConsumedCounter = -getNumberOfEntries(Range.openClosed(position, ledger.getLastPosition()));
markDeletePosition = position;
persistentMarkDeletePosition = position;
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 9107b76c88a28..1bb23912b5e31 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
@@ -880,7 +880,10 @@ public void asyncDelete(String name, CompletableFuture mlCo
// If it's open, delete in the normal way
ml.asyncDelete(callback, ctx);
}).exceptionally(ex -> {
- // If it's failing to get open, just delete from metadata
+ // If it fails to get open, it will be cleaned by managed ledger opening error handling.
+ // then retry will go to `future=null` branch.
+ final Throwable rc = FutureUtil.unwrapCompletionException(ex);
+ callback.deleteLedgerFailed(getManagedLedgerException(rc), ctx);
return null;
});
}
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 df653a1196b63..146693f661071 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
@@ -59,7 +59,6 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;
@@ -3209,7 +3208,7 @@ public void asyncOffloadPrefix(Position pos, OffloadCallback callback, Object ct
}
}
- private void offloadLoop(CompletableFuture promise, Queue ledgersToOffload,
+ void offloadLoop(CompletableFuture promise, Queue ledgersToOffload,
PositionImpl firstUnoffloaded, Optional firstError) {
State currentState = getState();
if (currentState == State.Closed) {
@@ -3257,6 +3256,7 @@ private void offloadLoop(CompletableFuture promise, Queue metadata) {
- AtomicBoolean ledgerCreated = new AtomicBoolean(false);
+ CompletableFuture ledgerFutureHook = new CompletableFuture<>();
Map finalMetadata = new HashMap<>();
finalMetadata.putAll(ledgerMetadata);
finalMetadata.putAll(metadata);
@@ -4044,33 +4044,39 @@ protected void asyncCreateLedger(BookKeeper bookKeeper, ManagedLedgerConfig conf
));
} catch (EnsemblePlacementPolicyConfig.ParseEnsemblePlacementPolicyConfigException e) {
log.error("[{}] Serialize the placement configuration failed", name, e);
- cb.createComplete(Code.UnexpectedConditionException, null, ledgerCreated);
+ cb.createComplete(Code.UnexpectedConditionException, null, ledgerFutureHook);
return;
}
}
createdLedgerCustomMetadata = finalMetadata;
-
try {
bookKeeper.asyncCreateLedger(config.getEnsembleSize(), config.getWriteQuorumSize(),
- config.getAckQuorumSize(), digestType, config.getPassword(), cb, ledgerCreated, finalMetadata);
+ config.getAckQuorumSize(), digestType, config.getPassword(), cb, ledgerFutureHook, finalMetadata);
} catch (Throwable cause) {
log.error("[{}] Encountered unexpected error when creating ledger",
name, cause);
- cb.createComplete(Code.UnexpectedConditionException, null, ledgerCreated);
+ ledgerFutureHook.completeExceptionally(cause);
+ cb.createComplete(Code.UnexpectedConditionException, null, ledgerFutureHook);
return;
}
- scheduledExecutor.schedule(() -> {
- if (!ledgerCreated.get()) {
+
+ ScheduledFuture timeoutChecker = scheduledExecutor.schedule(() -> {
+ if (!ledgerFutureHook.isDone()
+ && ledgerFutureHook.completeExceptionally(new TimeoutException(name + " Create ledger timeout"))) {
if (log.isDebugEnabled()) {
log.debug("[{}] Timeout creating ledger", name);
}
- cb.createComplete(BKException.Code.TimeoutException, null, ledgerCreated);
+ cb.createComplete(BKException.Code.TimeoutException, null, ledgerFutureHook);
} else {
if (log.isDebugEnabled()) {
log.debug("[{}] Ledger already created when timeout task is triggered", name);
}
}
}, config.getMetadataOperationsTimeoutSeconds(), TimeUnit.SECONDS);
+
+ ledgerFutureHook.whenComplete((ignore, ex) -> {
+ timeoutChecker.cancel(false);
+ });
}
public Clock getClock() {
@@ -4079,16 +4085,12 @@ public Clock getClock() {
/**
* check if ledger-op task is already completed by timeout-task. If completed then delete the created ledger
- *
- * @param rc
- * @param lh
- * @param ctx
* @return
*/
protected boolean checkAndCompleteLedgerOpTask(int rc, LedgerHandle lh, Object ctx) {
- if (ctx instanceof AtomicBoolean) {
+ if (ctx instanceof CompletableFuture) {
// ledger-creation is already timed out and callback is already completed so, delete this ledger and return.
- if (((AtomicBoolean) (ctx)).compareAndSet(false, true)) {
+ if (((CompletableFuture) ctx).complete(lh)) {
return false;
} else {
if (rc == BKException.Code.OK) {
@@ -4447,7 +4449,7 @@ private void cancelScheduledTasks() {
}
@Override
- public void checkInactiveLedgerAndRollOver() {
+ public boolean checkInactiveLedgerAndRollOver() {
long currentTimeMs = System.currentTimeMillis();
if (inactiveLedgerRollOverTimeMs > 0 && currentTimeMs > (lastAddEntryTimeMs + inactiveLedgerRollOverTimeMs)) {
log.info("[{}] Closing inactive ledger, last-add entry {}", name, lastAddEntryTimeMs);
@@ -4468,10 +4470,13 @@ public void checkInactiveLedgerAndRollOver() {
}
ledgerClosed(lh);
+ createLedgerAfterClosed();
// we do not create ledger here, since topic is inactive for a long time.
}, null);
+ return true;
}
}
+ return false;
}
diff --git a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/util/RangeCache.java b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/util/RangeCache.java
index 7599e2cc1874f..d34857e5e5177 100644
--- a/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/util/RangeCache.java
+++ b/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/util/RangeCache.java
@@ -27,7 +27,6 @@
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
-import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.tuple.Pair;
/**
@@ -74,13 +73,18 @@ public RangeCache(Weighter weighter, TimestampExtractor timestampE
* @return whether the entry was inserted in the cache
*/
public boolean put(Key key, Value value) {
- MutableBoolean flag = new MutableBoolean();
- entries.computeIfAbsent(key, (k) -> {
- size.addAndGet(weighter.getSize(value));
- flag.setValue(true);
- return value;
- });
- return flag.booleanValue();
+ // retain value so that it's not released before we put it in the cache and calculate the weight
+ value.retain();
+ try {
+ if (entries.putIfAbsent(key, value) == null) {
+ size.addAndGet(weighter.getSize(value));
+ return true;
+ } else {
+ return false;
+ }
+ } finally {
+ value.release();
+ }
}
public boolean exists(Key key) {
@@ -242,7 +246,6 @@ public synchronized Pair clear() {
value.release();
}
- entries.clear();
size.getAndAdd(-removedSize);
return Pair.of(removedCount, removedSize);
}
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 5fc2da22b661e..b990e434df330 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
@@ -21,7 +21,9 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -55,17 +57,21 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
+import java.util.Queue;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -90,6 +96,8 @@
import org.apache.bookkeeper.client.api.LedgerEntries;
import org.apache.bookkeeper.client.api.LedgerMetadata;
import org.apache.bookkeeper.client.api.ReadHandle;
+import org.apache.bookkeeper.common.util.BoundedScheduledExecutorService;
+import org.apache.bookkeeper.common.util.OrderedScheduler;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.mledger.AsyncCallbacks;
import org.apache.bookkeeper.mledger.AsyncCallbacks.AddEntryCallback;
@@ -124,6 +132,7 @@
import org.apache.bookkeeper.mledger.util.Futures;
import org.apache.bookkeeper.test.MockedBookKeeperTestCase;
import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.pulsar.common.api.proto.CommandSubscribe.InitialPosition;
@@ -135,6 +144,7 @@
import org.apache.pulsar.metadata.api.extended.SessionEvent;
import org.apache.pulsar.metadata.impl.FaultInjectionMetadataStore;
import org.awaitility.Awaitility;
+import org.awaitility.reflect.WhiteboxImpl;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
@@ -3085,9 +3095,9 @@ public void testManagedLedgerWithCreateLedgerTimeOut() throws Exception {
latch.await(config.getMetadataOperationsTimeoutSeconds() + 2, TimeUnit.SECONDS);
assertEquals(response.get(), BKException.Code.TimeoutException);
- assertTrue(ctxHolder.get() instanceof AtomicBoolean);
- AtomicBoolean ledgerCreated = (AtomicBoolean) ctxHolder.get();
- assertFalse(ledgerCreated.get());
+ assertTrue(ctxHolder.get() instanceof CompletableFuture);
+ CompletableFuture ledgerCreateHook = (CompletableFuture) ctxHolder.get();
+ assertTrue(ledgerCreateHook.isCompletedExceptionally());
ledger.close();
}
@@ -4074,4 +4084,126 @@ public void operationFailed(MetaStoreException e) {
});
future.join();
}
+
+ @Test
+ public void testNonDurableCursorCreateForInactiveLedger() throws Exception {
+ String mlName = "testLedgerInfoMetaCorrectIfAddEntryTimeOut";
+ BookKeeper spyBookKeeper = spy(bkc);
+ ManagedLedgerFactoryImpl factory = new ManagedLedgerFactoryImpl(metadataStore, spyBookKeeper);
+ ManagedLedgerConfig config = new ManagedLedgerConfig();
+ config.setInactiveLedgerRollOverTime(10, TimeUnit.MILLISECONDS);
+ ManagedLedgerImpl ml = (ManagedLedgerImpl) factory.open(mlName, config);
+
+ MutableBoolean isRolledOver = new MutableBoolean(false);
+ retryStrategically((test) -> {
+ if (isRolledOver.booleanValue()) {
+ return true;
+ }
+ isRolledOver.setValue(ml.checkInactiveLedgerAndRollOver());
+ return isRolledOver.booleanValue();
+ }, 5, 1000);
+ assertTrue(isRolledOver.booleanValue());
+
+ Position Position = new PositionImpl(-1L, -1L);
+ assertNotNull(ml.newNonDurableCursor(Position));
+ }
+
+ /***
+ * When a ML tries to create a ledger, it will create a delay task to check if the ledger create request is timeout.
+ * But we should guarantee that the delay task should be canceled after the ledger create request responded.
+ */
+ @Test
+ public void testNoOrphanScheduledTasksAfterCloseML() throws Exception {
+ String mlName = UUID.randomUUID().toString();
+ ManagedLedgerFactoryImpl factory = new ManagedLedgerFactoryImpl(metadataStore, bkc);
+ ManagedLedgerConfig config = new ManagedLedgerConfig();
+ config.setMetadataOperationsTimeoutSeconds(3600);
+
+ // Calculate pending task count.
+ long pendingTaskCountBefore = calculatePendingTaskCount(factory.getScheduledExecutor());
+ // Trigger create & close ML 1000 times.
+ for (int i = 0; i < 1000; i++) {
+ ManagedLedger ml = factory.open(mlName, config);
+ ml.close();
+ }
+ // Verify there is no orphan scheduled task.
+ long pendingTaskCountAfter = calculatePendingTaskCount(factory.getScheduledExecutor());
+ // Maybe there are other components also appended scheduled tasks, so leave 100 tasks to avoid flaky.
+ assertTrue(pendingTaskCountAfter - pendingTaskCountBefore < 100);
+ }
+
+ /**
+ * Calculate how many pending tasks in {@link OrderedScheduler}
+ */
+ private long calculatePendingTaskCount(OrderedScheduler orderedScheduler) {
+ ExecutorService[] threads = WhiteboxImpl.getInternalState(orderedScheduler, "threads");
+ long taskCounter = 0;
+ for (ExecutorService thread : threads) {
+ BoundedScheduledExecutorService boundedScheduledExecutorService =
+ WhiteboxImpl.getInternalState(thread, "delegate");
+ BlockingQueue queue = WhiteboxImpl.getInternalState(boundedScheduledExecutorService, "queue");
+ for (Runnable r : queue) {
+ if (r instanceof FutureTask) {
+ FutureTask futureTask = (FutureTask) r;
+ if (!futureTask.isCancelled() && !futureTask.isDone()) {
+ taskCounter++;
+ }
+ } else {
+ taskCounter++;
+ }
+ }
+ }
+ return taskCounter;
+ }
+
+ @Test
+ public void testNoCleanupOffloadLedgerWhenMetadataExceptionHappens() throws Exception {
+ ManagedLedgerConfig config = spy(new ManagedLedgerConfig());
+ ManagedLedgerImpl ml = spy((ManagedLedgerImpl) factory.open("testNoCleanupOffloadLedger", config));
+
+ // mock the ledger offloader
+ LedgerOffloader ledgerOffloader = mock(NullLedgerOffloader.class);
+ when(config.getLedgerOffloader()).thenReturn(ledgerOffloader);
+ when(ledgerOffloader.getOffloadDriverName()).thenReturn("mock");
+
+ // There will have two put call to the metadata store, the first time is prepare the offload.
+ // And the second is the complete the offload. This case is testing when completing the offload,
+ // the metadata store meets an exception.
+ AtomicInteger metadataPutCallCount = new AtomicInteger(0);
+ metadataStore.failConditional(new MetadataStoreException("mock completion error"),
+ (key, value) -> key.equals(FaultInjectionMetadataStore.OperationType.PUT) &&
+ metadataPutCallCount.incrementAndGet() == 2);
+
+ // prepare the arguments for the offloadLoop method
+ CompletableFuture future = new CompletableFuture<>();
+ Queue ledgersToOffload = new LinkedList<>();
+ LedgerInfo ledgerInfo = LedgerInfo.getDefaultInstance().toBuilder().setLedgerId(1).setEntries(10).build();
+ ledgersToOffload.add(ledgerInfo);
+ PositionImpl firstUnoffloaded = new PositionImpl(1, 0);
+ Optional firstError = Optional.empty();
+
+ // mock the read handle to make the offload successful
+ CompletableFuture readHandle = new CompletableFuture<>();
+ readHandle.complete(mock(ReadHandle.class));
+ doReturn(readHandle).when(ml).getLedgerHandle(eq(ledgerInfo.getLedgerId()));
+ when(ledgerOffloader.offload(any(), any(), anyMap())).thenReturn(CompletableFuture.completedFuture(null));
+
+ ml.ledgers.put(ledgerInfo.getLedgerId(), ledgerInfo);
+
+ // do the offload
+ ml.offloadLoop(future, ledgersToOffload, firstUnoffloaded, firstError);
+
+ // waiting for the offload complete
+ try {
+ future.join();
+ fail("The offload should fail");
+ } catch (Exception e) {
+ // the offload should fail
+ assertTrue(e.getCause().getMessage().contains("mock completion error"));
+ }
+
+ // the ledger deletion shouldn't happen
+ verify(ledgerOffloader, times(0))
+ .deleteOffloaded(eq(ledgerInfo.getLedgerId()), any(), anyMap());
+ }
}
diff --git a/pom.xml b/pom.xml
index 8c8571597b84f..9eb54682d8cd3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
Pulsar
Pulsar is a distributed pub-sub messaging platform with a very
@@ -92,7 +92,7 @@ flexible messaging model and an intuitive client API.
UTF-8
UTF-8
- 2023-09-27T08:29:08Z
+ 2023-11-30T15:05:59Z
true
+ 1.1.10.5
4.1.12.1
5.1.0
- 4.1.94.Final
+ 4.1.100.Final
0.0.21.Final
- 9.4.51.v20230217
+ 9.4.53.v20231009
2.5.2
2.34
1.10.50
@@ -154,12 +154,12 @@ flexible messaging model and an intuitive client API.
2.18.0
1.75
1.0.6
- 1.0.2.3
+ 1.0.2.4
2.14.2
0.10.2
1.6.2
8.37
- 0.42.1
+ 0.43.3
true
0.5.0
3.19.6
@@ -175,9 +175,9 @@ flexible messaging model and an intuitive client API.
3.11.2
4.4.20
3.4.0
- 5.5.3
+ 5.18.0
1.12.262
- 1.10.2
+ 1.11.3
2.10.10
2.5.0
5.1.0
@@ -223,7 +223,7 @@ flexible messaging model and an intuitive client API.
0.9.1
2.1.0
3.24.2
- 1.18.26
+ 1.18.30
1.3.2
2.3.1
1.2.0
@@ -406,6 +406,12 @@ flexible messaging model and an intuitive client API.
io.dropwizard.metrics
metrics-graphite
${dropwizardmetrics.version}
+
+
+ com.rabbitmq
+ amqp-client
+
+
io.dropwizard.metrics
@@ -1423,7 +1429,7 @@ flexible messaging model and an intuitive client API.
- org.apache.pulsar
+ com.datastax.oss
buildtools
${project.version}
test
@@ -1677,7 +1683,7 @@ flexible messaging model and an intuitive client API.
**/ByteBufCodedOutputStream.java
**/ahc.properties
bin/proto/*
- conf/schema_example.conf
+ conf/schema_example.json
data/**
logs/**
**/circe/**
@@ -1802,7 +1808,7 @@ flexible messaging model and an intuitive client API.
**/requirements.txt
- conf/schema_example.conf
+ conf/schema_example.json
**/templates/*.tpl
diff --git a/pulsar-broker-auth-athenz/pom.xml b/pulsar-broker-auth-athenz/pom.xml
index ff4e78e1d4a9f..6ee5e862f9d6f 100644
--- a/pulsar-broker-auth-athenz/pom.xml
+++ b/pulsar-broker-auth-athenz/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
pulsar-broker-auth-athenz
diff --git a/pulsar-broker-auth-oidc/pom.xml b/pulsar-broker-auth-oidc/pom.xml
index 4b20bab7bbb6e..20cd6d40d7fb9 100644
--- a/pulsar-broker-auth-oidc/pom.xml
+++ b/pulsar-broker-auth-oidc/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
pulsar-broker-auth-oidc
diff --git a/pulsar-broker-auth-sasl/pom.xml b/pulsar-broker-auth-sasl/pom.xml
index fd8c193ce7bd6..0a9b75fff06cd 100644
--- a/pulsar-broker-auth-sasl/pom.xml
+++ b/pulsar-broker-auth-sasl/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
pulsar-broker-auth-sasl
diff --git a/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/ProxySaslAuthenticationTest.java b/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/ProxySaslAuthenticationTest.java
index 261efe680f862..f0e45aa734afb 100644
--- a/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/ProxySaslAuthenticationTest.java
+++ b/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/ProxySaslAuthenticationTest.java
@@ -49,6 +49,7 @@
import org.apache.pulsar.client.api.Schema;
import org.apache.pulsar.client.impl.auth.AuthenticationSasl;
import org.apache.pulsar.common.configuration.PulsarConfigurationLoader;
+import org.apache.pulsar.common.util.ObjectMapperFactory;
import org.apache.pulsar.proxy.server.ProxyConfiguration;
import org.apache.pulsar.proxy.server.ProxyService;
import org.slf4j.Logger;
@@ -193,15 +194,17 @@ protected void setup() throws Exception {
conf.setAuthenticationProviders(providers);
conf.setClusterName("test");
conf.setSuperUserRoles(ImmutableSet.of("client/" + localHostname + "@" + kdc.getRealm()));
-
- super.init();
-
- lookupUrl = new URI(pulsar.getBrokerServiceUrl());
-
// set admin auth, to verify admin web resources
Map clientSaslConfig = new HashMap<>();
clientSaslConfig.put("saslJaasClientSectionName", "PulsarClient");
clientSaslConfig.put("serverType", "broker");
+ conf.setBrokerClientAuthenticationPlugin(AuthenticationSasl.class.getName());
+ conf.setBrokerClientAuthenticationParameters(ObjectMapperFactory
+ .getMapper().getObjectMapper().writeValueAsString(clientSaslConfig));
+
+ super.init();
+
+ lookupUrl = new URI(pulsar.getBrokerServiceUrl());
log.info("set client jaas section name: PulsarClient");
admin = PulsarAdmin.builder()
.serviceHttpUrl(brokerUrl.toString())
diff --git a/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/SaslAuthenticateTest.java b/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/SaslAuthenticateTest.java
index 5cace2221dea8..230c2ad787de4 100644
--- a/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/SaslAuthenticateTest.java
+++ b/pulsar-broker-auth-sasl/src/test/java/org/apache/pulsar/broker/authentication/SaslAuthenticateTest.java
@@ -58,6 +58,7 @@
import org.apache.pulsar.client.impl.auth.AuthenticationSasl;
import org.apache.pulsar.common.api.AuthData;
import org.apache.pulsar.common.sasl.SaslConstants;
+import org.apache.pulsar.common.util.ObjectMapperFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
@@ -186,7 +187,12 @@ protected void setup() throws Exception {
conf.setAuthenticationProviders(providers);
conf.setClusterName("test");
conf.setSuperUserRoles(ImmutableSet.of("client" + "@" + kdc.getRealm()));
-
+ Map clientSaslConfig = new HashMap<>();
+ clientSaslConfig.put("saslJaasClientSectionName", "PulsarClient");
+ clientSaslConfig.put("serverType", "broker");
+ conf.setBrokerClientAuthenticationPlugin(AuthenticationSasl.class.getName());
+ conf.setBrokerClientAuthenticationParameters(ObjectMapperFactory
+ .getMapper().getObjectMapper().writeValueAsString(clientSaslConfig));
super.init();
lookupUrl = new URI(pulsar.getWebServiceAddress());
@@ -197,9 +203,6 @@ protected void setup() throws Exception {
.authentication(authSasl));
// set admin auth, to verify admin web resources
- Map clientSaslConfig = new HashMap<>();
- clientSaslConfig.put("saslJaasClientSectionName", "PulsarClient");
- clientSaslConfig.put("serverType", "broker");
log.info("set client jaas section name: PulsarClient");
admin = PulsarAdmin.builder()
.serviceHttpUrl(brokerUrl.toString())
diff --git a/pulsar-broker-common/pom.xml b/pulsar-broker-common/pom.xml
index c3796a96927e4..958a1d96cbd9e 100644
--- a/pulsar-broker-common/pom.xml
+++ b/pulsar-broker-common/pom.xml
@@ -26,7 +26,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
pulsar-broker-common
diff --git a/pulsar-broker-common/src/main/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMapping.java b/pulsar-broker-common/src/main/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMapping.java
index e9e350800b44e..983822f22941b 100644
--- a/pulsar-broker-common/src/main/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMapping.java
+++ b/pulsar-broker-common/src/main/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMapping.java
@@ -121,8 +121,6 @@ public synchronized void setConf(Configuration conf) {
store.registerListener(this::handleUpdates);
racksWithHost = bookieMappingCache.get(BOOKIE_INFO_ROOT_PATH).get()
.orElseGet(BookiesRackConfiguration::new);
- updateRacksWithHost(racksWithHost);
- watchAvailableBookies();
for (Map bookieMapping : racksWithHost.values()) {
for (String address : bookieMapping.keySet()) {
bookieAddressListLastTime.add(BookieId.parse(address));
@@ -132,6 +130,8 @@ public synchronized void setConf(Configuration conf) {
bookieAddressListLastTime);
}
}
+ updateRacksWithHost(racksWithHost);
+ watchAvailableBookies();
} catch (InterruptedException | ExecutionException | MetadataException e) {
throw new RuntimeException(METADATA_STORE_INSTANCE + " failed to init BookieId list");
}
@@ -245,6 +245,7 @@ private void handleUpdates(Notification n) {
bookieMappingCache.get(BOOKIE_INFO_ROOT_PATH)
.thenAccept(optVal -> {
+ Set bookieIdSet = new HashSet<>();
synchronized (this) {
LOG.info("Bookie rack info updated to {}. Notifying rackaware policy.", optVal);
this.updateRacksWithHost(optVal.orElseGet(BookiesRackConfiguration::new));
@@ -259,12 +260,12 @@ private void handleUpdates(Notification n) {
LOG.debug("Bookies with rack update from {} to {}", bookieAddressListLastTime,
bookieAddressList);
}
- Set bookieIdSet = new HashSet<>(bookieAddressList);
+ bookieIdSet.addAll(bookieAddressList);
bookieIdSet.addAll(bookieAddressListLastTime);
bookieAddressListLastTime = bookieAddressList;
- if (rackawarePolicy != null) {
- rackawarePolicy.onBookieRackChange(new ArrayList<>(bookieIdSet));
- }
+ }
+ if (rackawarePolicy != null) {
+ rackawarePolicy.onBookieRackChange(new ArrayList<>(bookieIdSet));
}
});
}
diff --git a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
index 5b6b3acad3fac..307943ea37523 100644
--- a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
+++ b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
@@ -2777,6 +2777,12 @@ The delayed message index time step(in seconds) in per bucket snapshot segment,
)
private long brokerServiceCompactionPhaseOneLoopTimeInSeconds = 30;
+ @FieldContext(
+ category = CATEGORY_SERVER,
+ doc = "Whether retain null-key message during topic compaction."
+ )
+ private boolean topicCompactionRetainNullKey = true;
+
@FieldContext(
category = CATEGORY_SERVER,
doc = "Interval between checks to see if cluster is migrated and marks topic migrated "
diff --git a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationDataSubscription.java b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationDataSubscription.java
index 69ef526012daa..9a7324a6d077a 100644
--- a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationDataSubscription.java
+++ b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationDataSubscription.java
@@ -71,4 +71,8 @@ public boolean hasSubscription() {
public String getSubscription() {
return subscription;
}
+
+ public AuthenticationDataSource getAuthData() {
+ return authData;
+ }
}
diff --git a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProvider.java b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProvider.java
index fa613245cfa27..fdab233a51098 100644
--- a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProvider.java
+++ b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProvider.java
@@ -35,6 +35,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
+import org.apache.pulsar.broker.authentication.AuthenticationDataSubscription;
import org.apache.pulsar.broker.resources.PulsarResources;
import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.common.naming.TopicName;
@@ -89,15 +90,18 @@ public void initialize(ServiceConfiguration conf, PulsarResources pulsarResource
@Override
public CompletableFuture isSuperUser(String role, AuthenticationDataSource authenticationData,
ServiceConfiguration serviceConfiguration) {
- Set roles = getRoles(authenticationData);
- if (roles.isEmpty()) {
- return CompletableFuture.completedFuture(false);
- }
+ // if superUser role contains in config, return true.
Set superUserRoles = serviceConfiguration.getSuperUserRoles();
if (superUserRoles.isEmpty()) {
return CompletableFuture.completedFuture(false);
}
-
+ if (role != null && superUserRoles.contains(role)) {
+ return CompletableFuture.completedFuture(true);
+ }
+ Set roles = getRoles(role, authenticationData);
+ if (roles.isEmpty()) {
+ return CompletableFuture.completedFuture(false);
+ }
return CompletableFuture.completedFuture(roles.stream().anyMatch(superUserRoles::contains));
}
@@ -109,7 +113,7 @@ public CompletableFuture validateTenantAdminAccess(String tenantName, S
if (isSuperUser) {
return CompletableFuture.completedFuture(true);
}
- Set roles = getRoles(authData);
+ Set roles = getRoles(role, authData);
if (roles.isEmpty()) {
return CompletableFuture.completedFuture(false);
}
@@ -140,7 +144,12 @@ public CompletableFuture validateTenantAdminAccess(String tenantName, S
});
}
- private Set getRoles(AuthenticationDataSource authData) {
+ private Set getRoles(String role, AuthenticationDataSource authData) {
+ if (authData == null || (authData instanceof AuthenticationDataSubscription
+ && ((AuthenticationDataSubscription) authData).getAuthData() == null)) {
+ return Collections.singleton(role);
+ }
+
String token = null;
if (authData.hasDataFromCommand()) {
@@ -174,7 +183,14 @@ private Set getRoles(AuthenticationDataSource authData) {
Jwt, Claims> jwt = parser.parseClaimsJwt(unsignedToken);
try {
- return new HashSet<>(Collections.singletonList(jwt.getBody().get(roleClaim, String.class)));
+ final String jwtRole = jwt.getBody().get(roleClaim, String.class);
+ if (jwtRole == null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Do not have corresponding claim in jwt token. claim={}", roleClaim);
+ }
+ return Collections.emptySet();
+ }
+ return new HashSet<>(Collections.singletonList(jwtRole));
} catch (RequiredTypeException requiredTypeException) {
try {
List list = jwt.getBody().get(roleClaim, List.class);
@@ -189,15 +205,21 @@ private Set getRoles(AuthenticationDataSource authData) {
return Collections.emptySet();
}
- public CompletableFuture authorize(AuthenticationDataSource authenticationData, Function> authorizeFunc) {
- Set roles = getRoles(authenticationData);
- if (roles.isEmpty()) {
- return CompletableFuture.completedFuture(false);
- }
- List> futures = new ArrayList<>(roles.size());
- roles.forEach(r -> futures.add(authorizeFunc.apply(r)));
- return FutureUtil.waitForAny(futures, ret -> (boolean) ret).thenApply(v -> v.isPresent());
+ public CompletableFuture authorize(String role, AuthenticationDataSource authenticationData,
+ Function> authorizeFunc) {
+ return isSuperUser(role, authenticationData, conf)
+ .thenCompose(superUser -> {
+ if (superUser) {
+ return CompletableFuture.completedFuture(true);
+ }
+ Set roles = getRoles(role, authenticationData);
+ if (roles.isEmpty()) {
+ return CompletableFuture.completedFuture(false);
+ }
+ List> futures = new ArrayList<>(roles.size());
+ roles.forEach(r -> futures.add(authorizeFunc.apply(r)));
+ return FutureUtil.waitForAny(futures, ret -> (boolean) ret).thenApply(v -> v.isPresent());
+ });
}
/**
@@ -209,7 +231,7 @@ public CompletableFuture authorize(AuthenticationDataSource authenticat
@Override
public CompletableFuture canProduceAsync(TopicName topicName, String role,
AuthenticationDataSource authenticationData) {
- return authorize(authenticationData, r -> super.canProduceAsync(topicName, r, authenticationData));
+ return authorize(role, authenticationData, r -> super.canProduceAsync(topicName, r, authenticationData));
}
/**
@@ -224,7 +246,7 @@ public CompletableFuture canProduceAsync(TopicName topicName, String ro
public CompletableFuture canConsumeAsync(TopicName topicName, String role,
AuthenticationDataSource authenticationData,
String subscription) {
- return authorize(authenticationData, r -> super.canConsumeAsync(topicName, r, authenticationData,
+ return authorize(role, authenticationData, r -> super.canConsumeAsync(topicName, r, authenticationData,
subscription));
}
@@ -241,25 +263,27 @@ public CompletableFuture canConsumeAsync(TopicName topicName, String ro
@Override
public CompletableFuture canLookupAsync(TopicName topicName, String role,
AuthenticationDataSource authenticationData) {
- return authorize(authenticationData, r -> super.canLookupAsync(topicName, r, authenticationData));
+ return authorize(role, authenticationData, r -> super.canLookupAsync(topicName, r, authenticationData));
}
@Override
public CompletableFuture allowFunctionOpsAsync(NamespaceName namespaceName, String role,
AuthenticationDataSource authenticationData) {
- return authorize(authenticationData, r -> super.allowFunctionOpsAsync(namespaceName, r, authenticationData));
+ return authorize(role, authenticationData,
+ r -> super.allowFunctionOpsAsync(namespaceName, r, authenticationData));
}
@Override
public CompletableFuture allowSourceOpsAsync(NamespaceName namespaceName, String role,
AuthenticationDataSource authenticationData) {
- return authorize(authenticationData, r -> super.allowSourceOpsAsync(namespaceName, r, authenticationData));
+ return authorize(role, authenticationData,
+ r -> super.allowSourceOpsAsync(namespaceName, r, authenticationData));
}
@Override
public CompletableFuture allowSinkOpsAsync(NamespaceName namespaceName, String role,
AuthenticationDataSource authenticationData) {
- return authorize(authenticationData, r -> super.allowSinkOpsAsync(namespaceName, r, authenticationData));
+ return authorize(role, authenticationData, r -> super.allowSinkOpsAsync(namespaceName, r, authenticationData));
}
@Override
@@ -267,7 +291,7 @@ public CompletableFuture allowTenantOperationAsync(String tenantName,
String role,
TenantOperation operation,
AuthenticationDataSource authData) {
- return authorize(authData, r -> super.allowTenantOperationAsync(tenantName, r, operation, authData));
+ return authorize(role, authData, r -> super.allowTenantOperationAsync(tenantName, r, operation, authData));
}
@Override
@@ -275,7 +299,8 @@ public CompletableFuture allowNamespaceOperationAsync(NamespaceName nam
String role,
NamespaceOperation operation,
AuthenticationDataSource authData) {
- return authorize(authData, r -> super.allowNamespaceOperationAsync(namespaceName, r, operation, authData));
+ return authorize(role, authData,
+ r -> super.allowNamespaceOperationAsync(namespaceName, r, operation, authData));
}
@Override
@@ -284,8 +309,8 @@ public CompletableFuture allowNamespacePolicyOperationAsync(NamespaceNa
PolicyOperation operation,
String role,
AuthenticationDataSource authData) {
- return authorize(authData, r -> super.allowNamespacePolicyOperationAsync(namespaceName, policy, operation, r,
- authData));
+ return authorize(role, authData,
+ r -> super.allowNamespacePolicyOperationAsync(namespaceName, policy, operation, r, authData));
}
@Override
@@ -293,7 +318,7 @@ public CompletableFuture allowTopicOperationAsync(TopicName topicName,
String role,
TopicOperation operation,
AuthenticationDataSource authData) {
- return authorize(authData, r -> super.allowTopicOperationAsync(topicName, r, operation, authData));
+ return authorize(role, authData, r -> super.allowTopicOperationAsync(topicName, r, operation, authData));
}
@Override
@@ -302,7 +327,7 @@ public CompletableFuture allowTopicPolicyOperationAsync(TopicName topic
PolicyName policyName,
PolicyOperation policyOperation,
AuthenticationDataSource authData) {
- return authorize(authData, r -> super.allowTopicPolicyOperationAsync(topicName, r, policyName, policyOperation,
- authData));
+ return authorize(role, authData,
+ r -> super.allowTopicPolicyOperationAsync(topicName, r, policyName, policyOperation, authData));
}
}
diff --git a/pulsar-broker-common/src/test/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMappingTest.java b/pulsar-broker-common/src/test/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMappingTest.java
index d7be7dabd0db1..9cd8160444249 100644
--- a/pulsar-broker-common/src/test/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMappingTest.java
+++ b/pulsar-broker-common/src/test/java/org/apache/pulsar/bookie/rackawareness/BookieRackAffinityMappingTest.java
@@ -21,6 +21,7 @@
import static org.apache.bookkeeper.feature.SettableFeatureProvider.DISABLE_ALL;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -28,6 +29,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -35,7 +37,11 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import lombok.Cleanup;
import org.apache.bookkeeper.client.DefaultBookieAddressResolver;
import org.apache.bookkeeper.client.EnsemblePlacementPolicy;
import org.apache.bookkeeper.client.RackawareEnsemblePlacementPolicy;
@@ -46,6 +52,7 @@
import org.apache.bookkeeper.net.BookieId;
import org.apache.bookkeeper.net.BookieNode;
import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.net.NetworkTopology;
import org.apache.bookkeeper.proto.BookieAddressResolver;
import org.apache.bookkeeper.stats.NullStatsLogger;
import org.apache.bookkeeper.stats.StatsLogger;
@@ -55,6 +62,8 @@
import org.apache.pulsar.metadata.api.MetadataStore;
import org.apache.pulsar.metadata.api.MetadataStoreConfig;
import org.apache.pulsar.metadata.api.MetadataStoreFactory;
+import org.apache.pulsar.metadata.api.Notification;
+import org.apache.pulsar.metadata.api.NotificationType;
import org.apache.pulsar.metadata.bookkeeper.BookieServiceInfoSerde;
import org.apache.pulsar.metadata.bookkeeper.PulsarRegistrationClient;
import org.awaitility.Awaitility;
@@ -254,6 +263,7 @@ public void testWithPulsarRegistrationClient() throws Exception {
bkClientConf.getTimeoutTimerNumTicks());
RackawareEnsemblePlacementPolicy repp = new RackawareEnsemblePlacementPolicy();
+ mapping.registerRackChangeListener(repp);
Class> clazz1 = Class.forName("org.apache.bookkeeper.client.TopologyAwareEnsemblePlacementPolicy");
Field field1 = clazz1.getDeclaredField("knownBookies");
field1.setAccessible(true);
@@ -323,6 +333,81 @@ public void testWithPulsarRegistrationClient() throws Exception {
assertEquals(knownBookies.get(BOOKIE2.toBookieId()).getNetworkLocation(), "/rack1");
assertEquals(knownBookies.get(BOOKIE3.toBookieId()).getNetworkLocation(), "/default-rack");
+ //remove bookie2 rack, the bookie2 rack should be /default-rack
+ data = "{\"group1\": {\"" + BOOKIE1
+ + "\": {\"rack\": \"/rack0\", \"hostname\": \"bookie1.example.com\"}}}";
+ store.put(BookieRackAffinityMapping.BOOKIE_INFO_ROOT_PATH, data.getBytes(), Optional.empty()).join();
+ Awaitility.await().atMost(30, TimeUnit.SECONDS).until(() -> ((BookiesRackConfiguration)field.get(mapping)).get("group1").size() == 1);
+
+ racks = mapping
+ .resolve(Lists.newArrayList(BOOKIE1.getHostName(), BOOKIE2.getHostName(), BOOKIE3.getHostName()))
+ .stream().filter(Objects::nonNull).toList();
+ assertEquals(racks.size(), 1);
+ assertEquals(racks.get(0), "/rack0");
+ assertEquals(knownBookies.size(), 3);
+ assertEquals(knownBookies.get(BOOKIE1.toBookieId()).getNetworkLocation(), "/rack0");
+ assertEquals(knownBookies.get(BOOKIE2.toBookieId()).getNetworkLocation(), "/default-rack");
+ assertEquals(knownBookies.get(BOOKIE3.toBookieId()).getNetworkLocation(), "/default-rack");
+
timer.stop();
}
+
+ @Test
+ public void testNoDeadlockWithRackawarePolicy() throws Exception {
+ ClientConfiguration bkClientConf = new ClientConfiguration();
+ bkClientConf.setProperty(BookieRackAffinityMapping.METADATA_STORE_INSTANCE, store);
+
+ BookieRackAffinityMapping mapping = new BookieRackAffinityMapping();
+ mapping.setBookieAddressResolver(BookieSocketAddress.LEGACY_BOOKIEID_RESOLVER);
+ mapping.setConf(bkClientConf);
+
+ @Cleanup("stop")
+ HashedWheelTimer timer = new HashedWheelTimer(new ThreadFactoryBuilder().setNameFormat("TestTimer-%d").build(),
+ bkClientConf.getTimeoutTimerTickDurationMs(), TimeUnit.MILLISECONDS,
+ bkClientConf.getTimeoutTimerNumTicks());
+
+ RackawareEnsemblePlacementPolicy repp = new RackawareEnsemblePlacementPolicy();
+ repp.initialize(bkClientConf, Optional.of(mapping), timer,
+ DISABLE_ALL, NullStatsLogger.INSTANCE, BookieSocketAddress.LEGACY_BOOKIEID_RESOLVER);
+ repp.withDefaultRack(NetworkTopology.DEFAULT_REGION_AND_RACK);
+
+ mapping.registerRackChangeListener(repp);
+
+ @Cleanup("shutdownNow")
+ ExecutorService executor1 = Executors.newSingleThreadExecutor();
+ @Cleanup("shutdownNow")
+ ExecutorService executor2 = Executors.newSingleThreadExecutor();
+
+ CountDownLatch count = new CountDownLatch(2);
+
+ executor1.submit(() -> {
+ try {
+ Method handleUpdates =
+ BookieRackAffinityMapping.class.getDeclaredMethod("handleUpdates", Notification.class);
+ handleUpdates.setAccessible(true);
+ Notification n =
+ new Notification(NotificationType.Modified, BookieRackAffinityMapping.BOOKIE_INFO_ROOT_PATH);
+ long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() - start < 2_000) {
+ handleUpdates.invoke(mapping, n);
+ }
+ count.countDown();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ executor2.submit(() -> {
+ Set writableBookies = new HashSet<>();
+ writableBookies.add(BOOKIE1.toBookieId());
+ long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() - start < 2_000) {
+ repp.onClusterChanged(writableBookies, Collections.emptySet());
+ repp.onClusterChanged(Collections.emptySet(), Collections.emptySet());
+ }
+ count.countDown();
+ });
+
+ assertTrue(count.await(3, TimeUnit.SECONDS));
+ }
}
diff --git a/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProviderTest.java b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProviderTest.java
index 7e329d14307c7..ed9626dffe23f 100644
--- a/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProviderTest.java
+++ b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authorization/MultiRolesTokenAuthorizationProviderTest.java
@@ -24,13 +24,16 @@
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Properties;
+import java.util.function.Function;
+import lombok.Cleanup;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
+import org.apache.pulsar.broker.authentication.AuthenticationDataSubscription;
import org.apache.pulsar.broker.authentication.utils.AuthTokenUtils;
import org.apache.pulsar.broker.resources.PulsarResources;
import org.testng.annotations.Test;
-
import javax.crypto.SecretKey;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class MultiRolesTokenAuthorizationProviderTest {
@@ -43,6 +46,8 @@ public void testMultiRolesAuthz() throws Exception {
String token = Jwts.builder().claim("sub", new String[]{userA, userB}).signWith(secretKey).compact();
MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ ServiceConfiguration conf = new ServiceConfiguration();
+ provider.initialize(conf, mock(PulsarResources.class));
AuthenticationDataSource ads = new AuthenticationDataSource() {
@Override
@@ -60,18 +65,18 @@ public String getHttpHeader(String name) {
}
};
- assertTrue(provider.authorize(ads, role -> {
+ assertTrue(provider.authorize("test", ads, role -> {
if (role.equals(userB)) {
return CompletableFuture.completedFuture(true); // only userB has permission
}
return CompletableFuture.completedFuture(false);
}).get());
- assertTrue(provider.authorize(ads, role -> {
+ assertTrue(provider.authorize("test", ads, role -> {
return CompletableFuture.completedFuture(true); // all users has permission
}).get());
- assertFalse(provider.authorize(ads, role -> {
+ assertFalse(provider.authorize("test", ads, role -> {
return CompletableFuture.completedFuture(false); // all users has no permission
}).get());
}
@@ -82,6 +87,8 @@ public void testMultiRolesAuthzWithEmptyRoles() throws Exception {
String token = Jwts.builder().claim("sub", new String[]{}).signWith(secretKey).compact();
MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ ServiceConfiguration conf = new ServiceConfiguration();
+ provider.initialize(conf, mock(PulsarResources.class));
AuthenticationDataSource ads = new AuthenticationDataSource() {
@Override
@@ -99,7 +106,7 @@ public String getHttpHeader(String name) {
}
};
- assertFalse(provider.authorize(ads, role -> CompletableFuture.completedFuture(false)).get());
+ assertFalse(provider.authorize("test", ads, role -> CompletableFuture.completedFuture(false)).get());
}
@Test
@@ -109,6 +116,8 @@ public void testMultiRolesAuthzWithSingleRole() throws Exception {
String token = Jwts.builder().claim("sub", testRole).signWith(secretKey).compact();
MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ ServiceConfiguration conf = new ServiceConfiguration();
+ provider.initialize(conf, mock(PulsarResources.class));
AuthenticationDataSource ads = new AuthenticationDataSource() {
@Override
@@ -126,7 +135,7 @@ public String getHttpHeader(String name) {
}
};
- assertTrue(provider.authorize(ads, role -> {
+ assertTrue(provider.authorize("test", ads, role -> {
if (role.equals(testRole)) {
return CompletableFuture.completedFuture(true);
}
@@ -134,11 +143,66 @@ public String getHttpHeader(String name) {
}).get());
}
+ @Test
+ public void testMultiRolesAuthzWithoutClaim() throws Exception {
+ final SecretKey secretKey = AuthTokenUtils.createSecretKey(SignatureAlgorithm.HS256);
+ final String testRole = "test-role";
+ // broker will use "sub" as the claim by default.
+ final String token = Jwts.builder()
+ .claim("whatever", testRole).signWith(secretKey).compact();
+ ServiceConfiguration conf = new ServiceConfiguration();
+ final MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ provider.initialize(conf, mock(PulsarResources.class));
+ final AuthenticationDataSource ads = new AuthenticationDataSource() {
+ @Override
+ public boolean hasDataFromHttp() {
+ return true;
+ }
+
+ @Override
+ public String getHttpHeader(String name) {
+ if (name.equals("Authorization")) {
+ return "Bearer " + token;
+ } else {
+ throw new IllegalArgumentException("Wrong HTTP header");
+ }
+ }
+ };
+
+ assertFalse(provider.authorize("test", ads, role -> {
+ if (role == null) {
+ throw new IllegalStateException("We should avoid pass null to sub providers");
+ }
+ return CompletableFuture.completedFuture(role.equals(testRole));
+ }).get());
+ }
+
+ @Test
+ public void testMultiRolesAuthzWithAnonymousUser() throws Exception {
+ @Cleanup
+ MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ ServiceConfiguration conf = new ServiceConfiguration();
+
+ provider.initialize(conf, mock(PulsarResources.class));
+
+ Function> authorizeFunc = (String role) -> {
+ if (role.equals("test-role")) {
+ return CompletableFuture.completedFuture(true);
+ }
+ return CompletableFuture.completedFuture(false);
+ };
+ assertTrue(provider.authorize("test-role", null, authorizeFunc).get());
+ assertFalse(provider.authorize("test-role-x", null, authorizeFunc).get());
+ assertTrue(provider.authorize("test-role", new AuthenticationDataSubscription(null, "test-sub"), authorizeFunc).get());
+ }
+
@Test
public void testMultiRolesNotFailNonJWT() throws Exception {
String token = "a-non-jwt-token";
MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ ServiceConfiguration conf = new ServiceConfiguration();
+ provider.initialize(conf, mock(PulsarResources.class));
AuthenticationDataSource ads = new AuthenticationDataSource() {
@Override
@@ -156,7 +220,7 @@ public String getHttpHeader(String name) {
}
};
- assertFalse(provider.authorize(ads, role -> CompletableFuture.completedFuture(false)).get());
+ assertFalse(provider.authorize("test", ads, role -> CompletableFuture.completedFuture(false)).get());
}
@Test
@@ -191,11 +255,51 @@ public String getHttpHeader(String name) {
}
};
- assertTrue(provider.authorize(ads, role -> {
+ assertTrue(provider.authorize("test", ads, role -> {
if (role.equals(testRole)) {
return CompletableFuture.completedFuture(true);
}
return CompletableFuture.completedFuture(false);
}).get());
}
+
+ @Test
+ public void testMultiRolesAuthzWithSuperUser() throws Exception {
+ SecretKey secretKey = AuthTokenUtils.createSecretKey(SignatureAlgorithm.HS256);
+ String testAdminRole = "admin";
+ String token = Jwts.builder().claim("sub", testAdminRole).signWith(secretKey).compact();
+
+ ServiceConfiguration conf = new ServiceConfiguration();
+ conf.setSuperUserRoles(Set.of(testAdminRole));
+
+ MultiRolesTokenAuthorizationProvider provider = new MultiRolesTokenAuthorizationProvider();
+ provider.initialize(conf, mock(PulsarResources.class));
+
+ AuthenticationDataSource ads = new AuthenticationDataSource() {
+ @Override
+ public boolean hasDataFromHttp() {
+ return true;
+ }
+
+ @Override
+ public String getHttpHeader(String name) {
+ if (name.equals("Authorization")) {
+ return "Bearer " + token;
+ } else {
+ throw new IllegalArgumentException("Wrong HTTP header");
+ }
+ }
+ };
+
+ assertTrue(provider.isSuperUser(testAdminRole, ads, conf).get());
+ Function> authorizeFunc = (String role) -> {
+ if (role.equals("admin1")) {
+ return CompletableFuture.completedFuture(true);
+ }
+ return CompletableFuture.completedFuture(false);
+ };
+ assertTrue(provider.authorize(testAdminRole, ads, (String role) -> CompletableFuture.completedFuture(false)).get());
+ assertTrue(provider.authorize("admin1", null, authorizeFunc).get());
+ assertFalse(provider.authorize("admin2", null, authorizeFunc).get());
+ }
}
diff --git a/pulsar-broker/pom.xml b/pulsar-broker/pom.xml
index 2be655f64138f..7915044a891f7 100644
--- a/pulsar-broker/pom.xml
+++ b/pulsar-broker/pom.xml
@@ -25,7 +25,7 @@
com.datastax.oss
pulsar
- 3.1.1
+ 3.1.2
..
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/BookKeeperClientFactoryImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/BookKeeperClientFactoryImpl.java
index 0ecca75595603..e5293cee24e4a 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/BookKeeperClientFactoryImpl.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/BookKeeperClientFactoryImpl.java
@@ -219,7 +219,7 @@ static void setDefaultEnsemblePlacementPolicy(
}
}
- private void setEnsemblePlacementPolicy(ClientConfiguration bkConf, ServiceConfiguration conf, MetadataStore store,
+ static void setEnsemblePlacementPolicy(ClientConfiguration bkConf, ServiceConfiguration conf, MetadataStore store,
Class extends EnsemblePlacementPolicy> policyClass) {
bkConf.setEnsemblePlacementPolicy(policyClass);
bkConf.setProperty(BookieRackAffinityMapping.METADATA_STORE_INSTANCE, store);
@@ -227,6 +227,9 @@ private void setEnsemblePlacementPolicy(ClientConfiguration bkConf, ServiceConfi
bkConf.setProperty(REPP_DNS_RESOLVER_CLASS, conf.getProperties().getProperty(REPP_DNS_RESOLVER_CLASS,
BookieRackAffinityMapping.class.getName()));
+ bkConf.setMinNumRacksPerWriteQuorum(conf.getBookkeeperClientMinNumRacksPerWriteQuorum());
+ bkConf.setEnforceMinNumRacksPerWriteQuorum(conf.isBookkeeperClientEnforceMinNumRacksPerWriteQuorum());
+
bkConf.setProperty(NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY,
conf.getProperties().getProperty(
NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY,
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/PulsarService.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/PulsarService.java
index 4ffb5b77d5424..a366cf25aa023 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/PulsarService.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/PulsarService.java
@@ -640,14 +640,18 @@ private synchronized void resetMetricsServlet() {
}
private CompletableFuture addTimeoutHandling(CompletableFuture future) {
+ long brokerShutdownTimeoutMs = getConfiguration().getBrokerShutdownTimeoutMs();
+ if (brokerShutdownTimeoutMs <= 0) {
+ return future;
+ }
ScheduledExecutorService shutdownExecutor = Executors.newSingleThreadScheduledExecutor(
new ExecutorProvider.ExtendedThreadFactory(getClass().getSimpleName() + "-shutdown"));
FutureUtil.addTimeoutHandling(future,
- Duration.ofMillis(Math.max(1L, getConfiguration().getBrokerShutdownTimeoutMs())),
+ Duration.ofMillis(brokerShutdownTimeoutMs),
shutdownExecutor, () -> FutureUtil.createTimeoutException("Timeout in close", getClass(), "close"));
future.handle((v, t) -> {
- if (t != null && getConfiguration().getBrokerShutdownTimeoutMs() > 0) {
- LOG.info("Shutdown timed out after {} ms", getConfiguration().getBrokerShutdownTimeoutMs());
+ if (t instanceof TimeoutException) {
+ LOG.info("Shutdown timed out after {} ms", brokerShutdownTimeoutMs);
LOG.info(ThreadDumpUtil.buildThreadDiagnosticString());
}
// shutdown the shutdown executor
@@ -1176,7 +1180,7 @@ protected void startLeaderElectionService() {
protected void acquireSLANamespace() {
try {
// Namespace not created hence no need to unload it
- NamespaceName nsName = NamespaceService.getSLAMonitorNamespace(getAdvertisedAddress(), config);
+ NamespaceName nsName = NamespaceService.getSLAMonitorNamespace(getLookupServiceAddress(), config);
if (!this.pulsarResources.getNamespaceResources().namespaceExists(nsName)) {
LOG.info("SLA Namespace = {} doesn't exist.", nsName);
return;
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java
index 9c5813cbb1777..d88988ec349b5 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java
@@ -667,7 +667,9 @@ protected CompletableFuture internalSetNamespaceReplicationClusters(List validatePoliciesReadOnlyAccessAsync())
.thenApply(__ -> {
- checkNotNull(clusterIds, "ClusterIds should not be null");
+ if (CollectionUtils.isEmpty(clusterIds)) {
+ throw new RestException(Status.PRECONDITION_FAILED, "ClusterIds should not be null or empty");
+ }
if (!namespaceName.isGlobal() && !(clusterIds.size() == 1
&& clusterIds.get(0).equals(pulsar().getConfiguration().getClusterName()))) {
throw new RestException(Status.PRECONDITION_FAILED,
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java
index 682eb7b12e97e..aeaad3e44243f 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/PersistentTopicsBase.java
@@ -3349,6 +3349,9 @@ protected CompletableFuture internalSetReplicationClusters(List cl
return validateTopicPolicyOperationAsync(topicName, PolicyName.REPLICATION, PolicyOperation.WRITE)
.thenCompose(__ -> validatePoliciesReadOnlyAccessAsync())
.thenCompose(__ -> {
+ if (CollectionUtils.isEmpty(clusterIds)) {
+ throw new RestException(Status.PRECONDITION_FAILED, "ClusterIds should not be null or empty");
+ }
Set replicationClusters = Sets.newHashSet(clusterIds);
if (replicationClusters.contains("global")) {
throw new RestException(Status.PRECONDITION_FAILED,
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/SchemasResourceBase.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/SchemasResourceBase.java
index 0bab772044a6d..454b8f0fac82c 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/SchemasResourceBase.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/SchemasResourceBase.java
@@ -34,6 +34,7 @@
import org.apache.pulsar.broker.service.schema.SchemaRegistry.SchemaAndMetadata;
import org.apache.pulsar.broker.service.schema.SchemaRegistryService;
import org.apache.pulsar.broker.web.RestException;
+import org.apache.pulsar.client.impl.schema.SchemaUtils;
import org.apache.pulsar.client.internal.DefaultImplementation;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.policies.data.SchemaCompatibilityStrategy;
@@ -146,8 +147,13 @@ public CompletableFuture> testCompati
.thenCompose(__ -> getSchemaCompatibilityStrategyAsync())
.thenCompose(strategy -> {
String schemaId = getSchemaId();
+ final SchemaType schemaType = SchemaType.valueOf(payload.getType());
+ byte[] data = payload.getSchema().getBytes(StandardCharsets.UTF_8);
+ if (schemaType.getValue() == SchemaType.KEY_VALUE.getValue()) {
+ data = SchemaUtils.convertKeyValueDataStringToSchemaInfoSchema(data);
+ }
return pulsar().getSchemaRegistryService().isCompatible(schemaId,
- SchemaData.builder().data(payload.getSchema().getBytes(StandardCharsets.UTF_8))
+ SchemaData.builder().data(data)
.isDeleted(false)
.timestamp(clock.millis()).type(SchemaType.valueOf(payload.getType()))
.user(defaultIfEmpty(clientAppId(), ""))
@@ -161,10 +167,14 @@ public CompletableFuture getVersionBySchemaAsync(PostSchemaPayload payload
return validateOwnershipAndOperationAsync(authoritative, TopicOperation.GET_METADATA)
.thenCompose(__ -> {
String schemaId = getSchemaId();
+ final SchemaType schemaType = SchemaType.valueOf(payload.getType());
+ byte[] data = payload.getSchema().getBytes(StandardCharsets.UTF_8);
+ if (schemaType.getValue() == SchemaType.KEY_VALUE.getValue()) {
+ data = SchemaUtils.convertKeyValueDataStringToSchemaInfoSchema(data);
+ }
return pulsar().getSchemaRegistryService()
.findSchemaVersion(schemaId,
- SchemaData.builder().data(payload.getSchema().getBytes(StandardCharsets.UTF_8))
- .isDeleted(false).timestamp(clock.millis())
+ SchemaData.builder().data(data).isDeleted(false).timestamp(clock.millis())
.type(SchemaType.valueOf(payload.getType()))
.user(defaultIfEmpty(clientAppId(), ""))
.props(payload.getProperties()).build());
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java
index 3921334cff30a..470cdc3e74ba1 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TransactionsBase.java
@@ -501,11 +501,13 @@ protected CompletableFuture getExistingPersistentTopicAsync(boo
CompletableFuture> topicFuture = pulsar().getBrokerService()
.getTopics().get(topicName.toString());
if (topicFuture == null) {
- return FutureUtil.failedFuture(new RestException(NOT_FOUND, "Topic not found"));
+ return FutureUtil.failedFuture(new RestException(NOT_FOUND,
+ String.format("Topic not found %s", topicName.toString())));
}
return topicFuture.thenCompose(optionalTopic -> {
if (!optionalTopic.isPresent()) {
- return FutureUtil.failedFuture(new RestException(NOT_FOUND, "Topic not found"));
+ return FutureUtil.failedFuture(new RestException(NOT_FOUND,
+ String.format("Topic not found %s", topicName.toString())));
}
return CompletableFuture.completedFuture((PersistentTopic) optionalTopic.get());
});
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v1/NonPersistentTopics.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v1/NonPersistentTopics.java
index 0d857f2211f41..1c1dd74719641 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v1/NonPersistentTopics.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v1/NonPersistentTopics.java
@@ -284,11 +284,12 @@ public List getListFromBundle(@PathParam("property") String property, @P
}
}
- private Topic getTopicReference(TopicName topicName) {
+ private Topic getTopicReference(final TopicName topicName) {
try {
return pulsar().getBrokerService().getTopicIfExists(topicName.toString())
.get(config().getMetadataStoreOperationTimeoutSeconds(), TimeUnit.SECONDS)
- .orElseThrow(() -> new RestException(Status.NOT_FOUND, "Topic not found"));
+ .orElseThrow(() -> new RestException(Status.NOT_FOUND,
+ String.format("Topic not found %s", topicName.toString())));
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
} catch (InterruptedException | TimeoutException e) {
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java
index c360eeabb5838..386b9749ef959 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java
@@ -233,7 +233,8 @@ public void getPartitionedStats(
getPartitionedTopicMetadataAsync(topicName,
authoritative, false).thenAccept(partitionMetadata -> {
if (partitionMetadata.partitions == 0) {
- asyncResponse.resume(new RestException(Status.NOT_FOUND, "Partitioned Topic not found"));
+ asyncResponse.resume(new RestException(Status.NOT_FOUND,
+ String.format("Partitioned topic not found %s", topicName.toString())));
return;
}
NonPersistentPartitionedTopicStatsImpl stats =
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/BucketDelayedDeliveryTrackerFactory.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/BucketDelayedDeliveryTrackerFactory.java
index 6a00bfd199584..33076fd51a8e9 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/BucketDelayedDeliveryTrackerFactory.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/BucketDelayedDeliveryTrackerFactory.java
@@ -27,6 +27,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.bookkeeper.mledger.ManagedCursor;
+import org.apache.commons.collections4.MapUtils;
import org.apache.pulsar.broker.PulsarService;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.delayed.bucket.BookkeeperBucketSnapshotStorage;
@@ -85,6 +86,9 @@ public DelayedDeliveryTracker newTracker(PersistentDispatcherMultipleConsumers d
*/
public CompletableFuture cleanResidualSnapshots(ManagedCursor cursor) {
Map cursorProperties = cursor.getCursorProperties();
+ if (MapUtils.isEmpty(cursorProperties)) {
+ return CompletableFuture.completedFuture(null);
+ }
List> futures = new ArrayList<>();
FutureUtil.Sequencer sequencer = FutureUtil.Sequencer.create();
cursorProperties.forEach((k, v) -> {
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/bucket/BucketDelayedDeliveryTracker.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/bucket/BucketDelayedDeliveryTracker.java
index 67a7de1f01339..f98c9e000f150 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/bucket/BucketDelayedDeliveryTracker.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/bucket/BucketDelayedDeliveryTracker.java
@@ -50,6 +50,7 @@
import org.apache.bookkeeper.mledger.ManagedCursor;
import org.apache.bookkeeper.mledger.impl.PositionImpl;
import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.mutable.MutableLong;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.pulsar.broker.delayed.AbstractDelayedDeliveryTracker;
@@ -137,9 +138,15 @@ public BucketDelayedDeliveryTracker(PersistentDispatcherMultipleConsumers dispat
private synchronized long recoverBucketSnapshot() throws RuntimeException {
ManagedCursor cursor = this.lastMutableBucket.getCursor();
+ Map cursorProperties = cursor.getCursorProperties();
+ if (MapUtils.isEmpty(cursorProperties)) {
+ log.info("[{}] Recover delayed message index bucket snapshot finish, don't find bucket snapshot",
+ dispatcher.getName());
+ return 0;
+ }
FutureUtil.Sequencer sequencer = this.lastMutableBucket.getSequencer();
Map, ImmutableBucket> toBeDeletedBucketMap = new HashMap<>();
- cursor.getCursorProperties().keySet().forEach(key -> {
+ cursorProperties.keySet().forEach(key -> {
if (key.startsWith(DELAYED_BUCKET_KEY_PREFIX)) {
String[] keys = key.split(DELIMITER);
checkArgument(keys.length == 3);
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/LoadData.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/LoadData.java
index a632a47f05116..c1fe2a4930c34 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/LoadData.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/LoadData.java
@@ -64,7 +64,7 @@ public Map getBundleData() {
public Map getBundleDataForLoadShedding() {
return bundleData.entrySet().stream()
- .filter(e -> !NamespaceService.filterNamespaceForShedding(
+ .filter(e -> !NamespaceService.isSLAOrHeartbeatNamespace(
NamespaceBundle.getBundleNamespace(e.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/ExtensibleLoadManagerImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/ExtensibleLoadManagerImpl.java
index cba499eb8eedb..d3119365ddfea 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/ExtensibleLoadManagerImpl.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/ExtensibleLoadManagerImpl.java
@@ -44,6 +44,7 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.pulsar.broker.PulsarServerException;
import org.apache.pulsar.broker.PulsarService;
import org.apache.pulsar.broker.ServiceConfiguration;
@@ -86,6 +87,7 @@
import org.apache.pulsar.broker.loadbalance.impl.LoadManagerShared;
import org.apache.pulsar.broker.loadbalance.impl.SimpleResourceAllocationPolicies;
import org.apache.pulsar.broker.namespace.NamespaceEphemeralData;
+import org.apache.pulsar.broker.namespace.NamespaceService;
import org.apache.pulsar.client.admin.PulsarAdminException;
import org.apache.pulsar.common.naming.NamespaceBundle;
import org.apache.pulsar.common.naming.NamespaceBundleSplitAlgorithm;
@@ -95,7 +97,6 @@
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.stats.Metrics;
import org.apache.pulsar.common.util.FutureUtil;
-import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap;
import org.apache.pulsar.metadata.api.coordination.LeaderElectionState;
import org.slf4j.Logger;
@@ -152,6 +153,7 @@ public class ExtensibleLoadManagerImpl implements ExtensibleLoadManager {
@Getter
private final List brokerFilterPipeline;
+
/**
* The load data reporter.
*/
@@ -181,10 +183,8 @@ public class ExtensibleLoadManagerImpl implements ExtensibleLoadManager {
// record split metrics
private final AtomicReference> splitMetrics = new AtomicReference<>();
- private final ConcurrentOpenHashMap>>
- lookupRequests = ConcurrentOpenHashMap.>>newBuilder()
- .build();
+ private final ConcurrentHashMap>>
+ lookupRequests = new ConcurrentHashMap<>();
private final CountDownLatch initWaiter = new CountDownLatch(1);
/**
@@ -197,7 +197,7 @@ public Set getOwnedServiceUnits() {
}
Set> entrySet = serviceUnitStateChannel.getOwnershipEntrySet();
String brokerId = brokerRegistry.getBrokerId();
- return entrySet.stream()
+ Set ownedServiceUnits = entrySet.stream()
.filter(entry -> {
var stateData = entry.getValue();
return stateData.state() == ServiceUnitState.Owned
@@ -207,6 +207,36 @@ public Set getOwnedServiceUnits() {
var bundle = entry.getKey();
return getNamespaceBundle(pulsar, bundle);
}).collect(Collectors.toSet());
+ // Add heartbeat and SLA monitor namespace bundle.
+ NamespaceName heartbeatNamespace = NamespaceService.getHeartbeatNamespace(brokerId, pulsar.getConfiguration());
+ try {
+ NamespaceBundle fullBundle = pulsar.getNamespaceService().getNamespaceBundleFactory()
+ .getFullBundle(heartbeatNamespace);
+ ownedServiceUnits.add(fullBundle);
+ } catch (Exception e) {
+ log.warn("Failed to get heartbeat namespace bundle.", e);
+ }
+ NamespaceName heartbeatNamespaceV2 = NamespaceService
+ .getHeartbeatNamespaceV2(brokerId, pulsar.getConfiguration());
+ try {
+ NamespaceBundle fullBundle = pulsar.getNamespaceService().getNamespaceBundleFactory()
+ .getFullBundle(heartbeatNamespaceV2);
+ ownedServiceUnits.add(fullBundle);
+ } catch (Exception e) {
+ log.warn("Failed to get heartbeat namespace V2 bundle.", e);
+ }
+
+ NamespaceName slaMonitorNamespace = NamespaceService
+ .getSLAMonitorNamespace(brokerId, pulsar.getConfiguration());
+ try {
+ NamespaceBundle fullBundle = pulsar.getNamespaceService().getNamespaceBundleFactory()
+ .getFullBundle(slaMonitorNamespace);
+ ownedServiceUnits.add(fullBundle);
+ } catch (Exception e) {
+ log.warn("Failed to get SLA Monitor namespace bundle.", e);
+ }
+
+ return ownedServiceUnits;
}
public enum Role {
@@ -261,102 +291,108 @@ public void start() throws PulsarServerException {
if (this.started) {
return;
}
- this.brokerRegistry = new BrokerRegistryImpl(pulsar);
- this.leaderElectionService = new LeaderElectionService(
- pulsar.getCoordinationService(), pulsar.getSafeWebServiceAddress(), ELECTION_ROOT,
- state -> {
- pulsar.getLoadManagerExecutor().execute(() -> {
- if (state == LeaderElectionState.Leading) {
- playLeader();
- } else {
- playFollower();
- }
+ try {
+ this.brokerRegistry = new BrokerRegistryImpl(pulsar);
+ this.leaderElectionService = new LeaderElectionService(
+ pulsar.getCoordinationService(), pulsar.getSafeWebServiceAddress(), ELECTION_ROOT,
+ state -> {
+ pulsar.getLoadManagerExecutor().execute(() -> {
+ if (state == LeaderElectionState.Leading) {
+ playLeader();
+ } else {
+ playFollower();
+ }
+ });
});
- });
- this.serviceUnitStateChannel = new ServiceUnitStateChannelImpl(pulsar);
- this.brokerRegistry.start();
- this.splitManager = new SplitManager(splitCounter);
- this.unloadManager = new UnloadManager(unloadCounter);
- this.serviceUnitStateChannel.listen(unloadManager);
- this.serviceUnitStateChannel.listen(splitManager);
- this.leaderElectionService.start();
- this.serviceUnitStateChannel.start();
- this.antiAffinityGroupPolicyHelper =
- new AntiAffinityGroupPolicyHelper(pulsar, serviceUnitStateChannel);
- antiAffinityGroupPolicyHelper.listenFailureDomainUpdate();
- this.antiAffinityGroupPolicyFilter = new AntiAffinityGroupPolicyFilter(antiAffinityGroupPolicyHelper);
- this.brokerFilterPipeline.add(antiAffinityGroupPolicyFilter);
- SimpleResourceAllocationPolicies policies = new SimpleResourceAllocationPolicies(pulsar);
- this.isolationPoliciesHelper = new IsolationPoliciesHelper(policies);
- this.brokerFilterPipeline.add(new BrokerIsolationPoliciesFilter(isolationPoliciesHelper));
-
- createSystemTopic(pulsar, BROKER_LOAD_DATA_STORE_TOPIC);
- createSystemTopic(pulsar, TOP_BUNDLES_LOAD_DATA_STORE_TOPIC);
+ this.serviceUnitStateChannel = ServiceUnitStateChannelImpl.newInstance(pulsar);
+ this.brokerRegistry.start();
+ this.splitManager = new SplitManager(splitCounter);
+ this.unloadManager = new UnloadManager(unloadCounter);
+ this.serviceUnitStateChannel.listen(unloadManager);
+ this.serviceUnitStateChannel.listen(splitManager);
+ this.leaderElectionService.start();
+ this.serviceUnitStateChannel.start();
+ this.antiAffinityGroupPolicyHelper =
+ new AntiAffinityGroupPolicyHelper(pulsar, serviceUnitStateChannel);
+ antiAffinityGroupPolicyHelper.listenFailureDomainUpdate();
+ this.antiAffinityGroupPolicyFilter = new AntiAffinityGroupPolicyFilter(antiAffinityGroupPolicyHelper);
+ this.brokerFilterPipeline.add(antiAffinityGroupPolicyFilter);
+ SimpleResourceAllocationPolicies policies = new SimpleResourceAllocationPolicies(pulsar);
+ this.isolationPoliciesHelper = new IsolationPoliciesHelper(policies);
+ this.brokerFilterPipeline.add(new BrokerIsolationPoliciesFilter(isolationPoliciesHelper));
+
+ createSystemTopic(pulsar, BROKER_LOAD_DATA_STORE_TOPIC);
+ createSystemTopic(pulsar, TOP_BUNDLES_LOAD_DATA_STORE_TOPIC);
- try {
- this.brokerLoadDataStore = LoadDataStoreFactory
- .create(pulsar.getClient(), BROKER_LOAD_DATA_STORE_TOPIC, BrokerLoadData.class);
- this.brokerLoadDataStore.startTableView();
- this.topBundlesLoadDataStore = LoadDataStoreFactory
- .create(pulsar.getClient(), TOP_BUNDLES_LOAD_DATA_STORE_TOPIC, TopBundlesLoadData.class);
- } catch (LoadDataStoreException e) {
- throw new PulsarServerException(e);
- }
+ try {
+ this.brokerLoadDataStore = LoadDataStoreFactory
+ .create(pulsar.getClient(), BROKER_LOAD_DATA_STORE_TOPIC, BrokerLoadData.class);
+ this.brokerLoadDataStore.startTableView();
+ this.topBundlesLoadDataStore = LoadDataStoreFactory
+ .create(pulsar.getClient(), TOP_BUNDLES_LOAD_DATA_STORE_TOPIC, TopBundlesLoadData.class);
+ } catch (LoadDataStoreException e) {
+ throw new PulsarServerException(e);
+ }
- this.context = LoadManagerContextImpl.builder()
- .configuration(conf)
- .brokerRegistry(brokerRegistry)
- .brokerLoadDataStore(brokerLoadDataStore)
- .topBundleLoadDataStore(topBundlesLoadDataStore).build();
-
- this.brokerLoadDataReporter =
- new BrokerLoadDataReporter(pulsar, brokerRegistry.getBrokerId(), brokerLoadDataStore);
-
- this.topBundleLoadDataReporter =
- new TopBundleLoadDataReporter(pulsar, brokerRegistry.getBrokerId(), topBundlesLoadDataStore);
- this.serviceUnitStateChannel.listen(brokerLoadDataReporter);
- this.serviceUnitStateChannel.listen(topBundleLoadDataReporter);
- var interval = conf.getLoadBalancerReportUpdateMinIntervalMillis();
- this.brokerLoadDataReportTask = this.pulsar.getLoadManagerExecutor()
- .scheduleAtFixedRate(() -> {
- try {
- brokerLoadDataReporter.reportAsync(false);
- // TODO: update broker load metrics using getLocalData
- } catch (Throwable e) {
- log.error("Failed to run the broker load manager executor job.", e);
- }
- },
- interval,
- interval, TimeUnit.MILLISECONDS);
-
- this.topBundlesLoadDataReportTask = this.pulsar.getLoadManagerExecutor()
- .scheduleAtFixedRate(() -> {
- try {
- // TODO: consider excluding the bundles that are in the process of split.
- topBundleLoadDataReporter.reportAsync(false);
- } catch (Throwable e) {
- log.error("Failed to run the top bundles load manager executor job.", e);
- }
- },
- interval,
- interval, TimeUnit.MILLISECONDS);
-
- this.monitorTask = this.pulsar.getLoadManagerExecutor()
- .scheduleAtFixedRate(() -> {
- monitor();
- },
- MONITOR_INTERVAL_IN_MILLIS,
- MONITOR_INTERVAL_IN_MILLIS, TimeUnit.MILLISECONDS);
-
- this.unloadScheduler = new UnloadScheduler(
- pulsar, pulsar.getLoadManagerExecutor(), unloadManager, context,
- serviceUnitStateChannel, unloadCounter, unloadMetrics);
- this.unloadScheduler.start();
- this.splitScheduler = new SplitScheduler(
- pulsar, serviceUnitStateChannel, splitManager, splitCounter, splitMetrics, context);
- this.splitScheduler.start();
- this.initWaiter.countDown();
- this.started = true;
+ this.context = LoadManagerContextImpl.builder()
+ .configuration(conf)
+ .brokerRegistry(brokerRegistry)
+ .brokerLoadDataStore(brokerLoadDataStore)
+ .topBundleLoadDataStore(topBundlesLoadDataStore).build();
+
+ this.brokerLoadDataReporter =
+ new BrokerLoadDataReporter(pulsar, brokerRegistry.getBrokerId(), brokerLoadDataStore);
+
+ this.topBundleLoadDataReporter =
+ new TopBundleLoadDataReporter(pulsar, brokerRegistry.getBrokerId(), topBundlesLoadDataStore);
+ this.serviceUnitStateChannel.listen(brokerLoadDataReporter);
+ this.serviceUnitStateChannel.listen(topBundleLoadDataReporter);
+ var interval = conf.getLoadBalancerReportUpdateMinIntervalMillis();
+ this.brokerLoadDataReportTask = this.pulsar.getLoadManagerExecutor()
+ .scheduleAtFixedRate(() -> {
+ try {
+ brokerLoadDataReporter.reportAsync(false);
+ // TODO: update broker load metrics using getLocalData
+ } catch (Throwable e) {
+ log.error("Failed to run the broker load manager executor job.", e);
+ }
+ },
+ interval,
+ interval, TimeUnit.MILLISECONDS);
+
+ this.topBundlesLoadDataReportTask = this.pulsar.getLoadManagerExecutor()
+ .scheduleAtFixedRate(() -> {
+ try {
+ // TODO: consider excluding the bundles that are in the process of split.
+ topBundleLoadDataReporter.reportAsync(false);
+ } catch (Throwable e) {
+ log.error("Failed to run the top bundles load manager executor job.", e);
+ }
+ },
+ interval,
+ interval, TimeUnit.MILLISECONDS);
+
+ this.monitorTask = this.pulsar.getLoadManagerExecutor()
+ .scheduleAtFixedRate(() -> {
+ monitor();
+ },
+ MONITOR_INTERVAL_IN_MILLIS,
+ MONITOR_INTERVAL_IN_MILLIS, TimeUnit.MILLISECONDS);
+
+ this.unloadScheduler = new UnloadScheduler(
+ pulsar, pulsar.getLoadManagerExecutor(), unloadManager, context,
+ serviceUnitStateChannel, unloadCounter, unloadMetrics);
+ this.unloadScheduler.start();
+ this.splitScheduler = new SplitScheduler(
+ pulsar, serviceUnitStateChannel, splitManager, splitCounter, splitMetrics, context);
+ this.splitScheduler.start();
+ this.initWaiter.countDown();
+ this.started = true;
+ } catch (Exception ex) {
+ if (this.brokerRegistry != null) {
+ brokerRegistry.close();
+ }
+ }
}
@Override
@@ -377,25 +413,38 @@ public CompletableFuture> assign(Optional getOwnerAsync(
- ServiceUnitId serviceUnit, String bundle, boolean ownByLocalBrokerIfAbsent) {
+ private String getHeartbeatOrSLAMonitorBrokerId(ServiceUnitId serviceUnit) {
+ // Check if this is Heartbeat or SLAMonitor namespace
+ String candidateBroker = NamespaceService.checkHeartbeatNamespace(serviceUnit);
+ if (candidateBroker == null) {
+ candidateBroker = NamespaceService.checkHeartbeatNamespaceV2(serviceUnit);
+ }
+ if (candidateBroker == null) {
+ candidateBroker = NamespaceService.getSLAMonitorBrokerName(serviceUnit);
+ }
+ if (candidateBroker != null) {
+ return candidateBroker.substring(candidateBroker.lastIndexOf('/') + 1);
+ }
+ return candidateBroker;
+ }
+
+ private CompletableFuture getOrSelectOwnerAsync(ServiceUnitId serviceUnit,
+ String bundle) {
return serviceUnitStateChannel.getOwnerAsync(bundle).thenCompose(broker -> {
// If the bundle not assign yet, select and publish assign event to channel.
if (broker.isEmpty()) {
- CompletableFuture> selectedBroker;
- if (ownByLocalBrokerIfAbsent) {
- String brokerId = this.brokerRegistry.getBrokerId();
- selectedBroker = CompletableFuture.completedFuture(Optional.of(brokerId));
- } else {
- selectedBroker = this.selectAsync(serviceUnit);
- }
- return selectedBroker.thenCompose(brokerOpt -> {
+ return this.selectAsync(serviceUnit).thenCompose(brokerOpt -> {
if (brokerOpt.isPresent()) {
assignCounter.incrementSuccess();
log.info("Selected new owner broker: {} for bundle: {}.", brokerOpt.get(), bundle);
@@ -425,7 +474,8 @@ private CompletableFuture> getBrokerLookupData(
}).thenCompose(broker -> this.getBrokerRegistry().lookupAsync(broker).thenCompose(brokerLookupData -> {
if (brokerLookupData.isEmpty()) {
String errorMsg = String.format(
- "Failed to look up a broker registry:%s for bundle:%s", broker, bundle);
+ "Failed to lookup broker:%s for bundle:%s, the broker has not been registered.",
+ broker, bundle);
log.error(errorMsg);
throw new IllegalStateException(errorMsg);
}
@@ -443,30 +493,37 @@ private CompletableFuture> getBrokerLookupData(
public CompletableFuture tryAcquiringOwnership(NamespaceBundle namespaceBundle) {
log.info("Try acquiring ownership for bundle: {} - {}.", namespaceBundle, brokerRegistry.getBrokerId());
final String bundle = namespaceBundle.toString();
- return dedupeLookupRequest(bundle, k -> {
- final CompletableFuture owner =
- this.getOwnerAsync(namespaceBundle, bundle, true);
- return getBrokerLookupData(owner.thenApply(Optional::ofNullable), bundle);
- }).thenApply(brokerLookupData -> {
- if (brokerLookupData.isEmpty()) {
- throw new IllegalStateException(
- "Failed to get the broker lookup data for bundle: " + bundle);
- }
- return brokerLookupData.get().toNamespaceEphemeralData();
- });
+ return assign(Optional.empty(), namespaceBundle)
+ .thenApply(brokerLookupData -> {
+ if (brokerLookupData.isEmpty()) {
+ String errorMsg = String.format(
+ "Failed to get the broker lookup data for bundle:%s", bundle);
+ log.error(errorMsg);
+ throw new IllegalStateException(errorMsg);
+ }
+ return brokerLookupData.get().toNamespaceEphemeralData();
+ });
}
private CompletableFuture> dedupeLookupRequest(
String key, Function>> provider) {
- CompletableFuture> future = lookupRequests.computeIfAbsent(key, provider);
- future.whenComplete((r, t) -> {
- if (t != null) {
+ final MutableObject>> newFutureCreated = new MutableObject<>();
+ try {
+ return lookupRequests.computeIfAbsent(key, k -> {
+ CompletableFuture> future = provider.apply(k);
+ newFutureCreated.setValue(future);
+ return future;
+ });
+ } finally {
+ if (newFutureCreated.getValue() != null) {
+ newFutureCreated.getValue().whenComplete((v, ex) -> {
+ if (ex != null) {
assignCounter.incrementFailure();
}
- lookupRequests.remove(key);
- }
- );
- return future;
+ lookupRequests.remove(key, newFutureCreated.getValue());
+ });
+ }
+ }
}
public CompletableFuture> selectAsync(ServiceUnitId bundle) {
@@ -521,15 +578,16 @@ public CompletableFuture checkOwnershipAsync(Optional to
}
public CompletableFuture> getOwnershipAsync(Optional topic,
- ServiceUnitId bundleUnit) {
- final String bundle = bundleUnit.toString();
- CompletableFuture> owner;
+ ServiceUnitId serviceUnit) {
+ final String bundle = serviceUnit.toString();
if (topic.isPresent() && isInternalTopic(topic.get().toString())) {
- owner = serviceUnitStateChannel.getChannelOwnerAsync();
- } else {
- owner = serviceUnitStateChannel.getOwnerAsync(bundle);
+ return serviceUnitStateChannel.getChannelOwnerAsync();
}
- return owner;
+ String candidateBroker = getHeartbeatOrSLAMonitorBrokerId(serviceUnit);
+ if (candidateBroker != null) {
+ return CompletableFuture.completedFuture(Optional.of(candidateBroker));
+ }
+ return serviceUnitStateChannel.getOwnerAsync(bundle);
}
public CompletableFuture> getOwnershipWithLookupDataAsync(ServiceUnitId bundleUnit) {
@@ -543,6 +601,10 @@ public CompletableFuture> getOwnershipWithLookupDataA
public CompletableFuture unloadNamespaceBundleAsync(ServiceUnitId bundle,
Optional destinationBroker) {
+ if (NamespaceService.isSLAOrHeartbeatNamespace(bundle.getNamespaceObject().toString())) {
+ log.info("Skip unloading namespace bundle: {}.", bundle);
+ return CompletableFuture.completedFuture(null);
+ }
return getOwnershipAsync(Optional.empty(), bundle)
.thenCompose(brokerOpt -> {
if (brokerOpt.isEmpty()) {
@@ -577,6 +639,10 @@ private CompletableFuture unloadAsync(UnloadDecision unloadDecision,
public CompletableFuture splitNamespaceBundleAsync(ServiceUnitId bundle,
NamespaceBundleSplitAlgorithm splitAlgorithm,
List boundaries) {
+ if (NamespaceService.isSLAOrHeartbeatNamespace(bundle.getNamespaceObject().toString())) {
+ log.info("Skip split namespace bundle: {}.", bundle);
+ return CompletableFuture.completedFuture(null);
+ }
final String namespaceName = LoadManagerShared.getNamespaceNameFromBundleName(bundle.toString());
final String bundleRange = LoadManagerShared.getBundleRangeFromBundleName(bundle.toString());
NamespaceBundle namespaceBundle =
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelImpl.java
index 98aa02d4e72b4..3cf16709cde1b 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelImpl.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelImpl.java
@@ -41,8 +41,6 @@
import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateChannelImpl.MetadataState.Stable;
import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateChannelImpl.MetadataState.Unstable;
import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateData.state;
-import static org.apache.pulsar.broker.namespace.NamespaceService.HEARTBEAT_NAMESPACE_FMT;
-import static org.apache.pulsar.broker.namespace.NamespaceService.HEARTBEAT_NAMESPACE_FMT_V2;
import static org.apache.pulsar.common.naming.NamespaceName.SYSTEM_NAMESPACE;
import static org.apache.pulsar.common.topics.TopicCompactionStrategy.TABLE_VIEW_TAG;
import static org.apache.pulsar.metadata.api.extended.SessionEvent.SessionLost;
@@ -56,6 +54,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture;
@@ -69,6 +68,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.pulsar.PulsarClusterMetadataSetup;
import org.apache.pulsar.broker.PulsarServerException;
import org.apache.pulsar.broker.PulsarService;
@@ -94,13 +94,11 @@
import org.apache.pulsar.common.naming.NamespaceBundleFactory;
import org.apache.pulsar.common.naming.NamespaceBundleSplitAlgorithm;
import org.apache.pulsar.common.naming.NamespaceBundles;
-import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.common.naming.TopicDomain;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.stats.Metrics;
import org.apache.pulsar.common.topics.TopicCompactionStrategy;
import org.apache.pulsar.common.util.FutureUtil;
-import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap;
import org.apache.pulsar.metadata.api.MetadataStoreException;
import org.apache.pulsar.metadata.api.NotificationType;
import org.apache.pulsar.metadata.api.extended.SessionEvent;
@@ -128,9 +126,9 @@ public class ServiceUnitStateChannelImpl implements ServiceUnitStateChannel {
private final PulsarService pulsar;
private final ServiceConfiguration config;
private final Schema schema;
- private final ConcurrentOpenHashMap> getOwnerRequests;
+ private final Map> getOwnerRequests;
private final String lookupServiceAddress;
- private final ConcurrentOpenHashMap> cleanupJobs;
+ private final Map> cleanupJobs;
private final StateChangeListeners stateChangeListeners;
private ExtensibleLoadManagerImpl loadManager;
private BrokerRegistry brokerRegistry;
@@ -202,19 +200,29 @@ enum MetadataState {
Unstable
}
+ public static ServiceUnitStateChannelImpl newInstance(PulsarService pulsar) {
+ return new ServiceUnitStateChannelImpl(pulsar);
+ }
+
public ServiceUnitStateChannelImpl(PulsarService pulsar) {
+ this(pulsar, MAX_IN_FLIGHT_STATE_WAITING_TIME_IN_MILLIS, OWNERSHIP_MONITOR_DELAY_TIME_IN_SECS);
+ }
+
+ @VisibleForTesting
+ public ServiceUnitStateChannelImpl(PulsarService pulsar,
+ long inFlightStateWaitingTimeInMillis,
+ long ownershipMonitorDelayTimeInSecs) {
this.pulsar = pulsar;
this.config = pulsar.getConfig();
this.lookupServiceAddress = pulsar.getLookupServiceAddress();
this.schema = Schema.JSON(ServiceUnitStateData.class);
- this.getOwnerRequests = ConcurrentOpenHashMap.>newBuilder().build();
- this.cleanupJobs = ConcurrentOpenHashMap.>newBuilder().build();
+ this.getOwnerRequests = new ConcurrentHashMap<>();
+ this.cleanupJobs = new ConcurrentHashMap<>();
this.stateChangeListeners = new StateChangeListeners();
this.semiTerminalStateWaitingTimeInMillis = config.getLoadBalancerServiceUnitStateTombstoneDelayTimeInSeconds()
* 1000;
- this.inFlightStateWaitingTimeInMillis = MAX_IN_FLIGHT_STATE_WAITING_TIME_IN_MILLIS;
- this.ownershipMonitorDelayTimeInSecs = OWNERSHIP_MONITOR_DELAY_TIME_IN_SECS;
+ this.inFlightStateWaitingTimeInMillis = inFlightStateWaitingTimeInMillis;
+ this.ownershipMonitorDelayTimeInSecs = ownershipMonitorDelayTimeInSecs;
if (semiTerminalStateWaitingTimeInMillis < inFlightStateWaitingTimeInMillis) {
throw new IllegalArgumentException(
"Invalid Config: loadBalancerServiceUnitStateCleanUpDelayTimeInSeconds < "
@@ -829,20 +837,28 @@ private boolean isTargetBroker(String broker) {
}
private CompletableFuture deferGetOwnerRequest(String serviceUnit) {
- return getOwnerRequests
- .computeIfAbsent(serviceUnit, k -> {
- CompletableFuture future = new CompletableFuture<>();
- future.orTimeout(inFlightStateWaitingTimeInMillis, TimeUnit.MILLISECONDS)
- .whenComplete((v, e) -> {
- if (e != null) {
- getOwnerRequests.remove(serviceUnit, future);
- log.warn("Failed to getOwner for serviceUnit:{}",
- serviceUnit, e);
- }
+ var requested = new MutableObject>();
+ try {
+ return getOwnerRequests
+ .computeIfAbsent(serviceUnit, k -> {
+ CompletableFuture future = new CompletableFuture<>();
+ requested.setValue(future);
+ return future;
+ });
+ } finally {
+ var future = requested.getValue();
+ if (future != null) {
+ future.orTimeout(inFlightStateWaitingTimeInMillis + 5 * 1000, TimeUnit.MILLISECONDS)
+ .whenComplete((v, e) -> {
+ if (e != null) {
+ getOwnerRequests.remove(serviceUnit, future);
+ log.warn("Failed to getOwner for serviceUnit:{}",
+ serviceUnit, e);
}
- );
- return future;
- });
+ }
+ );
+ }
+ }
}
private CompletableFuture closeServiceUnit(String serviceUnit) {
@@ -1117,24 +1133,34 @@ private void handleBrokerDeletionEvent(String broker) {
}
private void scheduleCleanup(String broker, long delayInSecs) {
- cleanupJobs.computeIfAbsent(broker, k -> {
- Executor delayed = CompletableFuture
- .delayedExecutor(delayInSecs, TimeUnit.SECONDS, pulsar.getLoadManagerExecutor());
- totalInactiveBrokerCleanupScheduledCnt++;
- return CompletableFuture
- .runAsync(() -> {
- try {
- doCleanup(broker);
- } catch (Throwable e) {
- log.error("Failed to run the cleanup job for the broker {}, "
- + "totalCleanupErrorCnt:{}.",
- broker, totalCleanupErrorCnt.incrementAndGet(), e);
- } finally {
- cleanupJobs.remove(broker);
+ var scheduled = new MutableObject>();
+ try {
+ cleanupJobs.computeIfAbsent(broker, k -> {
+ Executor delayed = CompletableFuture
+ .delayedExecutor(delayInSecs, TimeUnit.SECONDS, pulsar.getLoadManagerExecutor());
+ totalInactiveBrokerCleanupScheduledCnt++;
+ var future = CompletableFuture
+ .runAsync(() -> {
+ try {
+ doCleanup(broker);
+ } catch (Throwable e) {
+ log.error("Failed to run the cleanup job for the broker {}, "
+ + "totalCleanupErrorCnt:{}.",
+ broker, totalCleanupErrorCnt.incrementAndGet(), e);
+ }
}
- }
- , delayed);
- });
+ , delayed);
+ scheduled.setValue(future);
+ return future;
+ });
+ } finally {
+ var future = scheduled.getValue();
+ if (future != null) {
+ future.whenComplete((v, ex) -> {
+ cleanupJobs.remove(broker);
+ });
+ }
+ }
log.info("Scheduled ownership cleanup for broker:{} with delay:{} secs. Pending clean jobs:{}.",
broker, delayInSecs, cleanupJobs.size());
@@ -1216,48 +1242,19 @@ private synchronized void doCleanup(String broker) {
log.info("Started ownership cleanup for the inactive broker:{}", broker);
int orphanServiceUnitCleanupCnt = 0;
long totalCleanupErrorCntStart = totalCleanupErrorCnt.get();
- String heartbeatNamespace =
- NamespaceName.get(String.format(HEARTBEAT_NAMESPACE_FMT, config.getClusterName(), broker)).toString();
- String heartbeatNamespaceV2 =
- NamespaceName.get(String.format(HEARTBEAT_NAMESPACE_FMT_V2, broker)).toString();
-
Map orphanSystemServiceUnits = new HashMap<>();
for (var etr : tableview.entrySet()) {
var stateData = etr.getValue();
var serviceUnit = etr.getKey();
var state = state(stateData);
- if (StringUtils.equals(broker, stateData.dstBroker())) {
- if (isActiveState(state)) {
- if (serviceUnit.startsWith(SYSTEM_NAMESPACE.toString())) {
- orphanSystemServiceUnits.put(serviceUnit, stateData);
- } else if (serviceUnit.startsWith(heartbeatNamespace)
- || serviceUnit.startsWith(heartbeatNamespaceV2)) {
- // Skip the heartbeat namespace
- log.info("Skip override heartbeat namespace bundle"
- + " serviceUnit:{}, stateData:{}", serviceUnit, stateData);
- tombstoneAsync(serviceUnit).whenComplete((__, e) -> {
- if (e != null) {
- log.error("Failed cleaning the heartbeat namespace ownership serviceUnit:{}, "
- + "stateData:{}, cleanupErrorCnt:{}.",
- serviceUnit, stateData,
- totalCleanupErrorCnt.incrementAndGet() - totalCleanupErrorCntStart, e);
- }
- });
- } else {
- overrideOwnership(serviceUnit, stateData, broker);
- }
- orphanServiceUnitCleanupCnt++;
- }
-
- } else if (StringUtils.equals(broker, stateData.sourceBroker())) {
- if (isInFlightState(state)) {
- if (serviceUnit.startsWith(SYSTEM_NAMESPACE.toString())) {
- orphanSystemServiceUnits.put(serviceUnit, stateData);
- } else {
- overrideOwnership(serviceUnit, stateData, broker);
- }
- orphanServiceUnitCleanupCnt++;
+ if (StringUtils.equals(broker, stateData.dstBroker()) && isActiveState(state)
+ || StringUtils.equals(broker, stateData.sourceBroker()) && isInFlightState(state)) {
+ if (serviceUnit.startsWith(SYSTEM_NAMESPACE.toString())) {
+ orphanSystemServiceUnits.put(serviceUnit, stateData);
+ } else {
+ overrideOwnership(serviceUnit, stateData, broker);
}
+ orphanServiceUnitCleanupCnt++;
}
}
@@ -1401,16 +1398,21 @@ protected void monitorOwnerships(List brokers) {
String srcBroker = stateData.sourceBroker();
var state = stateData.state();
- if (isActiveState(state)) {
- if (StringUtils.isNotBlank(srcBroker) && !activeBrokers.contains(srcBroker)) {
- inactiveBrokers.add(srcBroker);
- } else if (StringUtils.isNotBlank(dstBroker) && !activeBrokers.contains(dstBroker)) {
- inactiveBrokers.add(dstBroker);
- } else if (isInFlightState(state)
- && now - stateData.timestamp() > inFlightStateWaitingTimeInMillis) {
- orphanServiceUnits.put(serviceUnit, stateData);
- }
- } else if (now - stateData.timestamp() > semiTerminalStateWaitingTimeInMillis) {
+ if (isActiveState(state) && StringUtils.isNotBlank(srcBroker) && !activeBrokers.contains(srcBroker)) {
+ inactiveBrokers.add(srcBroker);
+ continue;
+ }
+ if (isActiveState(state) && StringUtils.isNotBlank(dstBroker) && !activeBrokers.contains(dstBroker)) {
+ inactiveBrokers.add(dstBroker);
+ continue;
+ }
+ if (isActiveState(state) && isInFlightState(state)
+ && now - stateData.timestamp() > inFlightStateWaitingTimeInMillis) {
+ orphanServiceUnits.put(serviceUnit, stateData);
+ continue;
+ }
+
+ if (now - stateData.timestamp() > semiTerminalStateWaitingTimeInMillis) {
log.info("Found semi-terminal states to tombstone"
+ " serviceUnit:{}, stateData:{}", serviceUnit, stateData);
tombstoneAsync(serviceUnit).whenComplete((__, e) -> {
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/manager/UnloadManager.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/manager/UnloadManager.java
index 2dde0c4708e41..ffdbbc2af4219 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/manager/UnloadManager.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/manager/UnloadManager.java
@@ -88,6 +88,13 @@ public CompletableFuture waitAsync(CompletableFuture eventPubFuture,
@Override
public void handleEvent(String serviceUnit, ServiceUnitStateData data, Throwable t) {
+ if (t != null && inFlightUnloadRequest.containsKey(serviceUnit)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Handling {} for service unit {} with exception.", data, serviceUnit, t);
+ }
+ this.complete(serviceUnit, t);
+ return;
+ }
ServiceUnitState state = ServiceUnitStateData.state(data);
switch (state) {
case Free, Owned -> this.complete(serviceUnit, t);
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/models/TopKBundles.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/models/TopKBundles.java
index 2f5c32197c1fd..624546fdff837 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/models/TopKBundles.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/extensions/models/TopKBundles.java
@@ -30,6 +30,8 @@
import org.apache.pulsar.broker.loadbalance.extensions.data.TopBundlesLoadData;
import org.apache.pulsar.broker.loadbalance.impl.LoadManagerShared;
import org.apache.pulsar.broker.loadbalance.impl.SimpleResourceAllocationPolicies;
+import org.apache.pulsar.broker.namespace.NamespaceService;
+import org.apache.pulsar.common.naming.NamespaceBundle;
import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.metadata.api.MetadataStoreException;
import org.apache.pulsar.policies.data.loadbalancer.NamespaceBundleStats;
@@ -70,7 +72,8 @@ public void update(Map bundleStats, int topk) {
pulsar.getConfiguration().isLoadBalancerSheddingBundlesWithPoliciesEnabled();
for (var etr : bundleStats.entrySet()) {
String bundle = etr.getKey();
- if (bundle.startsWith(NamespaceName.SYSTEM_NAMESPACE.toString())) {
+ // TODO: do not filter system topic while shedding
+ if (NamespaceService.isSystemServiceNamespace(NamespaceBundle.getBundleNamespace(bundle))) {
continue;
}
if (!isLoadBalancerSheddingBundlesWithPoliciesEnabled && hasPolicies(bundle)) {
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java
index 0d5dbf489e90f..022f2fcbe39f4 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java
@@ -108,7 +108,7 @@ public class ModularLoadManagerImpl implements ModularLoadManager {
public static final int NUM_SHORT_SAMPLES = 10;
// Path to ZNode whose children contain ResourceQuota jsons.
- public static final String RESOURCE_QUOTA_ZPATH = "/loadbalance/resource-quota/namespace";
+ public static final String RESOURCE_QUOTA_ZPATH = "/loadbalance/resource-quota";
// Path to ZNode containing TimeAverageBrokerData jsons for each broker.
public static final String TIME_AVERAGE_BROKER_ZPATH = "/loadbalance/broker-time-average";
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/lookup/TopicLookupBase.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/lookup/TopicLookupBase.java
index bd70201cba55d..16d3f535780b1 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/lookup/TopicLookupBase.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/lookup/TopicLookupBase.java
@@ -56,7 +56,7 @@ public class TopicLookupBase extends PulsarWebResource {
private static final String LOOKUP_PATH_V1 = "/lookup/v2/destination/";
private static final String LOOKUP_PATH_V2 = "/lookup/v2/topic/";
- protected CompletableFuture internalLookupTopicAsync(TopicName topicName, boolean authoritative,
+ protected CompletableFuture internalLookupTopicAsync(final TopicName topicName, boolean authoritative,
String listenerName) {
if (!pulsar().getBrokerService().getLookupRequestSemaphore().tryAcquire()) {
log.warn("No broker was found available for topic {}", topicName);
@@ -79,7 +79,8 @@ protected CompletableFuture internalLookupTopicAsync(TopicName topic
})
.thenCompose(exist -> {
if (!exist) {
- throw new RestException(Response.Status.NOT_FOUND, "Topic not found.");
+ throw new RestException(Response.Status.NOT_FOUND,
+ String.format("Topic not found %s", topicName.toString()));
}
CompletableFuture> lookupFuture = pulsar().getNamespaceService()
.getBrokerServiceUrlAsync(topicName,
@@ -131,10 +132,10 @@ protected CompletableFuture internalLookupTopicAsync(TopicName topic
pulsar().getBrokerService().getLookupRequestSemaphore().release();
return result.getLookupData();
}
- }).exceptionally(ex->{
- pulsar().getBrokerService().getLookupRequestSemaphore().release();
- throw FutureUtil.wrapToCompletionException(ex);
});
+ }).exceptionally(ex -> {
+ pulsar().getBrokerService().getLookupRequestSemaphore().release();
+ throw FutureUtil.wrapToCompletionException(ex);
});
}
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/namespace/NamespaceService.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/namespace/NamespaceService.java
index d66e3c3b65d76..c69e30173aacb 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/namespace/NamespaceService.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/namespace/NamespaceService.java
@@ -135,7 +135,7 @@ public class NamespaceService implements AutoCloseable {
public static final Pattern SLA_NAMESPACE_PATTERN = Pattern.compile(SLA_NAMESPACE_PROPERTY + "/[^/]+/([^:]+:\\d+)");
public static final String HEARTBEAT_NAMESPACE_FMT = "pulsar/%s/%s";
public static final String HEARTBEAT_NAMESPACE_FMT_V2 = "pulsar/%s";
- public static final String SLA_NAMESPACE_FMT = SLA_NAMESPACE_PROPERTY + "/%s/%s:%s";
+ public static final String SLA_NAMESPACE_FMT = SLA_NAMESPACE_PROPERTY + "/%s/%s";
private final ConcurrentOpenHashMap namespaceClients;
@@ -189,7 +189,7 @@ public CompletableFuture> getBrokerServiceUrlAsync(TopicN
CompletableFuture> future = getBundleAsync(topic)
.thenCompose(bundle -> {
// Do redirection if the cluster is in rollback or deploying.
- return redirectManager.findRedirectLookupResultAsync().thenCompose(optResult -> {
+ return findRedirectLookupResultAsync(bundle).thenCompose(optResult -> {
if (optResult.isPresent()) {
LOG.info("[{}] Redirect lookup request to {} for topic {}",
pulsar.getSafeWebServiceAddress(), optResult.get(), topic);
@@ -221,6 +221,13 @@ public CompletableFuture> getBrokerServiceUrlAsync(TopicN
return future;
}
+ private CompletableFuture> findRedirectLookupResultAsync(ServiceUnitId bundle) {
+ if (isSLAOrHeartbeatNamespace(bundle.getNamespaceObject().toString())) {
+ return CompletableFuture.completedFuture(Optional.empty());
+ }
+ return redirectManager.findRedirectLookupResultAsync();
+ }
+
public CompletableFuture getBundleAsync(TopicName topic) {
return bundleFactory.getBundlesAsync(topic.getNamespaceObject())
.thenApply(bundles -> bundles.findBundle(topic));
@@ -288,8 +295,7 @@ public Optional getWebServiceUrl(ServiceUnitId suName, LookupOptions option
private CompletableFuture> internalGetWebServiceUrl(@Nullable ServiceUnitId topic,
NamespaceBundle bundle,
LookupOptions options) {
-
- return redirectManager.findRedirectLookupResultAsync().thenCompose(optResult -> {
+ return findRedirectLookupResultAsync(bundle).thenCompose(optResult -> {
if (optResult.isPresent()) {
LOG.info("[{}] Redirect lookup request to {} for topic {}",
pulsar.getSafeWebServiceAddress(), optResult.get(), topic);
@@ -695,7 +701,7 @@ public CompletableFuture createLookupResult(String candidateBroker
return lookupFuture;
}
- private boolean isBrokerActive(String candidateBroker) {
+ public boolean isBrokerActive(String candidateBroker) {
String candidateBrokerHostAndPort = parseHostAndPort(candidateBroker);
Set availableBrokers = getAvailableBrokers();
if (availableBrokers.contains(candidateBrokerHostAndPort)) {
@@ -1552,10 +1558,6 @@ public boolean checkOwnershipPresent(NamespaceBundle bundle) throws Exception {
public CompletableFuture checkOwnershipPresentAsync(NamespaceBundle bundle) {
if (ExtensibleLoadManagerImpl.isLoadManagerExtensionEnabled(config)) {
- if (bundle.getNamespaceObject().equals(SYSTEM_NAMESPACE)) {
- return FutureUtil.failedFuture(new UnsupportedOperationException(
- "Ownership check for system namespace is not supported"));
- }
ExtensibleLoadManagerImpl extensibleLoadManager = ExtensibleLoadManagerImpl.get(loadManager.get());
return extensibleLoadManager.getOwnershipAsync(Optional.empty(), bundle)
.thenApply(Optional::isPresent);
@@ -1564,7 +1566,7 @@ public CompletableFuture checkOwnershipPresentAsync(NamespaceBundle bun
}
public void unloadSLANamespace() throws Exception {
- NamespaceName namespaceName = getSLAMonitorNamespace(host, config);
+ NamespaceName namespaceName = getSLAMonitorNamespace(pulsar.getLookupServiceAddress(), config);
LOG.info("Checking owner for SLA namespace {}", namespaceName);
@@ -1589,14 +1591,8 @@ public static NamespaceName getHeartbeatNamespaceV2(String lookupBroker, Service
return NamespaceName.get(String.format(HEARTBEAT_NAMESPACE_FMT_V2, lookupBroker));
}
- public static NamespaceName getSLAMonitorNamespace(String host, ServiceConfiguration config) {
- Integer port = null;
- if (config.getWebServicePort().isPresent()) {
- port = config.getWebServicePort().get();
- } else if (config.getWebServicePortTls().isPresent()) {
- port = config.getWebServicePortTls().get();
- }
- return NamespaceName.get(String.format(SLA_NAMESPACE_FMT, config.getClusterName(), host, port));
+ public static NamespaceName getSLAMonitorNamespace(String lookupBroker, ServiceConfiguration config) {
+ return NamespaceName.get(String.format(SLA_NAMESPACE_FMT, config.getClusterName(), lookupBroker));
}
public static String checkHeartbeatNamespace(ServiceUnitId ns) {
@@ -1640,7 +1636,7 @@ public static boolean isSystemServiceNamespace(String namespace) {
* @param namespace the namespace name
* @return True if namespace is HEARTBEAT_NAMESPACE or SLA_NAMESPACE
*/
- public static boolean filterNamespaceForShedding(String namespace) {
+ public static boolean isSLAOrHeartbeatNamespace(String namespace) {
return SLA_NAMESPACE_PATTERN.matcher(namespace).matches()
|| HEARTBEAT_NAMESPACE_PATTERN.matcher(namespace).matches()
|| HEARTBEAT_NAMESPACE_PATTERN_V2.matcher(namespace).matches();
@@ -1653,14 +1649,16 @@ public static boolean isHeartbeatNamespace(ServiceUnitId ns) {
}
public boolean registerSLANamespace() throws PulsarServerException {
- boolean isNameSpaceRegistered = registerNamespace(getSLAMonitorNamespace(host, config), false);
+ String lookupServiceAddress = pulsar.getLookupServiceAddress();
+ boolean isNameSpaceRegistered = registerNamespace(getSLAMonitorNamespace(lookupServiceAddress, config), false);
if (isNameSpaceRegistered) {
if (LOG.isDebugEnabled()) {
LOG.debug("Added SLA Monitoring namespace name in local cache: ns={}",
- getSLAMonitorNamespace(host, config));
+ getSLAMonitorNamespace(lookupServiceAddress, config));
}
} else if (LOG.isDebugEnabled()) {
- LOG.debug("SLA Monitoring not owned by the broker: ns={}", getSLAMonitorNamespace(host, config));
+ LOG.debug("SLA Monitoring not owned by the broker: ns={}",
+ getSLAMonitorNamespace(lookupServiceAddress, config));
}
return isNameSpaceRegistered;
}
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractBaseDispatcher.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractBaseDispatcher.java
index eb8b015139586..b36389ab2dada 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractBaseDispatcher.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractBaseDispatcher.java
@@ -176,14 +176,16 @@ public int filterEntriesForConsumer(@Nullable MessageMetadata[] metadataArray, i
if (Markers.isTxnMarker(msgMetadata)) {
// because consumer can receive message is smaller than maxReadPosition,
// so this marker is useless for this subscription
- individualAcknowledgeMessageIfNeeded(entry.getPosition(), Collections.emptyMap());
+ individualAcknowledgeMessageIfNeeded(Collections.singletonList(entry.getPosition()),
+ Collections.emptyMap());
entries.set(i, null);
entry.release();
continue;
} else if (((PersistentTopic) subscription.getTopic())
.isTxnAborted(new TxnID(msgMetadata.getTxnidMostBits(), msgMetadata.getTxnidLeastBits()),
(PositionImpl) entry.getPosition())) {
- individualAcknowledgeMessageIfNeeded(entry.getPosition(), Collections.emptyMap());
+ individualAcknowledgeMessageIfNeeded(Collections.singletonList(entry.getPosition()),
+ Collections.emptyMap());
entries.set(i, null);
entry.release();
continue;
@@ -200,7 +202,8 @@ public int filterEntriesForConsumer(@Nullable MessageMetadata[] metadataArray, i
entries.set(i, null);
entry.release();
- individualAcknowledgeMessageIfNeeded(pos, Collections.emptyMap());
+ individualAcknowledgeMessageIfNeeded(Collections.singletonList(pos),
+ Collections.emptyMap());
continue;
} else if (trackDelayedDelivery(entry.getLedgerId(), entry.getEntryId(), msgMetadata)) {
// The message is marked for delayed delivery. Ignore for now.
@@ -271,8 +274,7 @@ public int filterEntriesForConsumer(@Nullable MessageMetadata[] metadataArray, i
}
}
if (CollectionUtils.isNotEmpty(entriesToFiltered)) {
- subscription.acknowledgeMessage(entriesToFiltered, AckType.Individual,
- Collections.emptyMap());
+ individualAcknowledgeMessageIfNeeded(entriesToFiltered, Collections.emptyMap());
int filtered = entriesToFiltered.size();
Topic topic = subscription.getTopic();
@@ -301,9 +303,9 @@ public int filterEntriesForConsumer(@Nullable MessageMetadata[] metadataArray, i
return totalEntries;
}
- private void individualAcknowledgeMessageIfNeeded(Position position, Map properties) {
+ private void individualAcknowledgeMessageIfNeeded(List positions, Map properties) {
if (!(subscription instanceof PulsarCompactorSubscription)) {
- subscription.acknowledgeMessage(Collections.singletonList(position), AckType.Individual, properties);
+ subscription.acknowledgeMessage(positions, AckType.Individual, properties);
}
}
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractTopic.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractTopic.java
index cef2dd2080cf0..90ca196792b5f 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractTopic.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/AbstractTopic.java
@@ -703,15 +703,14 @@ public CompletableFuture> addProducer(Producer producer,
log.warn("[{}] Attempting to add producer to a terminated topic", topic);
throw new TopicTerminatedException("Topic was already terminated");
}
- internalAddProducer(producer);
-
- USAGE_COUNT_UPDATER.incrementAndGet(this);
- if (log.isDebugEnabled()) {
- log.debug("[{}] [{}] Added producer -- count: {}", topic, producer.getProducerName(),
- USAGE_COUNT_UPDATER.get(this));
- }
-
- return CompletableFuture.completedFuture(producerEpoch);
+ return internalAddProducer(producer).thenApply(ignore -> {
+ USAGE_COUNT_UPDATER.incrementAndGet(this);
+ if (log.isDebugEnabled()) {
+ log.debug("[{}] [{}] Added producer -- count: {}", topic, producer.getProducerName(),
+ USAGE_COUNT_UPDATER.get(this));
+ }
+ return producerEpoch;
+ });
} catch (BrokerServiceException e) {
return FutureUtil.failedFuture(e);
} finally {
@@ -957,15 +956,17 @@ protected void checkTopicFenced() throws BrokerServiceException {
}
}
- protected void internalAddProducer(Producer producer) throws BrokerServiceException {
+ protected CompletableFuture internalAddProducer(Producer producer) {
if (isProducersExceeded(producer)) {
log.warn("[{}] Attempting to add producer to topic which reached max producers limit", topic);
- throw new BrokerServiceException.ProducerBusyException("Topic reached max producers limit");
+ return CompletableFuture.failedFuture(
+ new BrokerServiceException.ProducerBusyException("Topic reached max producers limit"));
}
if (isSameAddressProducersExceeded(producer)) {
log.warn("[{}] Attempting to add producer to topic which reached max same address producers limit", topic);
- throw new BrokerServiceException.ProducerBusyException("Topic reached max same address producers limit");
+ return CompletableFuture.failedFuture(
+ new BrokerServiceException.ProducerBusyException("Topic reached max same address producers limit"));
}
if (log.isDebugEnabled()) {
@@ -974,27 +975,46 @@ protected void internalAddProducer(Producer producer) throws BrokerServiceExcept
Producer existProducer = producers.putIfAbsent(producer.getProducerName(), producer);
if (existProducer != null) {
- tryOverwriteOldProducer(existProducer, producer);
+ return tryOverwriteOldProducer(existProducer, producer);
} else if (!producer.isRemote()) {
USER_CREATED_PRODUCER_COUNTER_UPDATER.incrementAndGet(this);
}
+ return CompletableFuture.completedFuture(null);
}
- private void tryOverwriteOldProducer(Producer oldProducer, Producer newProducer)
- throws BrokerServiceException {
- if (newProducer.isSuccessorTo(oldProducer) && !isUserProvidedProducerName(oldProducer)
- && !isUserProvidedProducerName(newProducer)) {
+ private CompletableFuture tryOverwriteOldProducer(Producer oldProducer, Producer newProducer) {
+ if (newProducer.isSuccessorTo(oldProducer)) {
oldProducer.close(false);
if (!producers.replace(newProducer.getProducerName(), oldProducer, newProducer)) {
// Met concurrent update, throw exception here so that client can try reconnect later.
- throw new BrokerServiceException.NamingException("Producer with name '" + newProducer.getProducerName()
- + "' replace concurrency error");
+ return CompletableFuture.failedFuture(new BrokerServiceException.NamingException("Producer with name '"
+ + newProducer.getProducerName() + "' replace concurrency error"));
} else {
handleProducerRemoved(oldProducer);
+ return CompletableFuture.completedFuture(null);
}
} else {
- throw new BrokerServiceException.NamingException(
- "Producer with name '" + newProducer.getProducerName() + "' is already connected to topic");
+ // If a producer with the same name tries to use a new connection, async check the old connection is
+ // available. The producers related the connection that not available are automatically cleaned up.
+ if (!Objects.equals(oldProducer.getCnx(), newProducer.getCnx())) {
+ return oldProducer.getCnx().checkConnectionLiveness().thenCompose(previousIsActive -> {
+ if (previousIsActive) {
+ return CompletableFuture.failedFuture(new BrokerServiceException.NamingException(
+ "Producer with name '" + newProducer.getProducerName()
+ + "' is already connected to topic"));
+ } else {
+ // If the connection of the previous producer is not active, the method
+ // "cnx().checkConnectionLiveness()" will trigger the close for it and kick off the previous
+ // producer. So try to add current producer again.
+ // The recursive call will be stopped by these two case(This prevents infinite call):
+ // 1. add current producer success.
+ // 2. once another same name producer registered.
+ return internalAddProducer(newProducer);
+ }
+ });
+ }
+ return CompletableFuture.failedFuture(new BrokerServiceException.NamingException(
+ "Producer with name '" + newProducer.getProducerName() + "' is already connected to topic"));
}
}
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/BrokerService.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/BrokerService.java
index 1702a23ef3db6..382bca68dffc9 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/BrokerService.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/BrokerService.java
@@ -49,6 +49,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CancellationException;
@@ -70,6 +71,7 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -825,6 +827,7 @@ public CompletableFuture closeAsync() {
for (EventLoopGroup group : protocolHandlersWorkerGroups) {
shutdownEventLoops.add(shutdownEventLoopGracefully(group));
}
+
CompletableFuture shutdownFuture =
CompletableFuture.allOf(shutdownEventLoops.toArray(new CompletableFuture[0]))
.handle((v, t) -> {
@@ -835,7 +838,7 @@ public CompletableFuture closeAsync() {
}
return null;
})
- .thenCompose(__ -> {
+ .thenComposeAsync(__ -> {
log.info("Continuing to second phase in shutdown.");
List> asyncCloseFutures = new ArrayList<>();
@@ -899,6 +902,12 @@ public CompletableFuture closeAsync() {
return null;
});
return combined;
+ }, runnable -> {
+ // run the 2nd phase of the shutdown in a separate thread
+ Thread thread = new Thread(runnable);
+ thread.setName("BrokerService-shutdown-phase2");
+ thread.setDaemon(false);
+ thread.start();
});
FutureUtil.whenCancelledOrTimedOut(shutdownFuture, () -> cancellableDownstreamFutureReference
.thenAccept(future -> future.cancel(false)));
@@ -1044,19 +1053,44 @@ public CompletableFuture> getTopic(final TopicName topicName, bo
}
final boolean isPersistentTopic = topicName.getDomain().equals(TopicDomain.persistent);
if (isPersistentTopic) {
- return topics.computeIfAbsent(topicName.toString(), (tpName) -> {
- if (topicName.isPartitioned()) {
- return fetchPartitionedTopicMetadataAsync(TopicName.get(topicName.getPartitionedTopicName()))
- .thenCompose((metadata) -> {
- // Allow crate non-partitioned persistent topic that name includes `partition`
- if (metadata.partitions == 0
- || topicName.getPartitionIndex() < metadata.partitions) {
- return loadOrCreatePersistentTopic(tpName, createIfMissing, properties);
- }
- return CompletableFuture.completedFuture(Optional.empty());
- });
- }
- return loadOrCreatePersistentTopic(tpName, createIfMissing, properties);
+ final CompletableFuture> topicPoliciesFuture =
+ getTopicPoliciesBypassSystemTopic(topicName);
+ return topicPoliciesFuture.exceptionally(ex -> {
+ final Throwable rc = FutureUtil.unwrapCompletionException(ex);
+ final String errorInfo = String.format("Topic creation encountered an exception by initialize"
+ + " topic policies service. topic_name=%s error_message=%s", topicName, rc.getMessage());
+ log.error(errorInfo, rc);
+ throw FutureUtil.wrapToCompletionException(new ServiceUnitNotReadyException(errorInfo));
+ }).thenCompose(optionalTopicPolicies -> {
+ final TopicPolicies topicPolicies = optionalTopicPolicies.orElse(null);
+ return topics.computeIfAbsent(topicName.toString(), (tpName) -> {
+ if (topicName.isPartitioned()) {
+ final TopicName topicNameEntity = TopicName.get(topicName.getPartitionedTopicName());
+ return fetchPartitionedTopicMetadataAsync(topicNameEntity)
+ .thenCompose((metadata) -> {
+ // Allow crate non-partitioned persistent topic that name includes `partition`
+ if (metadata.partitions == 0
+ || topicName.getPartitionIndex() < metadata.partitions) {
+ return loadOrCreatePersistentTopic(tpName, createIfMissing,
+ properties, topicPolicies);
+ }
+ final String errorMsg =
+ String.format("Illegal topic partition name %s with max allowed "
+ + "%d partitions", topicName, metadata.partitions);
+ log.warn(errorMsg);
+ return FutureUtil
+ .failedFuture(new BrokerServiceException.NotAllowedException(errorMsg));
+ });
+ }
+ return loadOrCreatePersistentTopic(tpName, createIfMissing, properties, topicPolicies);
+ }).thenCompose(optionalTopic -> {
+ if (!optionalTopic.isPresent() && createIfMissing) {
+ log.warn("[{}] Try to recreate the topic with createIfMissing=true "
+ + "but the returned topic is empty", topicName);
+ return getTopic(topicName, createIfMissing, properties);
+ }
+ return CompletableFuture.completedFuture(optionalTopic);
+ });
});
} else {
return topics.computeIfAbsent(topicName.toString(), (name) -> {
@@ -1110,6 +1144,18 @@ public CompletableFuture> getTopic(final TopicName topicName, bo
}
}
+ private CompletableFuture> getTopicPoliciesBypassSystemTopic(@Nonnull TopicName topicName) {
+ Objects.requireNonNull(topicName);
+ final ServiceConfiguration serviceConfiguration = pulsar.getConfiguration();
+ if (serviceConfiguration.isSystemTopicEnabled() && serviceConfiguration.isTopicLevelPoliciesEnabled()
+ && !NamespaceService.isSystemServiceNamespace(topicName.getNamespace())
+ && !SystemTopicNames.isTopicPoliciesSystemTopic(topicName.toString())) {
+ return pulsar.getTopicPoliciesService().getTopicPoliciesAsync(topicName);
+ } else {
+ return CompletableFuture.completedFuture(Optional.empty());
+ }
+ }
+
public CompletableFuture deleteTopic(String topic, boolean forceDelete) {
topicEventsDispatcher.notify(topic, TopicEvent.DELETE, EventStage.BEFORE);
CompletableFuture result = deleteTopicInternal(topic, forceDelete);
@@ -1499,7 +1545,7 @@ public PulsarAdmin getClusterPulsarAdmin(String cluster, Optional c
* @throws RuntimeException
*/
protected CompletableFuture> loadOrCreatePersistentTopic(final String topic,
- boolean createIfMissing, Map properties) throws RuntimeException {
+ boolean createIfMissing, Map properties, @Nullable TopicPolicies topicPolicies) {
final CompletableFuture> topicFuture = FutureUtil.createFutureWithTimeout(
Duration.ofSeconds(pulsar.getConfiguration().getTopicLoadTimeoutSeconds()), executor(),
() -> FAILED_TO_LOAD_TOPIC_TIMEOUT_EXCEPTION);
@@ -1517,7 +1563,8 @@ protected CompletableFuture> loadOrCreatePersistentTopic(final S
final Semaphore topicLoadSemaphore = topicLoadRequestSemaphore.get();
if (topicLoadSemaphore.tryAcquire()) {
- checkOwnershipAndCreatePersistentTopic(topic, createIfMissing, topicFuture, properties);
+ checkOwnershipAndCreatePersistentTopic(topic, createIfMissing, topicFuture,
+ properties, topicPolicies);
topicFuture.handle((persistentTopic, ex) -> {
// release permit and process pending topic
topicLoadSemaphore.release();
@@ -1526,7 +1573,7 @@ protected CompletableFuture> loadOrCreatePersistentTopic(final S
});
} else {
pendingTopicLoadingQueue.add(new TopicLoadingContext(topic,
- createIfMissing, topicFuture, properties));
+ createIfMissing, topicFuture, properties, topicPolicies));
if (log.isDebugEnabled()) {
log.debug("topic-loading for {} added into pending queue", topic);
}
@@ -1567,7 +1614,7 @@ protected CompletableFuture