diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index be18a1befd..63e7b6a4b4 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -18,7 +18,7 @@ jobs: distribution: 'zulu' java-version: '21' cache: 'maven' - server-id: ossrh + server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} @@ -28,6 +28,6 @@ jobs: - name: Publish snapshot run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06569df11c..1524b4b8ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Evaluate release type - run: ci/evaluate-release.sh - name: Set up Python uses: actions/setup-python@v5 with: @@ -21,30 +19,19 @@ jobs: distribution: 'temurin' java-version: '8' cache: 'maven' - server-id: ${{ env.maven_server_id }} + server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Get dependencies run: make deps - - name: Release AMQP Java Client (GA) - if: ${{ env.ga_release == 'true' }} + - name: Release AMQP Java Client run: | git config user.name "rabbitmq-ci" git config user.email "rabbitmq-ci@users.noreply.github.com" ci/release-java-client.sh env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - name: Release AMQP Java Client (Milestone/RC) - if: ${{ env.ga_release != 'true' }} - run: | - git config user.name "rabbitmq-ci" - git config user.email "rabbitmq-ci@users.noreply.github.com" - ci/release-java-client.sh - env: - MAVEN_USERNAME: '' - MAVEN_PASSWORD: ${{ secrets.PACKAGECLOUD_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index e3f6a21750..692ed30679 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -16,7 +16,9 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - rabbitmq-image: [ 'pivotalrabbitmq/rabbitmq:v4.0.x', 'pivotalrabbitmq/rabbitmq:main' ] + rabbitmq-image: + - pivotalrabbitmq/rabbitmq:v4.1.x-otp27 + - pivotalrabbitmq/rabbitmq:main-otp27 name: Test against ${{ matrix.rabbitmq-image }} steps: - uses: actions/checkout@v4 @@ -43,19 +45,19 @@ jobs: run: make deps - name: Test with NIO run: | - ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dtest-broker.B.nodename=hare@$(hostname) \ + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ --no-transfer-progress - name: Test with blocking IO run: | - ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dtest-broker.B.nodename=hare@$(hostname) \ - -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ - --no-transfer-progress - - name: Stop broker A - run: docker stop rabbitmq && docker rm rabbitmq - - name: Stop broker B - run: docker stop hare && docker rm hare + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + --no-transfer-progress + - name: Stop cluster + run: docker compose --file ci/cluster/docker-compose.yml down diff --git a/.github/workflows/test-supported-java-versions-5.x.yml b/.github/workflows/test-supported-java-versions-5.x.yml index 60c09da605..bb43ce3806 100644 --- a/.github/workflows/test-supported-java-versions-5.x.yml +++ b/.github/workflows/test-supported-java-versions-5.x.yml @@ -45,15 +45,18 @@ jobs: run: | ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress - name: Test with blocking IO run: | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ - -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ - --no-transfer-progress + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true - name: Stop broker run: docker stop rabbitmq && docker rm rabbitmq diff --git a/.github/workflows/test-supported-java-versions-main.yml b/.github/workflows/test-supported-java-versions-main.yml index e96dbc2c54..a5428233eb 100644 --- a/.github/workflows/test-supported-java-versions-main.yml +++ b/.github/workflows/test-supported-java-versions-main.yml @@ -43,15 +43,18 @@ jobs: run: | ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress - name: Test with blocking IO run: | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ - -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ - --no-transfer-progress + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true - name: Stop broker run: docker stop rabbitmq && docker rm rabbitmq diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8b2d5101b..97c0ab4361 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: distribution: 'zulu' java-version: '21' cache: 'maven' - server-id: ossrh + server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} @@ -41,26 +41,27 @@ jobs: run: make deps - name: Test with NIO run: | - ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dtest-broker.B.nodename=hare@$(hostname) \ + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ --no-transfer-progress - name: Test with blocking IO run: | - ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ - -Dtest-broker.A.nodename=rabbit@$(hostname) -Dtest-broker.B.nodename=hare@$(hostname) \ - -Dmaven.javadoc.skip=true \ - -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ - --no-transfer-progress - - name: Stop broker A - run: docker stop rabbitmq && docker rm rabbitmq - - name: Stop broker B - run: docker stop hare && docker rm hare + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ + --no-transfer-progress + - name: Stop cluster + run: docker compose --file ci/cluster/docker-compose.yml down - name: Publish snapshot if: ${{ github.event_name != 'pull_request' }} run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/Makefile b/Makefile index d9041b2657..f320fd349f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ $(DEPS_DIR)/rabbitmq_codegen: git clone -n --depth=1 --filter=tree:0 https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server git -C $(DEPS_DIR)/rabbitmq-server sparse-checkout set --no-cone deps/rabbitmq_codegen git -C $(DEPS_DIR)/rabbitmq-server checkout - cp -r $(DEPS_DIR)/rabbitmq-server/deps/rabbitmq_codegen "$@" + cp -R $(DEPS_DIR)/rabbitmq-server/deps/rabbitmq_codegen "$@" rm -rf $(DEPS_DIR)/rabbitmq-server tests: deps diff --git a/ci/_start-cluster.sh b/ci/_start-cluster.sh new file mode 100755 index 0000000000..6b01992d96 --- /dev/null +++ b/ci/_start-cluster.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.0} + +wait_for_message() { + while ! docker logs "$1" | grep -q "$2"; + do + sleep 5 + echo "Waiting 5 seconds for $1 to start..." + done +} + +make -C "${PWD}"/tls-gen/basic + +mv tls-gen/basic/result/server_$(hostname -s)_certificate.pem tls-gen/basic/result/server_certificate.pem +mv tls-gen/basic/result/server_$(hostname -s)_key.pem tls-gen/basic/result/server_key.pem +mv tls-gen/basic/server_$(hostname -s) tls-gen/basic/server +mv tls-gen/basic/client_$(hostname -s) tls-gen/basic/client + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls + +cp -R "${PWD}"/tls-gen/basic/* rabbitmq-configuration/tls +chmod -R o+r rabbitmq-configuration/tls/* +chmod -R g+r rabbitmq-configuration/tls/* +./mvnw -q clean resources:testResources -Dtest-tls-certs.dir=/etc/rabbitmq/tls +cp target/test-classes/rabbit@localhost.config rabbitmq-configuration/rabbit@localhost.config +cp target/test-classes/hare@localhost.config rabbitmq-configuration/hare@localhost.config + +echo "Running RabbitMQ ${RABBITMQ_IMAGE}" + +docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" +docker run -d --name rabbitmq \ + --network host \ + -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ + --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/rabbit@localhost.config \ + --env RABBITMQ_NODENAME=rabbit@$(hostname) \ + --env RABBITMQ_NODE_PORT=5672 \ + --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ + "${RABBITMQ_IMAGE}" + +# for CLI commands to share the same cookie +docker exec rabbitmq bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" +docker exec rabbitmq chmod 0600 /var/lib/rabbitmq/.erlang.cookie + +wait_for_message rabbitmq "completed with" + +docker run -d --name hare \ + --network host \ + -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ + --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/hare@localhost.config \ + --env RABBITMQ_NODENAME=hare@$(hostname) \ + --env RABBITMQ_NODE_PORT=5673 \ + --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ + "${RABBITMQ_IMAGE}" + +# for CLI commands to share the same cookie +docker exec hare bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" +docker exec hare chmod 0600 /var/lib/rabbitmq/.erlang.cookie + +wait_for_message hare "completed with" + +docker exec hare rabbitmqctl --node hare@$(hostname) status + +docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) is_running +docker exec hare rabbitmq-diagnostics --node hare@$(hostname) is_running + +docker exec hare rabbitmqctl --node hare@$(hostname) stop_app +docker exec hare rabbitmqctl --node hare@$(hostname) join_cluster rabbit@$(hostname) +docker exec hare rabbitmqctl --node hare@$(hostname) start_app + +sleep 10 + +docker exec hare rabbitmqctl --node hare@$(hostname) await_startup + +docker exec hare rabbitmqctl --node hare@$(hostname) enable_feature_flag --opt-in khepri_db +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) enable_feature_flag --opt-in khepri_db + +docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) erlang_version +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) version +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) status +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) cluster_status diff --git a/ci/cluster/configuration/rabbitmq.conf b/ci/cluster/configuration/rabbitmq.conf new file mode 100644 index 0000000000..652395d768 --- /dev/null +++ b/ci/cluster/configuration/rabbitmq.conf @@ -0,0 +1,20 @@ +cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config +cluster_formation.classic_config.nodes.1 = rabbit@node0 +cluster_formation.classic_config.nodes.2 = rabbit@node1 +cluster_formation.classic_config.nodes.3 = rabbit@node2 +loopback_users = none + +listeners.ssl.default = 5671 + +ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem +ssl_options.certfile = /etc/rabbitmq/tls/server_certificate.pem +ssl_options.keyfile = /etc/rabbitmq/tls/server_key.pem +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = false +ssl_options.honor_cipher_order = true + +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO diff --git a/ci/cluster/docker-compose.yml b/ci/cluster/docker-compose.yml new file mode 100644 index 0000000000..cf0dd75c54 --- /dev/null +++ b/ci/cluster/docker-compose.yml @@ -0,0 +1,49 @@ +services: + node0: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node0 + container_name: rabbitmq0 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5672:5672" + - "5671:5671" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ + node1: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node1 + container_name: rabbitmq1 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5673:5672" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ + node2: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node2 + container_name: rabbitmq2 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5674:5672" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ +networks: + rabbitmq-cluster: diff --git a/ci/evaluate-release.sh b/ci/evaluate-release.sh deleted file mode 100755 index 4ad656d7a0..0000000000 --- a/ci/evaluate-release.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -source ./release-versions.txt - -if [[ $RELEASE_VERSION == *[RCM]* ]] -then - echo "prerelease=true" >> $GITHUB_ENV - echo "ga_release=false" >> $GITHUB_ENV - echo "maven_server_id=packagecloud-rabbitmq-maven-milestones" >> $GITHUB_ENV -else - echo "prerelease=false" >> $GITHUB_ENV - echo "ga_release=true" >> $GITHUB_ENV - echo "maven_server_id=ossrh" >> $GITHUB_ENV -fi \ No newline at end of file diff --git a/ci/start-broker.sh b/ci/start-broker.sh index 86d81ece98..a73a08f270 100755 --- a/ci/start-broker.sh +++ b/ci/start-broker.sh @@ -2,7 +2,7 @@ LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.0} +RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.1} wait_for_message() { while ! docker logs "$1" | grep -q "$2"; @@ -12,20 +12,33 @@ wait_for_message() { done } -make -C "${PWD}"/tls-gen/basic +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls -mv tls-gen/basic/result/server_$(hostname -s)_certificate.pem tls-gen/basic/result/server_certificate.pem -mv tls-gen/basic/result/server_$(hostname -s)_key.pem tls-gen/basic/result/server_key.pem -mv tls-gen/basic/server_$(hostname -s) tls-gen/basic/server -mv tls-gen/basic/client_$(hostname -s) tls-gen/basic/client +make -C "${PWD}"/tls-gen/basic +rm -rf rabbitmq-configuration mkdir -p rabbitmq-configuration/tls +cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls +chmod o+r rabbitmq-configuration/tls/* +chmod g+r rabbitmq-configuration/tls/* + +echo "loopback_users = none + +listeners.ssl.default = 5671 + +ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem +ssl_options.certfile = /etc/rabbitmq/tls/server_$(hostname)_certificate.pem +ssl_options.keyfile = /etc/rabbitmq/tls/server_$(hostname)_key.pem +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = false +ssl_options.honor_cipher_order = true -cp -R "${PWD}"/tls-gen/basic/* rabbitmq-configuration/tls -chmod -R o+r rabbitmq-configuration/tls/* -chmod -R g+r rabbitmq-configuration/tls/* -./mvnw -q clean resources:testResources -Dtest-tls-certs.dir=/etc/rabbitmq/tls -cp target/test-classes/rabbit@localhost.config rabbitmq-configuration/rabbitmq.config +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO" >> rabbitmq-configuration/rabbitmq.conf echo "Running RabbitMQ ${RABBITMQ_IMAGE}" @@ -37,5 +50,6 @@ docker run -d --name rabbitmq \ wait_for_message rabbitmq "completed with" +docker exec rabbitmq rabbitmqctl enable_feature_flag --opt-in khepri_db docker exec rabbitmq rabbitmq-diagnostics erlang_version docker exec rabbitmq rabbitmqctl version diff --git a/ci/start-cluster.sh b/ci/start-cluster.sh index efee59ee2e..6a5042c098 100755 --- a/ci/start-cluster.sh +++ b/ci/start-cluster.sh @@ -1,80 +1,39 @@ #!/usr/bin/env bash -LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.0} +export RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.1} wait_for_message() { while ! docker logs "$1" | grep -q "$2"; do - sleep 5 - echo "Waiting 5 seconds for $1 to start..." + sleep 2 + echo "Waiting 2 seconds for $1 to start..." done } -make -C "${PWD}"/tls-gen/basic - -mv tls-gen/basic/result/server_$(hostname -s)_certificate.pem tls-gen/basic/result/server_certificate.pem -mv tls-gen/basic/result/server_$(hostname -s)_key.pem tls-gen/basic/result/server_key.pem -mv tls-gen/basic/server_$(hostname -s) tls-gen/basic/server -mv tls-gen/basic/client_$(hostname -s) tls-gen/basic/client - +rm -rf rabbitmq-configuration mkdir -p rabbitmq-configuration/tls -cp -R "${PWD}"/tls-gen/basic/* rabbitmq-configuration/tls -chmod -R o+r rabbitmq-configuration/tls/* -chmod -R g+r rabbitmq-configuration/tls/* -./mvnw -q clean resources:testResources -Dtest-tls-certs.dir=/etc/rabbitmq/tls -cp target/test-classes/rabbit@localhost.config rabbitmq-configuration/rabbit@localhost.config -cp target/test-classes/hare@localhost.config rabbitmq-configuration/hare@localhost.config - -echo "Running RabbitMQ ${RABBITMQ_IMAGE}" - -docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" -docker run -d --name rabbitmq \ - --network host \ - -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ - --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/rabbit@localhost.config \ - --env RABBITMQ_NODENAME=rabbit@$(hostname) \ - --env RABBITMQ_NODE_PORT=5672 \ - --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ - "${RABBITMQ_IMAGE}" - -# for CLI commands to share the same cookie -docker exec rabbitmq bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" -docker exec rabbitmq chmod 0600 /var/lib/rabbitmq/.erlang.cookie - -wait_for_message rabbitmq "completed with" - -docker run -d --name hare \ - --network host \ - -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ - --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/hare@localhost.config \ - --env RABBITMQ_NODENAME=hare@$(hostname) \ - --env RABBITMQ_NODE_PORT=5673 \ - --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ - "${RABBITMQ_IMAGE}" - -# for CLI commands to share the same cookie -docker exec hare bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" -docker exec hare chmod 0600 /var/lib/rabbitmq/.erlang.cookie +make -C "${PWD}"/tls-gen/basic -wait_for_message hare "completed with" +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls +cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls +mv rabbitmq-configuration/tls/server_$(hostname)_certificate.pem rabbitmq-configuration/tls/server_certificate.pem +mv rabbitmq-configuration/tls/server_$(hostname)_key.pem rabbitmq-configuration/tls/server_key.pem +chmod o+r rabbitmq-configuration/tls/* +chmod g+r rabbitmq-configuration/tls/* -docker exec hare rabbitmqctl --node hare@$(hostname) status +docker compose --file ci/cluster/docker-compose.yml down +docker compose --file ci/cluster/docker-compose.yml up --detach -docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) is_running -docker exec hare rabbitmq-diagnostics --node hare@$(hostname) is_running +wait_for_message rabbitmq0 "completed with" -docker exec hare rabbitmqctl --node hare@$(hostname) stop_app -docker exec hare rabbitmqctl --node hare@$(hostname) join_cluster rabbit@$(hostname) -docker exec hare rabbitmqctl --node hare@$(hostname) start_app +docker exec rabbitmq0 rabbitmqctl await_online_nodes 3 -sleep 10 +docker exec rabbitmq0 rabbitmqctl enable_feature_flag --opt-in khepri_db +docker exec rabbitmq1 rabbitmqctl enable_feature_flag --opt-in khepri_db +docker exec rabbitmq2 rabbitmqctl enable_feature_flag --opt-in khepri_db -docker exec hare rabbitmqctl --node hare@$(hostname) await_startup +docker exec rabbitmq0 rabbitmqctl cluster_status -docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) erlang_version -docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) version -docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) status -docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) cluster_status +docker compose --file ci/cluster/docker-compose.yml ps diff --git a/pom.xml b/pom.xml index e200535bf4..4bbc1a2394 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.rabbitmq amqp-client - 5.25.0 + 5.26.0 jar RabbitMQ Java Client @@ -42,7 +42,7 @@ https://github.com/rabbitmq/rabbitmq-java-client scm:git:git://github.com/rabbitmq/rabbitmq-java-client.git scm:git:https://github.com/rabbitmq/rabbitmq-java-client.git - v5.25.0 + v5.26.0 @@ -56,20 +56,20 @@ true 1.7.36 - 4.2.30 - 1.14.3 - 1.46.0 - 2.18.2 + 4.2.33 + 1.15.2 + 1.52.0 + 2.19.2 1.2.13 - 5.11.4 - 5.15.2 + 5.13.4 + 5.18.0 3.27.3 - 1.4.2 + 1.5.2 1.0.4 9.4.57.v20241219 - 1.80 + 1.81 0.10 - 2.12.1 + 2.13.1 3.11.2 3.1.1 @@ -78,20 +78,18 @@ 3.3.1 2.1.1 2.4.21 - 1.7 - 3.6.0 - 3.13.0 - 3.5.2 + 3.6.1 + 3.14.0 + 3.5.3 3.8.1 - 3.5.2 - 3.2.7 + 3.5.3 + 3.2.8 3.4.2 5.1.9 - 0.0.6 - 1.7.0 1.11 + 0.8.0 1.4 - 2.44.2 + 2.46.1 1.19.2 6026DFCA @@ -185,35 +177,6 @@ - - - use-provided-test-keystores - - - ${test-tls-certs.dir}/testca/cacert.pem - - - - - - org.codehaus.mojo - keytool-maven-plugin - ${keytool.maven.plugin.version} - - false - - - - - - - - milestone - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven.javadoc.plugin.version} - - ${javadoc.opts} - ${javadoc.joption} - true - 8 - - - - - jar - - - - - - - net.nicoulaj.maven.plugins - checksum-maven-plugin - ${checksum.maven.plugin.version} - - - sign-artifacts - package - - files - - - - - ${project.build.directory} - - *.jar - *.pom - - - - - MD5 - SHA-1 - - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven.gpg.plugin.version} - - - sign-artifacts - package - - sign - - - ${gpg.keyname} - - - - - - - - - packagecloud-rabbitmq-maven-milestones - packagecloud+https://packagecloud.io/rabbitmq/maven-milestones - - - mockito-4-on-java-8 @@ -683,7 +530,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 5.0.0.4389 + 5.1.0.4751 @@ -773,24 +620,6 @@ - - - - generate-test-resources - remove-old-test-keystores - - execute - - - - ${groovy-scripts.dir}/remove_old_test_keystores.groovy - - - @@ -840,47 +669,6 @@ - - - org.codehaus.mojo - keytool-maven-plugin - ${keytool.maven.plugin.version} - - true - - - - generate-test-ca-keystore - generate-test-resources - - importCertificate - - - ${test-tls-certs.dir}/testca/cacert.pem - ${test-keystore.ca} - ${test-keystore.password} - true - server1 - - - - generate-test-empty-keystore - generate-test-resources - - importCertificate - deleteAlias - - - ${test-tls-certs.dir}/testca/cacert.pem - ${test-keystore.empty} - ${test-keystore.password} - true - server1 - - - - - org.apache.maven.plugins maven-jar-plugin @@ -1010,14 +798,18 @@ + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + false + + + - - - io.packagecloud.maven.wagon - maven-packagecloud-wagon - ${maven.packagecloud.wagon.version} - - diff --git a/release-versions.txt b/release-versions.txt index 8b3d8b35c8..8550fe9f2f 100644 --- a/release-versions.txt +++ b/release-versions.txt @@ -1,3 +1,3 @@ -RELEASE_VERSION="5.25.0" -DEVELOPMENT_VERSION="5.26.0-SNAPSHOT" +RELEASE_VERSION="5.26.0" +DEVELOPMENT_VERSION="5.27.0-SNAPSHOT" RELEASE_BRANCH="5.x.x-stable" diff --git a/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java b/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java index ec66056361..887578a7a6 100644 --- a/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java +++ b/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java @@ -160,9 +160,12 @@ public void basicConsume(Channel channel, String consumerTag, boolean autoAck) { try { if(!autoAck) { ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } channelState.lock.lock(); try { - channelState(channel).consumersWithManualAck.add(consumerTag); + channelState.consumersWithManualAck.add(consumerTag); } finally { channelState.lock.unlock(); } @@ -176,9 +179,12 @@ public void basicConsume(Channel channel, String consumerTag, boolean autoAck) { public void basicCancel(Channel channel, String consumerTag) { try { ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } channelState.lock.lock(); try { - channelState(channel).consumersWithManualAck.remove(consumerTag); + channelState.consumersWithManualAck.remove(consumerTag); } finally { channelState.lock.unlock(); } @@ -193,9 +199,12 @@ public void consumedMessage(Channel channel, long deliveryTag, boolean autoAck) markConsumedMessage(); if(!autoAck) { ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } channelState.lock.lock(); try { - channelState(channel).unackedMessageDeliveryTags.add(deliveryTag); + channelState.unackedMessageDeliveryTags.add(deliveryTag); } finally { channelState.lock.unlock(); } @@ -210,6 +219,9 @@ public void consumedMessage(Channel channel, long deliveryTag, String consumerTa try { markConsumedMessage(); ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } channelState.lock.lock(); try { if(channelState.consumersWithManualAck.contains(consumerTag)) { diff --git a/src/main/java/com/rabbitmq/client/impl/ChannelN.java b/src/main/java/com/rabbitmq/client/impl/ChannelN.java index b44c60e868..4ee3712731 100644 --- a/src/main/java/com/rabbitmq/client/impl/ChannelN.java +++ b/src/main/java/com/rabbitmq/client/impl/ChannelN.java @@ -33,6 +33,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; /** * Main interface to AMQP protocol functionality. Public API - @@ -609,10 +610,13 @@ protected void close(int closeCode, signal.initCause(cause); } + AtomicBoolean finishProcessShutdownSignalCalled = new AtomicBoolean(false); BlockingRpcContinuation k = new BlockingRpcContinuation(){ @Override public AMQCommand transformReply(AMQCommand command) { - ChannelN.this.finishProcessShutdownSignal(); + if (finishProcessShutdownSignalCalled.compareAndSet(false, true)) { + ChannelN.this.finishProcessShutdownSignal(); + } return command; }}; boolean notify = false; @@ -643,6 +647,13 @@ public AMQCommand transformReply(AMQCommand command) { if (!abort) throw ioe; } finally { + if (finishProcessShutdownSignalCalled.compareAndSet(false, true)) { + try { + ChannelN.this.finishProcessShutdownSignal(); + } catch (Exception e) { + LOGGER.info("Error while processing shutdown signal: {}", e.getMessage()); + } + } if (abort || notify) { // Now we know everything's been cleaned up and there should // be no more surprises arriving on the wire. Release the diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java index 84ceee72b9..f78701ce67 100644 --- a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java @@ -540,9 +540,9 @@ public String basicConsume(String queue, boolean autoAck, Map ar @Override public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException { - final String result = delegate.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback); - recordConsumer(result, queue, autoAck, exclusive, arguments, callback); - return result; + final String tag = delegate.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback); + recordConsumer(tag, queue, autoAck, exclusive, arguments, callback); + return tag; } @Override @@ -886,7 +886,7 @@ private void deleteRecordedExchange(String exchange) { this.connection.deleteRecordedExchange(exchange); } - private void recordConsumer(String result, + private void recordConsumer(String consumerTag, String queue, boolean autoAck, boolean exclusive, @@ -894,12 +894,12 @@ private void recordConsumer(String result, Consumer callback) { RecordedConsumer consumer = new RecordedConsumer(this, queue). autoAck(autoAck). - consumerTag(result). + consumerTag(consumerTag). exclusive(exclusive). arguments(arguments). consumer(callback); - this.consumerTags.add(result); - this.connection.recordConsumer(result, consumer); + this.consumerTags.add(consumerTag); + this.connection.recordConsumer(consumerTag, consumer); } /** diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java index ca5131687f..adc804d622 100644 --- a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java @@ -1068,7 +1068,8 @@ void deleteRecordedQueue(String queue) { /** * Exclude the queue from the list of queues to recover after connection failure. - * Intended to be used in usecases where you want to remove the queue from this connection's recovery list but don't want to delete the queue from the server. + * Intended to be used in scenarios where you want to remove the queue from this connection's recovery list but don't want to delete the queue from the server. + * For example, in tests. * * @param queue queue name to exclude from recorded recovery queues * @param ifUnused if true, the RecordedQueue will only be excluded if no local consumers are using it. @@ -1094,8 +1095,12 @@ void recordExchange(String exchange, RecordedExchange x) { void deleteRecordedExchange(String exchange) { this.recordedExchanges.remove(exchange); - Set xs = this.removeBindingsWithDestination(exchange); - for (RecordedBinding b : xs) { + Set xs1 = this.removeBindingsWithDestination(exchange); + for (RecordedBinding b : xs1) { + this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); + } + Set xs2 = this.removeBindingsWithSource(exchange); + for (RecordedBinding b : xs2) { this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); } } @@ -1113,8 +1118,8 @@ void maybeDeleteRecordedAutoDeleteQueue(String queue) { synchronized (this.recordedQueues) { if(!hasMoreConsumersOnQueue(this.consumers.values(), queue)) { RecordedQueue q = this.recordedQueues.get(queue); - // last consumer on this connection is gone, remove recorded queue - // if it is auto-deleted. See bug 26364. + // the last consumer on this connection is gone, remove the recorded queue + // if it is auto-deleted if(q != null && q.isAutoDelete()) { deleteRecordedQueue(queue); } @@ -1127,8 +1132,8 @@ void maybeDeleteRecordedAutoDeleteExchange(String exchange) { synchronized (this.recordedExchanges) { if(!hasMoreDestinationsBoundToExchange(Utility.copy(this.recordedBindings), exchange)) { RecordedExchange x = this.recordedExchanges.get(exchange); - // last binding where this exchange is the source is gone, remove recorded exchange - // if it is auto-deleted. See bug 26364. + // the last binding where this exchange is the source is gone, remove the recorded exchange + // if it is auto-deleted if(x != null && x.isAutoDelete()) { deleteRecordedExchange(exchange); } @@ -1159,11 +1164,19 @@ boolean hasMoreConsumersOnQueue(Collection consumers, String q } Set removeBindingsWithDestination(String s) { + return this.removeBindingsWithCondition(b -> b.getDestination().equals(s)); + } + + Set removeBindingsWithSource(String s) { + return this.removeBindingsWithCondition(b -> b.getSource().equals(s)); + } + + private Set removeBindingsWithCondition(Predicate condition) { final Set result = new LinkedHashSet<>(); synchronized (this.recordedBindings) { for (Iterator it = this.recordedBindings.iterator(); it.hasNext(); ) { RecordedBinding b = it.next(); - if(b.getDestination().equals(s)) { + if (condition.test(b)) { it.remove(); result.add(b); } diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java b/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java index 9250c74f7f..491a34fcd8 100644 --- a/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java @@ -16,7 +16,7 @@ package com.rabbitmq.client.impl.recovery; /** - * The retry of a retried topology recovery operation. + * The retry of a retriable topology recovery operation. * * @since 5.4.0 */ diff --git a/src/main/java/com/rabbitmq/utility/BlockingCell.java b/src/main/java/com/rabbitmq/utility/BlockingCell.java index 0792ebde71..dae25c1b5e 100644 --- a/src/main/java/com/rabbitmq/utility/BlockingCell.java +++ b/src/main/java/com/rabbitmq/utility/BlockingCell.java @@ -16,27 +16,24 @@ package com.rabbitmq.utility; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * Simple one-shot IPC mechanism. Essentially a one-place buffer that cannot be emptied once filled. */ public class BlockingCell { - /** Indicator of not-yet-filledness */ - private boolean _filled = false; - /** Will be null until a value is supplied, and possibly still then. */ - private T _value; + private final AtomicReference value = new AtomicReference<>(); + private final CountDownLatch latch = new CountDownLatch(1); private static final long NANOS_IN_MILLI = 1000L * 1000L; private static final long INFINITY = -1; - /** Instantiate a new BlockingCell waiting for a value of the specified type. */ - public BlockingCell() { - // no special handling required in default constructor - } - /** * Wait for a value, and when one arrives, return it (without clearing it). If there's already a value present, there's no need to wait - the existing value * is returned. @@ -44,11 +41,9 @@ public BlockingCell() { * * @throws InterruptedException if this thread is interrupted */ - public synchronized T get() throws InterruptedException { - while (!_filled) { - wait(); - } - return _value; + public T get() throws InterruptedException { + this.latch.await(); + return this.value.get(); } /** @@ -60,30 +55,27 @@ public synchronized T get() throws InterruptedException { * @return the waited-for value * @throws InterruptedException if this thread is interrupted */ - public synchronized T get(long timeout) throws InterruptedException, TimeoutException { + public T get(long timeout) throws InterruptedException, TimeoutException { if (timeout == INFINITY) return get(); if (timeout < 0) { throw new IllegalArgumentException("Timeout cannot be less than zero"); } - long now = System.nanoTime() / NANOS_IN_MILLI; - long maxTime = now + timeout; - while (!_filled && (now = (System.nanoTime() / NANOS_IN_MILLI)) < maxTime) { - wait(maxTime - now); - } + boolean done = this.latch.await(timeout, MILLISECONDS); - if (!_filled) + if (!done) { throw new TimeoutException(); + } - return _value; + return this.value.get(); } /** * As get(), but catches and ignores InterruptedException, retrying until a value appears. * @return the waited-for value */ - public synchronized T uninterruptibleGet() { + public T uninterruptibleGet() { boolean wasInterrupted = false; try { while (true) { @@ -110,7 +102,7 @@ public synchronized T uninterruptibleGet() { * @param timeout timeout in milliseconds. -1 means 'infinity': never time out * @return the waited-for value */ - public synchronized T uninterruptibleGet(int timeout) throws TimeoutException { + public T uninterruptibleGet(int timeout) throws TimeoutException { long now = System.nanoTime() / NANOS_IN_MILLI; long runTime = now + timeout; boolean wasInterrupted = false; @@ -128,7 +120,6 @@ public synchronized T uninterruptibleGet(int timeout) throws TimeoutException { Thread.currentThread().interrupt(); } } - throw new TimeoutException(); } @@ -136,13 +127,12 @@ public synchronized T uninterruptibleGet(int timeout) throws TimeoutException { * Store a value in this BlockingCell, throwing {@link IllegalStateException} if the cell already has a value. * @param newValue the new value to store */ - public synchronized void set(T newValue) { - if (_filled) { + public void set(T newValue) { + if (this.value.compareAndSet(null, newValue)) { + this.latch.countDown(); + } else { throw new IllegalStateException("BlockingCell can only be set once"); } - _value = newValue; - _filled = true; - notifyAll(); } /** @@ -150,11 +140,12 @@ public synchronized void set(T newValue) { * @return true if this call to setIfUnset actually updated the BlockingCell; false if the cell already had a value. * @param newValue the new value to store */ - public synchronized boolean setIfUnset(T newValue) { - if (_filled) { + public boolean setIfUnset(T newValue) { + if (this.value.compareAndSet(null, newValue)) { + this.latch.countDown(); + return true; + } else { return false; } - set(newValue); - return true; } } diff --git a/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java b/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java index 41f93249dd..7201277455 100644 --- a/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java +++ b/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java @@ -21,11 +21,11 @@ public class BlockingValueOrException> extends BlockingCell> { public void setValue(V v) { - super.set(ValueOrException.makeValue(v)); + super.set(ValueOrException.makeValue(v)); } public void setException(E e) { - super.set(ValueOrException.makeException(e)); + super.set(ValueOrException.makeException(e)); } public V uninterruptibleGetValue() throws E { diff --git a/src/main/scripts/query_test_tls_certs_dir.groovy b/src/main/scripts/query_test_tls_certs_dir.groovy deleted file mode 100644 index 2c86bb8c10..0000000000 --- a/src/main/scripts/query_test_tls_certs_dir.groovy +++ /dev/null @@ -1,25 +0,0 @@ -String[] command = [ - properties['make.bin'], - '-C', properties['rabbitmq.dir'], - '--no-print-directory', - 'show-test-tls-certs-dir', - "DEPS_DIR=${properties['deps.dir']}", -] - -def pb = new ProcessBuilder(command) -pb.redirectErrorStream(true) - -def process = pb.start() - -// We are only interested in the last line of output. Previous lines, if -// any, are related to the generation of the test certificates. -def whole_output = "" -process.inputStream.eachLine { - whole_output += it - project.properties['test-tls-certs.dir'] = it.trim() -} -process.waitFor() -if (process.exitValue() != 0) { - println(whole_output.trim()) - fail("Failed to query test TLS certs directory with command: ${command.join(' ')}") -} diff --git a/src/main/scripts/remove_old_test_keystores.groovy b/src/main/scripts/remove_old_test_keystores.groovy deleted file mode 100644 index e08775e4e0..0000000000 --- a/src/main/scripts/remove_old_test_keystores.groovy +++ /dev/null @@ -1,8 +0,0 @@ -def dir = new File(project.build.directory) - -// This pattern starts with `.*`. This is normally useless and even -// inefficient but the matching doesn't work without it... -def pattern = ~/.*\.keystore$/ -dir.eachFileMatch(pattern) { file -> - file.delete() -} diff --git a/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java b/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java index bd41d2a2cd..dcffa744b2 100644 --- a/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java +++ b/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java @@ -1,4 +1,5 @@ -// Copyright (c) 2023-2024 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// Copyright (c) 2023-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 @@ -24,9 +25,7 @@ import com.rabbitmq.client.test.server.ServerTestSuite; import com.rabbitmq.client.test.ssl.SslTestSuite; import com.rabbitmq.tools.Host; -import java.io.File; import java.net.Socket; -import java.util.Properties; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -36,31 +35,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AmqpClientTestExtension implements ExecutionCondition, BeforeAllCallback, - BeforeEachCallback, - AfterEachCallback { +public class AmqpClientTestExtension + implements ExecutionCondition, BeforeAllCallback, BeforeEachCallback, AfterEachCallback { private static final Logger LOGGER = LoggerFactory.getLogger(AmqpClientTestExtension.class); - static { - Properties TESTS_PROPS = new Properties(System.getProperties()); - String make = System.getenv("MAKE"); - if (make != null) { - TESTS_PROPS.setProperty("make.bin", make); - } - try { - TESTS_PROPS.load(Host.class.getClassLoader().getResourceAsStream("config.properties")); - } catch (Exception e) { - System.out.println( - "\"build.properties\" or \"config.properties\" not found" + - " in classpath. Please copy \"build.properties\" and" + - " \"config.properties\" into src/test/resources. Ignore" + - " this message if running with ant."); - } finally { - System.setProperties(TESTS_PROPS); - } - } - private static boolean isFunctionalSuite(ExtensionContext context) { return isTestSuite(context, FunctionalTestSuite.class); } @@ -86,8 +65,7 @@ public static boolean requiredProperties() { String rabbitmqctl = Host.rabbitmqctlCommand(); if (rabbitmqctl == null) { System.err.println( - "rabbitmqctl required; please set \"rabbitmqctl.bin\" system" + - " property"); + "rabbitmqctl required; please set \"rabbitmqctl.bin\" system" + " property"); return false; } @@ -95,20 +73,7 @@ public static boolean requiredProperties() { } public static boolean isSSLAvailable() { - String sslClientCertsDir = System.getProperty("test-client-cert.path"); - String hostname = System.getProperty("broker.hostname"); - String port = System.getProperty("broker.sslport"); - if (sslClientCertsDir == null || hostname == null || port == null) { - return false; - } - - // If certificate is present and some server is listening on port 5671 - if (new File(sslClientCertsDir).exists() && - checkServerListening(hostname, Integer.parseInt(port))) { - return true; - } else { - return false; - } + return checkServerListening("localhost", 5671); } private static boolean checkServerListening(String host, int port) { @@ -132,40 +97,42 @@ private static boolean checkServerListening(String host, int port) { public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { // HA test suite must be checked first because it contains other test suites if (isHaSuite(context)) { - return requiredProperties() ? enabled("Required properties available") + return requiredProperties() + ? enabled("Required properties available") : disabled("Required properties not available"); } else if (isServerSuite(context)) { - return requiredProperties() ? enabled("Required properties available") + return requiredProperties() + ? enabled("Required properties available") : disabled("Required properties not available"); } else if (isFunctionalSuite(context)) { - return requiredProperties() ? enabled("Required properties available") + return requiredProperties() + ? enabled("Required properties available") : disabled("Required properties not available"); } else if (isSslSuite(context)) { - return requiredProperties() && isSSLAvailable() ? enabled( - "Required properties and TLS available") + return requiredProperties() && isSSLAvailable() + ? enabled("Required properties and TLS available") : disabled("Required properties or TLS not available"); } return enabled("ok"); } @Override - public void beforeAll(ExtensionContext context) { - - } + public void beforeAll(ExtensionContext context) {} @Override public void beforeEach(ExtensionContext context) { LOGGER.info( "Starting test: {}.{} (nio? {})", - context.getTestClass().get().getSimpleName(), context.getTestMethod().get().getName(), - TestUtils.USE_NIO - ); + context.getTestClass().get().getSimpleName(), + context.getTestMethod().get().getName(), + TestUtils.USE_NIO); } @Override public void afterEach(ExtensionContext context) { - LOGGER.info("Test finished: {}.{}", - context.getTestClass().get().getSimpleName(), context.getTestMethod().get().getName()); + LOGGER.info( + "Test finished: {}.{}", + context.getTestClass().get().getSimpleName(), + context.getTestMethod().get().getName()); } - } diff --git a/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java b/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java index 19d874ced6..8306c10556 100644 --- a/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java +++ b/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java @@ -41,9 +41,8 @@ public class WorkPoolTests { /** * Test add work and remove work - * @throws Exception untested */ - @Test public void basicInOut() throws Exception { + @Test public void basicInOut() { Object one = new Object(); Object two = new Object(); @@ -70,9 +69,8 @@ public class WorkPoolTests { /** * Test add work when work in progress. - * @throws Exception untested */ - @Test public void workInWhileInProgress() throws Exception { + @Test public void workInWhileInProgress() { Object one = new Object(); Object two = new Object(); @@ -98,9 +96,8 @@ public class WorkPoolTests { /** * Test multiple work keys. - * @throws Exception untested */ - @Test public void interleavingKeys() throws Exception { + @Test public void interleavingKeys() { Object one = new Object(); Object two = new Object(); Object three = new Object(); @@ -129,9 +126,8 @@ public class WorkPoolTests { /** * Test removal of key (with work) - * @throws Exception untested */ - @Test public void unregisterKey() throws Exception { + @Test public void unregisterKey() { Object one = new Object(); Object two = new Object(); Object three = new Object(); @@ -154,9 +150,8 @@ public class WorkPoolTests { /** * Test removal of all keys (with work). - * @throws Exception untested */ - @Test public void unregisterAllKeys() throws Exception { + @Test public void unregisterAllKeys() { Object one = new Object(); Object two = new Object(); Object three = new Object(); diff --git a/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java b/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java index d3ceb146fc..7af27fbfde 100644 --- a/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java +++ b/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java @@ -1,4 +1,5 @@ -// Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 @@ -18,11 +19,18 @@ import static com.rabbitmq.client.test.TestUtils.LatchConditions.completed; import static com.rabbitmq.client.test.TestUtils.waitAtMost; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; + +import java.time.Duration; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.MessageProperties; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -59,4 +67,38 @@ void errorInBlockListenerShouldCloseConnection(boolean nio) throws Exception { waitAtMost(() -> !c.isOpen()); } + @Test + void shutdownListenerShouldBeCalledWhenChannelDies() throws Exception { + long confirmTimeout = Duration.ofSeconds(2).toMillis(); + ConnectionFactory cf = TestUtils.connectionFactory(); + Connection c = cf.newConnection(); + boolean blocked = false; + try { + CountDownLatch blockedLatch = new CountDownLatch(1); + c.addBlockedListener(reason -> blockedLatch.countDown(), () -> {}); + Channel ch = c.createChannel(); + String q = ch.queueDeclare().getQueue(); + CountDownLatch consShutdownLatch = new CountDownLatch(1); + ch.basicConsume(q, (ctag, msg) -> { }, (ctag, r) -> consShutdownLatch.countDown()); + CountDownLatch chShutdownLatch = new CountDownLatch(1); + ch.addShutdownListener(cause -> chShutdownLatch.countDown()); + ch.confirmSelect(); + ch.basicPublish("", "", MessageProperties.BASIC, "".getBytes()); + ch.waitForConfirmsOrDie(confirmTimeout); + block(); + blocked = true; + ch.basicPublish("", "", MessageProperties.BASIC, "".getBytes()); + assertThat(blockedLatch).is(completed()); + ch.basicPublish("", "", MessageProperties.BASIC, "".getBytes()); + assertThatThrownBy(() -> ch.waitForConfirmsOrDie(confirmTimeout)) + .isInstanceOf(TimeoutException.class); + assertThat(consShutdownLatch).is(completed()); + assertThat(chShutdownLatch).is(completed()); + } finally { + if (blocked) { + unblock(); + } + c.close(); + } + } } diff --git a/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java b/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java index 22b2042f71..cdd988930e 100644 --- a/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java +++ b/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java @@ -315,12 +315,12 @@ protected void clearResourceAlarm(String source) throws IOException { Host.clearResourceAlarm(source); } - protected void block() throws IOException, InterruptedException { + protected void block() throws IOException { Host.rabbitmqctl("set_vm_memory_high_watermark 0.000000001"); setResourceAlarm("disk"); } - protected void unblock() throws IOException, InterruptedException { + protected void unblock() throws IOException { Host.rabbitmqctl("set_vm_memory_high_watermark 0.4"); clearResourceAlarm("disk"); } diff --git a/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java b/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java index 1fb6c2e826..acc8266c17 100644 --- a/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java +++ b/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java @@ -39,11 +39,7 @@ protected void createResources() throws IOException, TimeoutException { @Override protected void releaseResources() throws IOException { channel.queueDelete(queue); - try { - unblock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + unblock(); } @Test public void shutdownListener() throws Exception { @@ -78,13 +74,7 @@ protected void releaseResources() throws IOException { final CountDownLatch latch = new CountDownLatch(1); try(Connection connection = TestUtils.connectionFactory().newConnection()) { connection.addBlockedListener( - reason -> { - try { - unblock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }, + reason -> unblock(), () -> latch.countDown() ); block(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java b/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java index 8dfb268ae5..f50f0aba3d 100644 --- a/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java @@ -865,6 +865,21 @@ public void handleDelivery(String consumerTag, Envelope envelope, BasicPropertie } } + @Test public void thatBindingFromDeletedExchangeIsDeleted() throws IOException, InterruptedException { + String q = generateQueueName(); + channel.queueDeclare(q, false, false, false, null); + try { + String x = generateExchangeName(); + channel.exchangeDeclare(x, "fanout"); + channel.queueBind(q, x, ""); + assertRecordedBinding(connection, 1); + channel.exchangeDelete(x); + assertRecordedBinding(connection, 0); + } finally { + channel.queueDelete(q); + } + } + private void assertConsumerCount(int exp, String q) throws IOException { assertThat(channel.queueDeclarePassive(q).getConsumerCount()).isEqualTo(exp); } @@ -1017,4 +1032,8 @@ private static void assertRecordedQueues(Connection conn, int size) { private static void assertRecordedExchanges(Connection conn, int size) { assertThat(((AutorecoveringConnection)conn).getRecordedExchanges()).hasSize(size); } + + private static void assertRecordedBinding(Connection conn, int size) { + assertThat(((AutorecoveringConnection)conn).getRecordedBindings()).hasSize(size); + } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java b/src/test/java/com/rabbitmq/client/test/functional/ConsumerNotifications.java similarity index 83% rename from src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java rename to src/test/java/com/rabbitmq/client/test/functional/ConsumerNotifications.java index e7f32dedbe..3f32da1521 100644 --- a/src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ConsumerNotifications.java @@ -28,10 +28,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -public class ConsumerCancelNotification extends BrokerTestCase { +public class ConsumerNotifications extends BrokerTestCase { private final String queue = "cancel_notification_queue"; @@ -42,7 +43,7 @@ public class ConsumerCancelNotification extends BrokerTestCase { channel.queueDeclare(queue, false, true, false, null); Consumer consumer = new DefaultConsumer(channel) { @Override - public void handleCancel(String consumerTag) throws IOException { + public void handleCancel(String consumerTag) { try { result.put(true); } catch (InterruptedException e) { @@ -55,7 +56,31 @@ public void handleCancel(String consumerTag) throws IOException { assertTrue(result.take()); } - class AlteringConsumer extends DefaultConsumer { + @Test public void consumerCancellationHandlerUsesBlockingOperations() + throws IOException, InterruptedException { + final String altQueue = "basic.cancel.fallback"; + channel.queueDeclare(queue, false, true, false, null); + + CountDownLatch latch = new CountDownLatch(1); + final AlteringConsumer consumer = new AlteringConsumer(channel, altQueue, latch); + + channel.basicConsume(queue, consumer); + channel.queueDelete(queue); + + latch.await(2, TimeUnit.SECONDS); + } + + @Test + void handleShutdownShouldBeCalledWhenChannelIsClosed() throws Exception { + Channel ch = connection.createChannel(); + String q = ch.queueDeclare().getQueue(); + CountDownLatch latch = new CountDownLatch(1); + ch.basicConsume(q, (ctag, msg) -> {}, (ctag, r) -> latch.countDown()); + ch.close(); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + + private static class AlteringConsumer extends DefaultConsumer { private final String altQueue; private final CountDownLatch latch; @@ -81,18 +106,4 @@ public void handleCancel(String consumerTag) { } } } - - @Test public void consumerCancellationHandlerUsesBlockingOperations() - throws IOException, InterruptedException { - final String altQueue = "basic.cancel.fallback"; - channel.queueDeclare(queue, false, true, false, null); - - CountDownLatch latch = new CountDownLatch(1); - final AlteringConsumer consumer = new AlteringConsumer(channel, altQueue, latch); - - channel.basicConsume(queue, consumer); - channel.queueDelete(queue); - - latch.await(2, TimeUnit.SECONDS); - } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java b/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java index 06c788da12..767e32df02 100644 --- a/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java +++ b/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java @@ -76,18 +76,25 @@ protected void releaseResources() throws IOException { channel.queueBind("q", "x", "k"); stopSecondary(); + boolean restarted = false; + try { + deleteExchange("x"); - deleteExchange("x"); - - startSecondary(); + startSecondary(); + restarted = true; - declareTransientTopicExchange("x"); + declareTransientTopicExchange("x"); - basicPublishVolatile("x", "k"); - assertDelivered("q", 0); + basicPublishVolatile("x", "k"); + assertDelivered("q", 0); - deleteQueue("q"); - deleteExchange("x"); + deleteQueue("q"); + deleteExchange("x"); + } finally { + if (!restarted) { + startSecondary(); + } + } } } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java b/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java index 650e9f2333..4fef629cf4 100644 --- a/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java +++ b/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java @@ -53,7 +53,7 @@ DefaultExchange.class, UnbindAutoDeleteExchange.class, Confirm.class, - ConsumerCancelNotification.class, + ConsumerNotifications.class, UnexpectedFrames.class, PerQueueTTL.class, PerMessageTTL.class, diff --git a/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java b/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java index cc8be03e08..3de06c3d85 100644 --- a/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java +++ b/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java @@ -35,11 +35,7 @@ public class BlockedConnection extends BrokerTestCase { protected void releaseResources() throws IOException { - try { - unblock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + unblock(); } // this test first opens a connection, then triggers // and alarm and blocks @@ -79,14 +75,10 @@ private Connection connection(final CountDownLatch latch) throws IOException, Ti Connection connection = factory.newConnection(); connection.addBlockedListener(new BlockedListener() { public void handleBlocked(String reason) throws IOException { - try { - unblock(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + unblock(); } - public void handleUnblocked() throws IOException { + public void handleUnblocked() { latch.countDown(); } }); diff --git a/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java b/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java index 813e4d23f5..e3bd70a7b6 100644 --- a/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java +++ b/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java @@ -48,14 +48,26 @@ public class LoopbackUsers { @Test public void loopback() throws IOException, TimeoutException { if (!Host.isOnDocker()) { String addr = findRealIPAddress().getHostAddress(); - assertGuestFail(addr); - Host.rabbitmqctl("eval 'application:set_env(rabbit, loopback_users, []).'"); - assertGuestSucceed(addr); - Host.rabbitmqctl("eval 'application:set_env(rabbit, loopback_users, [<<\"guest\">>]).'"); - assertGuestFail(addr); + String initialValue = getLoopbackUsers(); + try { + setLoopbackUsers("[]"); + assertGuestSucceed(addr); + setLoopbackUsers("[<<\"guest\">>]"); + assertGuestFail(addr); + } finally { + setLoopbackUsers(initialValue); + } } } + private static String getLoopbackUsers() throws IOException { + return Host.rabbitmqctl("eval '{ok, V} = application:get_env(rabbit, loopback_users), V.'").output(); + } + + private static void setLoopbackUsers(String value) throws IOException { + Host.rabbitmqctl(String.format("eval 'application:set_env(rabbit, loopback_users, %s).'", value)); + } + private void assertGuestSucceed(String addr) throws IOException, TimeoutException { succeedConnect("guest", addr); succeedConnect("guest", "localhost"); diff --git a/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java index 65815d4222..f85829c119 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java @@ -1,4 +1,5 @@ -// Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 @@ -15,82 +16,38 @@ package com.rabbitmq.client.test.ssl; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import com.rabbitmq.tools.Host; import java.io.FileInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.stream.Collectors; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; class TlsTestUtils { + static final String[] PROTOCOLS = new String[] {"TLSv1.3", "TLSv1.2"}; + private TlsTestUtils() {} static SSLContext badVerifiedSslContext() throws Exception { - return verifiedSslContext(() -> getSSLContext(), emptyKeystoreCa()); + return sslContext(trustManagerFactory(clientCertificate())); } static SSLContext verifiedSslContext() throws Exception { - return verifiedSslContext(() -> getSSLContext(), keystoreCa()); - } - - static SSLContext verifiedSslContext(CallableSupplier sslContextSupplier) throws Exception { - return verifiedSslContext(sslContextSupplier, keystoreCa()); - } - - static SSLContext verifiedSslContext(CallableSupplier sslContextSupplier, String keystorePath) throws Exception { - // for local testing, run ./mvnw test-compile -Dtest-tls-certs.dir=/tmp/tls-gen/basic - // (generates the Java keystores) - assertNotNull(keystorePath); - String keystorePasswd = keystorePassword(); - assertNotNull(keystorePasswd); - char [] keystorePassword = keystorePasswd.toCharArray(); - - KeyStore tks = KeyStore.getInstance("JKS"); - tks.load(new FileInputStream(keystorePath), keystorePassword); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(tks); - - String p12Path = clientCertPath(); - assertNotNull(p12Path); - String p12Passwd = clientCertPassword(); - assertNotNull(p12Passwd); - KeyStore ks = KeyStore.getInstance("PKCS12"); - char [] p12Password = p12Passwd.toCharArray(); - ks.load(new FileInputStream(p12Path), p12Password); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, p12Password); - - SSLContext c = sslContextSupplier.get(); - c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return c; + return sslContext(trustManagerFactory(caCertificate())); } - static String keystoreCa() { - return System.getProperty("test-keystore.ca", "./target/ca.keystore"); - } - - static String emptyKeystoreCa() { - return System.getProperty("test-keystore.empty", "./target/empty.keystore"); - } - - static String keystorePassword() { - return System.getProperty("test-keystore.password", "bunnies"); - } - - static String clientCertPath() { - return System.getProperty("test-client-cert.path", "/tmp/tls-gen/basic/client/keycert.p12"); - } - - static String clientCertPassword() { - return System.getProperty("test-client-cert.password", ""); + static SSLContext verifiedSslContext(CallableSupplier sslContextSupplier) + throws Exception { + return sslContext(sslContextSupplier, trustManagerFactory(caCertificate())); } public static SSLContext getSSLContext() throws NoSuchAlgorithmException { @@ -112,15 +69,78 @@ public static SSLContext getSSLContext() throws NoSuchAlgorithmException { static Collection availableTlsProtocols() { try { String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); - return Arrays.stream(protocols).filter(p -> p.toLowerCase().startsWith("tls")).collect( - Collectors.toList()); + return Arrays.stream(protocols) + .filter(p -> p.toLowerCase().startsWith("tls")) + .collect(Collectors.toList()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } + static SSLContext sslContext(TrustManagerFactory trustManagerFactory) throws Exception { + return sslContext(() -> SSLContext.getInstance(PROTOCOLS[0]), trustManagerFactory); + } + + static SSLContext sslContext( + CallableSupplier sslContextSupplier, TrustManagerFactory trustManagerFactory) + throws Exception { + SSLContext sslContext = sslContextSupplier.get(); + sslContext.init( + null, trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(), null); + return sslContext; + } + + static TrustManagerFactory trustManagerFactory(Certificate certificate) throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("some-certificate", certificate); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return trustManagerFactory; + } + + static X509Certificate caCertificate() throws Exception { + return loadCertificate(caCertificateFile()); + } + + static String caCertificateFile() { + return tlsArtefactPath( + System.getProperty("ca.certificate", "./rabbitmq-configuration/tls/ca_certificate.pem")); + } + + static X509Certificate clientCertificate() throws Exception { + return loadCertificate(clientCertificateFile()); + } + + static String clientCertificateFile() { + return tlsArtefactPath( + System.getProperty( + "client.certificate", + "./rabbitmq-configuration/tls/client_" + hostname() + "_certificate.pem")); + } + + static X509Certificate loadCertificate(String file) throws Exception { + try (FileInputStream inputStream = new FileInputStream(file)) { + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + return (X509Certificate) fact.generateCertificate(inputStream); + } + } + + private static String tlsArtefactPath(String in) { + return in.replace("$(hostname)", hostname()).replace("$(hostname -s)", hostname()); + } + + private static String hostname() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + return Host.hostname(); + } + } + @FunctionalInterface - interface CallableSupplier { + interface CallableSupplier { T get() throws Exception; } diff --git a/src/test/java/com/rabbitmq/tools/Host.java b/src/test/java/com/rabbitmq/tools/Host.java index 96cf6b0dac..36976ee682 100644 --- a/src/test/java/com/rabbitmq/tools/Host.java +++ b/src/test/java/com/rabbitmq/tools/Host.java @@ -42,6 +42,14 @@ public class Host { private static final String DOCKER_PREFIX = "DOCKER:"; private static final Pattern CONNECTION_NAME_PATTERN = Pattern.compile("\"connection_name\",\"(?[a-zA-Z0-9\\-]+)?\""); + public static String hostname() { + try { + return executeCommand("hostname").output(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static String capture(InputStream is) throws IOException { @@ -72,7 +80,7 @@ public static ProcessState executeCommand(String command) throws IOException return new ProcessState(pr, inputState, errorState); } - static class ProcessState { + public static class ProcessState { private final Process process; private final InputStreamPumpState inputState; @@ -85,7 +93,7 @@ static class ProcessState { this.errorState = errorState; } - private String output() { + public String output() { return inputState.buffer.toString(); } @@ -189,17 +197,6 @@ public static void clearResourceAlarm(String source) throws IOException { rabbitmqctl("eval 'rabbit_alarm:clear_alarm({resource_limit, " + source + ", node()}).'"); } - public static ProcessState invokeMakeTarget(String command) throws IOException { - File rabbitmqctl = new File(rabbitmqctlCommand()); - return executeCommand(makeCommand() + - " -C \'" + rabbitmqDir() + "\'" + - " RABBITMQCTL=\'" + rabbitmqctl.getAbsolutePath() + "\'" + - " RABBITMQ_NODENAME=\'" + nodenameA() + "\'" + - " RABBITMQ_NODE_PORT=" + node_portA() + - " RABBITMQ_CONFIG_FILE=\'" + config_fileA() + "\'" + - " " + command); - } - public static void startRabbitOnNode() throws IOException { rabbitmqctl("start_app"); tryConnectFor(10_000); @@ -210,7 +207,7 @@ public static void stopRabbitOnNode() throws IOException { } public static void tryConnectFor(int timeoutInMs) throws IOException { - tryConnectFor(timeoutInMs, node_portA() == null ? 5672 : Integer.valueOf(node_portA())); + tryConnectFor(timeoutInMs, 5672); } public static void tryConnectFor(int timeoutInMs, int port) throws IOException { @@ -245,15 +242,6 @@ public static String nodenameA() return System.getProperty("test-broker.A.nodename"); } - public static String node_portA() - { - return System.getProperty("test-broker.A.node_port"); - } - - public static String config_fileA() - { - return System.getProperty("test-broker.A.config_file"); - } public static String nodenameB() { @@ -265,16 +253,8 @@ public static String node_portB() return System.getProperty("test-broker.B.node_port"); } - public static String config_fileB() - { - return System.getProperty("test-broker.B.config_file"); - } - public static String rabbitmqctlCommand() { - String rabbitmqCtl = System.getProperty("rabbitmqctl.bin"); - if (rabbitmqCtl == null) { - throw new IllegalStateException("Please define the rabbitmqctl.bin system property"); - } + String rabbitmqCtl = rabbitmqctl(); if (rabbitmqCtl.startsWith(DOCKER_PREFIX)) { String containerId = rabbitmqCtl.split(":")[1]; return "docker exec " + containerId + " rabbitmqctl"; @@ -283,17 +263,13 @@ public static String rabbitmqctlCommand() { } } - public static boolean isOnDocker() { - String rabbitmqCtl = System.getProperty("rabbitmqctl.bin"); - if (rabbitmqCtl == null) { - throw new IllegalStateException("Please define the rabbitmqctl.bin system property"); - } - return rabbitmqCtl.startsWith(DOCKER_PREFIX); + private static String rabbitmqctl() { + return System.getProperty("rabbitmqctl.bin", "DOCKER:rabbitmq"); } - public static String rabbitmqDir() - { - return System.getProperty("rabbitmq.dir"); + public static boolean isOnDocker() { + String rabbitmqCtl = rabbitmqctl(); + return rabbitmqCtl.startsWith(DOCKER_PREFIX); } public static void closeConnection(String pid) throws IOException { diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties deleted file mode 100644 index 6562e2f80e..0000000000 --- a/src/test/resources/config.properties +++ /dev/null @@ -1,3 +0,0 @@ -broker.hostname=localhost -broker.port=5672 -broker.sslport=5671 diff --git a/src/test/resources/hare@localhost.config b/src/test/resources/hare@localhost.config deleted file mode 100644 index 41667a9c70..0000000000 --- a/src/test/resources/hare@localhost.config +++ /dev/null @@ -1,15 +0,0 @@ -% vim:ft=erlang: - -[ - {rabbit, [ - {ssl_listeners, [5670]}, - {ssl_options, [ - {cacertfile, "${test-tls-certs.dir}/testca/cacert.pem"}, - {certfile, "${test-tls-certs.dir}/server/cert.pem"}, - {keyfile, "${test-tls-certs.dir}/server/key.pem"}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}, - {honor_cipher_order, true}]}, - {auth_mechanisms, ['PLAIN', 'ANONYMOUS', 'AMQPLAIN', 'EXTERNAL', 'RABBIT-CR-DEMO']} - ]} -]. diff --git a/src/test/resources/log4j2-test.properties b/src/test/resources/log4j2-test.properties deleted file mode 100644 index b7e0a68699..0000000000 --- a/src/test/resources/log4j2-test.properties +++ /dev/null @@ -1,12 +0,0 @@ -status = error -dest = err -name = PropertiesConfig - -appender.console.type = Console -appender.console.name = STDOUT -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %m%n - -logger.com.rabbitmq.level = info -rootLogger.level = error -rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file diff --git a/src/test/resources/rabbit@localhost.config b/src/test/resources/rabbit@localhost.config deleted file mode 100644 index 9e3b77c94d..0000000000 --- a/src/test/resources/rabbit@localhost.config +++ /dev/null @@ -1,15 +0,0 @@ -% vim:ft=erlang: - -[ - {rabbit, [ - {ssl_listeners, [5671]}, - {ssl_options, [ - {cacertfile, "${test-tls-certs.dir}/testca/cacert.pem"}, - {certfile, "${test-tls-certs.dir}/server/cert.pem"}, - {keyfile, "${test-tls-certs.dir}/server/key.pem"}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}, - {honor_cipher_order, true}]}, - {auth_mechanisms, ['PLAIN', 'ANONYMOUS', 'AMQPLAIN', 'EXTERNAL', 'RABBIT-CR-DEMO']} - ]} -]. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy